feat: 完成客户第12条需求+多系统改造

1. 新增预警表添加工单号字段,支持按工单号检索预警
2. 添加工单过滤在制品/绩效报表/预警中心查询
3. 补全WMS台账字段口径与权限配置
4. 修复备料单与数量不符处理流程
5. 新增配送进度/库位联动/自动出库功能
6. 修复AGV配送状态更新逻辑
7. 优化退料与库存管理细节
This commit is contained in:
SunYF
2026-09-19 17:04:58 +08:00
parent 62e45b7379
commit 05b0b8a909
89 changed files with 7912 additions and 429 deletions
+54
View File
@@ -6,6 +6,7 @@ import (
"strings"
"bj_power_wms/ent"
"bj_power_wms/ent/inventory"
"bj_power_wms/ent/zone"
"bj_power_wms/internal/svc"
)
@@ -299,6 +300,59 @@ func updateZoneNodeHandler(ctx *svc.ServiceContext) http.HandlerFunc {
// listZonesForPicker 库位下拉(兼容 + 层级两用):
// - 不带 level:返回所有启用位置号(level4)扁平结构(兼容 Outbound/Stocktake/Inventory 旧调用)
// - 带 level:返回该层级节点(用于入库页区域→货架→层→位置 级联下拉)
// locKey 四级货位比较键:区域/货架忽略大小写(存量数据存在 z01/Z01 混用),层/位置原样。
func locKey(zoneCode, shelfNo, layerNo, positionNo string) string {
return strings.ToLower(strings.TrimSpace(zoneCode)) + "|" +
strings.ToLower(strings.TrimSpace(shelfNo)) + "|" +
strings.TrimSpace(layerNo) + "|" + strings.TrimSpace(positionNo)
}
// vacantZoneHandler 同区空位推荐:返回指定区域(不传 zoneCode 则不限区域)内「未被库存占用」的位置节点(level=4)。
// 用途:入库选中物料后,若该物料无历史库位(新物料),自动推荐同区空位,替代人工逐个挑选货架/层/位置。
// 占用判定:inventories 中 quantity>0 且 status 非「出库/报废」的记录,其四级货位视为已占用。
func vacantZoneHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
wantZone := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("zoneCode")))
limit := atoi(r.URL.Query().Get("limit"), 20)
if limit <= 0 || limit > 200 {
limit = 20
}
rows, err := ctx.EntClient.Zone.Query().
Where(zone.LevelEQ(zoneLevelPos), zone.StatusEQ("启用")).
Order(ent.Asc("zone_code"), ent.Asc("shelf_no"), ent.Asc("layer_no"), ent.Asc("code")).
All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
occupied := map[string]bool{}
if invs, ierr := ctx.EntClient.Inventory.Query().
Where(inventory.QuantityGT(0), inventory.StatusNotIn("出库", "报废")).
All(ctx0()); ierr == nil {
for _, iv := range invs {
occupied[locKey(iv.ZoneCode, iv.ShelfNo, iv.LayerNo, iv.PositionNo)] = true
}
}
list := make([]map[string]any, 0, limit)
for _, z := range rows {
if wantZone != "" && strings.ToLower(z.ZoneCode) != wantZone {
continue
}
if occupied[locKey(z.ZoneCode, z.ShelfNo, z.LayerNo, z.PositionNo)] {
continue
}
list = append(list, map[string]any{
"zoneCode": z.ZoneCode, "zoneName": z.ZoneName, "shelfNo": z.ShelfNo,
"layerNo": z.LayerNo, "positionNo": z.PositionNo, "name": z.Name, "code": z.Code,
})
if len(list) >= limit {
break
}
}
ok(w, list)
}
}
func listZonesForPicker(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
level := atoi(r.URL.Query().Get("level"), 0)