1. 新增预警表添加工单号字段,支持按工单号检索预警 2. 添加工单过滤在制品/绩效报表/预警中心查询 3. 补全WMS台账字段口径与权限配置 4. 修复备料单与数量不符处理流程 5. 新增配送进度/库位联动/自动出库功能 6. 修复AGV配送状态更新逻辑 7. 优化退料与库存管理细节
127 lines
4.5 KiB
Go
127 lines
4.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"bj_power_wms/ent/ordermaterialledger"
|
||
"bj_power_wms/internal/svc"
|
||
|
||
"github.com/zeromicro/go-zero/core/logx"
|
||
)
|
||
|
||
// backflushRequest 报工反冲核销请求(MES 报工完成时调用,X-API-TOKEN 内部接口)
|
||
//
|
||
// 口径(2026-09-19 定稿 · 方案 A「出库扣减,反冲只核销」):
|
||
// 一次物理位移 = 一次库存账变。备料出库时库存已经扣过,报工消耗**不再动库存、不再生成出库单**,
|
||
// 只把台账「已消耗(consumed_qty)」累加,供 已发出 / 已接料 / 已消耗 三方对账。
|
||
//
|
||
// 核销上限 = net_out_qty − consumed_qty,其中 net_out_qty = out_qty − returned_qty。
|
||
// 上限的意义:没走出库的料(工位自取辅料、现场替换)不会被核销成负数,
|
||
// 避免出现「库存从未扣过、台账却记了消耗」的漏扣假象。
|
||
type backflushRequest struct {
|
||
OrderNo string `json:"orderNo"` // 关联工单号(空=不做核销)
|
||
StationNo int `json:"stationNo"` // 当前工位号(记录用)
|
||
ProcessCode int `json:"processCode"` // 当前工序号(记录用)
|
||
Operator string `json:"operator"`
|
||
Items []backflushItem `json:"items"` // 本次消耗的物料明细
|
||
}
|
||
|
||
type backflushItem struct {
|
||
MaterialCode string `json:"materialCode"`
|
||
Qty int `json:"qty"` // 该工序单件消耗(已含损耗)* 完成件数
|
||
}
|
||
|
||
// backflushInternalHandler 报工反冲核销:只累加台账 consumed_qty。
|
||
// 不扣库存、不写出库单、不写 out_qty。库存不足/无台账不阻塞产线,只记 shortfall 供对账。
|
||
func backflushInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
var req backflushRequest
|
||
if err := parseJSON(r, &req); err != nil {
|
||
fail(w, http.StatusBadRequest, "参数错误: "+err.Error())
|
||
return
|
||
}
|
||
if len(req.Items) == 0 {
|
||
ok(w, map[string]any{"consumed": 0, "shortfall": false, "note": "无反冲物料"})
|
||
return
|
||
}
|
||
if req.OrderNo == "" {
|
||
// 不挂工单的消耗没有台账可承载,不阻塞产线
|
||
logx.Infof("backflush skipped: no orderNo, station=%d", req.StationNo)
|
||
ok(w, map[string]any{"consumed": 0, "shortfall": false, "note": "未挂工单,不做核销"})
|
||
return
|
||
}
|
||
|
||
tx, err := ctx.EntClient.Tx(ctx0())
|
||
if err != nil {
|
||
fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error())
|
||
return
|
||
}
|
||
committed := false
|
||
defer func() {
|
||
if !committed {
|
||
_ = tx.Rollback()
|
||
}
|
||
}()
|
||
|
||
totalConsumed := 0
|
||
shortfall := false
|
||
notes := make([]string, 0, 4)
|
||
for _, it := range req.Items {
|
||
if it.MaterialCode == "" || it.Qty <= 0 {
|
||
continue
|
||
}
|
||
ledger, lerr := tx.OrderMaterialLedger.Query().
|
||
Where(
|
||
ordermaterialledger.OrderNoEQ(req.OrderNo),
|
||
ordermaterialledger.MaterialCodeEQ(it.MaterialCode),
|
||
).Only(ctx0())
|
||
if lerr != nil || ledger == nil {
|
||
shortfall = true
|
||
notes = append(notes, it.MaterialCode+" 无台账")
|
||
continue
|
||
}
|
||
// 可核销量 = 净领用 − 已消耗(负数归零:退料后可能出现「已消耗 > 净领用」)
|
||
capa := (ledger.OutQty - ledger.ReturnedQty) - ledger.ConsumedQty
|
||
if capa < 0 {
|
||
capa = 0
|
||
}
|
||
take := it.Qty
|
||
if take > capa {
|
||
take = capa
|
||
shortfall = true
|
||
notes = append(notes, fmt.Sprintf("%s 超出可核销量(需%d/可%d)", it.MaterialCode, it.Qty, capa))
|
||
}
|
||
if take <= 0 {
|
||
continue
|
||
}
|
||
if _, uerr := tx.OrderMaterialLedger.UpdateOneID(ledger.ID).
|
||
AddConsumedQty(take).
|
||
SetUpdatedAt(time.Now().Unix()).
|
||
Save(ctx0()); uerr != nil {
|
||
fail(w, http.StatusInternalServerError, "更新台账失败: "+uerr.Error())
|
||
return
|
||
}
|
||
totalConsumed += take
|
||
}
|
||
|
||
if err = tx.Commit(); err != nil {
|
||
fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error())
|
||
return
|
||
}
|
||
committed = true
|
||
|
||
ctx.EventLog.Write(ctx0(), "material.backflush", req.Operator, "order_material_ledger", req.OrderNo,
|
||
"报工消耗核销 工单 "+req.OrderNo+" 工序"+strconv.Itoa(req.ProcessCode)+" 共核销 ×"+strconv.Itoa(totalConsumed),
|
||
map[string]any{"orderNo": req.OrderNo, "stationNo": req.StationNo, "consumed": totalConsumed, "shortfall": shortfall, "notes": notes})
|
||
logx.Infof("backflush(consume-only): order=%s station=%d consumed=%d shortfall=%v", req.OrderNo, req.StationNo, totalConsumed, shortfall)
|
||
ok(w, map[string]any{
|
||
"consumed": totalConsumed,
|
||
"shortfall": shortfall,
|
||
"notes": notes,
|
||
})
|
||
}
|
||
}
|