docs(deployment): 更新部署手册和业务架构说明

- 更新MES和WMS服务端口配置说明
- 详细说明产线工位设备配置(扫码枪+拧紧枪)
- 明确AGV配送和接驳台的模拟模式与真实对接方案
- 更新WMS中AGV配送默认模拟模式说明
- 完善厂内物流(AGV/接驳台)的业务架构约束
- 修订临时文档为详细的项目问题和解决方案规划
This commit is contained in:
SunYF
2026-09-20 14:52:36 +08:00
parent 5a33028003
commit f35e9b5b75
57 changed files with 6096 additions and 1000 deletions
+69 -7
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
@@ -165,6 +166,19 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
}
}
s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产+工位派工自动生成工位级备料单", map[string]any{"planDate": planDate, "count": created})
// 需求10:把每个工单的「工单级需求总量」同步给 WMS 台账(幂等,失败降级不阻塞)。
// 必须先同步 total_qty,后续自动出库才能在台账上累加 out_qty 并判定缺口/领料完结。
synced := map[string]bool{}
for _, g := range groups {
if synced[g.plan.OrderNo] {
continue
}
synced[g.plan.OrderNo] = true
if e := s.SyncOrderLedger(ctx, g.plan.OrderNo, operator); e != nil {
s.ctx.EventLog.Write(ctx, "material.ledger.sync_fail", "", operator, "material_request", g.plan.OrderNo,
"工单需求量同步 WMS 台账失败", map[string]any{"orderNo": g.plan.OrderNo, "error": e.Error()})
}
}
// 生成后立即自动出库(客户第1条:平时无需操作)。缺料/失败不阻塞生成,仅记预警等待补库存。
if _, short := s.AutoOutboundRequests(ctx, planDate, "", operator); len(short) > 0 {
s.ctx.EventLog.Write(ctx, "material.outbound.short", "", operator, "material_request", planDate,
@@ -174,6 +188,58 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
return created, nil, nil
}
// SyncOrderLedger 计算并向 WMS 下发某工单的「工单级物料需求总量」(需求10)。
//
// 需求量口径(临时.md §四 工单域):需求量 = 工单数量 × 单台用量 × (1+损耗率),向上取整。
// 工单级而非日级——用日级会误卡跨天生产(临时.md L283-285)。
// 多级 BOM 展开到叶子后按物料编码聚合(同编码合并),一个物料一行下发。
// WMS 侧按 orderNo+materialCode 幂等 upsert total_qty,可重复调用。
func (s *Service) SyncOrderLedger(ctx context.Context, orderNo, operator string) error {
wo, err := s.ctx.EntClient.WorkOrder.Query().
Where(workorder.WorkOrderNo(orderNo)).Only(ctx)
if err != nil {
return err
}
leaves := s.expandBomToLeaf(ctx, wo.ProductCode, bomNameOrDefault(""), 1, 0, map[string]bool{})
if len(leaves) == 0 {
return nil
}
type agg struct {
name string
qty float64
}
m := map[string]*agg{}
order := make([]string, 0, len(leaves))
for _, it := range leaves {
if it.materialCode == "" {
continue
}
need := float64(wo.Quantity) * it.unitQty * (1 + it.lossRate/100)
if need <= 0 {
continue
}
if a, ok := m[it.materialCode]; ok {
a.qty += need
} else {
m[it.materialCode] = &agg{name: it.materialName, qty: need}
order = append(order, it.materialCode)
}
}
items := make([]wmsclient.LedgerSyncItem, 0, len(order))
for _, code := range order {
a := m[code]
items = append(items, wmsclient.LedgerSyncItem{
MaterialCode: code,
MaterialName: a.name,
TotalQty: int(math.Ceil(a.qty - 1e-9)),
})
}
if len(items) == 0 {
return nil
}
return s.ctx.Wms.LedgerSync(ctx, orderNo, wo.ProductCode, operator, items)
}
// AutoOutboundRequests 工单备料自动出库(客户第1条:备料出库根据 MES 排产自动生成,平时无需操作)。
//
// 对指定排产日 / 工单下的备料单,按 FIFO 从 WMS「合格」在库库存自动领料:
@@ -356,7 +422,7 @@ type MaterialRequestQuery struct {
TargetDock string
StationNo string
Source string // PLAN / REFILL
Keyword string // 兜底匹配 备料单号/工单号/物料编码
RequestNo string // 备料单号(独立模糊筛选;列表页禁用关键字混搜)
}
// ListMaterialRequests 备料单查询(大小写不敏感)。
@@ -383,12 +449,8 @@ func (s *Service) ListMaterialRequests(ctx context.Context, o MaterialRequestQue
if o.Source != "" {
q = q.Where(materialrequest.Source(o.Source))
}
if o.Keyword != "" {
q = q.Where(materialrequest.Or(
materialrequest.RequestNoContainsFold(o.Keyword),
materialrequest.OrderNoContainsFold(o.Keyword),
materialrequest.MaterialCodeContainsFold(o.Keyword),
))
if o.RequestNo != "" {
q = q.Where(materialrequest.RequestNoContainsFold(o.RequestNo))
}
if o.StationNo != "" {
if n, e := strconv.Atoi(o.StationNo); e == nil {