diff --git a/attachments/2026/09/22/OTHER/2afc6827-a8e7-4577-83cf-d75f63a35b1c.png b/attachments/2026/09/22/OTHER/2afc6827-a8e7-4577-83cf-d75f63a35b1c.png new file mode 100644 index 0000000..b8a6131 Binary files /dev/null and b/attachments/2026/09/22/OTHER/2afc6827-a8e7-4577-83cf-d75f63a35b1c.png differ diff --git a/attachments/2026/09/22/OTHER/437e8739-c23c-45a1-9864-d2c456b411cd.png b/attachments/2026/09/22/OTHER/437e8739-c23c-45a1-9864-d2c456b411cd.png new file mode 100644 index 0000000..b8a6131 Binary files /dev/null and b/attachments/2026/09/22/OTHER/437e8739-c23c-45a1-9864-d2c456b411cd.png differ diff --git a/attachments/2026/09/22/PROCESS_PDF/3c0f89f3-7373-4a35-ba5f-ed16dbac7624.pdf b/attachments/2026/09/22/PROCESS_PDF/3c0f89f3-7373-4a35-ba5f-ed16dbac7624.pdf new file mode 100644 index 0000000..6ee4a11 Binary files /dev/null and b/attachments/2026/09/22/PROCESS_PDF/3c0f89f3-7373-4a35-ba5f-ed16dbac7624.pdf differ diff --git a/attachments/2026/09/22/PROCESS_PDF/d52509e5-3975-4ee3-9d9f-efc94e712ca9.png b/attachments/2026/09/22/PROCESS_PDF/d52509e5-3975-4ee3-9d9f-efc94e712ca9.png new file mode 100644 index 0000000..b8a6131 Binary files /dev/null and b/attachments/2026/09/22/PROCESS_PDF/d52509e5-3975-4ee3-9d9f-efc94e712ca9.png differ diff --git a/bj_power_mes/ent/migrate/schema.go b/bj_power_mes/ent/migrate/schema.go index 8013bf3..cf24e55 100644 --- a/bj_power_mes/ent/migrate/schema.go +++ b/bj_power_mes/ent/migrate/schema.go @@ -681,6 +681,7 @@ var ( {Name: "sn", Type: field.TypeString, Size: 64}, {Name: "order_no", Type: field.TypeString, Size: 64, Default: ""}, {Name: "done_process_codes", Type: field.TypeJSON, Nullable: true}, + {Name: "completed_text", Type: field.TypeString, Size: 64, Default: ""}, {Name: "action", Type: field.TypeString, Size: 20, Default: "INBOUND"}, {Name: "target_dock", Type: field.TypeString, Size: 20, Default: ""}, {Name: "operator", Type: field.TypeString, Size: 64, Default: ""}, @@ -705,7 +706,7 @@ var ( { Name: "semiflow_action", Unique: false, - Columns: []*schema.Column{SemiFlowColumns[4]}, + Columns: []*schema.Column{SemiFlowColumns[5]}, }, }, } diff --git a/bj_power_mes/ent/mutation.go b/bj_power_mes/ent/mutation.go index b2d2c4e..fb3a245 100644 --- a/bj_power_mes/ent/mutation.go +++ b/bj_power_mes/ent/mutation.go @@ -18563,6 +18563,7 @@ type SemiFlowMutation struct { orderNo *string doneProcessCodes *[]int appenddoneProcessCodes []int + completedText *string action *string targetDock *string operator *string @@ -18814,6 +18815,42 @@ func (m *SemiFlowMutation) ResetDoneProcessCodes() { delete(m.clearedFields, semiflow.FieldDoneProcessCodes) } +// SetCompletedText sets the "completedText" field. +func (m *SemiFlowMutation) SetCompletedText(s string) { + m.completedText = &s +} + +// CompletedText returns the value of the "completedText" field in the mutation. +func (m *SemiFlowMutation) CompletedText() (r string, exists bool) { + v := m.completedText + if v == nil { + return + } + return *v, true +} + +// OldCompletedText returns the old "completedText" field's value of the SemiFlow entity. +// If the SemiFlow object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SemiFlowMutation) OldCompletedText(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCompletedText is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCompletedText requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCompletedText: %w", err) + } + return oldValue.CompletedText, nil +} + +// ResetCompletedText resets all changes to the "completedText" field. +func (m *SemiFlowMutation) ResetCompletedText() { + m.completedText = nil +} + // SetAction sets the "action" field. func (m *SemiFlowMutation) SetAction(s string) { m.action = &s @@ -18992,7 +19029,7 @@ func (m *SemiFlowMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *SemiFlowMutation) Fields() []string { - fields := make([]string, 0, 7) + fields := make([]string, 0, 8) if m.sn != nil { fields = append(fields, semiflow.FieldSn) } @@ -19002,6 +19039,9 @@ func (m *SemiFlowMutation) Fields() []string { if m.doneProcessCodes != nil { fields = append(fields, semiflow.FieldDoneProcessCodes) } + if m.completedText != nil { + fields = append(fields, semiflow.FieldCompletedText) + } if m.action != nil { fields = append(fields, semiflow.FieldAction) } @@ -19028,6 +19068,8 @@ func (m *SemiFlowMutation) Field(name string) (ent.Value, bool) { return m.OrderNo() case semiflow.FieldDoneProcessCodes: return m.DoneProcessCodes() + case semiflow.FieldCompletedText: + return m.CompletedText() case semiflow.FieldAction: return m.Action() case semiflow.FieldTargetDock: @@ -19051,6 +19093,8 @@ func (m *SemiFlowMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldOrderNo(ctx) case semiflow.FieldDoneProcessCodes: return m.OldDoneProcessCodes(ctx) + case semiflow.FieldCompletedText: + return m.OldCompletedText(ctx) case semiflow.FieldAction: return m.OldAction(ctx) case semiflow.FieldTargetDock: @@ -19089,6 +19133,13 @@ func (m *SemiFlowMutation) SetField(name string, value ent.Value) error { } m.SetDoneProcessCodes(v) return nil + case semiflow.FieldCompletedText: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCompletedText(v) + return nil case semiflow.FieldAction: v, ok := value.(string) if !ok { @@ -19184,6 +19235,9 @@ func (m *SemiFlowMutation) ResetField(name string) error { case semiflow.FieldDoneProcessCodes: m.ResetDoneProcessCodes() return nil + case semiflow.FieldCompletedText: + m.ResetCompletedText() + return nil case semiflow.FieldAction: m.ResetAction() return nil diff --git a/bj_power_mes/ent/runtime.go b/bj_power_mes/ent/runtime.go index 4407a12..f55db85 100644 --- a/bj_power_mes/ent/runtime.go +++ b/bj_power_mes/ent/runtime.go @@ -1171,26 +1171,32 @@ func init() { semiflow.DefaultOrderNo = semiflowDescOrderNo.Default.(string) // semiflow.OrderNoValidator is a validator for the "orderNo" field. It is called by the builders before save. semiflow.OrderNoValidator = semiflowDescOrderNo.Validators[0].(func(string) error) + // semiflowDescCompletedText is the schema descriptor for completedText field. + semiflowDescCompletedText := semiflowFields[4].Descriptor() + // semiflow.DefaultCompletedText holds the default value on creation for the completedText field. + semiflow.DefaultCompletedText = semiflowDescCompletedText.Default.(string) + // semiflow.CompletedTextValidator is a validator for the "completedText" field. It is called by the builders before save. + semiflow.CompletedTextValidator = semiflowDescCompletedText.Validators[0].(func(string) error) // semiflowDescAction is the schema descriptor for action field. - semiflowDescAction := semiflowFields[4].Descriptor() + semiflowDescAction := semiflowFields[5].Descriptor() // semiflow.DefaultAction holds the default value on creation for the action field. semiflow.DefaultAction = semiflowDescAction.Default.(string) // semiflow.ActionValidator is a validator for the "action" field. It is called by the builders before save. semiflow.ActionValidator = semiflowDescAction.Validators[0].(func(string) error) // semiflowDescTargetDock is the schema descriptor for targetDock field. - semiflowDescTargetDock := semiflowFields[5].Descriptor() + semiflowDescTargetDock := semiflowFields[6].Descriptor() // semiflow.DefaultTargetDock holds the default value on creation for the targetDock field. semiflow.DefaultTargetDock = semiflowDescTargetDock.Default.(string) // semiflow.TargetDockValidator is a validator for the "targetDock" field. It is called by the builders before save. semiflow.TargetDockValidator = semiflowDescTargetDock.Validators[0].(func(string) error) // semiflowDescOperator is the schema descriptor for operator field. - semiflowDescOperator := semiflowFields[6].Descriptor() + semiflowDescOperator := semiflowFields[7].Descriptor() // semiflow.DefaultOperator holds the default value on creation for the operator field. semiflow.DefaultOperator = semiflowDescOperator.Default.(string) // semiflow.OperatorValidator is a validator for the "operator" field. It is called by the builders before save. semiflow.OperatorValidator = semiflowDescOperator.Validators[0].(func(string) error) // semiflowDescCreatedAt is the schema descriptor for createdAt field. - semiflowDescCreatedAt := semiflowFields[7].Descriptor() + semiflowDescCreatedAt := semiflowFields[8].Descriptor() // semiflow.DefaultCreatedAt holds the default value on creation for the createdAt field. semiflow.DefaultCreatedAt = semiflowDescCreatedAt.Default.(func() time.Time) // semiflowDescID is the schema descriptor for id field. diff --git a/bj_power_mes/ent/semiflow.go b/bj_power_mes/ent/semiflow.go index e094e3b..4190d6f 100644 --- a/bj_power_mes/ent/semiflow.go +++ b/bj_power_mes/ent/semiflow.go @@ -22,8 +22,10 @@ type SemiFlow struct { Sn string `json:"sn,omitempty"` // OrderNo holds the value of the "orderNo" field. OrderNo string `json:"orderNo,omitempty"` - // 已完成工序码 + // 已完成工序码(已废除,历史数据保留) DoneProcessCodes []int `json:"doneProcessCodes,omitempty"` + // 完成至工位快照(如:已完成至工位3;空=尚未报工) + CompletedText string `json:"completedText,omitempty"` // Action holds the value of the "action" field. Action string `json:"action,omitempty"` // TargetDock holds the value of the "targetDock" field. @@ -44,7 +46,7 @@ func (*SemiFlow) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case semiflow.FieldID: values[i] = new(sql.NullInt64) - case semiflow.FieldSn, semiflow.FieldOrderNo, semiflow.FieldAction, semiflow.FieldTargetDock, semiflow.FieldOperator: + case semiflow.FieldSn, semiflow.FieldOrderNo, semiflow.FieldCompletedText, semiflow.FieldAction, semiflow.FieldTargetDock, semiflow.FieldOperator: values[i] = new(sql.NullString) case semiflow.FieldCreatedAt: values[i] = new(sql.NullTime) @@ -89,6 +91,12 @@ func (_m *SemiFlow) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field doneProcessCodes: %w", err) } } + case semiflow.FieldCompletedText: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field completedText", values[i]) + } else if value.Valid { + _m.CompletedText = value.String + } case semiflow.FieldAction: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field action", values[i]) @@ -158,6 +166,9 @@ func (_m *SemiFlow) String() string { builder.WriteString("doneProcessCodes=") builder.WriteString(fmt.Sprintf("%v", _m.DoneProcessCodes)) builder.WriteString(", ") + builder.WriteString("completedText=") + builder.WriteString(_m.CompletedText) + builder.WriteString(", ") builder.WriteString("action=") builder.WriteString(_m.Action) builder.WriteString(", ") diff --git a/bj_power_mes/ent/semiflow/semiflow.go b/bj_power_mes/ent/semiflow/semiflow.go index 2bb2db8..624e3ad 100644 --- a/bj_power_mes/ent/semiflow/semiflow.go +++ b/bj_power_mes/ent/semiflow/semiflow.go @@ -19,6 +19,8 @@ const ( FieldOrderNo = "order_no" // FieldDoneProcessCodes holds the string denoting the doneprocesscodes field in the database. FieldDoneProcessCodes = "done_process_codes" + // FieldCompletedText holds the string denoting the completedtext field in the database. + FieldCompletedText = "completed_text" // FieldAction holds the string denoting the action field in the database. FieldAction = "action" // FieldTargetDock holds the string denoting the targetdock field in the database. @@ -37,6 +39,7 @@ var Columns = []string{ FieldSn, FieldOrderNo, FieldDoneProcessCodes, + FieldCompletedText, FieldAction, FieldTargetDock, FieldOperator, @@ -60,6 +63,10 @@ var ( DefaultOrderNo string // OrderNoValidator is a validator for the "orderNo" field. It is called by the builders before save. OrderNoValidator func(string) error + // DefaultCompletedText holds the default value on creation for the "completedText" field. + DefaultCompletedText string + // CompletedTextValidator is a validator for the "completedText" field. It is called by the builders before save. + CompletedTextValidator func(string) error // DefaultAction holds the default value on creation for the "action" field. DefaultAction string // ActionValidator is a validator for the "action" field. It is called by the builders before save. @@ -96,6 +103,11 @@ func ByOrderNo(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldOrderNo, opts...).ToFunc() } +// ByCompletedText orders the results by the completedText field. +func ByCompletedText(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCompletedText, opts...).ToFunc() +} + // ByAction orders the results by the action field. func ByAction(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAction, opts...).ToFunc() diff --git a/bj_power_mes/ent/semiflow/where.go b/bj_power_mes/ent/semiflow/where.go index 64eb8ab..c535d5f 100644 --- a/bj_power_mes/ent/semiflow/where.go +++ b/bj_power_mes/ent/semiflow/where.go @@ -64,6 +64,11 @@ func OrderNo(v string) predicate.SemiFlow { return predicate.SemiFlow(sql.FieldEQ(FieldOrderNo, v)) } +// CompletedText applies equality check predicate on the "completedText" field. It's identical to CompletedTextEQ. +func CompletedText(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldEQ(FieldCompletedText, v)) +} + // Action applies equality check predicate on the "action" field. It's identical to ActionEQ. func Action(v string) predicate.SemiFlow { return predicate.SemiFlow(sql.FieldEQ(FieldAction, v)) @@ -224,6 +229,71 @@ func DoneProcessCodesNotNil() predicate.SemiFlow { return predicate.SemiFlow(sql.FieldNotNull(FieldDoneProcessCodes)) } +// CompletedTextEQ applies the EQ predicate on the "completedText" field. +func CompletedTextEQ(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldEQ(FieldCompletedText, v)) +} + +// CompletedTextNEQ applies the NEQ predicate on the "completedText" field. +func CompletedTextNEQ(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldNEQ(FieldCompletedText, v)) +} + +// CompletedTextIn applies the In predicate on the "completedText" field. +func CompletedTextIn(vs ...string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldIn(FieldCompletedText, vs...)) +} + +// CompletedTextNotIn applies the NotIn predicate on the "completedText" field. +func CompletedTextNotIn(vs ...string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldNotIn(FieldCompletedText, vs...)) +} + +// CompletedTextGT applies the GT predicate on the "completedText" field. +func CompletedTextGT(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldGT(FieldCompletedText, v)) +} + +// CompletedTextGTE applies the GTE predicate on the "completedText" field. +func CompletedTextGTE(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldGTE(FieldCompletedText, v)) +} + +// CompletedTextLT applies the LT predicate on the "completedText" field. +func CompletedTextLT(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldLT(FieldCompletedText, v)) +} + +// CompletedTextLTE applies the LTE predicate on the "completedText" field. +func CompletedTextLTE(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldLTE(FieldCompletedText, v)) +} + +// CompletedTextContains applies the Contains predicate on the "completedText" field. +func CompletedTextContains(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldContains(FieldCompletedText, v)) +} + +// CompletedTextHasPrefix applies the HasPrefix predicate on the "completedText" field. +func CompletedTextHasPrefix(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldHasPrefix(FieldCompletedText, v)) +} + +// CompletedTextHasSuffix applies the HasSuffix predicate on the "completedText" field. +func CompletedTextHasSuffix(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldHasSuffix(FieldCompletedText, v)) +} + +// CompletedTextEqualFold applies the EqualFold predicate on the "completedText" field. +func CompletedTextEqualFold(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldEqualFold(FieldCompletedText, v)) +} + +// CompletedTextContainsFold applies the ContainsFold predicate on the "completedText" field. +func CompletedTextContainsFold(v string) predicate.SemiFlow { + return predicate.SemiFlow(sql.FieldContainsFold(FieldCompletedText, v)) +} + // ActionEQ applies the EQ predicate on the "action" field. func ActionEQ(v string) predicate.SemiFlow { return predicate.SemiFlow(sql.FieldEQ(FieldAction, v)) diff --git a/bj_power_mes/ent/semiflow_create.go b/bj_power_mes/ent/semiflow_create.go index 0b48424..12ab5e6 100644 --- a/bj_power_mes/ent/semiflow_create.go +++ b/bj_power_mes/ent/semiflow_create.go @@ -46,6 +46,20 @@ func (_c *SemiFlowCreate) SetDoneProcessCodes(v []int) *SemiFlowCreate { return _c } +// SetCompletedText sets the "completedText" field. +func (_c *SemiFlowCreate) SetCompletedText(v string) *SemiFlowCreate { + _c.mutation.SetCompletedText(v) + return _c +} + +// SetNillableCompletedText sets the "completedText" field if the given value is not nil. +func (_c *SemiFlowCreate) SetNillableCompletedText(v *string) *SemiFlowCreate { + if v != nil { + _c.SetCompletedText(*v) + } + return _c +} + // SetAction sets the "action" field. func (_c *SemiFlowCreate) SetAction(v string) *SemiFlowCreate { _c.mutation.SetAction(v) @@ -147,6 +161,10 @@ func (_c *SemiFlowCreate) defaults() { v := semiflow.DefaultOrderNo _c.mutation.SetOrderNo(v) } + if _, ok := _c.mutation.CompletedText(); !ok { + v := semiflow.DefaultCompletedText + _c.mutation.SetCompletedText(v) + } if _, ok := _c.mutation.Action(); !ok { v := semiflow.DefaultAction _c.mutation.SetAction(v) @@ -183,6 +201,14 @@ func (_c *SemiFlowCreate) check() error { return &ValidationError{Name: "orderNo", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.orderNo": %w`, err)} } } + if _, ok := _c.mutation.CompletedText(); !ok { + return &ValidationError{Name: "completedText", err: errors.New(`ent: missing required field "SemiFlow.completedText"`)} + } + if v, ok := _c.mutation.CompletedText(); ok { + if err := semiflow.CompletedTextValidator(v); err != nil { + return &ValidationError{Name: "completedText", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.completedText": %w`, err)} + } + } if _, ok := _c.mutation.Action(); !ok { return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "SemiFlow.action"`)} } @@ -259,6 +285,10 @@ func (_c *SemiFlowCreate) createSpec() (*SemiFlow, *sqlgraph.CreateSpec) { _spec.SetField(semiflow.FieldDoneProcessCodes, field.TypeJSON, value) _node.DoneProcessCodes = value } + if value, ok := _c.mutation.CompletedText(); ok { + _spec.SetField(semiflow.FieldCompletedText, field.TypeString, value) + _node.CompletedText = value + } if value, ok := _c.mutation.Action(); ok { _spec.SetField(semiflow.FieldAction, field.TypeString, value) _node.Action = value diff --git a/bj_power_mes/ent/semiflow_update.go b/bj_power_mes/ent/semiflow_update.go index 718542e..c34d02a 100644 --- a/bj_power_mes/ent/semiflow_update.go +++ b/bj_power_mes/ent/semiflow_update.go @@ -75,6 +75,20 @@ func (_u *SemiFlowUpdate) ClearDoneProcessCodes() *SemiFlowUpdate { return _u } +// SetCompletedText sets the "completedText" field. +func (_u *SemiFlowUpdate) SetCompletedText(v string) *SemiFlowUpdate { + _u.mutation.SetCompletedText(v) + return _u +} + +// SetNillableCompletedText sets the "completedText" field if the given value is not nil. +func (_u *SemiFlowUpdate) SetNillableCompletedText(v *string) *SemiFlowUpdate { + if v != nil { + _u.SetCompletedText(*v) + } + return _u +} + // SetAction sets the "action" field. func (_u *SemiFlowUpdate) SetAction(v string) *SemiFlowUpdate { _u.mutation.SetAction(v) @@ -161,6 +175,11 @@ func (_u *SemiFlowUpdate) check() error { return &ValidationError{Name: "orderNo", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.orderNo": %w`, err)} } } + if v, ok := _u.mutation.CompletedText(); ok { + if err := semiflow.CompletedTextValidator(v); err != nil { + return &ValidationError{Name: "completedText", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.completedText": %w`, err)} + } + } if v, ok := _u.mutation.Action(); ok { if err := semiflow.ActionValidator(v); err != nil { return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.action": %w`, err)} @@ -214,6 +233,9 @@ func (_u *SemiFlowUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.DoneProcessCodesCleared() { _spec.ClearField(semiflow.FieldDoneProcessCodes, field.TypeJSON) } + if value, ok := _u.mutation.CompletedText(); ok { + _spec.SetField(semiflow.FieldCompletedText, field.TypeString, value) + } if value, ok := _u.mutation.Action(); ok { _spec.SetField(semiflow.FieldAction, field.TypeString, value) } @@ -291,6 +313,20 @@ func (_u *SemiFlowUpdateOne) ClearDoneProcessCodes() *SemiFlowUpdateOne { return _u } +// SetCompletedText sets the "completedText" field. +func (_u *SemiFlowUpdateOne) SetCompletedText(v string) *SemiFlowUpdateOne { + _u.mutation.SetCompletedText(v) + return _u +} + +// SetNillableCompletedText sets the "completedText" field if the given value is not nil. +func (_u *SemiFlowUpdateOne) SetNillableCompletedText(v *string) *SemiFlowUpdateOne { + if v != nil { + _u.SetCompletedText(*v) + } + return _u +} + // SetAction sets the "action" field. func (_u *SemiFlowUpdateOne) SetAction(v string) *SemiFlowUpdateOne { _u.mutation.SetAction(v) @@ -390,6 +426,11 @@ func (_u *SemiFlowUpdateOne) check() error { return &ValidationError{Name: "orderNo", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.orderNo": %w`, err)} } } + if v, ok := _u.mutation.CompletedText(); ok { + if err := semiflow.CompletedTextValidator(v); err != nil { + return &ValidationError{Name: "completedText", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.completedText": %w`, err)} + } + } if v, ok := _u.mutation.Action(); ok { if err := semiflow.ActionValidator(v); err != nil { return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "SemiFlow.action": %w`, err)} @@ -460,6 +501,9 @@ func (_u *SemiFlowUpdateOne) sqlSave(ctx context.Context) (_node *SemiFlow, err if _u.mutation.DoneProcessCodesCleared() { _spec.ClearField(semiflow.FieldDoneProcessCodes, field.TypeJSON) } + if value, ok := _u.mutation.CompletedText(); ok { + _spec.SetField(semiflow.FieldCompletedText, field.TypeString, value) + } if value, ok := _u.mutation.Action(); ok { _spec.SetField(semiflow.FieldAction, field.TypeString, value) } diff --git a/bj_power_mes/frontend/src/help.js b/bj_power_mes/frontend/src/help.js index 48ef3d4..cf4f3e5 100644 --- a/bj_power_mes/frontend/src/help.js +++ b/bj_power_mes/frontend/src/help.js @@ -84,7 +84,7 @@ export const helpDailyPlan = { export const helpBom = { title: '产品物料清单', overview: - '物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是电气件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。', + '物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、按序列号还是按批次管理、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。', fields: [ { name: '物料清单名称', source: '下拉选择/输入(可搜索,输入新名即创建)。缺省「默认」。', purpose: '同一产品编号下多份物料清单的区分名,工单建单时按此选定', fill: '如「默认」「A厂料单」「含半成品」;同一型号下名称唯一' }, { name: '产品编号', source: '下拉选择。来自「物料档案」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编号' }, @@ -92,7 +92,7 @@ export const helpBom = { { name: '物料名称', source: '手工填写。', purpose: '物料中文名,便于识别', fill: '如 十字螺丝、主控芯片' }, { name: '规格', source: '手工填写。', purpose: '物料规格型号', fill: '如 M16×80 / SMT-48脚' }, { name: '单位', source: '手工填写。', purpose: '计量单位,用于算量', fill: '个 / 件 / 套 / kg' }, - { name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电子件/电气件选"电气件 SN";螺丝等标准件选"结构件 批次"' }, + { name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电气件/电子件选"序列号";螺丝等标准件选"批次"' }, { name: '单台用量', source: '手工填写。', purpose: '生产 1 台产品用的该物料数量', fill: '如每台 8 颗螺丝 → 填 8' }, { name: '损耗率%', source: '手工填写。', purpose: '考虑报废/损耗的加放比例', fill: '如 2% 填 2;0=不计算损耗' }, { name: '需求量', source: '自动计算 = 数量×单台用量×(1+损耗率),可覆盖。', purpose: '该物料总需求,出库不能超它', fill: '一般自动算,不必改' }, @@ -313,7 +313,7 @@ export const helpEventLog = { export const helpProcessFlow = { title: '工艺(作业指导,含工艺步骤与物料清单)', overview: - '页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(编码/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。', + '页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(物料编号/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。', sections: [ { title: '工艺基本信息', @@ -324,7 +324,7 @@ export const helpProcessFlow = { { name: '作业指导PDF', source: '本页上传。文件由 MES 服务器保存。', purpose: '该工艺的作业指导图,工位终端打开预览', fill: '选择 PDF 上传,成功后显示文件名可预览' }, { name: '工艺步骤', source: '本页维护(步骤名/采集方式/是否拧紧/考核标准)。', purpose: '工位终端报工时按此动态渲染参数输入', fill: '一个工艺可含多个工艺步骤,按序号顺序执行' }, { name: '步骤附件/图纸', source: '每个工艺步骤单独上传(绑定式)。文件由 MES 服务器保存。', purpose: '把图纸精确绑定到具体步骤,工位终端按当前步骤自动展示对应图纸', fill: '在步骤行上传该步骤图纸/附件(可多个);随步骤自动分发到工位终端' }, - { name: '物料清单', source: '本页维护:物料编码/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编码/名称/规格/单位可手填,须属于产品物料清单范围,工单保存时校验;管控方式:批次·结构件=1、序列号·电气件=2' } + { name: '物料清单', source: '本页维护:物料编号(下拉选择,取自物料档案)/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编号取自物料档案编码(结构件通常即图号),选中后名称/规格/单位/管控方式自动带出、不可改;管控方式:批次=1、序列号=2' } ] }, { diff --git a/bj_power_mes/frontend/src/pages/AccountManage.vue b/bj_power_mes/frontend/src/pages/AccountManage.vue index 22d35d8..44e7c3c 100644 --- a/bj_power_mes/frontend/src/pages/AccountManage.vue +++ b/bj_power_mes/frontend/src/pages/AccountManage.vue @@ -66,7 +66,7 @@ - + - + @@ -66,8 +66,8 @@ - 电气件(SN) - 结构件(批次) + 序列号 + 批次 diff --git a/bj_power_mes/frontend/src/pages/DailyPlan.vue b/bj_power_mes/frontend/src/pages/DailyPlan.vue index c1f44f7..4116aa9 100644 --- a/bj_power_mes/frontend/src/pages/DailyPlan.vue +++ b/bj_power_mes/frontend/src/pages/DailyPlan.vue @@ -131,7 +131,7 @@ const query = reactive({ orderNo: '', planDate: '' }) const list = ref([]) const workOrders = ref([]) const productTypes = ref([]) -const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: {}, combination: [] }) +const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', woStart: '', woDue: '', stationPlanQty: {}, combination: [] }) // 工位号→接驳台编码、工位号→名称(送达接驳台由工单工艺组合派生) const stationDockMap = ref({}) @@ -210,6 +210,7 @@ function loadWorkOrderInfo() { if (!wo) { dialog.productType = ''; dialog.productCode = ''; dialog.productName = ''; dialog.woQty = '' dialog.combination = [] + dialog.woStart = ''; dialog.woDue = '' return } dialog.productCode = wo.productCode || '' @@ -218,9 +219,20 @@ function loadWorkOrderInfo() { dialog.combination = Array.isArray(wo.flowCombination) ? wo.flowCombination : [] const pt = productTypes.value.find((p) => p.id === wo.productTypeId) dialog.productType = pt ? (pt.name + (pt.code ? ' / ' + pt.code : '')) : '' + // 记录工单起止日期(planStart/dueDate),用于日期预填与超期提示 + dialog.woStart = wo.planStart ? String(wo.planStart).slice(0, 10) : '' + dialog.woDue = wo.dueDate ? String(wo.dueDate).slice(0, 10) : '' + // 新建排产:日期范围默认取所选工单的开始/结束日期(可修改) + if (!dialog.id && (dialog.woStart || dialog.woDue)) { + dialog.planRange = [dialog.woStart || dialog.woDue, dialog.woDue || dialog.woStart] + } } function onOrderChange() { loadWorkOrderInfo() + // 编辑弹窗切换工单:排产日期默认取该工单开始日期(无则取结束日期,可修改) + if (dialog.id && (dialog.woStart || dialog.woDue)) { + dialog.planDate = dialog.woStart || dialog.woDue + } } function openAdd() { const today = new Date().toISOString().slice(0, 10) @@ -242,6 +254,15 @@ function openEdit(row) { : dialog.combination loadAlloc() } +// 所选日期超出工单执行期间(planStart~dueDate)时提示,但不阻断提交 +function warnOutOfWindow(dates) { + const s = dialog.woStart || '' + const e = dialog.woDue || '' + if ((!s && !e) || !dialog.orderNo) return + if (dates.some((d) => (s && d < s) || (e && d > e))) { + ElMessage.warning('所选日期超出工单执行期间') + } +} async function save() { if (!dialog.orderNo) return ElMessage.warning('请选择工单号') if (dialog.id) { @@ -251,6 +272,7 @@ async function save() { if (alloc[st]) spq[String(st)] = Number(alloc[st]) || 0 }) if (!dialog.planDate) return ElMessage.warning('请选择排产日期') + warnOutOfWindow([dialog.planDate]) await request.put(`/daily-plans/${dialog.id}`, { id: dialog.id, orderNo: dialog.orderNo, planDate: dialog.planDate, planQty: dialog.planQty, stationPlanQty: spq }) @@ -259,6 +281,7 @@ async function save() { if (!dialog.planRange || dialog.planRange.length !== 2 || !dialog.planRange[0] || !dialog.planRange[1]) return ElMessage.warning('请选择排产日期范围') if (!dialog.planQty || dialog.planQty < 1) return ElMessage.warning('请填写数量') + warnOutOfWindow([dialog.planRange[0], dialog.planRange[1]]) await request.post('/daily-plans', { orderNo: dialog.orderNo, planRange: dialog.planRange, mode: dialog.mode, planQty: dialog.planQty }) @@ -292,9 +315,7 @@ async function toggleStatus(row) { const target = row.status === 'CONFIRMED' ? 'PENDING' : 'CONFIRMED' const label = target === 'CONFIRMED' ? '启用' : '停用' await ElMessageBox.confirm(`确认${label}该排产(${row.orderNo} ${row.planDate})?`, '提示', { type: 'warning' }) - await request.put(`/daily-plans/${row.id}`, { - orderNo: row.orderNo, planDate: row.planDate, planQty: row.planQty, status: target - }) + await request.post(`/daily-plans/${row.id}/status`, { status: target }) ElMessage.success(`已${label}`) load() } diff --git a/bj_power_mes/frontend/src/pages/MaterialRequest.vue b/bj_power_mes/frontend/src/pages/MaterialRequest.vue index dd3017e..62633f6 100644 --- a/bj_power_mes/frontend/src/pages/MaterialRequest.vue +++ b/bj_power_mes/frontend/src/pages/MaterialRequest.vue @@ -48,7 +48,7 @@ - + diff --git a/bj_power_mes/frontend/src/pages/ProcessFlow.vue b/bj_power_mes/frontend/src/pages/ProcessFlow.vue index b89e0dc..0ef8465 100644 --- a/bj_power_mes/frontend/src/pages/ProcessFlow.vue +++ b/bj_power_mes/frontend/src/pages/ProcessFlow.vue @@ -161,53 +161,62 @@ - +
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- 新增物料行 - 保存物料清单 - 物料编码/名称/规格/单位手填,须属于产品物料清单范围,工单保存时校验;备料需求 = 各工位分产量 × 单台用量 ×(1+损耗率%)。 -
+
@@ -369,6 +378,31 @@ function blankMaterial() { return { materialCode: '', materialName: '', spec: '', unit: '', manageMode: '2', unitQty: 1, lossRate: 0 } } +/* ---------- 物料档案(物料清单「物料编号」下拉数据源) ---------- */ +const materialOptions = ref([]) +const materialsUnavailable = ref(false) + +async function loadMaterialArchive() { + materialsUnavailable.value = false + try { + const list = (await request.get('/materials')) || [] + materialOptions.value = Array.isArray(list) ? list : [] + } catch { + materialOptions.value = [] + } + if (!materialOptions.value.length) materialsUnavailable.value = true +} + +// 选中物料编号后,快照字段(名称/规格/单位/管控方式)自动带出,均不可手改 +function onMaterialPicked(row, code) { + const m = materialOptions.value.find((x) => x.code === code) + if (!m) return + row.materialName = m.name || '' + row.spec = m.spec || '' + row.unit = m.unit || '' + if (m.manageMode != null && m.manageMode !== '') row.manageMode = String(m.manageMode) +} + // 加载该工艺的物料清单(整表编辑,保存时整表覆盖) async function loadMaterials(flowId) { flowDlg.materials = [] @@ -399,7 +433,7 @@ async function saveMaterials() { unitQty: Number(m.unitQty) || 0, lossRate: Number(m.lossRate) || 0 })) - if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未填写物料编码的行,请补全或删除') + if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未选择物料编号的行,请补全或删除') matSaving.value = true try { await request.post(`/process-flows/${flowDlg.id}/materials`, { items }) @@ -552,6 +586,7 @@ async function toggleStatus(row) { onMounted(() => { loadFlows() + loadMaterialArchive() }) diff --git a/bj_power_mes/frontend/src/pages/Trace.vue b/bj_power_mes/frontend/src/pages/Trace.vue index 7f69e3d..d478c39 100644 --- a/bj_power_mes/frontend/src/pages/Trace.vue +++ b/bj_power_mes/frontend/src/pages/Trace.vue @@ -82,6 +82,27 @@

批次号:{{ (data.associationTrace?.batchItems||[]).join(', ') || '无' }}

电气件SN:{{ (data.associationTrace?.serialItems||[]).join(', ') || '无' }}

+ + + + + + + + + + + + + + + + + + @@ -91,7 +112,7 @@ - + @@ -109,7 +130,7 @@ - + diff --git a/bj_power_mes/frontend/src/pages/WorkOrder.vue b/bj_power_mes/frontend/src/pages/WorkOrder.vue index f0f65d5..7c09304 100644 --- a/bj_power_mes/frontend/src/pages/WorkOrder.vue +++ b/bj_power_mes/frontend/src/pages/WorkOrder.vue @@ -134,7 +134,7 @@ 执行路线:{{ routeText }} (执行顺序=工位号升序,可跳过未选工位) -
默认每个已绑定工艺的工位全选;至少选一道工艺;全不选某工艺则该工艺不进入组合。
+
仅显示已绑定启用工位的工艺;默认每个工艺的启用工位全选,至少选一道工艺;全不选某工艺则该工艺不进入组合。
@@ -246,16 +246,13 @@ const flowGroups = computed(() => { flows.value.forEach((f) => { if (map.has(f.id) && f.name) map.get(f.id).flowName = f.name }) - const arr = Array.from(map.values()) - arr.sort((a, b) => { - if (a.flowId === 0) return 1 - if (b.flowId === 0) return -1 - return (a.flowName || '').localeCompare(b.flowName || '', 'zh') - }) + // 只显示「至少绑定了一个启用工位」的工艺;未绑定工位、或绑定的工位全部停用的工艺整组隐藏 + const arr = Array.from(map.values()).filter((g) => g.flowId > 0 && g.stations.some((s) => s.isActive)) + arr.sort((a, b) => (a.flowName || '').localeCompare(b.flowName || '', 'zh')) return arr.map((g) => ({ key: g.flowId, flowId: g.flowId, - title: g.flowId ? (g.flowName || `工艺${g.flowId}`) : '未绑定工艺', + title: g.flowName || `工艺${g.flowId}`, stations: [...g.stations].sort((a, b) => (a.stationNo || 0) - (b.stationNo || 0)) })) }) @@ -272,7 +269,12 @@ const routeText = computed(() => flowCombination.value.map((c) => c.stationNo).j const hasCombination = (row) => Array.isArray(row.flowCombination) && row.flowCombination.length > 0 function defaultSelStations() { - return stations.value.filter((s) => s.flowId > 0).map((s) => s.stationNo).sort((a, b) => a - b) + // 默认全选「可见工艺组」内的启用工位(被隐藏工艺组下的工位不参与组合) + const visible = new Set(flowGroups.value.map((g) => g.flowId)) + return stations.value + .filter((s) => s.flowId > 0 && visible.has(s.flowId) && s.isActive) + .map((s) => s.stationNo) + .sort((a, b) => a - b) } async function loadComboData() { diff --git a/bj_power_mes/internal/db/db.go b/bj_power_mes/internal/db/db.go index a880f6f..81beca8 100644 --- a/bj_power_mes/internal/db/db.go +++ b/bj_power_mes/internal/db/db.go @@ -199,6 +199,10 @@ var schemaPatchSQL = []string{ `ALTER TABLE workpiece_process ADD COLUMN IF NOT EXISTS flow_id integer NOT NULL DEFAULT 0`, `ALTER TABLE workpiece_bind ADD COLUMN IF NOT EXISTS flow_id integer NOT NULL DEFAULT 0`, `ALTER TABLE material_request ADD COLUMN IF NOT EXISTS flow_id integer NOT NULL DEFAULT 0`, + + // ---- 半成品流转新模型(2026-09-22 定稿):进度唯一权威源=workpiece.currentStationNo ---- + // semi_flow 补进度快照列 completed_text("已完成至工位N");旧 done_process_codes 保留历史数据、不再写入 + `ALTER TABLE semi_flow ADD COLUMN IF NOT EXISTS completed_text varchar(64) NOT NULL DEFAULT ''`, } // EnsureSchema ent 建表 + 幂等列补丁(只增,不删数据) diff --git a/bj_power_mes/internal/handler/production/daily_plan_status.go b/bj_power_mes/internal/handler/production/daily_plan_status.go new file mode 100644 index 0000000..cb8ada3 --- /dev/null +++ b/bj_power_mes/internal/handler/production/daily_plan_status.go @@ -0,0 +1,30 @@ +package production + +import ( + "net/http" + + "bj_power_mes/common/httpx" + "bj_power_mes/internal/logic" + "bj_power_mes/internal/svc" +) + +// DailyPlanStatusHandler POST /daily-plans/:id/status 日排产启用/停用专用状态接口。 +// body: {"status":"PENDING"|"CONFIRMED"};只更新 status 字段(启用前校验工单已下发), +// 前端此前误用 PUT /daily-plans/{id} 会 405,且全量保存会覆盖分产量。 +func DailyPlanStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + Status string `json:"status"` + } + if err := httpx.ParseJSON(r, &req); err != nil { + httpx.BadRequest(w, "请求体解析失败") + return + } + id := pathID(r) + if err := logic.New(svcCtx).SetDailyPlanStatus(r.Context(), id, req.Status, operator(r, "")); err != nil { + httpx.Fail(w, 3012, err.Error()) + return + } + httpx.OkMessage(w, "操作成功", nil) + } +} diff --git a/bj_power_mes/internal/handler/production/material.go b/bj_power_mes/internal/handler/production/material.go new file mode 100644 index 0000000..0d7d2e2 --- /dev/null +++ b/bj_power_mes/internal/handler/production/material.go @@ -0,0 +1,33 @@ +package production + +import ( + "net/http" + "strconv" + + "bj_power_mes/common/httpx" + "bj_power_mes/internal/svc" +) + +// ListMaterialsHandler GET /api/v1/materials 物料档案(代理 WMS GET /api/internal/materials)。 +// WMS 是物料主数据源(item_type=1原材料/2半成品/3成品/4其他),MES 管理端经 JWT 接口只读代理。 +// 响应 [{code,name,spec,unit,manageMode}],manageMode "1"=批次(结构件)/"2"=序列号(电气件), +// 与 WMS manage_mode 口径一致(参考 wmsclient.MaterialRow.ManageMode)。 +// WMS 不可达/异常时返回空列表(前端自行降级),不报错。 +func ListMaterialsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + list := []map[string]string{} + _, rows, err := svcCtx.Wms.MaterialPage(r.Context(), "", "", "", 0, 0, 1, 1000) + if err == nil { + for _, m := range rows { + list = append(list, map[string]string{ + "code": m.Code, + "name": m.Name, + "spec": m.Spec, + "unit": m.Unit, + "manageMode": strconv.Itoa(m.ManageMode), + }) + } + } + httpx.Ok(w, list) + } +} diff --git a/bj_power_mes/internal/handler/production/production.go b/bj_power_mes/internal/handler/production/production.go index 8624f3c..5e8cbd0 100644 --- a/bj_power_mes/internal/handler/production/production.go +++ b/bj_power_mes/internal/handler/production/production.go @@ -128,11 +128,12 @@ func OnlineWorkpieceInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc httpx.BadRequest(w, "请求体解析失败") return } - if err := logic.New(svcCtx).OnlineWorkpiece(r.Context(), req, operator(r, r.URL.Query().Get("operator"))); err != nil { + if msg, err := logic.New(svcCtx).OnlineWorkpiece(r.Context(), req, operator(r, r.URL.Query().Get("operator"))); err != nil { httpx.Fail(w, 2007, err.Error()) return + } else { + httpx.OkMessage(w, msg, nil) } - httpx.OkMessage(w, "进线登记成功", nil) } } diff --git a/bj_power_mes/internal/handler/production/workpiece_semi.go b/bj_power_mes/internal/handler/production/workpiece_semi.go index 1db1c37..654b302 100644 --- a/bj_power_mes/internal/handler/production/workpiece_semi.go +++ b/bj_power_mes/internal/handler/production/workpiece_semi.go @@ -23,11 +23,12 @@ func OnlineWorkpieceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { httpx.BadRequest(w, "请求体解析失败") return } - if err := logic.New(svcCtx).OnlineWorkpiece(r.Context(), req, operator(r, "")); err != nil { + if msg, err := logic.New(svcCtx).OnlineWorkpiece(r.Context(), req, operator(r, "")); err != nil { httpx.Fail(w, 3301, err.Error()) return + } else { + httpx.OkMessage(w, msg, nil) } - httpx.OkMessage(w, "进线登记成功", nil) } } diff --git a/bj_power_mes/internal/handler/routes_production.go b/bj_power_mes/internal/handler/routes_production.go index d7e5d57..cd1e317 100644 --- a/bj_power_mes/internal/handler/routes_production.go +++ b/bj_power_mes/internal/handler/routes_production.go @@ -172,6 +172,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPut, Path: "/daily-plans", Handler: production.SaveDailyPlanHandler(serverCtx)}, {Method: http.MethodGet, Path: "/daily-plans", Handler: production.ListDailyPlansHandler(serverCtx)}, {Method: http.MethodDelete, Path: "/daily-plans/:id", Handler: production.DeleteDailyPlanHandler(serverCtx)}, + // 日排产启用/停用专用状态接口(body {"status":"PENDING"|"CONFIRMED"}): + // 只改 status 列,避免前端此前误用 PUT /daily-plans/{id}(405)或全量保存覆盖分产量 + {Method: http.MethodPost, Path: "/daily-plans/:id/status", Handler: production.DailyPlanStatusHandler(serverCtx)}, {Method: http.MethodGet, Path: "/daily-plans/suggest-docks", Handler: production.SuggestedDockCodesHandler(serverCtx)}, {Method: http.MethodPut, Path: "/bom", Handler: production.SaveBomHandler(serverCtx)}, @@ -182,6 +185,8 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPost, Path: "/material-requests/generate", Handler: production.GenerateMaterialHandler(serverCtx)}, {Method: http.MethodGet, Path: "/material-requests", Handler: production.ListMaterialRequestsHandler(serverCtx)}, + // 物料档案(代理 WMS /api/internal/materials,WMS 是物料主数据源;WMS 不可达返回空列表) + {Method: http.MethodGet, Path: "/materials", Handler: production.ListMaterialsHandler(serverCtx)}, {Method: http.MethodPost, Path: "/material-requests/receive", Handler: production.ReceiveMaterialRequestHandler(serverCtx)}, {Method: http.MethodPost, Path: "/material-requests/auto-outbound", Handler: production.AutoOutboundHandler(serverCtx)}, // 配送进度(只读聚合:WMS 台账四段量 + MES 已接料 → 在途/线边) @@ -239,6 +244,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPost, Path: "/process-flows/status", Handler: SetFlowStatusHandler(serverCtx)}, {Method: http.MethodDelete, Path: "/process-flows/:id", Handler: DeleteProcessFlowHandler(serverCtx)}, {Method: http.MethodPost, Path: "/process-flows/upload", Handler: UploadPdfHandler(serverCtx)}, + // 统一存储深路径文件 URL(年/月/日/文件类型/uuid.ext,如 /api/v1/files/2026/09/22/PROCESS_PDF/xxx.pdf), + // 上传接口返回的 url 即此格式,未注册前直接访问 404 + {Method: http.MethodGet, Path: "/files/:y/:m/:d/:t/:f", Handler: FileDeepPathHandler(serverCtx)}, {Method: http.MethodGet, Path: "/stations", Handler: ListStationsHandler(serverCtx)}, {Method: http.MethodPost, Path: "/stations", Handler: SaveStationHandler(serverCtx)}, {Method: http.MethodDelete, Path: "/stations/:id", Handler: DeleteStationHandler(serverCtx)}, diff --git a/bj_power_mes/internal/handler/station.go b/bj_power_mes/internal/handler/station.go index 0bd9303..e3506ec 100644 --- a/bj_power_mes/internal/handler/station.go +++ b/bj_power_mes/internal/handler/station.go @@ -1,8 +1,8 @@ package handler import ( - "net/http" "errors" + "net/http" "net/url" "strconv" "time" @@ -118,7 +118,7 @@ func StationTaskInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } -// StationReportInternalHandler 工位终端工序报工(写工序实绩 + 步骤考核) +// StationReportInternalHandler 工位终端工艺报工(写工位实绩 + 步骤考核) func StationReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req logic.ReportProcessReq @@ -159,15 +159,14 @@ func FileDownloadInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } // StationCheckinInternalHandler 工位终端暂存退库(半成品入库,块4/5) -// 记录已完成工序后按半成品 INBOUND 流转,并通知 WMS 入库。 +// 进度由 MES 服务端自取(workpiece.currentStationNo),按半成品 INBOUND 流转并通知 WMS 入库。 func StationCheckinInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { - Sn string `json:"sn"` - OrderNo string `json:"orderNo"` - StationNo int `json:"stationNo"` - ProcessCode int `json:"processCode"` - Operator string `json:"operator"` + Sn string `json:"sn"` + OrderNo string `json:"orderNo"` + StationNo int `json:"stationNo"` + Operator string `json:"operator"` } if err := httpx.ParseJSON(r, &req); err != nil { httpx.BadRequest(w, "请求体解析失败") @@ -177,19 +176,14 @@ func StationCheckinInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc httpx.BadRequest(w, "缺少 sn") return } - doneCodes := []int{} - if req.ProcessCode > 0 { - doneCodes = append(doneCodes, req.ProcessCode) - } op := req.Operator if op == "" { op = operator(r, "") } if err := logic.New(svcCtx).SemiFlow(r.Context(), logic.SemiReq{ - Sn: req.Sn, - OrderNo: req.OrderNo, - DoneProcessCodes: doneCodes, - Action: "INBOUND", + Sn: req.Sn, + OrderNo: req.OrderNo, + Action: "INBOUND", }, op); err != nil { httpx.Fail(w, 2106, err.Error()) return @@ -198,7 +192,6 @@ func StationCheckinInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc } } - // PerformanceExportHandler GET /performance/export 绩效报表导出(一个 xlsx 三个 sheet:按人/按工位/明细) // 执行全项目统一导出时间硬规则:起止必填(默认近3个月)、间隔≤1年 func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { @@ -260,7 +253,6 @@ func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } - // normalizeExportRange MES 侧导出时间硬规则(与 WMS 同一套): // ① 起止都空 → 默认近 3 个月;② 只传一个 → 报错;③ 间隔 > 1 年 → 报错;④ 结束早于起始 → 报错。 func normalizeExportRange(startDate, endDate string) (string, string, error) { diff --git a/bj_power_mes/internal/handler/upload.go b/bj_power_mes/internal/handler/upload.go index 31aa861..89bba9b 100644 --- a/bj_power_mes/internal/handler/upload.go +++ b/bj_power_mes/internal/handler/upload.go @@ -261,6 +261,9 @@ func serveUploadFile(w http.ResponseWriter, r *http.Request, dir, name string) { ct = "application/vnd.ms-excel" } w.Header().Set("Content-Type", ct) + // 统一强制下载(Content-Disposition: attachment):文件名按 RFC 5987 编码(中文安全), + // 浏览器直接下载不预览;/