refactor: 清理MES端本地product_type冗余代码,对齐WMS主数据架构
1. 移除MES端本地product_type实体、schema及相关CRUD代码,改为通过WMS内部API代理获取物料主数据 2. 更新物料管理粒度逻辑,新增"其他"类型(3)支持,同步更新入库校验规则 3. 修复前端物料选择、库存筛选等页面的类型判断逻辑,统一使用manage_mode区分管理类型 4. 清理静态资源文件冗余的样式代码,调整前端页面展示逻辑 5. 更新帮助文档,修正物料类型的说明描述
This commit is contained in:
@@ -148,13 +148,15 @@ func displayWidth(s string) int {
|
||||
return n
|
||||
}
|
||||
|
||||
// manageModeLabel 管理粒度中文名:1=结构件 2=电气件
|
||||
// manageModeLabel 类型中文名:1=结构件 2=电气件 3=其他
|
||||
func manageModeLabel(m int) string {
|
||||
if m == 1 {
|
||||
switch m {
|
||||
case 1:
|
||||
return "结构件"
|
||||
}
|
||||
if m == 2 {
|
||||
case 2:
|
||||
return "电气件"
|
||||
case invManageOther:
|
||||
return "其他"
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import (
|
||||
// 物料品类:4=其他(辅料/工装/试验设备),与 schema/material.go 的 item_type 保持一致
|
||||
const invItemOther = 4
|
||||
|
||||
// 类型(管理粒度)第三值:3=其他(辅料/工装/试验设备),与结构件(1)/电气件(2) 同级。
|
||||
// 客户裁决(2026-09-21):结构件/电气件/其他 同属「类型」维度,独立于「品类」,编号 1/2/3。
|
||||
const invManageOther = 3
|
||||
|
||||
// nextDailyNo 生成日期+流水号,格式:IB/OB + YYYYMMDD + -NNN(如 IB20260921-001)。
|
||||
// 同一天内流水号递增,跨天重置为 001。并发冲突时自动重试最多 5 次。
|
||||
// 注意:历史旧单号(IB+UUID 片段)不受影响,仅新生成的用新格式。
|
||||
@@ -189,8 +193,8 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 其他类物料(item_type=4:辅料/工装/试验设备):规格强制必填(客户诉求 问题记录 L50)
|
||||
if m.ItemType == invItemOther {
|
||||
// 其他类物料(类型 manage_mode=3:辅料/工装/试验设备):规格强制必填(客户诉求 问题记录 L50)
|
||||
if m.ManageMode == invManageOther {
|
||||
if strings.TrimSpace(m.Spec) == "" {
|
||||
fail(w, http.StatusBadRequest, "其他类物料必须填写规格型号,请先在物料档案补全")
|
||||
return
|
||||
@@ -273,8 +277,8 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
|
||||
totalQty := 0
|
||||
|
||||
// 结构件(批次管理)
|
||||
if m.ManageMode == 1 {
|
||||
// 结构件(批次管理)+ 其他(辅料/工装/试验设备,同为数量/批次管理)
|
||||
if m.ManageMode == 1 || m.ManageMode == invManageOther {
|
||||
// 结构件走批次入库,不允许携带 SN 清单
|
||||
if len(req.SnList) > 0 {
|
||||
fail(w, http.StatusBadRequest, "该物料为结构件(按批次管理),不应提交 SN 清单,请使用『结构件入库』")
|
||||
|
||||
@@ -132,12 +132,12 @@ func batchFlipInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// 「其他」类物料(item_type=4)单独记对象类型 OTHER(§B1:其他类此前混在批次(结构件)里无法单独识别)
|
||||
// 「其他」类物料(类型 manage_mode=3)单独记对象类型 OTHER(§B1:其他类此前混在批次(结构件)里无法单独识别)
|
||||
if len(codes) > 0 {
|
||||
if mats, e := ctx.EntClient.Material.Query().Where(material.CodeIn(codes...)).All(ctx0()); e == nil {
|
||||
otherSet := map[string]bool{}
|
||||
for _, m := range mats {
|
||||
if m.ItemType == invItemOther {
|
||||
if m.ManageMode == invManageOther {
|
||||
otherSet[m.Code] = true
|
||||
}
|
||||
}
|
||||
@@ -652,7 +652,7 @@ func statsInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// materialCodesByItemType 按品类(itemType)取物料编码集合(来料检验「其他」类过滤用,B1)。
|
||||
// materialCodesByItemType 按品类(itemType)取物料编码集合。
|
||||
// 先查 material 取编码集,再 IN 查 inventory/inspection_record,避免逐行 join(同 materialCodesBySpec 套路)。
|
||||
func materialCodesByItemType(ctx *svc.ServiceContext, itemType int) []string {
|
||||
mats, err := ctx.EntClient.Material.Query().Where(material.ItemTypeEQ(itemType)).All(ctx0())
|
||||
@@ -666,6 +666,20 @@ func materialCodesByItemType(ctx *svc.ServiceContext, itemType int) []string {
|
||||
return codes
|
||||
}
|
||||
|
||||
// materialCodesByManageMode 按类型(manageMode=1结构件/2电气件/3其他)取物料编码集合(来料检验/库存「其他」类过滤用)。
|
||||
// 库存行里「其他」与结构件同为 manage_mode=1(批次数量管理),只能借物料档案的类型区分,故先取编码集再 IN。
|
||||
func materialCodesByManageMode(ctx *svc.ServiceContext, manageMode int) []string {
|
||||
mats, err := ctx.EntClient.Material.Query().Where(material.ManageModeEQ(manageMode)).All(ctx0())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
codes := make([]string, 0, len(mats))
|
||||
for _, m := range mats {
|
||||
codes = append(codes, m.Code)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
// inboundNosByType 取符合入库类型的入库单号集合(待检清单按入库类型过滤用,B1)。
|
||||
func inboundNosByType(ctx *svc.ServiceContext, inboundType string) ([]string, error) {
|
||||
orders, err := ctx.EntClient.InboundOrder.Query().Where(inboundorder.InboundTypeEQ(inboundType)).All(ctx0())
|
||||
@@ -690,6 +704,7 @@ type pendingInspectionRow struct {
|
||||
OwnershipNo string `json:"ownershipNo"`
|
||||
MaterialCode string `json:"materialCode"`
|
||||
MaterialName string `json:"materialName"`
|
||||
MatManageMode int `json:"matManageMode"` // 物料档案类型(1结构件/2电气件/3其他)
|
||||
UnInspectedQty int `json:"unInspectedQty"` // 未检数量(结构件=数量求和;电气件=SN 行数)
|
||||
DetailsTotal int `json:"detailsTotal"` // 未检批次数/SN 总数(抽屉分页拉明细)
|
||||
InboundTime int64 `json:"inboundTime"`
|
||||
@@ -718,8 +733,8 @@ func pendingInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
case "SN":
|
||||
q = q.Where(inventory.ManageModeEQ(2))
|
||||
case "OTHER":
|
||||
// 其他入库物料:itemType=4(先取编码集再 IN,避免逐行 join)
|
||||
codes := materialCodesByItemType(ctx, 4)
|
||||
// 其他入库物料:类型 manage_mode=3(先取编码集再 IN,避免逐行 join)
|
||||
codes := materialCodesByManageMode(ctx, invManageOther)
|
||||
if len(codes) == 0 {
|
||||
ok(w, map[string]any{"list": []any{}, "total": 0})
|
||||
return
|
||||
@@ -760,8 +775,9 @@ func pendingInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// 物料名称 map
|
||||
// 物料名称 / 类型 map
|
||||
matName := map[string]string{}
|
||||
matManageMode := map[string]int{}
|
||||
codeSet := []string{}
|
||||
seenC := map[string]bool{}
|
||||
for _, inv := range all {
|
||||
@@ -774,6 +790,7 @@ func pendingInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
if mats, e := ctx.EntClient.Material.Query().Where(material.CodeIn(codeSet...)).All(ctx0()); e == nil {
|
||||
for _, m := range mats {
|
||||
matName[m.Code] = m.Name
|
||||
matManageMode[m.Code] = m.ManageMode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -798,6 +815,7 @@ func pendingInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
OwnershipNo: ownNo,
|
||||
MaterialCode: inv.MaterialCode,
|
||||
MaterialName: matName[inv.MaterialCode],
|
||||
MatManageMode: matManageMode[inv.MaterialCode],
|
||||
}
|
||||
group[key] = g
|
||||
order = append(order, key)
|
||||
@@ -880,10 +898,12 @@ func pendingDetailsInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFun
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// itemType 从物料档案补查(一个 materialCode 唯一)
|
||||
// itemType / 类型 从物料档案补查(一个 materialCode 唯一)
|
||||
itemType := 0
|
||||
matManageMode := 0
|
||||
if m, e := svcCtx.EntClient.Material.Query().Where(material.CodeEQ(materialCode)).Only(ctx0()); e == nil {
|
||||
itemType = m.ItemType
|
||||
matManageMode = m.ManageMode
|
||||
}
|
||||
rows := make([]map[string]any, 0, len(list))
|
||||
for _, inv := range list {
|
||||
@@ -892,12 +912,13 @@ func pendingDetailsInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFun
|
||||
tid = inv.SnCode
|
||||
}
|
||||
rows = append(rows, map[string]any{
|
||||
"targetId": tid,
|
||||
"manageMode": inv.ManageMode,
|
||||
"itemType": itemType,
|
||||
"quantity": inv.Quantity,
|
||||
"zoneCode": inv.ZoneCode,
|
||||
"id": inv.ID,
|
||||
"targetId": tid,
|
||||
"manageMode": inv.ManageMode,
|
||||
"matManageMode": matManageMode,
|
||||
"itemType": itemType,
|
||||
"quantity": inv.Quantity,
|
||||
"zoneCode": inv.ZoneCode,
|
||||
"id": inv.ID,
|
||||
})
|
||||
}
|
||||
ok(w, map[string]any{"list": rows, "total": total, "page": page, "pageSize": pageSize})
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"bj_power_wms/ent"
|
||||
"bj_power_wms/ent/inventory"
|
||||
"bj_power_wms/ent/material"
|
||||
"bj_power_wms/internal/svc"
|
||||
|
||||
@@ -31,7 +32,7 @@ func itemTypeLabel(t int) string {
|
||||
// 新增/编辑表单除「说明」外全部必填)。返回空串表示通过,否则返回中文错误文案。
|
||||
//
|
||||
// 说明:图号 code 只在新增时必填(编辑不可改),由调用方单独校验;
|
||||
// 「其他」类物料不做 SN,管理粒度由后端强制收敛为 1(结构件),故不参与必填。
|
||||
// 「类型」= 管理粒度 1结构件/2电气件/3其他,与「品类」相互独立,均必填。
|
||||
func validateMaterialRequired(name, spec, unit string, itemType, manageMode int) string {
|
||||
switch {
|
||||
case strings.TrimSpace(name) == "":
|
||||
@@ -42,8 +43,8 @@ func validateMaterialRequired(name, spec, unit string, itemType, manageMode int)
|
||||
return "单位为必填项"
|
||||
case itemType < 1 || itemType > invItemOther:
|
||||
return "品类为必填项(原材料 / 半成品 / 成品 / 其他)"
|
||||
case itemType != invItemOther && manageMode != 1 && manageMode != 2:
|
||||
return "类型为必填项(结构件 / 电气件)"
|
||||
case manageMode != 1 && manageMode != 2 && manageMode != invManageOther:
|
||||
return "类型为必填项(结构件 / 电气件 / 其他)"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -89,13 +90,9 @@ func createMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 品类/管理粒度已在上方校验,这里只做「其他类不做 SN」的业务收敛
|
||||
// 品类/类型已在上方校验,原样落库(类型 1结构件/2电气件/3其他 相互独立,不再由品类收敛)
|
||||
manageMode := req.ManageMode
|
||||
itemType := req.ItemType
|
||||
// 其他类(辅料/工装/试验设备)只按数量管理,不做 SN
|
||||
if itemType == invItemOther {
|
||||
manageMode = 1
|
||||
}
|
||||
|
||||
m, err := ctx.EntClient.Material.Create().
|
||||
SetCode(req.Code).
|
||||
@@ -104,7 +101,7 @@ func createMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetNillableUnit(strPtr(req.Unit)).
|
||||
SetNillableDescription(strPtr(req.Description)).
|
||||
SetManageMode(manageMode).
|
||||
SetIsBatchManaged(manageMode == 1).
|
||||
SetIsBatchManaged(manageMode == 1 || manageMode == invManageOther).
|
||||
SetIsSerialManaged(manageMode == 2).
|
||||
SetNillableTemplateCode(strPtr(req.TemplateCode)).
|
||||
SetItemType(itemType).
|
||||
@@ -151,9 +148,6 @@ func updateMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
|
||||
manageMode := req.ManageMode
|
||||
if req.ItemType == invItemOther {
|
||||
manageMode = 1
|
||||
}
|
||||
m, err := ctx.EntClient.Material.UpdateOneID(req.ID).
|
||||
SetName(req.Name).
|
||||
SetSpec(req.Spec).
|
||||
@@ -162,7 +156,7 @@ func updateMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetItemType(req.ItemType).
|
||||
SetSafetyStock(req.SafetyStock).
|
||||
SetManageMode(manageMode).
|
||||
SetIsBatchManaged(manageMode == 1).
|
||||
SetIsBatchManaged(manageMode == 1 || manageMode == invManageOther).
|
||||
SetIsSerialManaged(manageMode == 2).
|
||||
Save(ctx0())
|
||||
if err != nil {
|
||||
@@ -175,6 +169,36 @@ func updateMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// deleteMaterialHandler 删除物料档案(全品类,供 MES 物料档案代理调用)
|
||||
// 删除保护:库存仍引用该物料时禁删(与产品型号口径一致)。
|
||||
func deleteMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
ID int `json:"id"`
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
cur, err := ctx.EntClient.Material.Get(ctx0(), req.ID)
|
||||
if err != nil {
|
||||
fail(w, http.StatusNotFound, "物料不存在")
|
||||
return
|
||||
}
|
||||
if n, _ := ctx.EntClient.Inventory.Query().Where(inventory.MaterialCodeEqualFold(cur.Code)).Count(ctx0()); n > 0 {
|
||||
fail(w, http.StatusConflict, "该物料档案已有库存记录("+itoa(n)+"条),不可删除;建议停用(下架)")
|
||||
return
|
||||
}
|
||||
if err := ctx.EntClient.Material.DeleteOneID(req.ID).Exec(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "删除物料失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ctx.EventLog.Write(ctx0(), "material.delete", r.Header.Get("X-Username"), "material", cur.Code,
|
||||
"删除物料档案 "+cur.Code, nil)
|
||||
ok(w, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// queryMaterialsHandler 分页查询物料
|
||||
func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -446,8 +470,8 @@ func exportMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// parseManageMode 解析导入模板"类型"列:支持中文(结构件/电气件,含"(批次)/(SN)"后缀)
|
||||
// 与旧数字格式(1/2)双向兼容;无法识别返回 0。
|
||||
// parseManageMode 解析导入模板"类型"列:支持中文(结构件/电气件/其他,含"(批次)/(SN)"后缀)
|
||||
// 与数字格式(1/2/3)双向兼容;无法识别返回 0。
|
||||
func parseManageMode(s string) int {
|
||||
if s == "" {
|
||||
return 0
|
||||
@@ -458,7 +482,10 @@ func parseManageMode(s string) int {
|
||||
if strings.Contains(s, "结构") || strings.Contains(s, "批次") {
|
||||
return 1
|
||||
}
|
||||
if n := atoi(s, 0); n == 1 || n == 2 {
|
||||
if strings.Contains(s, "其他") {
|
||||
return invManageOther
|
||||
}
|
||||
if n := atoi(s, 0); n >= 1 && n <= 3 {
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
@@ -613,7 +640,7 @@ func excelMaterialImportHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
continue
|
||||
}
|
||||
if pr.manageMode == 0 {
|
||||
errs = append(errs, importErr{pr.row, pr.code, "类型无法识别,请填写「结构件」或「电气件」(兼容 1/2)"})
|
||||
errs = append(errs, importErr{pr.row, pr.code, "类型无法识别,请填写「结构件/电气件/其他」(兼容 1/2/3)"})
|
||||
continue
|
||||
}
|
||||
if catCol >= 0 && cell(catCol) != "" && pr.itemType == 0 {
|
||||
@@ -656,7 +683,7 @@ func excelMaterialImportHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetNillableUnit(strPtr(pr.unit)).
|
||||
SetNillableDescription(strPtr(pr.description)).
|
||||
SetManageMode(pr.manageMode).
|
||||
SetIsBatchManaged(pr.manageMode == 1).
|
||||
SetIsBatchManaged(pr.manageMode == 1 || pr.manageMode == invManageOther).
|
||||
SetIsSerialManaged(pr.manageMode == 2)
|
||||
if pr.itemType > 0 {
|
||||
mc = mc.SetItemType(pr.itemType)
|
||||
|
||||
@@ -119,9 +119,9 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) {
|
||||
{Method: http.MethodPost, Path: "/api/stock/unlock", Handler: unlockStockHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/stock/deduct", Handler: deductStockHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/stock/check", Handler: checkStockHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/stock/shortage", Handler: shortageHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/stock/material-demand", Handler: materialDemandHandler(ctx)},
|
||||
},
|
||||
{Method: http.MethodGet, Path: "/api/stock/shortage", Handler: shortageHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/stock/material-demand", Handler: materialDemandHandler(ctx)},
|
||||
},
|
||||
)
|
||||
|
||||
// 检验
|
||||
@@ -240,6 +240,11 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) {
|
||||
{Method: http.MethodPost, Path: "/api/internal/product-types/delete", Handler: wrapInternal(ctx)(deleteProductTypeInternalHandler(ctx))},
|
||||
{Method: http.MethodPost, Path: "/api/internal/material/exists", Handler: wrapInternal(ctx)(materialExistsHandler(ctx))},
|
||||
{Method: http.MethodPost, Path: "/api/internal/material/types", Handler: wrapInternal(ctx)(materialTypesHandler(ctx))},
|
||||
// MES 物料档案代理:WMS 是物料主数据源,MES 前端通过 X-API-TOKEN 直接增改查 WMS 物料表
|
||||
{Method: http.MethodGet, Path: "/api/internal/materials", Handler: wrapInternal(ctx)(queryMaterialsHandler(ctx))},
|
||||
{Method: http.MethodPost, Path: "/api/internal/materials", Handler: wrapInternal(ctx)(createMaterialHandler(ctx))},
|
||||
{Method: http.MethodPut, Path: "/api/internal/materials", Handler: wrapInternal(ctx)(updateMaterialHandler(ctx))},
|
||||
{Method: http.MethodGet, Path: "/api/internal/material/list-all", Handler: wrapInternal(ctx)(listMaterialsHandler(ctx))},
|
||||
{Method: http.MethodPost, Path: "/api/internal/return-order", Handler: wrapInternal(ctx)(createReturnOrderInternalHandler(ctx))},
|
||||
{Method: http.MethodPost, Path: "/api/internal/material/backflush", Handler: wrapInternal(ctx)(backflushInternalHandler(ctx))},
|
||||
},
|
||||
|
||||
@@ -41,12 +41,12 @@ func categoryLabel(c int) string {
|
||||
}
|
||||
}
|
||||
|
||||
// typeLabelOf 汇总行类型标签(与区域汇总「其他」口径一致):itemType=4 → 其他;否则按管理粒度。
|
||||
func typeLabelOf(manageMode, itemType int) string {
|
||||
if itemType == invItemOther {
|
||||
// typeLabelOf 汇总行类型标签(与区域汇总「其他」口径一致):物料类型 manage_mode=3 → 其他;否则按管理粒度。
|
||||
func typeLabelOf(matManageMode int) string {
|
||||
if matManageMode == invManageOther {
|
||||
return "其他"
|
||||
}
|
||||
return manageModeLabel(manageMode)
|
||||
return manageModeLabel(matManageMode)
|
||||
}
|
||||
|
||||
// stockAggRow 聚合主列表的行结构(按 物料+区域+管理粒度 聚合,只返回数量,不铺开明细)
|
||||
@@ -57,8 +57,9 @@ type stockAggRow struct {
|
||||
MaterialCode string `json:"materialCode"` // 物料编码
|
||||
MaterialName string `json:"materialName"` // 物料名称
|
||||
Spec string `json:"spec"` // 规格型号
|
||||
ManageMode int `json:"manageMode"` // 1结构件/2电气件
|
||||
ItemType int `json:"itemType"` // 物料品类(4=其他:辅料/工装/试验设备)
|
||||
ManageMode int `json:"manageMode"` // 库存行管理粒度 1结构件(批次)/2电气件(SN)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/2半成品/3成品/4其他)
|
||||
MatManageMode int `json:"matManageMode"` // 物料档案类型(1结构件/2电气件/3其他):类型标签判定用
|
||||
ZoneCode string `json:"zoneCode"` // 区域
|
||||
ShelfNo string `json:"shelfNo"` // 货架号
|
||||
LayerNo string `json:"layerNo"` // 第几层
|
||||
@@ -91,8 +92,6 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
qualityStatus := r.URL.Query().Get("qualityStatus")
|
||||
manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部
|
||||
category := r.URL.Query().Get("category") // 1原材料/2半成品/3成品/空=全部(完工检 SN 拾取器按成品过滤)
|
||||
itemType := atoi(r.URL.Query().Get("itemType"), 0) // 品类(B1:4=其他入库物料)
|
||||
itemTypeNot := atoi(r.URL.Query().Get("itemTypeNot"), 0) // 排除品类(§A1 类型三分互斥:结构件 = 管理粒度1 且非「其他」类)
|
||||
inboundNo := r.URL.Query().Get("inboundNo")
|
||||
startDate := r.URL.Query().Get("startDate") // 默认近3个月(由接口保证)
|
||||
endDate := r.URL.Query().Get("endDate")
|
||||
@@ -110,8 +109,6 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
QualityStatus: qualityStatus,
|
||||
ManageMode: manageMode,
|
||||
Category: category,
|
||||
ItemType: itemType,
|
||||
ItemTypeNot: itemTypeNot,
|
||||
InboundNo: inboundNo,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
@@ -143,8 +140,6 @@ type aggregateStockArgs struct {
|
||||
QualityStatus string
|
||||
ManageMode string
|
||||
Category string // 1原材料/2半成品/3成品/空=全部
|
||||
ItemType int // 品类 itemType(B1:4=其他入库物料)
|
||||
ItemTypeNot int // 排除品类(§A1:结构件筛选时排除「其他」类 itemType=4)
|
||||
InboundNo string
|
||||
StartDate string
|
||||
EndDate string
|
||||
@@ -193,28 +188,23 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
// 质量状态仅作"筛选"维度(快捷定位未检/合格/不合格),不进分组键(方案 §A1/§A5)
|
||||
q = q.Where(inventory.QualityStatusEQ(a.QualityStatus))
|
||||
}
|
||||
if a.ItemType > 0 {
|
||||
// 按品类过滤(B1):先取编码集再 IN,避免逐行 join
|
||||
codes := materialCodesByItemType(ctx, a.ItemType)
|
||||
if a.InboundNo != "" {
|
||||
q = q.Where(inventory.InboundNoContains(a.InboundNo))
|
||||
}
|
||||
// 类型(manage_mode)下拉:结构件=1 / 电气件=2 / 其他=3,一律按物料档案类型取编码集 IN,不与品类(itemType)字段混用
|
||||
switch a.ManageMode {
|
||||
case "1", "2":
|
||||
codes := materialCodesByManageMode(ctx, atoi(a.ManageMode, 0))
|
||||
if len(codes) == 0 {
|
||||
return []*stockAggRow{}, 0, nil
|
||||
}
|
||||
q = q.Where(inventory.MaterialCodeIn(codes...))
|
||||
}
|
||||
if a.ItemTypeNot > 0 {
|
||||
// 排除品类(§A1 类型三分互斥:结构件 = 管理粒度1 且非「其他」类)
|
||||
codes := materialCodesByItemType(ctx, a.ItemTypeNot)
|
||||
if len(codes) > 0 {
|
||||
q = q.Where(inventory.MaterialCodeNotIn(codes...))
|
||||
case "3":
|
||||
codes := materialCodesByManageMode(ctx, invManageOther)
|
||||
if len(codes) == 0 {
|
||||
return []*stockAggRow{}, 0, nil
|
||||
}
|
||||
}
|
||||
if a.InboundNo != "" {
|
||||
q = q.Where(inventory.InboundNoContains(a.InboundNo))
|
||||
}
|
||||
if a.ManageMode == "1" {
|
||||
q = q.Where(inventory.ManageModeEQ(1))
|
||||
} else if a.ManageMode == "2" {
|
||||
q = q.Where(inventory.ManageModeEQ(2))
|
||||
q = q.Where(inventory.MaterialCodeIn(codes...))
|
||||
}
|
||||
if a.Category == "1" {
|
||||
q = q.Where(inventory.CategoryEQ(invCatRaw))
|
||||
@@ -255,6 +245,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
matName := map[string]string{}
|
||||
matSpec := map[string]string{}
|
||||
matItemType := map[string]int{}
|
||||
matManageMode := map[string]int{}
|
||||
matCodes := []string{}
|
||||
seen := map[string]bool{}
|
||||
for _, inv := range all {
|
||||
@@ -270,6 +261,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
matName[m.Code] = m.Name
|
||||
matSpec[m.Code] = m.Spec
|
||||
matItemType[m.Code] = m.ItemType
|
||||
matManageMode[m.Code] = m.ManageMode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +345,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
Spec: matSpec[key.MaterialCode],
|
||||
ManageMode: key.ManageMode,
|
||||
ItemType: matItemType[key.MaterialCode],
|
||||
MatManageMode: matManageMode[key.MaterialCode],
|
||||
ZoneCode: key.ZoneCode,
|
||||
ShelfNo: key.ShelfNo,
|
||||
LayerNo: key.LayerNo,
|
||||
@@ -380,8 +373,9 @@ type materialAggRow struct {
|
||||
MaterialCode string `json:"materialCode"`
|
||||
MaterialName string `json:"materialName"`
|
||||
Spec string `json:"spec"`
|
||||
ManageMode int `json:"manageMode"` // 1结构件/2电气件
|
||||
ItemType int `json:"itemType"` // 物料品类(4=其他:辅料/工装/试验设备)
|
||||
ManageMode int `json:"manageMode"` // 库存行管理粒度 1结构件(批次)/2电气件(SN)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/2半成品/3成品/4其他)
|
||||
MatManageMode int `json:"matManageMode"` // 物料档案类型(1结构件/2电气件/3其他):类型标签判定用
|
||||
Category int `json:"category"` // 品类 1原材料/2半成品/3成品
|
||||
TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;电气件=SN 行数)
|
||||
LockedQty int `json:"lockedQty"` // 锁定量
|
||||
@@ -404,8 +398,6 @@ type materialSummaryArgs struct {
|
||||
MaterialName string
|
||||
ManageMode string
|
||||
Category string // 1原材料/2半成品/3成品/空=全部
|
||||
ItemType int // 品类 itemType(4=其他入库物料,§A1 类型筛选)
|
||||
ItemTypeNot int // 排除品类(§A1:结构件筛选时排除「其他」类)
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
@@ -421,8 +413,6 @@ func materialSummaryHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
MaterialName: r.URL.Query().Get("materialName"),
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
ItemType: atoi(r.URL.Query().Get("itemType"), 0),
|
||||
ItemTypeNot: atoi(r.URL.Query().Get("itemTypeNot"), 0),
|
||||
Page: atoi(r.URL.Query().Get("page"), 1),
|
||||
PageSize: atoi(r.URL.Query().Get("pageSize"), 20),
|
||||
})
|
||||
@@ -446,25 +436,20 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
if a.MaterialName != "" {
|
||||
q = q.Where(inventory.MaterialNameContains(a.MaterialName))
|
||||
}
|
||||
if a.ManageMode == "1" {
|
||||
q = q.Where(inventory.ManageModeEQ(1))
|
||||
} else if a.ManageMode == "2" {
|
||||
q = q.Where(inventory.ManageModeEQ(2))
|
||||
}
|
||||
if a.ItemType > 0 {
|
||||
// 按品类过滤(§A1):先取编码集再 IN,避免逐行 join
|
||||
codes := materialCodesByItemType(ctx, a.ItemType)
|
||||
// 类型(manage_mode)下拉:结构件=1 / 电气件=2 / 其他=3,一律按物料档案类型取编码集 IN,不与品类(itemType)字段混用
|
||||
switch a.ManageMode {
|
||||
case "1", "2":
|
||||
codes := materialCodesByManageMode(ctx, atoi(a.ManageMode, 0))
|
||||
if len(codes) == 0 {
|
||||
return []*materialAggRow{}, 0, nil
|
||||
}
|
||||
q = q.Where(inventory.MaterialCodeIn(codes...))
|
||||
}
|
||||
if a.ItemTypeNot > 0 {
|
||||
// 排除品类(§A1 类型三分互斥:结构件 = 管理粒度1 且非「其他」类)
|
||||
codes := materialCodesByItemType(ctx, a.ItemTypeNot)
|
||||
if len(codes) > 0 {
|
||||
q = q.Where(inventory.MaterialCodeNotIn(codes...))
|
||||
case "3":
|
||||
codes := materialCodesByManageMode(ctx, invManageOther)
|
||||
if len(codes) == 0 {
|
||||
return []*materialAggRow{}, 0, nil
|
||||
}
|
||||
q = q.Where(inventory.MaterialCodeIn(codes...))
|
||||
}
|
||||
if a.Category == "1" {
|
||||
q = q.Where(inventory.CategoryEQ(invCatRaw))
|
||||
@@ -487,6 +472,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
matName := map[string]string{}
|
||||
matSpec := map[string]string{}
|
||||
matItemType := map[string]int{}
|
||||
matManageMode := map[string]int{}
|
||||
matID := map[string]int64{}
|
||||
matCreatedAt := map[string]int64{}
|
||||
matCodes := []string{}
|
||||
@@ -504,6 +490,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
matName[m.Code] = m.Name
|
||||
matSpec[m.Code] = m.Spec
|
||||
matItemType[m.Code] = m.ItemType
|
||||
matManageMode[m.Code] = m.ManageMode
|
||||
matID[m.Code] = int64(m.ID)
|
||||
matCreatedAt[m.Code] = m.CreatedAt
|
||||
}
|
||||
@@ -594,6 +581,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
Spec: matSpec[code],
|
||||
ManageMode: g.mMode,
|
||||
ItemType: matItemType[code],
|
||||
MatManageMode: matManageMode[code],
|
||||
Category: g.mCategory,
|
||||
TotalQty: g.totalQty,
|
||||
LockedQty: g.lockedQty,
|
||||
@@ -638,7 +626,6 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
keyword := r.URL.Query().Get("keyword")
|
||||
manageMode := atoi(r.URL.Query().Get("manageMode"), 0)
|
||||
category := atoi(r.URL.Query().Get("category"), 0)
|
||||
itemType := atoi(r.URL.Query().Get("itemType"), 0)
|
||||
page := atoi(r.URL.Query().Get("page"), 1)
|
||||
pageSize := atoi(r.URL.Query().Get("pageSize"), 50)
|
||||
|
||||
@@ -676,15 +663,6 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
if category != 0 {
|
||||
q = q.Where(inventory.CategoryEQ(category))
|
||||
}
|
||||
if itemType > 0 {
|
||||
// 按品类过滤(B1):先取编码集再 IN,避免逐行 join
|
||||
codes := materialCodesByItemType(ctx, itemType)
|
||||
if len(codes) == 0 {
|
||||
ok(w, map[string]any{"list": []any{}, "total": 0, "page": page, "pageSize": pageSize})
|
||||
return
|
||||
}
|
||||
q = q.Where(inventory.MaterialCodeIn(codes...))
|
||||
}
|
||||
|
||||
total, err := q.Count(ctx0())
|
||||
if err != nil {
|
||||
@@ -739,11 +717,9 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
rows, _, err := aggregateMaterialSummary(ctx, materialSummaryArgs{
|
||||
MaterialCode: r.URL.Query().Get("materialCode"),
|
||||
MaterialName: r.URL.Query().Get("materialName"),
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
ItemType: atoi(r.URL.Query().Get("itemType"), 0),
|
||||
ItemTypeNot: atoi(r.URL.Query().Get("itemTypeNot"), 0),
|
||||
Page: 1,
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
Page: 1,
|
||||
PageSize: 1000000, // 全量
|
||||
})
|
||||
if err != nil {
|
||||
@@ -760,7 +736,7 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
matrix = append(matrix, []any{
|
||||
id, rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
typeLabelOf(rw.ManageMode, rw.ItemType), rw.TotalQty, rw.LockedQty, rw.AvailQty,
|
||||
typeLabelOf(rw.MatManageMode), rw.TotalQty, rw.LockedQty, rw.AvailQty,
|
||||
rw.QtyQualified, rw.QtyPending, rw.QtyRejected,
|
||||
strings.Join(rw.Zones, "、"), rw.BatchCount, rw.SnCount,
|
||||
unixFmt(rw.MaterialCreatedAt), unixFmt(rw.CreatedAt),
|
||||
@@ -783,8 +759,6 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
PositionNo: r.URL.Query().Get("positionNo"),
|
||||
QualityStatus: r.URL.Query().Get("qualityStatus"),
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
ItemType: atoi(r.URL.Query().Get("itemType"), 0),
|
||||
ItemTypeNot: atoi(r.URL.Query().Get("itemTypeNot"), 0),
|
||||
InboundNo: r.URL.Query().Get("inboundNo"),
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
@@ -801,7 +775,7 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
for _, rw := range rows {
|
||||
matrix = append(matrix, []any{
|
||||
rw.ID, rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
typeLabelOf(rw.ManageMode, rw.ItemType), rw.ZoneCode, rw.ShelfNo, rw.LayerNo, rw.PositionNo,
|
||||
typeLabelOf(rw.MatManageMode), rw.ZoneCode, rw.ShelfNo, rw.LayerNo, rw.PositionNo,
|
||||
rw.QtyQualified, rw.QtyPending, rw.QtyRejected,
|
||||
rw.TotalQty, rw.LockedQty, rw.AvailQty, rw.BatchCount, rw.SnCount,
|
||||
unixFmt(rw.CreatedAt),
|
||||
@@ -849,11 +823,11 @@ func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 物料品类(itemType)映射:itemType=4 → 其他(辅料/工装/试验设备)
|
||||
// 物料类型(manageMode)映射:manage_mode=3 → 其他(辅料/工装/试验设备)
|
||||
mats, _ := ctx.EntClient.Material.Query().All(ctx0())
|
||||
itemTypeMap := make(map[string]int, len(mats))
|
||||
manageModeMap := make(map[string]int, len(mats))
|
||||
for _, m := range mats {
|
||||
itemTypeMap[m.Code] = m.ItemType
|
||||
manageModeMap[m.Code] = m.ManageMode
|
||||
}
|
||||
|
||||
// 一区域一行:质量与类型均为列(数量口径:结构件按行数量、电气件按 1)
|
||||
@@ -880,7 +854,7 @@ func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) {
|
||||
g.QtyPending += qty
|
||||
}
|
||||
switch {
|
||||
case itemTypeMap[inv.MaterialCode] == invItemOther:
|
||||
case manageModeMap[inv.MaterialCode] == invManageOther:
|
||||
g.QtyOther += qty
|
||||
case inv.ManageMode == 2:
|
||||
g.QtyElec += qty
|
||||
|
||||
Reference in New Issue
Block a user