feat(mes): 添加定时同步可生产数量到WMS及BOM项相关标准字段

- 在main.go中添加定时任务每10分钟同步可生产数量到WMS系统
- 为BomItem实体添加relatedStandard字段及相关CRUD方法
- 为InspectionRecord实体添加reportNo、materialCode、materialName等字段
- 更新ent schema确保新字段的验证和默认值设置
- 添加必要的数据库迁移和字段映射逻辑
This commit is contained in:
SunYF
2026-09-17 12:49:07 +08:00
parent b4b274301d
commit 1cc93795ce
191 changed files with 24504 additions and 1250 deletions
+55 -25
View File
@@ -64,6 +64,9 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
Quantity int `json:"quantity"`
SnList []string `json:"snList"`
ZoneCode string `json:"zoneCode"`
ShelfNo string `json:"shelfNo"`
LayerNo string `json:"layerNo"`
PositionNo string `json:"positionNo"`
Operator string `json:"operator"`
ProductionDate string `json:"productionDate"`
Supplier string `json:"supplier"`
@@ -77,7 +80,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return
}
if req.MaterialCode == "" {
fail(w, http.StatusBadRequest, "物料编码必填")
fail(w, http.StatusBadRequest, "图号必填")
return
}
if req.ZoneCode == "" {
@@ -116,6 +119,21 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
inboundNo := "IB" + uuid.NewString()[:12]
// 质量状态自动带出(客户诉求 问题记录 L129:入库记录增加 合格/不合格 列)
// 按检测单号查最近一次检验结论:合格→合格;不合格且部分入库→部分;不合格→不合格;无记录→空(未关联)
qualityStatus := ""
if req.InspectionNo != "" {
if rec, qerr := ctx.EntClient.InspectionRecord.Query().
Where(inspectionrecord.InspectionNoEQ(req.InspectionNo)).
Order(ent.Desc("created_at"), ent.Desc("id")).
First(ctx0()); qerr == nil && rec != nil {
qualityStatus = rec.Status
if rec.Status == "不合格" && rec.DisposalType == "PARTIAL" {
qualityStatus = "部分"
}
}
}
// 开启事务:入库单 + 库存行 + 明细 原子提交
tx, err := ctx.EntClient.Tx(ctx0())
if err != nil {
@@ -136,12 +154,16 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
SetNillableMaterialName(strPtr(m.Name)).
SetManageMode(m.ManageMode).
SetNillableZoneCode(strPtr(req.ZoneCode)).
SetNillableShelfNo(strPtr(req.ShelfNo)).
SetNillableLayerNo(strPtr(req.LayerNo)).
SetNillablePositionNo(strPtr(req.PositionNo)).
SetQuantity(0).
SetNillableOperator(strPtr(req.Operator)).
SetNillableRemark(strPtr(req.Remark)).
SetInspectionNo(req.InspectionNo).
SetOwnershipType(ownershipTypeOrNone(req.OwnershipType)).
SetNillableOwnershipNo(strPtr(req.OwnershipNo)).
SetNillableQualityStatus(strPtr(qualityStatus)).
Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, "创建入库单失败: "+err.Error())
@@ -159,7 +181,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
}
batchNo := req.BatchNo
if batchNo == "" {
batchNo = genBatchNo(req.MaterialCode)
batchNo = genBatchNo(ctx, req.MaterialCode)
}
qty := req.Quantity
if qty <= 0 {
@@ -189,6 +211,9 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
SetNillableSupplier(strPtr(req.Supplier)).
SetQualityStatus("未检").
SetNillableZoneCode(strPtr(req.ZoneCode)).
SetNillableShelfNo(strPtr(req.ShelfNo)).
SetNillableLayerNo(strPtr(req.LayerNo)).
SetNillablePositionNo(strPtr(req.PositionNo)).
SetStatus("在库").
SetInboundNo(inboundNo).
Save(ctx0()); err != nil {
@@ -217,9 +242,10 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return
}
for _, sn := range req.SnList {
// 查重
// 查重(P2-2):SN 全局唯一(一物一码)——不再按 manage_mode 分区隔离,
// 避免同一 SN 在不同管理模式/物料下共存;DB 侧另有部分唯一索引 ux_inventories_sn_code 兜底。
exists, _ := tx.Inventory.Query().
Where(inventory.ManageModeEQ(2), inventory.SnCodeEQ(sn)).
Where(inventory.SnCodeEQ(sn)).
Exist(ctx0())
if exists {
fail(w, http.StatusConflict, "SN 已存在: "+sn)
@@ -233,6 +259,9 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
SetQuantity(1).SetLockedQty(0).
SetQualityStatus("未检").
SetNillableZoneCode(strPtr(req.ZoneCode)).
SetNillableShelfNo(strPtr(req.ShelfNo)).
SetNillableLayerNo(strPtr(req.LayerNo)).
SetNillablePositionNo(strPtr(req.PositionNo)).
SetStatus("在库").
SetInboundNo(inboundNo).
Save(ctx0()); err != nil {
@@ -428,15 +457,15 @@ func exportInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
rows := buildInboundRows(ctx, list)
// xlsx 导出:格式化 = xlsx → 直接生成二进制文件;否则保持 JSON 兼容
if r.URL.Query().Get("format") == "xlsx" {
headers := []string{"入库单号", "物料编码", "物料名称", "类型", "数量", "区域", "操作人", "备注", "检测单号", "明细条数", "入库时间"}
headers := []string{"入库单号", "图号", "物料名称", "类型", "数量", "区域", "库位", "归属单号", "检测单号", "质量状态", "操作人", "备注", "明细条数", "入库时间"}
matrix := make([][]any, 0, len(rows))
for _, row := range rows {
ib := row.InboundOrder
matrix = append(matrix, []any{
ib.InboundNo, ib.MaterialCode, ib.MaterialName,
manageModeLabel(ib.ManageMode), ib.Quantity, ib.ZoneCode,
manageModeLabel(ib.ManageMode), ib.Quantity, ib.ZoneCode, locLabel(ib.ZoneCode, ib.ShelfNo, ib.LayerNo, ib.PositionNo),
ib.OwnershipNo, ib.InspectionNo, ib.QualityStatus,
ib.Operator, ib.Remark, row.DetailCount,
ib.InspectionNo,
unixFmt(ib.CreatedAt),
})
}
@@ -457,23 +486,24 @@ func ownershipTypeOrNone(v string) string {
}
}
// genBatchNo 生成批次号:日期 + 自增
func genBatchNo(materialCode string) string {
// TODO: 使用数据库序列或 Redis INCR 保证自增唯一
// 暂用时间戳 + 随机后缀
now := nowStr()
return "B" + now + materialCodeSuffix(materialCode)
}
func nowStr() string {
// YYYYMMDDHHMMSS
const layout = "20060102150405"
return timeNow().Format(layout)
}
func materialCodeSuffix(code string) string {
if len(code) > 4 {
code = code[len(code)-4:]
// genBatchNo 生成批次号:物料编码-日期-流水号(客户诉求 问题记录 L88-92)。
// 格式 {materialCode}-YYYYMMDD-{3位流水},流水号按"当日该物料已有最大流水+1"取,
// 保证同物料同日不重号(跨物料因前缀不同天然不冲突)。可手填覆盖,非强制系统生成。
func genBatchNo(ctx *svc.ServiceContext, materialCode string) string {
day := timeNow().Format("20060102")
prefix := materialCode + "-" + day + "-"
// 查当日该物料已有批次中最大流水号
maxSeq := 0
rows, err := ctx.EntClient.Inventory.Query().
Where(inventory.ManageModeEQ(1), inventory.BatchNoHasPrefix(prefix)).
All(ctx0())
if err == nil {
for _, r := range rows {
suffix := strings.TrimPrefix(r.BatchNo, prefix)
if n, e := strconv.Atoi(suffix); e == nil && n > maxSeq {
maxSeq = n
}
}
}
return code
return fmt.Sprintf("%s%03d", prefix, maxSeq+1)
}