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:
SunYF
2026-09-22 07:41:40 +08:00
parent 12ca493c61
commit 932700ca65
84 changed files with 698 additions and 3955 deletions
+6 -4
View File
@@ -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 "-"
}
+8 -4
View File
@@ -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 清单,请使用『结构件入库』")
+34 -13
View File
@@ -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})
+45 -18
View File
@@ -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)
+8 -3
View File
@@ -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))},
},
+46 -72
View File
@@ -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) // 品类(B14=其他入库物料)
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 // 品类 itemTypeB14=其他入库物料)
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