From 47573e44b0f0b5ec8ef2dbe45f266d4f388fc9ce Mon Sep 17 00:00:00 2001 From: SunYF <123@hard_man.com> Date: Tue, 1 Sep 2026 14:30:26 +0800 Subject: [PATCH] =?UTF-8?q?feat&fix:=20=E5=AE=8C=E5=96=84=E5=8D=8A?= =?UTF-8?q?=E6=88=90=E5=93=81/=E5=BA=8F=E5=88=97=E5=8F=B7=E9=97=AD?= =?UTF-8?q?=E7=8E=AF=E6=B5=81=E8=BD=AC=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 调整物料编码字段为可空,优化注释说明 2. 修复Vite构建自动清空产物目录冲突问题 3. 优化前端提示文案与表单占位符 4. 为各前端项目添加内联SVG图标 5. 重构MES半成品流转逻辑,不再静默吞掉WMS错误并记录日志 6. 生成并添加空的WMS客户端静态文件 7. 完善Ent ORM模型的空值支持与查询谓词 8. 优化半成品入库接口,支持doneProcessCodes数组格式,新增SN反查物料编码逻辑 9. 更新帮助文档与操作手册 --- bj_power_dashboard/index.html | 4 +- bj_power_mes/frontend/index.html | 2 + bj_power_mes/internal/logic/semi.go | 7 +++- bj_power_wms/ent/migrate/schema.go | 4 +- bj_power_wms/ent/mutation.go | 38 +++++++++++++++++++ bj_power_wms/ent/semifinished.go | 2 +- bj_power_wms/ent/semifinished/where.go | 10 +++++ bj_power_wms/ent/semifinished_create.go | 31 +++++++++++++-- bj_power_wms/ent/semifinished_update.go | 18 +++++++++ bj_power_wms/ent/serialnumber.go | 2 +- bj_power_wms/ent/serialnumber/where.go | 10 +++++ bj_power_wms/ent/serialnumber_create.go | 31 +++++++++++++-- bj_power_wms/ent/serialnumber_update.go | 18 +++++++++ bj_power_wms/internal/handler/misc.go | 37 +++++++++++++++--- bj_power_wms/schema/semi_finished.go | 2 +- bj_power_wms/schema/serial_number.go | 2 +- bj_power_wms_client/frontend/index.html | 4 +- bj_power_wms_client/frontend/src/help.js | 17 ++++++--- .../frontend/src/pages/Inspection.vue | 2 +- .../frontend/src/pages/Semi.vue | 2 +- .../frontend/src/pages/Stocktake.vue | 2 +- bj_power_wms_client/frontend/vite.config.js | 4 +- bj_power_wms_client/web/static/index.html | 15 ++++++++ bj_power_workstation/frontend/index.html | 2 + 操作手册.md | 33 ++++++++++++++++ 25 files changed, 267 insertions(+), 32 deletions(-) create mode 100644 bj_power_wms_client/web/static/index.html diff --git a/bj_power_dashboard/index.html b/bj_power_dashboard/index.html index 5bb281c..4a04c78 100644 --- a/bj_power_dashboard/index.html +++ b/bj_power_dashboard/index.html @@ -3,7 +3,9 @@ - 生产看板 - 北京电力智能产线 + 生产看板 + +
diff --git a/bj_power_mes/frontend/index.html b/bj_power_mes/frontend/index.html index 1262f02..7d18668 100644 --- a/bj_power_mes/frontend/index.html +++ b/bj_power_mes/frontend/index.html @@ -4,6 +4,8 @@ MES 产线控制 + +
diff --git a/bj_power_mes/internal/logic/semi.go b/bj_power_mes/internal/logic/semi.go index 842170d..219ef1e 100644 --- a/bj_power_mes/internal/logic/semi.go +++ b/bj_power_mes/internal/logic/semi.go @@ -31,7 +31,8 @@ func (s *Service) SemiFlow(ctx context.Context, req SemiReq, operator string) er if err != nil { return err } - // 调用 WMS 内部接口 + // 调用 WMS 内部接口(本地 SemiFlow 已保存,下面把 WMS 同步结果如实返回, + // 不再静默吞掉——WMS 不可达或校验失败时必须让调用方感知,才能形成闭环)。 var wmsErr error if action == "INBOUND" { wmsErr = s.ctx.Wms.SemiInbound(ctx, req.Sn, req.OrderNo, req.DoneProcessCodes, operator) @@ -39,7 +40,9 @@ func (s *Service) SemiFlow(ctx context.Context, req SemiReq, operator string) er wmsErr = s.ctx.Wms.SemiOutbound(ctx, req.Sn, req.OrderNo, req.DoneProcessCodes, operator) } if wmsErr != nil { - _ = wmsErr + s.ctx.EventLog.Write(ctx, "semi.flow.wms_error", req.OrderNo, operator, "semi_flow", req.Sn, + "WMS 同步失败", map[string]any{"action": action, "error": wmsErr.Error()}) + return wmsErr } s.ctx.EventLog.Write(ctx, "semi.flow", req.OrderNo, operator, "semi_flow", req.Sn, "半成品流转", map[string]any{"action": action, "doneProcessCodes": req.DoneProcessCodes}) diff --git a/bj_power_wms/ent/migrate/schema.go b/bj_power_wms/ent/migrate/schema.go index d133d50..347b45b 100644 --- a/bj_power_wms/ent/migrate/schema.go +++ b/bj_power_wms/ent/migrate/schema.go @@ -373,7 +373,7 @@ var ( SemiFinishedsColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "sn", Type: field.TypeString, Unique: true}, - {Name: "material_code", Type: field.TypeString}, + {Name: "material_code", Type: field.TypeString, Nullable: true}, {Name: "completed_process", Type: field.TypeString, Nullable: true}, {Name: "quantity", Type: field.TypeInt, Default: 1}, {Name: "zone_code", Type: field.TypeString, Nullable: true}, @@ -404,7 +404,7 @@ var ( SerialNumbersColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "sn_code", Type: field.TypeString, Unique: true}, - {Name: "material_code", Type: field.TypeString}, + {Name: "material_code", Type: field.TypeString, Nullable: true}, {Name: "batch_no", Type: field.TypeString, Nullable: true}, {Name: "status", Type: field.TypeString, Default: "在库"}, {Name: "current_zone", Type: field.TypeString, Nullable: true}, diff --git a/bj_power_wms/ent/mutation.go b/bj_power_wms/ent/mutation.go index 0da6da6..838c252 100644 --- a/bj_power_wms/ent/mutation.go +++ b/bj_power_wms/ent/mutation.go @@ -10250,9 +10250,22 @@ func (m *SemiFinishedMutation) OldMaterialCode(ctx context.Context) (v string, e return oldValue.MaterialCode, nil } +// ClearMaterialCode clears the value of the "material_code" field. +func (m *SemiFinishedMutation) ClearMaterialCode() { + m.material_code = nil + m.clearedFields[semifinished.FieldMaterialCode] = struct{}{} +} + +// MaterialCodeCleared returns if the "material_code" field was cleared in this mutation. +func (m *SemiFinishedMutation) MaterialCodeCleared() bool { + _, ok := m.clearedFields[semifinished.FieldMaterialCode] + return ok +} + // ResetMaterialCode resets all changes to the "material_code" field. func (m *SemiFinishedMutation) ResetMaterialCode() { m.material_code = nil + delete(m.clearedFields, semifinished.FieldMaterialCode) } // SetCompletedProcess sets the "completed_process" field. @@ -10862,6 +10875,9 @@ func (m *SemiFinishedMutation) AddField(name string, value ent.Value) error { // mutation. func (m *SemiFinishedMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(semifinished.FieldMaterialCode) { + fields = append(fields, semifinished.FieldMaterialCode) + } if m.FieldCleared(semifinished.FieldCompletedProcess) { fields = append(fields, semifinished.FieldCompletedProcess) } @@ -10885,6 +10901,9 @@ func (m *SemiFinishedMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *SemiFinishedMutation) ClearField(name string) error { switch name { + case semifinished.FieldMaterialCode: + m.ClearMaterialCode() + return nil case semifinished.FieldCompletedProcess: m.ClearCompletedProcess() return nil @@ -11171,9 +11190,22 @@ func (m *SerialNumberMutation) OldMaterialCode(ctx context.Context) (v string, e return oldValue.MaterialCode, nil } +// ClearMaterialCode clears the value of the "material_code" field. +func (m *SerialNumberMutation) ClearMaterialCode() { + m.material_code = nil + m.clearedFields[serialnumber.FieldMaterialCode] = struct{}{} +} + +// MaterialCodeCleared returns if the "material_code" field was cleared in this mutation. +func (m *SerialNumberMutation) MaterialCodeCleared() bool { + _, ok := m.clearedFields[serialnumber.FieldMaterialCode] + return ok +} + // ResetMaterialCode resets all changes to the "material_code" field. func (m *SerialNumberMutation) ResetMaterialCode() { m.material_code = nil + delete(m.clearedFields, serialnumber.FieldMaterialCode) } // SetBatchNo sets the "batch_no" field. @@ -11877,6 +11909,9 @@ func (m *SerialNumberMutation) AddField(name string, value ent.Value) error { // mutation. func (m *SerialNumberMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(serialnumber.FieldMaterialCode) { + fields = append(fields, serialnumber.FieldMaterialCode) + } if m.FieldCleared(serialnumber.FieldBatchNo) { fields = append(fields, serialnumber.FieldBatchNo) } @@ -11906,6 +11941,9 @@ func (m *SerialNumberMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *SerialNumberMutation) ClearField(name string) error { switch name { + case serialnumber.FieldMaterialCode: + m.ClearMaterialCode() + return nil case serialnumber.FieldBatchNo: m.ClearBatchNo() return nil diff --git a/bj_power_wms/ent/semifinished.go b/bj_power_wms/ent/semifinished.go index 138e8c4..83bba8e 100644 --- a/bj_power_wms/ent/semifinished.go +++ b/bj_power_wms/ent/semifinished.go @@ -18,7 +18,7 @@ type SemiFinished struct { ID int `json:"id,omitempty"` // 半成品 SN Sn string `json:"sn,omitempty"` - // 物料编码 + // 物料编码(MES 未传时按 SN 反查,可空) MaterialCode string `json:"materialCode,omitempty"` // 已完成工序(如 135) CompletedProcess string `json:"completedProcess,omitempty"` diff --git a/bj_power_wms/ent/semifinished/where.go b/bj_power_wms/ent/semifinished/where.go index 415af2b..c44ad5f 100644 --- a/bj_power_wms/ent/semifinished/where.go +++ b/bj_power_wms/ent/semifinished/where.go @@ -218,6 +218,16 @@ func MaterialCodeHasSuffix(v string) predicate.SemiFinished { return predicate.SemiFinished(sql.FieldHasSuffix(FieldMaterialCode, v)) } +// MaterialCodeIsNil applies the IsNil predicate on the "material_code" field. +func MaterialCodeIsNil() predicate.SemiFinished { + return predicate.SemiFinished(sql.FieldIsNull(FieldMaterialCode)) +} + +// MaterialCodeNotNil applies the NotNil predicate on the "material_code" field. +func MaterialCodeNotNil() predicate.SemiFinished { + return predicate.SemiFinished(sql.FieldNotNull(FieldMaterialCode)) +} + // MaterialCodeEqualFold applies the EqualFold predicate on the "material_code" field. func MaterialCodeEqualFold(v string) predicate.SemiFinished { return predicate.SemiFinished(sql.FieldEqualFold(FieldMaterialCode, v)) diff --git a/bj_power_wms/ent/semifinished_create.go b/bj_power_wms/ent/semifinished_create.go index 1b773d1..a5e56e0 100644 --- a/bj_power_wms/ent/semifinished_create.go +++ b/bj_power_wms/ent/semifinished_create.go @@ -33,6 +33,14 @@ func (_c *SemiFinishedCreate) SetMaterialCode(v string) *SemiFinishedCreate { return _c } +// SetNillableMaterialCode sets the "material_code" field if the given value is not nil. +func (_c *SemiFinishedCreate) SetNillableMaterialCode(v *string) *SemiFinishedCreate { + if v != nil { + _c.SetMaterialCode(*v) + } + return _c +} + // SetCompletedProcess sets the "completed_process" field. func (_c *SemiFinishedCreate) SetCompletedProcess(v string) *SemiFinishedCreate { _c.mutation.SetCompletedProcess(v) @@ -189,9 +197,6 @@ func (_c *SemiFinishedCreate) check() error { if _, ok := _c.mutation.Sn(); !ok { return &ValidationError{Name: "sn", err: errors.New(`ent: missing required field "SemiFinished.sn"`)} } - if _, ok := _c.mutation.MaterialCode(); !ok { - return &ValidationError{Name: "material_code", err: errors.New(`ent: missing required field "SemiFinished.material_code"`)} - } if _, ok := _c.mutation.Quantity(); !ok { return &ValidationError{Name: "quantity", err: errors.New(`ent: missing required field "SemiFinished.quantity"`)} } @@ -343,6 +348,12 @@ func (u *SemiFinishedUpsert) UpdateMaterialCode() *SemiFinishedUpsert { return u } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SemiFinishedUpsert) ClearMaterialCode() *SemiFinishedUpsert { + u.SetNull(semifinished.FieldMaterialCode) + return u +} + // SetCompletedProcess sets the "completed_process" field. func (u *SemiFinishedUpsert) SetCompletedProcess(v string) *SemiFinishedUpsert { u.Set(semifinished.FieldCompletedProcess, v) @@ -531,6 +542,13 @@ func (u *SemiFinishedUpsertOne) UpdateMaterialCode() *SemiFinishedUpsertOne { }) } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SemiFinishedUpsertOne) ClearMaterialCode() *SemiFinishedUpsertOne { + return u.Update(func(s *SemiFinishedUpsert) { + s.ClearMaterialCode() + }) +} + // SetCompletedProcess sets the "completed_process" field. func (u *SemiFinishedUpsertOne) SetCompletedProcess(v string) *SemiFinishedUpsertOne { return u.Update(func(s *SemiFinishedUpsert) { @@ -903,6 +921,13 @@ func (u *SemiFinishedUpsertBulk) UpdateMaterialCode() *SemiFinishedUpsertBulk { }) } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SemiFinishedUpsertBulk) ClearMaterialCode() *SemiFinishedUpsertBulk { + return u.Update(func(s *SemiFinishedUpsert) { + s.ClearMaterialCode() + }) +} + // SetCompletedProcess sets the "completed_process" field. func (u *SemiFinishedUpsertBulk) SetCompletedProcess(v string) *SemiFinishedUpsertBulk { return u.Update(func(s *SemiFinishedUpsert) { diff --git a/bj_power_wms/ent/semifinished_update.go b/bj_power_wms/ent/semifinished_update.go index 8e89224..57925ad 100644 --- a/bj_power_wms/ent/semifinished_update.go +++ b/bj_power_wms/ent/semifinished_update.go @@ -55,6 +55,12 @@ func (_u *SemiFinishedUpdate) SetNillableMaterialCode(v *string) *SemiFinishedUp return _u } +// ClearMaterialCode clears the value of the "material_code" field. +func (_u *SemiFinishedUpdate) ClearMaterialCode() *SemiFinishedUpdate { + _u.mutation.ClearMaterialCode() + return _u +} + // SetCompletedProcess sets the "completed_process" field. func (_u *SemiFinishedUpdate) SetCompletedProcess(v string) *SemiFinishedUpdate { _u.mutation.SetCompletedProcess(v) @@ -239,6 +245,9 @@ func (_u *SemiFinishedUpdate) sqlSave(ctx context.Context) (_node int, err error if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(semifinished.FieldMaterialCode, field.TypeString, value) } + if _u.mutation.MaterialCodeCleared() { + _spec.ClearField(semifinished.FieldMaterialCode, field.TypeString) + } if value, ok := _u.mutation.CompletedProcess(); ok { _spec.SetField(semifinished.FieldCompletedProcess, field.TypeString, value) } @@ -326,6 +335,12 @@ func (_u *SemiFinishedUpdateOne) SetNillableMaterialCode(v *string) *SemiFinishe return _u } +// ClearMaterialCode clears the value of the "material_code" field. +func (_u *SemiFinishedUpdateOne) ClearMaterialCode() *SemiFinishedUpdateOne { + _u.mutation.ClearMaterialCode() + return _u +} + // SetCompletedProcess sets the "completed_process" field. func (_u *SemiFinishedUpdateOne) SetCompletedProcess(v string) *SemiFinishedUpdateOne { _u.mutation.SetCompletedProcess(v) @@ -540,6 +555,9 @@ func (_u *SemiFinishedUpdateOne) sqlSave(ctx context.Context) (_node *SemiFinish if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(semifinished.FieldMaterialCode, field.TypeString, value) } + if _u.mutation.MaterialCodeCleared() { + _spec.ClearField(semifinished.FieldMaterialCode, field.TypeString) + } if value, ok := _u.mutation.CompletedProcess(); ok { _spec.SetField(semifinished.FieldCompletedProcess, field.TypeString, value) } diff --git a/bj_power_wms/ent/serialnumber.go b/bj_power_wms/ent/serialnumber.go index 95325b0..91b37b5 100644 --- a/bj_power_wms/ent/serialnumber.go +++ b/bj_power_wms/ent/serialnumber.go @@ -18,7 +18,7 @@ type SerialNumber struct { ID int `json:"id,omitempty"` // 序列号 SnCode string `json:"snCode,omitempty"` - // 物料编码 + // 物料编码(可为空) MaterialCode string `json:"materialCode,omitempty"` // 所属批次 BatchNo string `json:"batchNo,omitempty"` diff --git a/bj_power_wms/ent/serialnumber/where.go b/bj_power_wms/ent/serialnumber/where.go index 80174a2..94919ce 100644 --- a/bj_power_wms/ent/serialnumber/where.go +++ b/bj_power_wms/ent/serialnumber/where.go @@ -228,6 +228,16 @@ func MaterialCodeHasSuffix(v string) predicate.SerialNumber { return predicate.SerialNumber(sql.FieldHasSuffix(FieldMaterialCode, v)) } +// MaterialCodeIsNil applies the IsNil predicate on the "material_code" field. +func MaterialCodeIsNil() predicate.SerialNumber { + return predicate.SerialNumber(sql.FieldIsNull(FieldMaterialCode)) +} + +// MaterialCodeNotNil applies the NotNil predicate on the "material_code" field. +func MaterialCodeNotNil() predicate.SerialNumber { + return predicate.SerialNumber(sql.FieldNotNull(FieldMaterialCode)) +} + // MaterialCodeEqualFold applies the EqualFold predicate on the "material_code" field. func MaterialCodeEqualFold(v string) predicate.SerialNumber { return predicate.SerialNumber(sql.FieldEqualFold(FieldMaterialCode, v)) diff --git a/bj_power_wms/ent/serialnumber_create.go b/bj_power_wms/ent/serialnumber_create.go index 64c6957..6df9678 100644 --- a/bj_power_wms/ent/serialnumber_create.go +++ b/bj_power_wms/ent/serialnumber_create.go @@ -33,6 +33,14 @@ func (_c *SerialNumberCreate) SetMaterialCode(v string) *SerialNumberCreate { return _c } +// SetNillableMaterialCode sets the "material_code" field if the given value is not nil. +func (_c *SerialNumberCreate) SetNillableMaterialCode(v *string) *SerialNumberCreate { + if v != nil { + _c.SetMaterialCode(*v) + } + return _c +} + // SetBatchNo sets the "batch_no" field. func (_c *SerialNumberCreate) SetBatchNo(v string) *SerialNumberCreate { _c.mutation.SetBatchNo(v) @@ -217,9 +225,6 @@ func (_c *SerialNumberCreate) check() error { if _, ok := _c.mutation.SnCode(); !ok { return &ValidationError{Name: "sn_code", err: errors.New(`ent: missing required field "SerialNumber.sn_code"`)} } - if _, ok := _c.mutation.MaterialCode(); !ok { - return &ValidationError{Name: "material_code", err: errors.New(`ent: missing required field "SerialNumber.material_code"`)} - } if _, ok := _c.mutation.Status(); !ok { return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "SerialNumber.status"`)} } @@ -379,6 +384,12 @@ func (u *SerialNumberUpsert) UpdateMaterialCode() *SerialNumberUpsert { return u } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SerialNumberUpsert) ClearMaterialCode() *SerialNumberUpsert { + u.SetNull(serialnumber.FieldMaterialCode) + return u +} + // SetBatchNo sets the "batch_no" field. func (u *SerialNumberUpsert) SetBatchNo(v string) *SerialNumberUpsert { u.Set(serialnumber.FieldBatchNo, v) @@ -597,6 +608,13 @@ func (u *SerialNumberUpsertOne) UpdateMaterialCode() *SerialNumberUpsertOne { }) } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SerialNumberUpsertOne) ClearMaterialCode() *SerialNumberUpsertOne { + return u.Update(func(s *SerialNumberUpsert) { + s.ClearMaterialCode() + }) +} + // SetBatchNo sets the "batch_no" field. func (u *SerialNumberUpsertOne) SetBatchNo(v string) *SerialNumberUpsertOne { return u.Update(func(s *SerialNumberUpsert) { @@ -1004,6 +1022,13 @@ func (u *SerialNumberUpsertBulk) UpdateMaterialCode() *SerialNumberUpsertBulk { }) } +// ClearMaterialCode clears the value of the "material_code" field. +func (u *SerialNumberUpsertBulk) ClearMaterialCode() *SerialNumberUpsertBulk { + return u.Update(func(s *SerialNumberUpsert) { + s.ClearMaterialCode() + }) +} + // SetBatchNo sets the "batch_no" field. func (u *SerialNumberUpsertBulk) SetBatchNo(v string) *SerialNumberUpsertBulk { return u.Update(func(s *SerialNumberUpsert) { diff --git a/bj_power_wms/ent/serialnumber_update.go b/bj_power_wms/ent/serialnumber_update.go index 7fa8f74..2546d4d 100644 --- a/bj_power_wms/ent/serialnumber_update.go +++ b/bj_power_wms/ent/serialnumber_update.go @@ -55,6 +55,12 @@ func (_u *SerialNumberUpdate) SetNillableMaterialCode(v *string) *SerialNumberUp return _u } +// ClearMaterialCode clears the value of the "material_code" field. +func (_u *SerialNumberUpdate) ClearMaterialCode() *SerialNumberUpdate { + _u.mutation.ClearMaterialCode() + return _u +} + // SetBatchNo sets the "batch_no" field. func (_u *SerialNumberUpdate) SetBatchNo(v string) *SerialNumberUpdate { _u.mutation.SetBatchNo(v) @@ -272,6 +278,9 @@ func (_u *SerialNumberUpdate) sqlSave(ctx context.Context) (_node int, err error if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(serialnumber.FieldMaterialCode, field.TypeString, value) } + if _u.mutation.MaterialCodeCleared() { + _spec.ClearField(serialnumber.FieldMaterialCode, field.TypeString) + } if value, ok := _u.mutation.BatchNo(); ok { _spec.SetField(serialnumber.FieldBatchNo, field.TypeString, value) } @@ -368,6 +377,12 @@ func (_u *SerialNumberUpdateOne) SetNillableMaterialCode(v *string) *SerialNumbe return _u } +// ClearMaterialCode clears the value of the "material_code" field. +func (_u *SerialNumberUpdateOne) ClearMaterialCode() *SerialNumberUpdateOne { + _u.mutation.ClearMaterialCode() + return _u +} + // SetBatchNo sets the "batch_no" field. func (_u *SerialNumberUpdateOne) SetBatchNo(v string) *SerialNumberUpdateOne { _u.mutation.SetBatchNo(v) @@ -615,6 +630,9 @@ func (_u *SerialNumberUpdateOne) sqlSave(ctx context.Context) (_node *SerialNumb if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(serialnumber.FieldMaterialCode, field.TypeString, value) } + if _u.mutation.MaterialCodeCleared() { + _spec.ClearField(serialnumber.FieldMaterialCode, field.TypeString) + } if value, ok := _u.mutation.BatchNo(); ok { _spec.SetField(serialnumber.FieldBatchNo, field.TypeString, value) } diff --git a/bj_power_wms/internal/handler/misc.go b/bj_power_wms/internal/handler/misc.go index 9774dfd..c2460cb 100644 --- a/bj_power_wms/internal/handler/misc.go +++ b/bj_power_wms/internal/handler/misc.go @@ -3,6 +3,8 @@ package handler import ( "encoding/json" "net/http" + "strconv" + "strings" "bj_power_wms/ent" "bj_power_wms/ent/ordermaterialledger" @@ -15,11 +17,15 @@ import ( // 半成品 / 包装 / 台账 相关(盘点见 stocktake.go) // semiInboundHandler 半成品入库(携带已完成工序) +// 与 MES 闭环:MES 工位报工暂存退库时调用本接口,把半成品 SN 入 WMS 库。 +// 为保持两系统解耦,materialCode 不强制要求——优先用请求传入, +// 其次按 SN 反查 WMS 序列号台账,都没有则留空(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"` @@ -29,8 +35,8 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "参数错误") return } - if req.Sn == "" || req.MaterialCode == "" { - fail(w, http.StatusBadRequest, "sn 和 materialCode 必填") + if req.Sn == "" { + fail(w, http.StatusBadRequest, "sn 必填") return } @@ -46,10 +52,29 @@ 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, ",") + } + + // 物料编码:优先请求传入,其次按 SN 反查序列号台账 + materialCode := req.MaterialCode + if materialCode == "" { + if snRec, e := ctx.EntClient.SerialNumber.Query(). + Where(serialnumber.SnCodeEQ(req.Sn)).Only(ctx0()); e == nil { + materialCode = snRec.MaterialCode + } + } + rec, err := ctx.EntClient.SemiFinished.Create(). SetSn(req.Sn). - SetMaterialCode(req.MaterialCode). - SetNillableCompletedProcess(strPtr(req.CompletedProcess)). + SetNillableMaterialCode(strPtr(materialCode)). + SetNillableCompletedProcess(strPtr(completed)). SetNillableZoneCode(strPtr(req.ZoneCode)). SetStatus("在库"). SetNillableOrderNo(strPtr(req.OrderNo)). @@ -59,10 +84,10 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - // 同时记入 SN 台账(半成品也是精密件) + // 同时记入 SN 台账(半成品也是精密件,便于按 SN 追溯) ctx.EntClient.SerialNumber.Create(). SetSnCode(req.Sn). - SetMaterialCode(req.MaterialCode). + SetNillableMaterialCode(strPtr(materialCode)). SetStatus("在库"). SetNillableCurrentZone(strPtr(req.ZoneCode)). SaveX(ctx0()) diff --git a/bj_power_wms/schema/semi_finished.go b/bj_power_wms/schema/semi_finished.go index 83ca7ac..10abb88 100644 --- a/bj_power_wms/schema/semi_finished.go +++ b/bj_power_wms/schema/semi_finished.go @@ -15,7 +15,7 @@ type SemiFinished struct { func (SemiFinished) Fields() []ent.Field { return []ent.Field{ field.String("sn").Unique().Comment("半成品 SN"), - field.String("material_code").Comment("物料编码"), + field.String("material_code").Optional().Comment("物料编码(MES 未传时按 SN 反查,可空)"), field.String("completed_process").Optional().Comment("已完成工序(如 135)"), field.Int("quantity").Default(1).Comment("数量"), field.String("zone_code").Optional().Comment("所在区域"), diff --git a/bj_power_wms/schema/serial_number.go b/bj_power_wms/schema/serial_number.go index 12bf3d4..ea595b8 100644 --- a/bj_power_wms/schema/serial_number.go +++ b/bj_power_wms/schema/serial_number.go @@ -14,7 +14,7 @@ type SerialNumber struct { func (SerialNumber) Fields() []ent.Field { return []ent.Field{ field.String("sn_code").Unique().Comment("序列号"), - field.String("material_code").Comment("物料编码"), + field.String("material_code").Optional().Comment("物料编码(可为空)"), field.String("batch_no").Optional().Comment("所属批次"), // 状态:在库/锁定/出库/使用/报废 field.String("status").Default("在库").Comment("在库/锁定/出库/使用/报废"), diff --git a/bj_power_wms_client/frontend/index.html b/bj_power_wms_client/frontend/index.html index c17cd40..cd43927 100644 --- a/bj_power_wms_client/frontend/index.html +++ b/bj_power_wms_client/frontend/index.html @@ -4,9 +4,11 @@ 库房客户端 + +
- \ No newline at end of file + diff --git a/bj_power_wms_client/frontend/src/help.js b/bj_power_wms_client/frontend/src/help.js index 487ba72..156af8f 100644 --- a/bj_power_wms_client/frontend/src/help.js +++ b/bj_power_wms_client/frontend/src/help.js @@ -69,7 +69,7 @@ export const helpInspection = { { name: '目标类型', source: '单选。', purpose: '检验对象是批次还是 SN', fill: '批次 BATCH / 序列号 SN' }, { name: '检验结论', source: '单选。', purpose: '判定结果', fill: '合格 / 不合格' }, { name: '检验员', source: '手动填写(默认当前用户)。', purpose: '谁检验的,追溯用', fill: '检验员姓名' }, - { name: '扫码输入', source: '扫码枪。来自批次号/SN条码。', purpose: '单个待检目标编号', fill: '扫批次号或 SN,回车提交' }, + { name: '扫码输入', source: '扫码枪。来自批次号/SN条码。', purpose: '单个待检的批次号或 SN', fill: '扫批次号或 SN,回车提交' }, { name: '批量编号', source: '扫码枪连续录入。', purpose: '一次性翻转多个目标的检验状态', fill: '每行一个编号' } ] } @@ -103,14 +103,19 @@ export const helpPackage = { export const helpSemi = { title: '半成品/成品(流转)', overview: - '产线工位"暂存/退回"的半成品或完工成品可在此入库(记录已完成工序),需要时再出库(重上线),MES 从下一工序继续。\n\n半成品生命周期:工位暂存退库 → 本页入库(记已完成工序) → 需要时本页出库→ MES 下发下一工序 → 工位继续装配。', + '产线工位"暂存/退回"的半成品或完工成品先在此入库(记已完成工序),需要时再出库(重上线),MES 从下一工序继续。\n\n闭环逻辑(与 MES 自动打通):\n' + + '① 工位终端"暂存退库" → 调 MES 工序流转接口 → MES 自动调 WMS /api/internal/semi/inbound 入库(带 SN、工单号、已完成工序码)。\n' + + '② WMS 收到后生成"半成品库存"记录,并写入 SN 台账,状态=在库。\n' + + '③ 该件后续要接着做时,在本页"半成品出库"或 MES 重上线 → WMS /api/internal/semi/outbound 把状态置为出库。\n' + + '④ MES 据此下发下一工序,工位继续装配,直至完工。\n\n' + + 'SN 是两系统唯一的追溯锚点,因此物料编码非必填:MES 调用时不传,WMS 会优先用请求里的、其次按 SN 反查序列号台账,都没有则留空,不影响闭环。', sections: [ { title: '半成品/成品入库', - overview: '把产线退回的半成品或成品收进库房。', + overview: '把产线退回的半成品或成品收进库房(手工录入或 MES 自动同步均走此接口)。', fields: [ - { name: '物料', source: '下拉选择。来自物料档案。', purpose: '入库物料', fill: '选择物料编码' }, - { name: '已完成工序', source: '自动带出/手工填写。数据来自工位终端的"已完成工序"上报。', purpose: '该件做到第几道工序,重上线时 MES 从这里继续', fill: '如 135,可空' }, + { name: '物料', source: '下拉选择。来自物料档案。', purpose: '入库物料(MES 自动同步时通常留空,由 WMS 反查)', fill: '选择物料编码' }, + { name: '已完成工序', source: 'MES 自动带出(doneProcessCodes,如 [1,3,5]);手工录入填逗号分隔,如 1,3,5。', purpose: '该件已做到哪些工序,重上线时 MES 从下一工序继续', fill: '如 1,3,5,可空' }, { name: '工单号', source: '自动带出/手工填写。来自工单。', purpose: '来源工单', fill: '输入工单号' }, { name: '区域', source: '手动填写(可空)。', purpose: '存放库区', fill: '如 Z05' }, { name: '扫码录入', source: '扫码枪连续扫码。来自工件 SN 条码。', purpose: '半成品 SN 清单', fill: '扫入 SN 回车追加' } @@ -118,7 +123,7 @@ export const helpSemi = { }, { title: '半成品出库(重上线)', - overview: '把半成品重新送上线,MES 会从记录的下一个工序继续。', + overview: '把半成品重新送上线,WMS 状态置为出库,MES 从记录的下一工序继续。', fields: [ { name: 'SN', source: '扫码枪/手输。来自工件 SN 条码。', purpose: '要重上线的半成品 SN', fill: '扫描或输入 SN,回车出库' } ] diff --git a/bj_power_wms_client/frontend/src/pages/Inspection.vue b/bj_power_wms_client/frontend/src/pages/Inspection.vue index a5a34cd..14ed2ad 100644 --- a/bj_power_wms_client/frontend/src/pages/Inspection.vue +++ b/bj_power_wms_client/frontend/src/pages/Inspection.vue @@ -20,7 +20,7 @@ onMounted(() => nextTick(() => scanRef.value?.focus())) async function submitSingle() { const id = targetId.value.trim() if (!id) { - ElMessage.warning('请扫入或输入目标编号') + ElMessage.warning('请扫入或输入批次号或 SN 序列号') return } if (!inspector.value.trim()) { diff --git a/bj_power_wms_client/frontend/src/pages/Semi.vue b/bj_power_wms_client/frontend/src/pages/Semi.vue index f681330..d902fbd 100644 --- a/bj_power_wms_client/frontend/src/pages/Semi.vue +++ b/bj_power_wms_client/frontend/src/pages/Semi.vue @@ -133,7 +133,7 @@ function search() { - + diff --git a/bj_power_wms_client/frontend/src/pages/Stocktake.vue b/bj_power_wms_client/frontend/src/pages/Stocktake.vue index ca5985b..f77caaf 100644 --- a/bj_power_wms_client/frontend/src/pages/Stocktake.vue +++ b/bj_power_wms_client/frontend/src/pages/Stocktake.vue @@ -183,7 +183,7 @@ async function loadHistory() { style="margin-bottom:10px" /> - + diff --git a/bj_power_wms_client/frontend/vite.config.js b/bj_power_wms_client/frontend/vite.config.js index 51b4425..c719b28 100644 --- a/bj_power_wms_client/frontend/vite.config.js +++ b/bj_power_wms_client/frontend/vite.config.js @@ -6,7 +6,9 @@ export default defineConfig({ plugins: [vue()], build: { outDir: '../web/static', - emptyOutDir: true + // 不自动清空产物目录:自动清空会与系统安全删除机制冲突导致构建失败, + // 需要重新完整构建时手动执行 rm -rf ../web/static 后再 build。 + emptyOutDir: false }, server: { port: 5173, diff --git a/bj_power_wms_client/web/static/index.html b/bj_power_wms_client/web/static/index.html new file mode 100644 index 0000000..7cea089 --- /dev/null +++ b/bj_power_wms_client/web/static/index.html @@ -0,0 +1,15 @@ + + + + + + 库房客户端 + + + + + + +
+ + diff --git a/bj_power_workstation/frontend/index.html b/bj_power_workstation/frontend/index.html index aca9ac6..b66a241 100644 --- a/bj_power_workstation/frontend/index.html +++ b/bj_power_workstation/frontend/index.html @@ -4,6 +4,8 @@ 工位终端 + +
diff --git a/操作手册.md b/操作手册.md index 4c7fa6a..477ae8e 100644 --- a/操作手册.md +++ b/操作手册.md @@ -134,3 +134,36 @@ MES 是装配线的控制中心:从「产品类型 → 工单 → 物料清单 | 排产保存失败 | 多天排产合计超过工单总数量,或该工单已完成/关闭 | | 工单删不掉 | 仅「已创建/待排产/已发布」且无排产、备料、生产数据的工单可删除 | | 修改密码后要重新登录 | 正常现象,密码变更即失效当前登录 | + +--- + +## 六、半成品闭环逻辑(MES ↔ WMS 重上线) + +**结论:能闭环。** 半成品(或完工成品)在产线中途暂存、或需跨工单续做时,通过 MES 与 WMS 的内部接口打通,SN 是唯一追溯锚点,两系统尽量解耦。 + +### 闭环流程 + +1. **暂存退库(MES 工位终端)** + 工位做到第 N 道工序后暂存退出 → 工位终端调 MES 工序流转接口(`action=INBOUND`)→ MES 本地记一条 `semi_flow`,并**自动**调用 WMS 内部接口 `/api/internal/semi/inbound`,带上:`sn`、`orderNo`、已完成的工序码 `doneProcessCodes`、操作人。 +2. **WMS 入库建账** + WMS 生成一条「半成品库存」记录(状态=在库),并同步写入 SN 台账。 + - 物料编码(materialCode)**不强制**:MES 调用时不传,WMS 优先用请求里的、其次按 SN 反查序列号台账,都没有则留空,不影响闭环。 + - 已完成工序:MES 传 `doneProcessCodes`(如 `[1,3,5]`),WMS 存为逗号串 `1,3,5`。 +3. **重上线(续做)** + 该件后续要接着做时,在本页「半成品出库」或 MES 侧触发 → WMS `/api/internal/semi/outbound` 把该 SN 状态置为出库。 +4. **MES 续做至完工** + MES 已知该件已完成的工序,下发下一工序 → 工位继续装配 → 直至完工、生成追溯。 + +> 也支持倒序:工单只建 1、3 序先做成半成品入库;新建另一个工单只勾 2 序,把半成品出库重上线,补齐中间工序变成成品。 + +### 同步失败怎么办 + +- MES 调 WMS 若失败(WMS 不可达 / 校验不通过),**不再静默吞掉**:MES 会写一条 `semi.flow.wms_error` 操作日志并返回错误,工位终端/接口会提示「WMS 同步失败」。 +- 本地 `semi_flow` 记录已保存,待 WMS 恢复后重试流转即可补齐闭环。 + +### 在 WMS 客户端怎么看 + +- **半成品/成品入库页**:手工录入时选物料、填已完成工序(逗号分隔)、扫 SN 提交。 +- **半成品出库页**:扫 SN 出库(重上线)。 +- **查询页**:按物料编码 / 状态(在库/出库)查半成品库存。 +- 帮助按钮(右上角)→ 本页有完整字段说明与数据链路。