chore: 批量完成项目多模块迭代优化

1. 废弃半成品库存表并统一库存主表
2. 修复列表排序与搜索大小写不敏感问题
3. 新增用户最近登录时间、工单BOM名称字段
4. 完善角色管理、工位选择器功能
5. 增加拧紧数据补录、成品自动回流WMS能力
6. 统一导出时间范围校验规则
7. 丰富物料/备料单筛选条件
8. 调整菜单与权限命名适配产品型号业务
This commit is contained in:
SunYF
2026-09-08 11:44:04 +08:00
parent 06689970e2
commit e852c7cd37
96 changed files with 4528 additions and 542 deletions
+57 -8
View File
@@ -22,6 +22,19 @@ type stockKey struct {
ZoneCode string
QualityStatus string
ManageMode int
Category int // 品类 1原材料/2半成品/3成品(统一主表收编后加入分组)
}
// categoryLabel 品类中文名
func categoryLabel(c int) string {
switch c {
case invCatSemi:
return "半成品"
case invCatFinished:
return "成品"
default:
return "原材料"
}
}
// stockAggRow 聚合主列表的行结构(按 物料+区域+质量+管理粒度 聚合,只返回数量,不铺开明细)
@@ -95,6 +108,7 @@ type aggregateStockArgs struct {
ZoneCode string
QualityStatus string
ManageMode string
Category string // 1原材料/2半成品/3成品/空=全部
InboundNo string
StartDate string
EndDate string
@@ -128,7 +142,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
))
}
if a.ZoneCode != "" {
q = q.Where(inventory.ZoneCodeEQ(a.ZoneCode))
q = q.Where(inventory.ZoneCodeEqualFold(a.ZoneCode))
}
if a.QualityStatus != "" {
q = q.Where(inventory.QualityStatusEQ(a.QualityStatus))
@@ -141,6 +155,13 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
} else if a.ManageMode == "2" {
q = q.Where(inventory.ManageModeEQ(2))
}
if a.Category == "1" {
q = q.Where(inventory.CategoryEQ(invCatRaw))
} else if a.Category == "2" {
q = q.Where(inventory.CategoryEQ(invCatSemi))
} else if a.Category == "3" {
q = q.Where(inventory.CategoryEQ(invCatFinished))
}
// 时间筛选:默认近3个月
startUnix := int64(0)
if a.StartDate != "" {
@@ -206,7 +227,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
if zc == "" {
zc = "未分区"
}
key := stockKey{inv.MaterialCode, zc, inv.QualityStatus, inv.ManageMode}
key := stockKey{inv.MaterialCode, zc, inv.QualityStatus, inv.ManageMode, inv.Category}
g, ok := group[key]
if !ok {
g = &agg{}
@@ -279,6 +300,7 @@ type materialAggRow struct {
MaterialName string `json:"materialName"`
Spec string `json:"spec"`
ManageMode int `json:"manageMode"` // 1结构件/2精密件
Category int `json:"category"` // 品类 1原材料/2半成品/3成品
TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数)
LockedQty int `json:"lockedQty"` // 锁定量
AvailQty int `json:"availQty"` // 可用量 = total - locked
@@ -299,6 +321,7 @@ type materialSummaryArgs struct {
MaterialCode string
MaterialName string
ManageMode string
Category string // 1原材料/2半成品/3成品/空=全部
Page int
PageSize int
}
@@ -341,6 +364,13 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
} else if a.ManageMode == "2" {
q = q.Where(inventory.ManageModeEQ(2))
}
if a.Category == "1" {
q = q.Where(inventory.CategoryEQ(invCatRaw))
} else if a.Category == "2" {
q = q.Where(inventory.CategoryEQ(invCatSemi))
} else if a.Category == "3" {
q = q.Where(inventory.CategoryEQ(invCatFinished))
}
all, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0())
if err != nil {
@@ -378,6 +408,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
qPending int
qRejected int
mMode int // 管理粒度(同物料唯一,取首见行的值)
mCategory int // 品类(同物料唯一,取首见行的值)
batchCnt int
snCnt int
zones []string
@@ -391,7 +422,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
key := inv.MaterialCode
g, ok := group[key]
if !ok {
g = &agg{mMode: inv.ManageMode, zoneSeen: map[string]bool{}}
g = &agg{mMode: inv.ManageMode, mCategory: inv.Category, zoneSeen: map[string]bool{}}
group[key] = g
order = append(order, key)
}
@@ -454,6 +485,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
MaterialName: name,
Spec: matSpec[code],
ManageMode: g.mMode,
Category: g.mCategory,
TotalQty: g.totalQty,
LockedQty: g.lockedQty,
AvailQty: g.totalQty - g.lockedQty,
@@ -492,6 +524,7 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
}
qualityStatus := r.URL.Query().Get("qualityStatus")
manageMode := atoi(r.URL.Query().Get("manageMode"), 0)
category := atoi(r.URL.Query().Get("category"), 0)
page := atoi(r.URL.Query().Get("page"), 1)
pageSize := atoi(r.URL.Query().Get("pageSize"), 50)
@@ -499,10 +532,10 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
q = q.Where(inventory.StatusIn("在库", "锁定"))
q = q.Where(inventory.Or(inventory.ManageModeEQ(2), inventory.QuantityGT(0)))
if materialCode != "" {
q = q.Where(inventory.MaterialCodeEQ(materialCode))
q = q.Where(inventory.MaterialCodeEqualFold(materialCode))
}
if zoneCode != "" {
q = q.Where(inventory.ZoneCodeEQ(zoneCode))
q = q.Where(inventory.ZoneCodeEqualFold(zoneCode))
}
if qualityStatus != "" {
q = q.Where(inventory.QualityStatusEQ(qualityStatus))
@@ -510,6 +543,9 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
if manageMode != 0 {
q = q.Where(inventory.ManageModeEQ(manageMode))
}
if category != 0 {
q = q.Where(inventory.CategoryEQ(category))
}
total, err := q.Count(ctx0())
if err != nil {
@@ -588,6 +624,11 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
sendExcel(w, xlsxFilename("物料汇总"), headers, matrix)
return
}
startDate, endDate, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate"))
if rerr != nil {
fail(w, http.StatusBadRequest, rerr.Error())
return
}
rows, _, err := aggregateStock(ctx, aggregateStockArgs{
MaterialCode: r.URL.Query().Get("materialCode"),
MaterialName: r.URL.Query().Get("materialName"),
@@ -595,8 +636,8 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
QualityStatus: r.URL.Query().Get("qualityStatus"),
ManageMode: r.URL.Query().Get("manageMode"),
InboundNo: r.URL.Query().Get("inboundNo"),
StartDate: r.URL.Query().Get("startDate"),
EndDate: r.URL.Query().Get("endDate"),
StartDate: startDate,
EndDate: endDate,
Page: 1,
PageSize: 1000000, // 全量
})
@@ -662,9 +703,17 @@ func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) {
if zc == "" {
zc = "未分区"
}
// 类型标签:批次 / 精密件 / 半成品 / 成品(统一主表收编后按品类细分)
t := "批次"
if inv.ManageMode == 2 {
t = "精密件"
switch inv.Category {
case invCatSemi:
t = "半成品"
case invCatFinished:
t = "成品"
default:
t = "精密件"
}
}
key := zc + "|" + inv.QualityStatus + "|" + t
g, ok := agg[key]