Files
bj_power/bj_power_wms/internal/handler/zone_recommend.go
T
SunYF 1cc93795ce feat(mes): 添加定时同步可生产数量到WMS及BOM项相关标准字段
- 在main.go中添加定时任务每10分钟同步可生产数量到WMS系统
- 为BomItem实体添加relatedStandard字段及相关CRUD方法
- 为InspectionRecord实体添加reportNo、materialCode、materialName等字段
- 更新ent schema确保新字段的验证和默认值设置
- 添加必要的数据库迁移和字段映射逻辑
2026-09-17 12:49:07 +08:00

117 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"net/http"
"bj_power_wms/ent"
"bj_power_wms/ent/inventory"
"bj_power_wms/ent/zone"
"bj_power_wms/internal/svc"
)
// zoneLoc 库位推荐候选(历史常用位 / 空闲位共用):四级货位。
type zoneLoc struct {
ZoneCode string `json:"zoneCode"`
ZoneName string `json:"zoneName"`
ShelfNo string `json:"shelfNo"`
LayerNo string `json:"layerNo"`
PositionNo string `json:"positionNo"`
}
// locKey 四级货位归一键(NULL/空串归一为""),与 DB 组合唯一索引 ux_zones_location 语义一致。
func locKey(zoneCode, shelfNo, layerNo, positionNo string) string {
return zoneCode + "\x00" + shelfNo + "\x00" + layerNo + "\x00" + positionNo
}
// zoneRecommendHandler 库位推荐(客户诉求 问题记录 附二#4:拒绝全自动识别,
// 采用「历史常用位 + 同区空位优先」推荐,由库房一键确认或手改)。
//
// 入参:materialCode(必填,图号)。
// 返回(history/empty 均为四级货位对象,前端点击可一键回填区域+货架/层/位):
// - history: 该物料历史入库使用过的货位(按最近使用排序,最多 5 个)
// - empty: 空闲货位(zones 中启用且当前无在库/锁定库存引用,最多 10 个;同区域优先)
func zoneRecommendHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
materialCode := r.URL.Query().Get("materialCode")
if materialCode == "" {
fail(w, http.StatusBadRequest, "materialCode 必填")
return
}
// 1. 历史常用货位:该物料库存行按最近更新倒序,去重取前 5 个四级货位。
history := []zoneLoc{}
histSet := map[string]bool{}
preferredZone := ""
if invs, err := ctx.EntClient.Inventory.Query().
Where(inventory.MaterialCodeEQ(materialCode)).
Order(ent.Desc("updated_at"), ent.Desc("id")).
Limit(200).All(ctx0()); err == nil {
for _, inv := range invs {
if inv.ZoneCode == "" {
continue
}
key := locKey(inv.ZoneCode, inv.ShelfNo, inv.LayerNo, inv.PositionNo)
if histSet[key] {
continue
}
histSet[key] = true
if preferredZone == "" {
preferredZone = inv.ZoneCode
}
history = append(history, zoneLoc{
ZoneCode: inv.ZoneCode, ShelfNo: inv.ShelfNo, LayerNo: inv.LayerNo, PositionNo: inv.PositionNo,
})
if len(history) >= 5 {
break
}
}
}
// 2. 被占用货位集合:在库/锁定库存行引用的四级货位(U18:按货位而非区域判占用)。
occupied := map[string]bool{}
if rows, err := ctx.EntClient.Inventory.Query().
Where(inventory.StatusIn("在库", "锁定")).
Select(inventory.FieldZoneCode, inventory.FieldShelfNo, inventory.FieldLayerNo, inventory.FieldPositionNo).
All(ctx0()); err == nil {
for _, row := range rows {
if row.ZoneCode != "" {
occupied[locKey(row.ZoneCode, row.ShelfNo, row.LayerNo, row.PositionNo)] = true
}
}
}
// 3. 空闲货位:启用且四级货位未被占用的 zones 行;同区域(preferredZone)的排前面。
sameArea := []zoneLoc{}
otherArea := []zoneLoc{}
if zones, err := ctx.EntClient.Zone.Query().
Where(zone.StatusEQ("启用")).
Order(ent.Asc("id")).
All(ctx0()); err == nil {
for _, z := range zones {
if occupied[locKey(z.ZoneCode, z.ShelfNo, z.LayerNo, z.PositionNo)] {
continue
}
zl := zoneLoc{
ZoneCode: z.ZoneCode, ZoneName: z.ZoneName,
ShelfNo: z.ShelfNo, LayerNo: z.LayerNo, PositionNo: z.PositionNo,
}
// 同区判定:区域(zone_code)相同(U18zone_code 即区域级,不再靠前缀猜测)。
if preferredZone != "" && z.ZoneCode == preferredZone {
sameArea = append(sameArea, zl)
} else {
otherArea = append(otherArea, zl)
}
}
}
empty := append(sameArea, otherArea...)
if len(empty) > 10 {
empty = empty[:10]
}
ok(w, map[string]any{
"history": history,
"empty": empty,
})
}
}