refactor: 重构产线工位与备料流程,移除冗余字段
1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段 2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据 3. 重构日排产为工位产量分配模式,替代原接驳台列表 4. 新增备料单工位级字段与分批出库支持 5. 移除定时同步可生产数量,改为WMS实时拉取接口 6. 过滤操作日志,排除登录/退出相关日志 7. 调整仪表盘工序展示逻辑为今日派工路线 8. 新增接驳台维护菜单与基础数据
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bj_power_wms/ent"
|
||||
"bj_power_wms/ent/inventory"
|
||||
"bj_power_wms/ent/ordermaterialledger"
|
||||
"bj_power_wms/internal/svc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// backflushRequest 反冲扣账请求(MES 报工完成时调用,X-API-TOKEN 内部接口)
|
||||
// 物料在该道工序被消耗,不依赖事先发料:报工完成即反冲扣库存。
|
||||
// items 由 MES 按「工单产品 BOM + 当前工序 processCode」算好(含损耗),WMS 只负责 FIFO 扣减与记账。
|
||||
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 反冲扣账(库存 FIFO 扣减 + 生成 backflush 类型出库单 + 台账累计)
|
||||
// 事务保证:出库单 + 库存扣减 + 出库明细 + 工单台账 原子提交。
|
||||
// 任意物料库存不足(FIFO 无法足额)时,扣足可用部分、记短欠日志、不阻塞产线(反冲降级同成品回流)。
|
||||
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{"outboundNo": "", "consumed": 0, "shortfall": false, "note": "无反冲物料"})
|
||||
return
|
||||
}
|
||||
|
||||
outboundNo := "BF" + uuid.NewString()[:12]
|
||||
|
||||
tx, err := ctx.EntClient.Tx(ctx0())
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
committed := false
|
||||
defer func() {
|
||||
if !committed {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 创建反冲出库单(type=backflush,数量后置更新)
|
||||
ob, err := tx.OutboundOrder.Create().
|
||||
SetOutboundNo(outboundNo).
|
||||
SetOutboundType("backflush").
|
||||
SetNillableOrderNo(strPtr(req.OrderNo)).
|
||||
SetMaterialCode(""). // 反冲单为整份 BOM,主表物料留空,明细承载
|
||||
SetManageMode(1).
|
||||
SetQuantity(0).
|
||||
SetNillableOperator(strPtr(req.Operator)).
|
||||
SetNillableTargetStation(strPtr(strconv.Itoa(req.StationNo))).
|
||||
SetRemark("反冲扣账(工序" + strconv.Itoa(req.ProcessCode) + ")").
|
||||
Save(ctx0())
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "创建反冲单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 2. FIFO 逐物料扣减 + 台账累计
|
||||
totalConsumed := 0
|
||||
shortfall := false
|
||||
for _, it := range req.Items {
|
||||
if it.MaterialCode == "" || it.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
consumed, sf := fifoConsume(tx, it.MaterialCode, it.Qty, outboundNo, req.OrderNo)
|
||||
totalConsumed += consumed
|
||||
if sf {
|
||||
shortfall = true
|
||||
logx.Errorf("backflush shortfall: order=%s material=%s need=%d got=%d", req.OrderNo, it.MaterialCode, it.Qty, consumed)
|
||||
}
|
||||
// 台账累计(仅挂工单时)
|
||||
if req.OrderNo != "" {
|
||||
ledger, lerr := tx.OrderMaterialLedger.Query().
|
||||
Where(
|
||||
ordermaterialledger.OrderNoEQ(req.OrderNo),
|
||||
ordermaterialledger.MaterialCodeEQ(it.MaterialCode),
|
||||
).Only(ctx0())
|
||||
if lerr == nil && ledger != nil {
|
||||
newOut := ledger.OutQty + consumed
|
||||
status := ledger.Status
|
||||
if newOut >= ledger.TotalQty {
|
||||
status = "领料完结"
|
||||
}
|
||||
upd := tx.OrderMaterialLedger.UpdateOneID(ledger.ID).
|
||||
SetOutQty(newOut).
|
||||
SetStatus(status)
|
||||
if status == "领料完结" && ledger.Status != "领料完结" {
|
||||
upd.SetCompletedAt(time.Now().Unix())
|
||||
}
|
||||
if _, lerr = upd.Save(ctx0()); lerr != nil {
|
||||
fail(w, http.StatusInternalServerError, "更新台账失败: "+lerr.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 回写出库单实际数量
|
||||
if _, err = tx.OutboundOrder.UpdateOneID(ob.ID).SetQuantity(totalConsumed).Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "更新反冲单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
committed = true
|
||||
|
||||
ctx.EventLog.Write(ctx0(), "material.backflush", req.Operator, "backflush", outboundNo,
|
||||
"反冲扣账 工单 "+req.OrderNo+" 工序"+strconv.Itoa(req.ProcessCode)+" 共消耗 ×"+strconv.Itoa(totalConsumed),
|
||||
map[string]any{"orderNo": req.OrderNo, "stationNo": req.StationNo, "consumed": totalConsumed, "shortfall": shortfall})
|
||||
logx.Infof("backflush: order=%s station=%d consumed=%d shortfall=%v", req.OrderNo, req.StationNo, totalConsumed, shortfall)
|
||||
ok(w, map[string]any{
|
||||
"outboundNo": outboundNo,
|
||||
"consumed": totalConsumed,
|
||||
"shortfall": shortfall,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// fifoConsume 按 created_at 升序(先进先出)扣减某物料库存,跨批次/单件 SN 统一处理。
|
||||
// 返回实际扣减数;库存不足足额时 shortfall=true(已扣可用部分),不报错。
|
||||
func fifoConsume(tx *ent.Tx, materialCode string, qty int, outboundNo, orderNo string) (int, bool) {
|
||||
rows, err := tx.Inventory.Query().
|
||||
Where(
|
||||
inventory.MaterialCodeEQ(materialCode),
|
||||
inventory.StatusEQ("在库"),
|
||||
inventory.QualityStatusNotIn("不合格", "待退"),
|
||||
).
|
||||
Order(ent.Asc("created_at")).
|
||||
All(ctx0())
|
||||
if err != nil {
|
||||
return 0, true
|
||||
}
|
||||
remaining := qty
|
||||
consumed := 0
|
||||
for _, row := range rows {
|
||||
if remaining <= 0 {
|
||||
break
|
||||
}
|
||||
avail := row.Quantity - row.LockedQty
|
||||
if avail <= 0 {
|
||||
continue
|
||||
}
|
||||
take := avail
|
||||
if take > remaining {
|
||||
take = remaining
|
||||
}
|
||||
if row.ManageMode == 2 {
|
||||
// SN 单件:整件消耗并置出库
|
||||
aff, e := tx.Inventory.Update().
|
||||
Where(inventory.ID(row.ID), inventory.StatusEQ("在库")).
|
||||
SetStatus("出库").Save(ctx0())
|
||||
if e != nil || aff == 0 {
|
||||
continue
|
||||
}
|
||||
if _, e = tx.OutboundDetail.Create().
|
||||
SetOutboundNo(outboundNo).
|
||||
SetSnCode(row.SnCode).
|
||||
SetMaterialCode(materialCode).
|
||||
SetQuantity(1).
|
||||
SetNillableOrderNo(strPtr(orderNo)).Save(ctx0()); e != nil {
|
||||
return consumed, true
|
||||
}
|
||||
consumed += 1
|
||||
remaining -= 1
|
||||
} else {
|
||||
// 批次:条件原子扣减(quantity>=take 才减,避免超扣/并发负库存)
|
||||
aff, e := tx.Inventory.Update().
|
||||
Where(inventory.ID(row.ID), inventory.QuantityGTE(take)).
|
||||
AddQuantity(-take).
|
||||
AddLockedQty(-min(take, row.LockedQty)).
|
||||
Save(ctx0())
|
||||
if e != nil || aff == 0 {
|
||||
continue
|
||||
}
|
||||
if _, e = tx.OutboundDetail.Create().
|
||||
SetOutboundNo(outboundNo).
|
||||
SetBatchNo(row.BatchNo).
|
||||
SetMaterialCode(materialCode).
|
||||
SetQuantity(take).
|
||||
SetNillableOrderNo(strPtr(orderNo)).Save(ctx0()); e != nil {
|
||||
return consumed, true
|
||||
}
|
||||
consumed += take
|
||||
remaining -= take
|
||||
}
|
||||
}
|
||||
return consumed, remaining > 0
|
||||
}
|
||||
Reference in New Issue
Block a user