feat: 完成多模块迭代更新
- 新增过程巡检按步骤上报、数量不符上报、工位退料功能 - 增加工单工程编号三级归属字段 - 新增物料安全库存与缺货预警 - 新增附件管理、退库单管理模块 - 优化生产看板展示逻辑与前端页面文案 - 清理冗余的工艺路线相关代码与备份文件
This commit is contained in:
@@ -36,19 +36,22 @@ func resolveInspectionTarget(ctx *svc.ServiceContext, targetId string) (*ent.Inv
|
||||
func createInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
TargetType string `json:"targetType"`
|
||||
TargetId string `json:"targetId"`
|
||||
MaterialCode string `json:"materialCode"`
|
||||
InspectionType string `json:"inspectionType"`
|
||||
Status string `json:"status"` // 合格/不合格
|
||||
ResultValue string `json:"resultValue"`
|
||||
InspectQty int `json:"inspectQty"` // 检验数量
|
||||
PassQty int `json:"passQty"` // 合格数量
|
||||
CheckDesc string `json:"checkDesc"` // 检测说明
|
||||
ReportNo string `json:"reportNo"` // 报检单号(生产报检关联)
|
||||
Inspector string `json:"inspector"`
|
||||
Remark string `json:"remark"`
|
||||
InspectionNo string `json:"inspectionNo"`
|
||||
TargetType string `json:"targetType"`
|
||||
TargetId string `json:"targetId"`
|
||||
MaterialCode string `json:"materialCode"`
|
||||
InspectionType string `json:"inspectionType"`
|
||||
Status string `json:"status"` // 合格/不合格
|
||||
ResultValue string `json:"resultValue"`
|
||||
InspectQty int `json:"inspectQty"` // 检验数量
|
||||
PassQty int `json:"passQty"` // 合格数量
|
||||
CheckDesc string `json:"checkDesc"` // 检测说明
|
||||
ReportNo string `json:"reportNo"` // 报检单号(生产报检关联)
|
||||
Inspector string `json:"inspector"`
|
||||
Remark string `json:"remark"`
|
||||
InspectionNo string `json:"inspectionNo"`
|
||||
DisposalType string `json:"disposalType"` // NONE/RETURN/SCRAP/PARTIAL
|
||||
DisposalRemark string `json:"disposalRemark"`
|
||||
ReturnTrackingNo string `json:"returnTrackingNo"`
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
@@ -102,15 +105,19 @@ func createInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetNillableRemark(strPtr(req.Remark)).
|
||||
SetNillableReportNo(strPtr(req.ReportNo)).
|
||||
SetNillableInspectionNo(strPtr(req.InspectionNo)).
|
||||
SetDisposalType(disposalTypeOrNone(req.DisposalType)).
|
||||
SetNillableDisposalRemark(strPtr(req.DisposalRemark)).
|
||||
SetNillableReturnTrackingNo(strPtr(req.ReturnTrackingNo)).
|
||||
Save(ctx0())
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 同步库存检验状态:直接更新定位到的那条库存行(精确,绝不误伤)
|
||||
// 同步库存检验状态:直接更新定位到的那条库存行(精确,绝不误伤)。
|
||||
// 处置为"退货"时置 待退(隔离、禁止出库);否则不合格置 不合格(同样隔离)。
|
||||
if _, err = tx.Inventory.UpdateOneID(inv.ID).
|
||||
SetQualityStatus(req.Status).Save(ctx0()); err != nil {
|
||||
SetQualityStatus(qualityAfterInspection(req.Status, req.DisposalType)).Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "同步库存检验状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -134,13 +141,18 @@ func createInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
func batchFlipInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
TargetType string `json:"targetType"`
|
||||
TargetIds []string `json:"targetIds"`
|
||||
Status string `json:"status"`
|
||||
InspectQty int `json:"inspectQty"` // 检验数量
|
||||
PassQty int `json:"passQty"` // 合格数量
|
||||
CheckDesc string `json:"checkDesc"` // 检测说明
|
||||
Inspector string `json:"inspector"`
|
||||
TargetType string `json:"targetType"`
|
||||
TargetIds []string `json:"targetIds"`
|
||||
Status string `json:"status"`
|
||||
InspectQty int `json:"inspectQty"` // 检验数量
|
||||
PassQty int `json:"passQty"` // 合格数量
|
||||
CheckDesc string `json:"checkDesc"` // 检测说明
|
||||
Inspector string `json:"inspector"`
|
||||
ReportNo string `json:"reportNo"` // 报检单号
|
||||
InspectionNo string `json:"inspectionNo"` // 检测单号
|
||||
DisposalType string `json:"disposalType"` // NONE/RETURN/SCRAP/PARTIAL
|
||||
DisposalRemark string `json:"disposalRemark"` // 处置说明
|
||||
ReturnTrackingNo string `json:"returnTrackingNo"` // 退货货运单号
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
@@ -195,12 +207,17 @@ func batchFlipInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetPassQty(req.PassQty).
|
||||
SetNillableCheckDesc(strPtr(req.CheckDesc)).
|
||||
SetNillableInspector(strPtr(req.Inspector)).
|
||||
SetNillableReportNo(strPtr(req.ReportNo)).
|
||||
SetNillableInspectionNo(strPtr(req.InspectionNo)).
|
||||
SetDisposalType(disposalTypeOrNone(req.DisposalType)).
|
||||
SetNillableDisposalRemark(strPtr(req.DisposalRemark)).
|
||||
SetNillableReturnTrackingNo(strPtr(req.ReturnTrackingNo)).
|
||||
Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "创建检验记录失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if _, err = tx.Inventory.UpdateOneID(t.inv.ID).
|
||||
SetQualityStatus(req.Status).Save(ctx0()); err != nil {
|
||||
SetQualityStatus(qualityAfterInspection(req.Status, req.DisposalType)).Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "同步库存检验状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -359,6 +376,149 @@ func exportInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// disposalTypeOrNone 处置方式归一化(客户诉求 L137-145:退货/报废/部分入库)
|
||||
func disposalTypeOrNone(v string) string {
|
||||
switch v {
|
||||
case "RETURN", "SCRAP", "PARTIAL":
|
||||
return v
|
||||
default:
|
||||
return "NONE"
|
||||
}
|
||||
}
|
||||
|
||||
// qualityAfterInspection 检验后的库存质量状态:
|
||||
// 合格 → 合格(可出库);不合格且选退货 → 待退(隔离);其余 → 不合格(隔离)。
|
||||
func qualityAfterInspection(status, disposalType string) string {
|
||||
if status == "合格" {
|
||||
return "合格"
|
||||
}
|
||||
if disposalTypeOrNone(disposalType) == "RETURN" {
|
||||
return "待退"
|
||||
}
|
||||
return "不合格"
|
||||
}
|
||||
|
||||
// disposeInspectionHandler 不合格品处置(退货 / 报废 / 部分入库)
|
||||
// POST /api/inspection/dispose body: { recordId, disposalType, disposalRemark, returnTrackingNo, operator }
|
||||
//
|
||||
// RETURN 退货 → 库存置「待退」隔离(保持数量,等待退货运单确认后出库)
|
||||
// SCRAP 报废 → 库存置「不合格」且生命周期「报废」,退出可用库存
|
||||
// PARTIAL 部分入库 → 仅批次管理:按合格数拆行(合格行入库 + 差额行隔离),数量守恒
|
||||
func disposeInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
RecordID int `json:"recordId"`
|
||||
DisposalType string `json:"disposalType"`
|
||||
DisposalRemark string `json:"disposalRemark"`
|
||||
ReturnTrackingNo string `json:"returnTrackingNo"`
|
||||
Operator string `json:"operator"`
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil || req.RecordID <= 0 {
|
||||
fail(w, http.StatusBadRequest, "参数错误:recordId 必填")
|
||||
return
|
||||
}
|
||||
dt := disposalTypeOrNone(req.DisposalType)
|
||||
if dt == "NONE" {
|
||||
fail(w, http.StatusBadRequest, "请选择处置方式(退货/报废/部分入库)")
|
||||
return
|
||||
}
|
||||
rec, err := ctx.EntClient.InspectionRecord.Get(ctx0(), req.RecordID)
|
||||
if err != nil {
|
||||
fail(w, http.StatusNotFound, "检验记录不存在")
|
||||
return
|
||||
}
|
||||
if rec.Status != "不合格" {
|
||||
fail(w, http.StatusBadRequest, "仅「不合格」记录可执行处置")
|
||||
return
|
||||
}
|
||||
inv, err := resolveInspectionTarget(ctx, rec.TargetID)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if dt == "RETURN" && req.ReturnTrackingNo == "" {
|
||||
fail(w, http.StatusBadRequest, "退货处置必须填写货运单号")
|
||||
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()
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = tx.InspectionRecord.UpdateOneID(rec.ID).
|
||||
SetDisposalType(dt).
|
||||
SetNillableDisposalRemark(strPtr(req.DisposalRemark)).
|
||||
SetNillableReturnTrackingNo(strPtr(req.ReturnTrackingNo)).
|
||||
Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "更新处置信息失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
switch dt {
|
||||
case "RETURN":
|
||||
if _, err = tx.Inventory.UpdateOneID(inv.ID).SetQualityStatus("待退").Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
case "SCRAP":
|
||||
if _, err = tx.Inventory.UpdateOneID(inv.ID).SetQualityStatus("不合格").SetStatus("报废").Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
case "PARTIAL":
|
||||
if inv.ManageMode != 1 {
|
||||
fail(w, http.StatusBadRequest, "部分入库仅适用于批次管理物料(结构件)")
|
||||
return
|
||||
}
|
||||
total := inv.Quantity
|
||||
if rec.PassQty <= 0 || rec.PassQty >= total {
|
||||
fail(w, http.StatusBadRequest, fmt.Sprintf("部分入库:合格数须在 0 与批次数量(%d) 之间", total))
|
||||
return
|
||||
}
|
||||
bad := total - rec.PassQty
|
||||
if _, err = tx.Inventory.UpdateOneID(inv.ID).
|
||||
SetQuantity(rec.PassQty).SetQualityStatus("合格").Save(ctx0()); err != nil {
|
||||
fail(w, http.StatusInternalServerError, "拆分合格批次失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if _, err = tx.Inventory.Create().
|
||||
SetManageMode(1).
|
||||
SetCategory(inv.Category).
|
||||
SetMaterialCode(inv.MaterialCode).
|
||||
SetNillableMaterialName(strPtr(inv.MaterialName)).
|
||||
SetBatchNo(inv.BatchNo + "-NG").
|
||||
SetQuantity(bad).SetLockedQty(0).
|
||||
SetQualityStatus("不合格").
|
||||
SetNillableZoneCode(strPtr(inv.ZoneCode)).
|
||||
SetStatus("在库").
|
||||
SetNillableInboundNo(strPtr(inv.InboundNo)).
|
||||
SetRemark("不合格隔离(部分入库差额)").
|
||||
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(), "inspection.dispose", r.Header.Get("X-Username"), "inspection", rec.TargetID,
|
||||
"不合格处置 "+rec.TargetID+" → "+dt, map[string]any{"recordId": rec.ID, "disposalType": dt})
|
||||
ok(w, map[string]any{"recordId": rec.ID, "disposalType": dt})
|
||||
}
|
||||
}
|
||||
|
||||
// statsInspectionHandler 检验统计:按物料汇总检验次数/合格数/不合格数/合格率
|
||||
// 对应《问题记录》附二9 的替代方案(不合并记录,另出统计视图)
|
||||
func statsInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
|
||||
Reference in New Issue
Block a user