package handler import ( "net/http" "bj_power_wms/ent" "bj_power_wms/ent/inbounddetail" "bj_power_wms/ent/inboundorder" "bj_power_wms/ent/inventorybatch" "bj_power_wms/ent/material" "bj_power_wms/ent/serialnumber" "bj_power_wms/internal/svc" "github.com/google/uuid" "github.com/zeromicro/go-zero/core/logx" ) // createInboundHandler 入库 // 结构件: { inboundType, materialCode, batchNo, quantity, zoneCode, operator } // 精密件: { inboundType, materialCode, snList: [sn...], zoneCode, operator } func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { InboundType string `json:"inboundType"` // purchase/semi/finished/return MaterialCode string `json:"materialCode"` BatchNo string `json:"batchNo"` Quantity int `json:"quantity"` SnList []string `json:"snList"` ZoneCode string `json:"zoneCode"` Operator string `json:"operator"` ProductionDate string `json:"productionDate"` Supplier string `json:"supplier"` Remark string `json:"remark"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误: "+err.Error()) return } if req.MaterialCode == "" { fail(w, http.StatusBadRequest, "materialCode 必填") return } // 物料档案 m, err := ctx.EntClient.Material.Query(). Where(material.CodeEQ(req.MaterialCode)). Only(ctx0()) if err != nil { fail(w, http.StatusBadRequest, "物料不存在: "+req.MaterialCode) return } inboundNo := "IB" + uuid.NewString()[:12] // 入库单 ib, err := ctx.EntClient.InboundOrder.Create(). SetInboundNo(inboundNo). SetNillableInboundType(strPtr(req.InboundType)). SetMaterialCode(req.MaterialCode). SetNillableMaterialName(strPtr(m.Name)). SetManageMode(m.ManageMode). SetNillableZoneCode(strPtr(req.ZoneCode)). SetQuantity(0). SetNillableOperator(strPtr(req.Operator)). SetNillableRemark(strPtr(req.Remark)). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "创建入库单失败: "+err.Error()) return } totalQty := 0 // 结构件(批次管理) if m.ManageMode == 1 { batchNo := req.BatchNo if batchNo == "" { batchNo = genBatchNo(req.MaterialCode) } qty := req.Quantity if qty <= 0 { fail(w, http.StatusBadRequest, "结构件入库 quantity 必填且 > 0") return } // 同批次追加(多批到货追加) existing, err := ctx.EntClient.InventoryBatch.Query(). Where(inventorybatch.BatchNoEQ(batchNo)). Only(ctx0()) if err == nil { ctx.EntClient.InventoryBatch.UpdateOneID(existing.ID). SetQuantity(existing.Quantity + qty). SetNillableSupplier(strPtr(req.Supplier)). ExecX(ctx0()) } else { ctx.EntClient.InventoryBatch.Create(). SetBatchNo(batchNo). SetMaterialCode(req.MaterialCode). SetNillableMaterialName(strPtr(m.Name)). SetQuantity(qty). SetLockedQty(0). SetNillableProductionDate(strPtr(req.ProductionDate)). SetNillableSupplier(strPtr(req.Supplier)). SetQualityStatus("未检"). SetNillableZoneCode(strPtr(req.ZoneCode)). SaveX(ctx0()) } // 明细 ctx.EntClient.InboundDetail.Create(). SetInboundNo(inboundNo). SetBatchNo(batchNo). SetMaterialCode(req.MaterialCode). SetQuantity(qty). SaveX(ctx0()) totalQty = qty } // 精密件(SN 管理) if m.ManageMode == 2 { if len(req.SnList) == 0 { fail(w, http.StatusBadRequest, "精密件入库 snList 必填") return } for _, sn := range req.SnList { // 查重 exists, _ := ctx.EntClient.SerialNumber.Query(). Where(serialnumber.SnCodeEQ(sn)). Exist(ctx0()) if exists { fail(w, http.StatusConflict, "SN 已存在: "+sn) return } ctx.EntClient.SerialNumber.Create(). SetSnCode(sn). SetMaterialCode(req.MaterialCode). SetStatus("在库"). SetNillableCurrentZone(strPtr(req.ZoneCode)). SetQualityStatus("未检"). SaveX(ctx0()) ctx.EntClient.InboundDetail.Create(). SetInboundNo(inboundNo). SetSnCode(sn). SetMaterialCode(req.MaterialCode). SetQuantity(1). SaveX(ctx0()) } totalQty = len(req.SnList) } // 更新入库单数量 ctx.EntClient.InboundOrder.UpdateOneID(ib.ID). SetQuantity(totalQty).ExecX(ctx0()) logx.Infof("inbound: no=%s material=%s qty=%d by=%s", inboundNo, req.MaterialCode, totalQty, req.Operator) ok(w, map[string]any{ "inboundNo": inboundNo, "quantity": totalQty, "batchNo": req.BatchNo, "snCount": len(req.SnList), }) } } // excelInboundHandler 实现见 excel.go // queryInboundHandler 入库单查询 func queryInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { page := atoi(r.URL.Query().Get("page"), 1) pageSize := atoi(r.URL.Query().Get("pageSize"), 20) materialCode := r.URL.Query().Get("materialCode") inboundType := r.URL.Query().Get("inboundType") inboundNo := r.URL.Query().Get("inboundNo") q := ctx.EntClient.InboundOrder.Query() if materialCode != "" { q = q.Where(inboundorder.MaterialCodeEQ(materialCode)) } if inboundType != "" { q = q.Where(inboundorder.InboundTypeEQ(inboundType)) } if inboundNo != "" { q = q.Where(inboundorder.InboundNoEQ(inboundNo)) } total, err := q.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } list, err := q.Order(ent.Desc("id")). Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } type row struct { *ent.InboundOrder Details []*ent.InboundDetail `json:"details"` } rows := make([]row, 0, len(list)) for _, ib := range list { details, _ := ctx.EntClient.InboundDetail.Query(). Where(inbounddetail.InboundNoEQ(ib.InboundNo)). All(ctx0()) rows = append(rows, row{ib, details}) } ok(w, map[string]any{ "total": total, "list": rows, "page": page, "pageSize": pageSize, }) } } // genBatchNo 生成批次号:日期 + 自增 func genBatchNo(materialCode string) string { // TODO: 使用数据库序列或 Redis INCR 保证自增唯一 // 暂用时间戳 + 随机后缀 now := nowStr() return "B" + now + materialCodeSuffix(materialCode) } func nowStr() string { // YYYYMMDDHHMMSS const layout = "20060102150405" return timeNow().Format(layout) } func materialCodeSuffix(code string) string { if len(code) > 4 { code = code[len(code)-4:] } return code }