feat&refactor: 2026-09-22 半成品业务模型重构与功能完善
- 重构半成品流转逻辑:进度权威源改为 workpiece.currentStationNo,新增 completedText 快照字段,废除 doneProcessCodes - 清理物料品类中半成品类型,存量数据幂等清理,调整物料管理方式文案为批次/序列号 - 新增日排产状态专用更新接口,修复工单工艺校验逻辑 - 新增物料档案代理接口、深路径文件下载接口 - 优化前端界面文案与工位选择逻辑,补充追溯页半成品流转展示 - 调整WMS端物料品类校验与入库接口契约 - 新增/更新数据库表结构与实体类代码
This commit is contained in:
@@ -296,6 +296,14 @@ func applyColumnPatches(ctx context.Context) {
|
||||
slog.Warn("补齐列失败(可忽略): " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 2026-09-22 物料品类收编:半成品不是物料品类(是工件状态,由库存 category=2 承载)=====
|
||||
// 物料档案不再允许 item_type=2,幂等清理存量半成品品类行(重复执行删 0 行,无害)。
|
||||
if res, err := rawDB.ExecContext(ctx, `DELETE FROM materials WHERE item_type=2`); err != nil {
|
||||
slog.Warn("清理 item_type=2 物料失败(可忽略): " + err.Error())
|
||||
} else if n, err := res.RowsAffected(); err == nil && n > 0 {
|
||||
slog.Info(fmt.Sprintf("物料品类清理:已删除半成品品类(item_type=2)物料档案 %d 行", n))
|
||||
}
|
||||
}
|
||||
|
||||
// DropAllTables 删除 public schema 下全部表(CASCADE 连带索引/序列/外键约束)。
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// itemTypeLabel 物料品类标签:1原材料/2半成品/3成品/4其他(辅料/工装/试验设备)
|
||||
// itemTypeLabel 物料品类标签:1原材料/3成品/4其他(半成品品类已下线,防御性保留 2 的显示)
|
||||
func itemTypeLabel(t int) string {
|
||||
switch t {
|
||||
case invCatSemi:
|
||||
@@ -41,8 +41,11 @@ func validateMaterialRequired(name, spec, unit string, itemType, manageMode int)
|
||||
return "规格型号为必填项"
|
||||
case strings.TrimSpace(unit) == "":
|
||||
return "单位为必填项"
|
||||
case itemType < 1 || itemType > invItemOther:
|
||||
return "品类为必填项(原材料 / 半成品 / 成品 / 其他)"
|
||||
case itemType == invCatSemi:
|
||||
// 2026-09-22 定稿:半成品是工件状态(由库存 category=2 承载),不再是物料品类
|
||||
return "半成品不再是物料品类,请选择成品档案并在库存中记录工序状态"
|
||||
case itemType != invCatRaw && itemType != invCatFinished && itemType != invItemOther:
|
||||
return "品类为必填项(原材料 / 成品 / 其他)"
|
||||
case manageMode != 1 && manageMode != 2 && manageMode != invManageOther:
|
||||
return "类型为必填项(结构件 / 电气件 / 其他)"
|
||||
}
|
||||
@@ -60,9 +63,9 @@ func createMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
Description string `json:"description"`
|
||||
ManageMode int `json:"manageMode"` // 1批次/2序列号
|
||||
TemplateCode string `json:"templateCode"`
|
||||
ItemType int `json:"itemType"` // 1原材料/2半成品/3成品/4其他
|
||||
ItemType int `json:"itemType"` // 1原材料/3成品/4其他(半成品不是物料品类)
|
||||
SafetyStock int `json:"safetyStock"` // 安全库存(低于触发缺货预警)
|
||||
IsActive *bool `json:"isActive"` // 启用状态(nil 视为启用)
|
||||
IsActive *bool `json:"isActive"` // 启用状态(nil 视为启用)
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
@@ -317,7 +320,7 @@ func getMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// materialTypesHandler 内部接口:批量查询物料品类(item_type)。
|
||||
// body: {"codes":["A","B"]} → data: {"types":{"A":1,"B":3}}(1原材料/2半成品/3成品/4其他)。
|
||||
// body: {"codes":["A","B"]} → data: {"types":{"A":1,"B":3}}(1原材料/3成品/4其他)。
|
||||
// 供 MES 递归展开产品物料清单使用:成品子项(item_type=3)需继续展开到原材料。
|
||||
func materialTypesHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -500,8 +503,8 @@ func parseManageMode(s string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// parseItemType 解析导入模板"品类"列:原材料/半成品/成品/其他(兼容 1/2/3/4);无法识别返回 0。
|
||||
// 注意:"半成品"含"成品"子串,必须先判半成品再判成品。
|
||||
// parseItemType 解析导入模板"品类"列:原材料/成品/其他(兼容 1/3/4);无法识别返回 0。
|
||||
// 注意:"半成品"不再是物料品类(2026-09-22),且含"成品"子串,必须先排除再判成品。
|
||||
func parseItemType(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
@@ -511,13 +514,13 @@ func parseItemType(s string) int {
|
||||
case strings.Contains(s, "原材料"):
|
||||
return 1
|
||||
case strings.Contains(s, "半成品"):
|
||||
return 2
|
||||
return 0 // 半成品已不是物料品类,导入该行按"品类无法识别"整体拒绝
|
||||
case strings.Contains(s, "成品"):
|
||||
return 3
|
||||
case strings.Contains(s, "其他"):
|
||||
return 4
|
||||
}
|
||||
if n := atoi(s, 0); n >= 1 && n <= 4 {
|
||||
if n := atoi(s, 0); n == 1 || n == 3 || n == 4 {
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
@@ -653,7 +656,7 @@ func excelMaterialImportHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
continue
|
||||
}
|
||||
if catCol >= 0 && cell(catCol) != "" && pr.itemType == 0 {
|
||||
errs = append(errs, importErr{pr.row, pr.code, "品类无法识别,请填写「原材料/半成品/成品/其他」(兼容 1/2/3/4)"})
|
||||
errs = append(errs, importErr{pr.row, pr.code, "品类无法识别,请填写「原材料/成品/其他」(兼容 1/3/4;半成品不是物料品类)"})
|
||||
continue
|
||||
}
|
||||
exists, _ := ctx.EntClient.Material.Query().
|
||||
|
||||
@@ -2,7 +2,6 @@ package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,29 +25,31 @@ const (
|
||||
// 全部落 inventories(manage_mode=2 SN单件, category=2 半成品)。
|
||||
// 三个 semi 接口签名与响应结构保持不变,MES / 前端零改造平滑切换。
|
||||
|
||||
// semiInboundHandler 半成品/成品入库(携带已完成工序)
|
||||
// semiInboundHandler 半成品/成品入库(携带「已完成至工位」展示快照)
|
||||
// 与 MES 闭环:MES 工位报工暂存退库 / 完工回流时调用本接口,把 SN 入 WMS 库
|
||||
// (inventories,category=2 半成品 / 3 成品,客户诉求 问题记录 L222 半成品与成品要区分)。
|
||||
// 成品入库 9 字段(L218):SN/图号/归属/产品状态/相关标准/试验记录单号/记录员/记录日期/工单号。
|
||||
// 契约(2026-09-22 定稿):废除 completedProcess/doneProcessCodes 字段,改为 completedText
|
||||
// ——人类可读展示快照(如"已完成至工位5",空=未报工)。WMS 只原样存入
|
||||
// inventories.completed_process 展示,不解析、不参与任何逻辑。
|
||||
// 为保持两系统解耦,materialCode 不强制要求——优先用请求传入,都没有则留空(SN 本身即追溯锚点)。
|
||||
func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Sn string `json:"sn"`
|
||||
MaterialCode string `json:"materialCode"`
|
||||
DoneProcessCodes []int `json:"doneProcessCodes"`
|
||||
CompletedProcess string `json:"completedProcess"`
|
||||
ZoneCode string `json:"zoneCode"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Operator string `json:"operator"`
|
||||
Category int `json:"category"` // 2=半成品 3=成品(默认 2)
|
||||
OwnershipType string `json:"ownershipType"`
|
||||
OwnershipNo string `json:"ownershipNo"`
|
||||
ProductStatus string `json:"productStatus"` // 已通过/部分通过/返修(成品用)
|
||||
RelatedStandard string `json:"relatedStandard"` // 相关标准(试验方案号)
|
||||
TestRecordNo string `json:"testRecordNo"` // 试验记录单号
|
||||
RecordedBy string `json:"recordedBy"` // 记录员
|
||||
RecordedAt int64 `json:"recordedAt"` // 记录日期(unix 秒)
|
||||
Sn string `json:"sn"`
|
||||
MaterialCode string `json:"materialCode"`
|
||||
CompletedText string `json:"completedText"` // 已完成至工位(展示快照,空=未报工)
|
||||
ZoneCode string `json:"zoneCode"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Operator string `json:"operator"`
|
||||
Category int `json:"category"` // 2=半成品 3=成品(默认 2)
|
||||
OwnershipType string `json:"ownershipType"`
|
||||
OwnershipNo string `json:"ownershipNo"`
|
||||
ProductStatus string `json:"productStatus"` // 已通过/部分通过/返修(成品用)
|
||||
RelatedStandard string `json:"relatedStandard"` // 相关标准(试验方案号)
|
||||
TestRecordNo string `json:"testRecordNo"` // 试验记录单号
|
||||
RecordedBy string `json:"recordedBy"` // 记录员
|
||||
RecordedAt int64 `json:"recordedAt"` // 记录日期(unix 秒)
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
@@ -80,15 +81,8 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 已完成工序:MES 传 doneProcessCodes([]int) 优先,否则用 completedProcess 字符串
|
||||
completed := req.CompletedProcess
|
||||
if len(req.DoneProcessCodes) > 0 {
|
||||
parts := make([]string, 0, len(req.DoneProcessCodes))
|
||||
for _, c := range req.DoneProcessCodes {
|
||||
parts = append(parts, strconv.Itoa(c))
|
||||
}
|
||||
completed = strings.Join(parts, ",")
|
||||
}
|
||||
// 已完成至工位:MES 传人类可读快照,WMS 原样存展示(不解析、不参与任何逻辑)
|
||||
completed := req.CompletedText
|
||||
|
||||
// 物料编码:以请求传入为准;名称从物料档案补齐(档案缺失则用编码兜底)
|
||||
materialCode := req.MaterialCode
|
||||
|
||||
@@ -58,7 +58,7 @@ type stockAggRow struct {
|
||||
MaterialName string `json:"materialName"` // 物料名称
|
||||
Spec string `json:"spec"` // 规格型号
|
||||
ManageMode int `json:"manageMode"` // 库存行管理粒度 1结构件(批次)/2电气件(SN)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/2半成品/3成品/4其他)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/3成品/4其他)
|
||||
MatManageMode int `json:"matManageMode"` // 物料档案类型(1结构件/2电气件/3其他):类型标签判定用
|
||||
ZoneCode string `json:"zoneCode"` // 区域
|
||||
ShelfNo string `json:"shelfNo"` // 货架号
|
||||
@@ -90,8 +90,8 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
layerNo := r.URL.Query().Get("layerNo") // 第几层
|
||||
positionNo := r.URL.Query().Get("positionNo") // 位置号
|
||||
qualityStatus := r.URL.Query().Get("qualityStatus")
|
||||
manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部
|
||||
category := r.URL.Query().Get("category") // 1原材料/2半成品/3成品/空=全部(完工检 SN 拾取器按成品过滤)
|
||||
manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部
|
||||
category := r.URL.Query().Get("category") // 1原材料/2半成品/3成品/空=全部(完工检 SN 拾取器按成品过滤)
|
||||
inboundNo := r.URL.Query().Get("inboundNo")
|
||||
startDate := r.URL.Query().Get("startDate") // 默认近3个月(由接口保证)
|
||||
endDate := r.URL.Query().Get("endDate")
|
||||
@@ -374,7 +374,7 @@ type materialAggRow struct {
|
||||
MaterialName string `json:"materialName"`
|
||||
Spec string `json:"spec"`
|
||||
ManageMode int `json:"manageMode"` // 库存行管理粒度 1结构件(批次)/2电气件(SN)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/2半成品/3成品/4其他)
|
||||
ItemType int `json:"itemType"` // 物料品类(1原材料/3成品/4其他)
|
||||
MatManageMode int `json:"matManageMode"` // 物料档案类型(1结构件/2电气件/3其他):类型标签判定用
|
||||
Category int `json:"category"` // 品类 1原材料/2半成品/3成品
|
||||
TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;电气件=SN 行数)
|
||||
@@ -717,9 +717,9 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
rows, _, err := aggregateMaterialSummary(ctx, materialSummaryArgs{
|
||||
MaterialCode: r.URL.Query().Get("materialCode"),
|
||||
MaterialName: r.URL.Query().Get("materialName"),
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
Page: 1,
|
||||
ManageMode: r.URL.Query().Get("manageMode"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
Page: 1,
|
||||
PageSize: 1000000, // 全量
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user