diff --git a/bj_power_mes/ent/bomitem.go b/bj_power_mes/ent/bomitem.go index e298bcd..9ae1e92 100644 --- a/bj_power_mes/ent/bomitem.go +++ b/bj_power_mes/ent/bomitem.go @@ -20,6 +20,8 @@ type BomItem struct { ID int `json:"id,omitempty"` // 产品编码 ProductCode string `json:"productCode,omitempty"` + // BOM名称:同一型号多份并存(如 厂家A/厂家B/含半成品),工单按名选用 + BomName string `json:"bomName,omitempty"` // 物料编码 MaterialCode string `json:"materialCode,omitempty"` // MaterialName holds the value of the "materialName" field. @@ -56,7 +58,7 @@ func (*BomItem) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullFloat64) case bomitem.FieldID, bomitem.FieldProcessCode: values[i] = new(sql.NullInt64) - case bomitem.FieldProductCode, bomitem.FieldMaterialCode, bomitem.FieldMaterialName, bomitem.FieldSpec, bomitem.FieldUnit, bomitem.FieldManageMode: + case bomitem.FieldProductCode, bomitem.FieldBomName, bomitem.FieldMaterialCode, bomitem.FieldMaterialName, bomitem.FieldSpec, bomitem.FieldUnit, bomitem.FieldManageMode: values[i] = new(sql.NullString) case bomitem.FieldCreatedAt: values[i] = new(sql.NullTime) @@ -87,6 +89,12 @@ func (_m *BomItem) assignValues(columns []string, values []any) error { } else if value.Valid { _m.ProductCode = value.String } + case bomitem.FieldBomName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field bomName", values[i]) + } else if value.Valid { + _m.BomName = value.String + } case bomitem.FieldMaterialCode: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field materialCode", values[i]) @@ -194,6 +202,9 @@ func (_m *BomItem) String() string { builder.WriteString("productCode=") builder.WriteString(_m.ProductCode) builder.WriteString(", ") + builder.WriteString("bomName=") + builder.WriteString(_m.BomName) + builder.WriteString(", ") builder.WriteString("materialCode=") builder.WriteString(_m.MaterialCode) builder.WriteString(", ") diff --git a/bj_power_mes/ent/bomitem/bomitem.go b/bj_power_mes/ent/bomitem/bomitem.go index 45a34ee..f361409 100644 --- a/bj_power_mes/ent/bomitem/bomitem.go +++ b/bj_power_mes/ent/bomitem/bomitem.go @@ -15,6 +15,8 @@ const ( FieldID = "id" // FieldProductCode holds the string denoting the productcode field in the database. FieldProductCode = "product_code" + // FieldBomName holds the string denoting the bomname field in the database. + FieldBomName = "bom_name" // FieldMaterialCode holds the string denoting the materialcode field in the database. FieldMaterialCode = "material_code" // FieldMaterialName holds the string denoting the materialname field in the database. @@ -45,6 +47,7 @@ const ( var Columns = []string{ FieldID, FieldProductCode, + FieldBomName, FieldMaterialCode, FieldMaterialName, FieldSpec, @@ -73,6 +76,10 @@ var ( DefaultProductCode string // ProductCodeValidator is a validator for the "productCode" field. It is called by the builders before save. ProductCodeValidator func(string) error + // DefaultBomName holds the default value on creation for the "bomName" field. + DefaultBomName string + // BomNameValidator is a validator for the "bomName" field. It is called by the builders before save. + BomNameValidator func(string) error // MaterialCodeValidator is a validator for the "materialCode" field. It is called by the builders before save. MaterialCodeValidator func(string) error // DefaultMaterialName holds the default value on creation for the "materialName" field. @@ -118,6 +125,11 @@ func ByProductCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldProductCode, opts...).ToFunc() } +// ByBomName orders the results by the bomName field. +func ByBomName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBomName, opts...).ToFunc() +} + // ByMaterialCode orders the results by the materialCode field. func ByMaterialCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMaterialCode, opts...).ToFunc() diff --git a/bj_power_mes/ent/bomitem/where.go b/bj_power_mes/ent/bomitem/where.go index 944f34a..f92e296 100644 --- a/bj_power_mes/ent/bomitem/where.go +++ b/bj_power_mes/ent/bomitem/where.go @@ -59,6 +59,11 @@ func ProductCode(v string) predicate.BomItem { return predicate.BomItem(sql.FieldEQ(FieldProductCode, v)) } +// BomName applies equality check predicate on the "bomName" field. It's identical to BomNameEQ. +func BomName(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldEQ(FieldBomName, v)) +} + // MaterialCode applies equality check predicate on the "materialCode" field. It's identical to MaterialCodeEQ. func MaterialCode(v string) predicate.BomItem { return predicate.BomItem(sql.FieldEQ(FieldMaterialCode, v)) @@ -174,6 +179,71 @@ func ProductCodeContainsFold(v string) predicate.BomItem { return predicate.BomItem(sql.FieldContainsFold(FieldProductCode, v)) } +// BomNameEQ applies the EQ predicate on the "bomName" field. +func BomNameEQ(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldEQ(FieldBomName, v)) +} + +// BomNameNEQ applies the NEQ predicate on the "bomName" field. +func BomNameNEQ(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldNEQ(FieldBomName, v)) +} + +// BomNameIn applies the In predicate on the "bomName" field. +func BomNameIn(vs ...string) predicate.BomItem { + return predicate.BomItem(sql.FieldIn(FieldBomName, vs...)) +} + +// BomNameNotIn applies the NotIn predicate on the "bomName" field. +func BomNameNotIn(vs ...string) predicate.BomItem { + return predicate.BomItem(sql.FieldNotIn(FieldBomName, vs...)) +} + +// BomNameGT applies the GT predicate on the "bomName" field. +func BomNameGT(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldGT(FieldBomName, v)) +} + +// BomNameGTE applies the GTE predicate on the "bomName" field. +func BomNameGTE(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldGTE(FieldBomName, v)) +} + +// BomNameLT applies the LT predicate on the "bomName" field. +func BomNameLT(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldLT(FieldBomName, v)) +} + +// BomNameLTE applies the LTE predicate on the "bomName" field. +func BomNameLTE(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldLTE(FieldBomName, v)) +} + +// BomNameContains applies the Contains predicate on the "bomName" field. +func BomNameContains(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldContains(FieldBomName, v)) +} + +// BomNameHasPrefix applies the HasPrefix predicate on the "bomName" field. +func BomNameHasPrefix(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldHasPrefix(FieldBomName, v)) +} + +// BomNameHasSuffix applies the HasSuffix predicate on the "bomName" field. +func BomNameHasSuffix(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldHasSuffix(FieldBomName, v)) +} + +// BomNameEqualFold applies the EqualFold predicate on the "bomName" field. +func BomNameEqualFold(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldEqualFold(FieldBomName, v)) +} + +// BomNameContainsFold applies the ContainsFold predicate on the "bomName" field. +func BomNameContainsFold(v string) predicate.BomItem { + return predicate.BomItem(sql.FieldContainsFold(FieldBomName, v)) +} + // MaterialCodeEQ applies the EQ predicate on the "materialCode" field. func MaterialCodeEQ(v string) predicate.BomItem { return predicate.BomItem(sql.FieldEQ(FieldMaterialCode, v)) diff --git a/bj_power_mes/ent/bomitem_create.go b/bj_power_mes/ent/bomitem_create.go index 6766045..d1dfdad 100644 --- a/bj_power_mes/ent/bomitem_create.go +++ b/bj_power_mes/ent/bomitem_create.go @@ -34,6 +34,20 @@ func (_c *BomItemCreate) SetNillableProductCode(v *string) *BomItemCreate { return _c } +// SetBomName sets the "bomName" field. +func (_c *BomItemCreate) SetBomName(v string) *BomItemCreate { + _c.mutation.SetBomName(v) + return _c +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_c *BomItemCreate) SetNillableBomName(v *string) *BomItemCreate { + if v != nil { + _c.SetBomName(*v) + } + return _c +} + // SetMaterialCode sets the "materialCode" field. func (_c *BomItemCreate) SetMaterialCode(v string) *BomItemCreate { _c.mutation.SetMaterialCode(v) @@ -217,6 +231,10 @@ func (_c *BomItemCreate) defaults() { v := bomitem.DefaultProductCode _c.mutation.SetProductCode(v) } + if _, ok := _c.mutation.BomName(); !ok { + v := bomitem.DefaultBomName + _c.mutation.SetBomName(v) + } if _, ok := _c.mutation.MaterialName(); !ok { v := bomitem.DefaultMaterialName _c.mutation.SetMaterialName(v) @@ -265,6 +283,14 @@ func (_c *BomItemCreate) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "BomItem.productCode": %w`, err)} } } + if _, ok := _c.mutation.BomName(); !ok { + return &ValidationError{Name: "bomName", err: errors.New(`ent: missing required field "BomItem.bomName"`)} + } + if v, ok := _c.mutation.BomName(); ok { + if err := bomitem.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "BomItem.bomName": %w`, err)} + } + } if _, ok := _c.mutation.MaterialCode(); !ok { return &ValidationError{Name: "materialCode", err: errors.New(`ent: missing required field "BomItem.materialCode"`)} } @@ -361,6 +387,10 @@ func (_c *BomItemCreate) createSpec() (*BomItem, *sqlgraph.CreateSpec) { _spec.SetField(bomitem.FieldProductCode, field.TypeString, value) _node.ProductCode = value } + if value, ok := _c.mutation.BomName(); ok { + _spec.SetField(bomitem.FieldBomName, field.TypeString, value) + _node.BomName = value + } if value, ok := _c.mutation.MaterialCode(); ok { _spec.SetField(bomitem.FieldMaterialCode, field.TypeString, value) _node.MaterialCode = value diff --git a/bj_power_mes/ent/bomitem_update.go b/bj_power_mes/ent/bomitem_update.go index bbbc6a8..0c9ab34 100644 --- a/bj_power_mes/ent/bomitem_update.go +++ b/bj_power_mes/ent/bomitem_update.go @@ -43,6 +43,20 @@ func (_u *BomItemUpdate) SetNillableProductCode(v *string) *BomItemUpdate { return _u } +// SetBomName sets the "bomName" field. +func (_u *BomItemUpdate) SetBomName(v string) *BomItemUpdate { + _u.mutation.SetBomName(v) + return _u +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_u *BomItemUpdate) SetNillableBomName(v *string) *BomItemUpdate { + if v != nil { + _u.SetBomName(*v) + } + return _u +} + // SetMaterialCode sets the "materialCode" field. func (_u *BomItemUpdate) SetMaterialCode(v string) *BomItemUpdate { _u.mutation.SetMaterialCode(v) @@ -254,6 +268,11 @@ func (_u *BomItemUpdate) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "BomItem.productCode": %w`, err)} } } + if v, ok := _u.mutation.BomName(); ok { + if err := bomitem.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "BomItem.bomName": %w`, err)} + } + } if v, ok := _u.mutation.MaterialCode(); ok { if err := bomitem.MaterialCodeValidator(v); err != nil { return &ValidationError{Name: "materialCode", err: fmt.Errorf(`ent: validator failed for field "BomItem.materialCode": %w`, err)} @@ -303,6 +322,9 @@ func (_u *BomItemUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.ProductCode(); ok { _spec.SetField(bomitem.FieldProductCode, field.TypeString, value) } + if value, ok := _u.mutation.BomName(); ok { + _spec.SetField(bomitem.FieldBomName, field.TypeString, value) + } if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(bomitem.FieldMaterialCode, field.TypeString, value) } @@ -389,6 +411,20 @@ func (_u *BomItemUpdateOne) SetNillableProductCode(v *string) *BomItemUpdateOne return _u } +// SetBomName sets the "bomName" field. +func (_u *BomItemUpdateOne) SetBomName(v string) *BomItemUpdateOne { + _u.mutation.SetBomName(v) + return _u +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_u *BomItemUpdateOne) SetNillableBomName(v *string) *BomItemUpdateOne { + if v != nil { + _u.SetBomName(*v) + } + return _u +} + // SetMaterialCode sets the "materialCode" field. func (_u *BomItemUpdateOne) SetMaterialCode(v string) *BomItemUpdateOne { _u.mutation.SetMaterialCode(v) @@ -613,6 +649,11 @@ func (_u *BomItemUpdateOne) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "BomItem.productCode": %w`, err)} } } + if v, ok := _u.mutation.BomName(); ok { + if err := bomitem.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "BomItem.bomName": %w`, err)} + } + } if v, ok := _u.mutation.MaterialCode(); ok { if err := bomitem.MaterialCodeValidator(v); err != nil { return &ValidationError{Name: "materialCode", err: fmt.Errorf(`ent: validator failed for field "BomItem.materialCode": %w`, err)} @@ -679,6 +720,9 @@ func (_u *BomItemUpdateOne) sqlSave(ctx context.Context) (_node *BomItem, err er if value, ok := _u.mutation.ProductCode(); ok { _spec.SetField(bomitem.FieldProductCode, field.TypeString, value) } + if value, ok := _u.mutation.BomName(); ok { + _spec.SetField(bomitem.FieldBomName, field.TypeString, value) + } if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(bomitem.FieldMaterialCode, field.TypeString, value) } diff --git a/bj_power_mes/ent/migrate/schema.go b/bj_power_mes/ent/migrate/schema.go index 25b42c9..7956387 100644 --- a/bj_power_mes/ent/migrate/schema.go +++ b/bj_power_mes/ent/migrate/schema.go @@ -37,6 +37,7 @@ var ( WorkOrderBomColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "product_code", Type: field.TypeString, Size: 64, Default: ""}, + {Name: "bom_name", Type: field.TypeString, Size: 64, Default: "默认"}, {Name: "material_code", Type: field.TypeString, Size: 64}, {Name: "material_name", Type: field.TypeString, Size: 128, Default: ""}, {Name: "spec", Type: field.TypeString, Size: 128, Default: ""}, @@ -61,9 +62,9 @@ var ( Columns: []*schema.Column{WorkOrderBomColumns[1]}, }, { - Name: "bomitem_product_code_material_code", + Name: "bomitem_product_code_bom_name_material_code", Unique: true, - Columns: []*schema.Column{WorkOrderBomColumns[1], WorkOrderBomColumns[2]}, + Columns: []*schema.Column{WorkOrderBomColumns[1], WorkOrderBomColumns[2], WorkOrderBomColumns[3]}, }, }, } @@ -647,6 +648,7 @@ var ( {Name: "can_login_workstation", Type: field.TypeBool, Default: false}, {Name: "workstation_password", Type: field.TypeString, Nullable: true, Size: 128}, {Name: "status", Type: field.TypeString, Size: 20, Default: "ENABLED"}, + {Name: "last_login_at", Type: field.TypeInt64, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime, Nullable: true}, } @@ -704,6 +706,7 @@ var ( {Name: "work_order_no", Type: field.TypeString, Size: 64}, {Name: "product_type_id", Type: field.TypeInt}, {Name: "product_code", Type: field.TypeString, Size: 64, Default: ""}, + {Name: "bom_name", Type: field.TypeString, Size: 64, Default: "默认"}, {Name: "product_name", Type: field.TypeString, Size: 128, Default: ""}, {Name: "quantity", Type: field.TypeInt, Default: 1}, {Name: "finished_num", Type: field.TypeInt, Default: 0}, @@ -731,7 +734,7 @@ var ( { Name: "workorder_status", Unique: false, - Columns: []*schema.Column{WorkOrderColumns[9]}, + Columns: []*schema.Column{WorkOrderColumns[10]}, }, { Name: "workorder_product_type_id", diff --git a/bj_power_mes/ent/mutation.go b/bj_power_mes/ent/mutation.go index c47dd05..4aa17d3 100644 --- a/bj_power_mes/ent/mutation.go +++ b/bj_power_mes/ent/mutation.go @@ -813,6 +813,7 @@ type BomItemMutation struct { typ string id *int productCode *string + bomName *string materialCode *string materialName *string spec *string @@ -975,6 +976,42 @@ func (m *BomItemMutation) ResetProductCode() { m.productCode = nil } +// SetBomName sets the "bomName" field. +func (m *BomItemMutation) SetBomName(s string) { + m.bomName = &s +} + +// BomName returns the value of the "bomName" field in the mutation. +func (m *BomItemMutation) BomName() (r string, exists bool) { + v := m.bomName + if v == nil { + return + } + return *v, true +} + +// OldBomName returns the old "bomName" field's value of the BomItem entity. +// If the BomItem 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 *BomItemMutation) OldBomName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBomName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBomName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBomName: %w", err) + } + return oldValue.BomName, nil +} + +// ResetBomName resets all changes to the "bomName" field. +func (m *BomItemMutation) ResetBomName() { + m.bomName = nil +} + // SetMaterialCode sets the "materialCode" field. func (m *BomItemMutation) SetMaterialCode(s string) { m.materialCode = &s @@ -1514,10 +1551,13 @@ func (m *BomItemMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *BomItemMutation) Fields() []string { - fields := make([]string, 0, 12) + fields := make([]string, 0, 13) if m.productCode != nil { fields = append(fields, bomitem.FieldProductCode) } + if m.bomName != nil { + fields = append(fields, bomitem.FieldBomName) + } if m.materialCode != nil { fields = append(fields, bomitem.FieldMaterialCode) } @@ -1561,6 +1601,8 @@ func (m *BomItemMutation) Field(name string) (ent.Value, bool) { switch name { case bomitem.FieldProductCode: return m.ProductCode() + case bomitem.FieldBomName: + return m.BomName() case bomitem.FieldMaterialCode: return m.MaterialCode() case bomitem.FieldMaterialName: @@ -1594,6 +1636,8 @@ func (m *BomItemMutation) OldField(ctx context.Context, name string) (ent.Value, switch name { case bomitem.FieldProductCode: return m.OldProductCode(ctx) + case bomitem.FieldBomName: + return m.OldBomName(ctx) case bomitem.FieldMaterialCode: return m.OldMaterialCode(ctx) case bomitem.FieldMaterialName: @@ -1632,6 +1676,13 @@ func (m *BomItemMutation) SetField(name string, value ent.Value) error { } m.SetProductCode(v) return nil + case bomitem.FieldBomName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBomName(v) + return nil case bomitem.FieldMaterialCode: v, ok := value.(string) if !ok { @@ -1821,6 +1872,9 @@ func (m *BomItemMutation) ResetField(name string) error { case bomitem.FieldProductCode: m.ResetProductCode() return nil + case bomitem.FieldBomName: + m.ResetBomName() + return nil case bomitem.FieldMaterialCode: m.ResetMaterialCode() return nil @@ -15929,6 +15983,8 @@ type UserMutation struct { canLoginWorkstation *bool workstationPassword *string status *string + lastLoginAt *int64 + addlastLoginAt *int64 createdAt *time.Time updatedAt *time.Time clearedFields map[string]struct{} @@ -16340,6 +16396,76 @@ func (m *UserMutation) ResetStatus() { m.status = nil } +// SetLastLoginAt sets the "lastLoginAt" field. +func (m *UserMutation) SetLastLoginAt(i int64) { + m.lastLoginAt = &i + m.addlastLoginAt = nil +} + +// LastLoginAt returns the value of the "lastLoginAt" field in the mutation. +func (m *UserMutation) LastLoginAt() (r int64, exists bool) { + v := m.lastLoginAt + if v == nil { + return + } + return *v, true +} + +// OldLastLoginAt returns the old "lastLoginAt" field's value of the User entity. +// If the User 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 *UserMutation) OldLastLoginAt(ctx context.Context) (v *int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastLoginAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastLoginAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastLoginAt: %w", err) + } + return oldValue.LastLoginAt, nil +} + +// AddLastLoginAt adds i to the "lastLoginAt" field. +func (m *UserMutation) AddLastLoginAt(i int64) { + if m.addlastLoginAt != nil { + *m.addlastLoginAt += i + } else { + m.addlastLoginAt = &i + } +} + +// AddedLastLoginAt returns the value that was added to the "lastLoginAt" field in this mutation. +func (m *UserMutation) AddedLastLoginAt() (r int64, exists bool) { + v := m.addlastLoginAt + if v == nil { + return + } + return *v, true +} + +// ClearLastLoginAt clears the value of the "lastLoginAt" field. +func (m *UserMutation) ClearLastLoginAt() { + m.lastLoginAt = nil + m.addlastLoginAt = nil + m.clearedFields[user.FieldLastLoginAt] = struct{}{} +} + +// LastLoginAtCleared returns if the "lastLoginAt" field was cleared in this mutation. +func (m *UserMutation) LastLoginAtCleared() bool { + _, ok := m.clearedFields[user.FieldLastLoginAt] + return ok +} + +// ResetLastLoginAt resets all changes to the "lastLoginAt" field. +func (m *UserMutation) ResetLastLoginAt() { + m.lastLoginAt = nil + m.addlastLoginAt = nil + delete(m.clearedFields, user.FieldLastLoginAt) +} + // SetCreatedAt sets the "createdAt" field. func (m *UserMutation) SetCreatedAt(t time.Time) { m.createdAt = &t @@ -16459,7 +16585,7 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 9) + fields := make([]string, 0, 10) if m.username != nil { fields = append(fields, user.FieldUsername) } @@ -16481,6 +16607,9 @@ func (m *UserMutation) Fields() []string { if m.status != nil { fields = append(fields, user.FieldStatus) } + if m.lastLoginAt != nil { + fields = append(fields, user.FieldLastLoginAt) + } if m.createdAt != nil { fields = append(fields, user.FieldCreatedAt) } @@ -16509,6 +16638,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.WorkstationPassword() case user.FieldStatus: return m.Status() + case user.FieldLastLoginAt: + return m.LastLoginAt() case user.FieldCreatedAt: return m.CreatedAt() case user.FieldUpdatedAt: @@ -16536,6 +16667,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldWorkstationPassword(ctx) case user.FieldStatus: return m.OldStatus(ctx) + case user.FieldLastLoginAt: + return m.OldLastLoginAt(ctx) case user.FieldCreatedAt: return m.OldCreatedAt(ctx) case user.FieldUpdatedAt: @@ -16598,6 +16731,13 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetStatus(v) return nil + case user.FieldLastLoginAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastLoginAt(v) + return nil case user.FieldCreatedAt: v, ok := value.(time.Time) if !ok { @@ -16623,6 +16763,9 @@ func (m *UserMutation) AddedFields() []string { if m.addroleId != nil { fields = append(fields, user.FieldRoleId) } + if m.addlastLoginAt != nil { + fields = append(fields, user.FieldLastLoginAt) + } return fields } @@ -16633,6 +16776,8 @@ func (m *UserMutation) AddedField(name string) (ent.Value, bool) { switch name { case user.FieldRoleId: return m.AddedRoleId() + case user.FieldLastLoginAt: + return m.AddedLastLoginAt() } return nil, false } @@ -16649,6 +16794,13 @@ func (m *UserMutation) AddField(name string, value ent.Value) error { } m.AddRoleId(v) return nil + case user.FieldLastLoginAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLastLoginAt(v) + return nil } return fmt.Errorf("unknown User numeric field %s", name) } @@ -16663,6 +16815,9 @@ func (m *UserMutation) ClearedFields() []string { if m.FieldCleared(user.FieldWorkstationPassword) { fields = append(fields, user.FieldWorkstationPassword) } + if m.FieldCleared(user.FieldLastLoginAt) { + fields = append(fields, user.FieldLastLoginAt) + } if m.FieldCleared(user.FieldUpdatedAt) { fields = append(fields, user.FieldUpdatedAt) } @@ -16686,6 +16841,9 @@ func (m *UserMutation) ClearField(name string) error { case user.FieldWorkstationPassword: m.ClearWorkstationPassword() return nil + case user.FieldLastLoginAt: + m.ClearLastLoginAt() + return nil case user.FieldUpdatedAt: m.ClearUpdatedAt() return nil @@ -16718,6 +16876,9 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldStatus: m.ResetStatus() return nil + case user.FieldLastLoginAt: + m.ResetLastLoginAt() + return nil case user.FieldCreatedAt: m.ResetCreatedAt() return nil @@ -17295,6 +17456,7 @@ type WorkOrderMutation struct { productTypeId *int addproductTypeId *int productCode *string + bomName *string productName *string quantity *int addquantity *int @@ -17548,6 +17710,42 @@ func (m *WorkOrderMutation) ResetProductCode() { m.productCode = nil } +// SetBomName sets the "bomName" field. +func (m *WorkOrderMutation) SetBomName(s string) { + m.bomName = &s +} + +// BomName returns the value of the "bomName" field in the mutation. +func (m *WorkOrderMutation) BomName() (r string, exists bool) { + v := m.bomName + if v == nil { + return + } + return *v, true +} + +// OldBomName returns the old "bomName" field's value of the WorkOrder entity. +// If the WorkOrder 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 *WorkOrderMutation) OldBomName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBomName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBomName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBomName: %w", err) + } + return oldValue.BomName, nil +} + +// ResetBomName resets all changes to the "bomName" field. +func (m *WorkOrderMutation) ResetBomName() { + m.bomName = nil +} + // SetProductName sets the "productName" field. func (m *WorkOrderMutation) SetProductName(s string) { m.productName = &s @@ -18139,7 +18337,7 @@ func (m *WorkOrderMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *WorkOrderMutation) Fields() []string { - fields := make([]string, 0, 15) + fields := make([]string, 0, 16) if m.workOrderNo != nil { fields = append(fields, workorder.FieldWorkOrderNo) } @@ -18149,6 +18347,9 @@ func (m *WorkOrderMutation) Fields() []string { if m.productCode != nil { fields = append(fields, workorder.FieldProductCode) } + if m.bomName != nil { + fields = append(fields, workorder.FieldBomName) + } if m.productName != nil { fields = append(fields, workorder.FieldProductName) } @@ -18199,6 +18400,8 @@ func (m *WorkOrderMutation) Field(name string) (ent.Value, bool) { return m.ProductTypeId() case workorder.FieldProductCode: return m.ProductCode() + case workorder.FieldBomName: + return m.BomName() case workorder.FieldProductName: return m.ProductName() case workorder.FieldQuantity: @@ -18238,6 +18441,8 @@ func (m *WorkOrderMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldProductTypeId(ctx) case workorder.FieldProductCode: return m.OldProductCode(ctx) + case workorder.FieldBomName: + return m.OldBomName(ctx) case workorder.FieldProductName: return m.OldProductName(ctx) case workorder.FieldQuantity: @@ -18292,6 +18497,13 @@ func (m *WorkOrderMutation) SetField(name string, value ent.Value) error { } m.SetProductCode(v) return nil + case workorder.FieldBomName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBomName(v) + return nil case workorder.FieldProductName: v, ok := value.(string) if !ok { @@ -18518,6 +18730,9 @@ func (m *WorkOrderMutation) ResetField(name string) error { case workorder.FieldProductCode: m.ResetProductCode() return nil + case workorder.FieldBomName: + m.ResetBomName() + return nil case workorder.FieldProductName: m.ResetProductName() return nil diff --git a/bj_power_mes/ent/runtime.go b/bj_power_mes/ent/runtime.go index c876a9a..8e2b67b 100644 --- a/bj_power_mes/ent/runtime.go +++ b/bj_power_mes/ent/runtime.go @@ -76,52 +76,58 @@ func init() { bomitem.DefaultProductCode = bomitemDescProductCode.Default.(string) // bomitem.ProductCodeValidator is a validator for the "productCode" field. It is called by the builders before save. bomitem.ProductCodeValidator = bomitemDescProductCode.Validators[0].(func(string) error) + // bomitemDescBomName is the schema descriptor for bomName field. + bomitemDescBomName := bomitemFields[2].Descriptor() + // bomitem.DefaultBomName holds the default value on creation for the bomName field. + bomitem.DefaultBomName = bomitemDescBomName.Default.(string) + // bomitem.BomNameValidator is a validator for the "bomName" field. It is called by the builders before save. + bomitem.BomNameValidator = bomitemDescBomName.Validators[0].(func(string) error) // bomitemDescMaterialCode is the schema descriptor for materialCode field. - bomitemDescMaterialCode := bomitemFields[2].Descriptor() + bomitemDescMaterialCode := bomitemFields[3].Descriptor() // bomitem.MaterialCodeValidator is a validator for the "materialCode" field. It is called by the builders before save. bomitem.MaterialCodeValidator = bomitemDescMaterialCode.Validators[0].(func(string) error) // bomitemDescMaterialName is the schema descriptor for materialName field. - bomitemDescMaterialName := bomitemFields[3].Descriptor() + bomitemDescMaterialName := bomitemFields[4].Descriptor() // bomitem.DefaultMaterialName holds the default value on creation for the materialName field. bomitem.DefaultMaterialName = bomitemDescMaterialName.Default.(string) // bomitem.MaterialNameValidator is a validator for the "materialName" field. It is called by the builders before save. bomitem.MaterialNameValidator = bomitemDescMaterialName.Validators[0].(func(string) error) // bomitemDescSpec is the schema descriptor for spec field. - bomitemDescSpec := bomitemFields[4].Descriptor() + bomitemDescSpec := bomitemFields[5].Descriptor() // bomitem.DefaultSpec holds the default value on creation for the spec field. bomitem.DefaultSpec = bomitemDescSpec.Default.(string) // bomitem.SpecValidator is a validator for the "spec" field. It is called by the builders before save. bomitem.SpecValidator = bomitemDescSpec.Validators[0].(func(string) error) // bomitemDescUnit is the schema descriptor for unit field. - bomitemDescUnit := bomitemFields[5].Descriptor() + bomitemDescUnit := bomitemFields[6].Descriptor() // bomitem.DefaultUnit holds the default value on creation for the unit field. bomitem.DefaultUnit = bomitemDescUnit.Default.(string) // bomitem.UnitValidator is a validator for the "unit" field. It is called by the builders before save. bomitem.UnitValidator = bomitemDescUnit.Validators[0].(func(string) error) // bomitemDescManageMode is the schema descriptor for manageMode field. - bomitemDescManageMode := bomitemFields[6].Descriptor() + bomitemDescManageMode := bomitemFields[7].Descriptor() // bomitem.DefaultManageMode holds the default value on creation for the manageMode field. bomitem.DefaultManageMode = bomitemDescManageMode.Default.(string) // bomitem.ManageModeValidator is a validator for the "manageMode" field. It is called by the builders before save. bomitem.ManageModeValidator = bomitemDescManageMode.Validators[0].(func(string) error) // bomitemDescProcessCode is the schema descriptor for processCode field. - bomitemDescProcessCode := bomitemFields[7].Descriptor() + bomitemDescProcessCode := bomitemFields[8].Descriptor() // bomitem.DefaultProcessCode holds the default value on creation for the processCode field. bomitem.DefaultProcessCode = bomitemDescProcessCode.Default.(int) // bomitemDescUnitQty is the schema descriptor for unitQty field. - bomitemDescUnitQty := bomitemFields[8].Descriptor() + bomitemDescUnitQty := bomitemFields[9].Descriptor() // bomitem.DefaultUnitQty holds the default value on creation for the unitQty field. bomitem.DefaultUnitQty = bomitemDescUnitQty.Default.(float64) // bomitemDescLossRate is the schema descriptor for lossRate field. - bomitemDescLossRate := bomitemFields[9].Descriptor() + bomitemDescLossRate := bomitemFields[10].Descriptor() // bomitem.DefaultLossRate holds the default value on creation for the lossRate field. bomitem.DefaultLossRate = bomitemDescLossRate.Default.(float64) // bomitemDescRequiredQty is the schema descriptor for requiredQty field. - bomitemDescRequiredQty := bomitemFields[10].Descriptor() + bomitemDescRequiredQty := bomitemFields[11].Descriptor() // bomitem.DefaultRequiredQty holds the default value on creation for the requiredQty field. bomitem.DefaultRequiredQty = bomitemDescRequiredQty.Default.(float64) // bomitemDescCreatedAt is the schema descriptor for createdAt field. - bomitemDescCreatedAt := bomitemFields[12].Descriptor() + bomitemDescCreatedAt := bomitemFields[13].Descriptor() // bomitem.DefaultCreatedAt holds the default value on creation for the createdAt field. bomitem.DefaultCreatedAt = bomitemDescCreatedAt.Default.(func() time.Time) // bomitemDescID is the schema descriptor for id field. @@ -889,11 +895,11 @@ func init() { // user.StatusValidator is a validator for the "status" field. It is called by the builders before save. user.StatusValidator = userDescStatus.Validators[0].(func(string) error) // userDescCreatedAt is the schema descriptor for createdAt field. - userDescCreatedAt := userFields[8].Descriptor() + userDescCreatedAt := userFields[9].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the createdAt field. user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) // userDescUpdatedAt is the schema descriptor for updatedAt field. - userDescUpdatedAt := userFields[9].Descriptor() + userDescUpdatedAt := userFields[10].Descriptor() // user.DefaultUpdatedAt holds the default value on creation for the updatedAt field. user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) // user.UpdateDefaultUpdatedAt holds the default value on update for the updatedAt field. @@ -924,42 +930,48 @@ func init() { workorder.DefaultProductCode = workorderDescProductCode.Default.(string) // workorder.ProductCodeValidator is a validator for the "productCode" field. It is called by the builders before save. workorder.ProductCodeValidator = workorderDescProductCode.Validators[0].(func(string) error) + // workorderDescBomName is the schema descriptor for bomName field. + workorderDescBomName := workorderFields[4].Descriptor() + // workorder.DefaultBomName holds the default value on creation for the bomName field. + workorder.DefaultBomName = workorderDescBomName.Default.(string) + // workorder.BomNameValidator is a validator for the "bomName" field. It is called by the builders before save. + workorder.BomNameValidator = workorderDescBomName.Validators[0].(func(string) error) // workorderDescProductName is the schema descriptor for productName field. - workorderDescProductName := workorderFields[4].Descriptor() + workorderDescProductName := workorderFields[5].Descriptor() // workorder.DefaultProductName holds the default value on creation for the productName field. workorder.DefaultProductName = workorderDescProductName.Default.(string) // workorder.ProductNameValidator is a validator for the "productName" field. It is called by the builders before save. workorder.ProductNameValidator = workorderDescProductName.Validators[0].(func(string) error) // workorderDescQuantity is the schema descriptor for quantity field. - workorderDescQuantity := workorderFields[5].Descriptor() + workorderDescQuantity := workorderFields[6].Descriptor() // workorder.DefaultQuantity holds the default value on creation for the quantity field. workorder.DefaultQuantity = workorderDescQuantity.Default.(int) // workorderDescFinishedNum is the schema descriptor for finishedNum field. - workorderDescFinishedNum := workorderFields[6].Descriptor() + workorderDescFinishedNum := workorderFields[7].Descriptor() // workorder.DefaultFinishedNum holds the default value on creation for the finishedNum field. workorder.DefaultFinishedNum = workorderDescFinishedNum.Default.(int) // workorderDescFailNum is the schema descriptor for failNum field. - workorderDescFailNum := workorderFields[7].Descriptor() + workorderDescFailNum := workorderFields[8].Descriptor() // workorder.DefaultFailNum holds the default value on creation for the failNum field. workorder.DefaultFailNum = workorderDescFailNum.Default.(int) // workorderDescProcessSeq is the schema descriptor for processSeq field. - workorderDescProcessSeq := workorderFields[8].Descriptor() + workorderDescProcessSeq := workorderFields[9].Descriptor() // workorder.DefaultProcessSeq holds the default value on creation for the processSeq field. workorder.DefaultProcessSeq = workorderDescProcessSeq.Default.(string) // workorder.ProcessSeqValidator is a validator for the "processSeq" field. It is called by the builders before save. workorder.ProcessSeqValidator = workorderDescProcessSeq.Validators[0].(func(string) error) // workorderDescStatus is the schema descriptor for status field. - workorderDescStatus := workorderFields[9].Descriptor() + workorderDescStatus := workorderFields[10].Descriptor() // workorder.DefaultStatus holds the default value on creation for the status field. workorder.DefaultStatus = workorderDescStatus.Default.(string) // workorder.StatusValidator is a validator for the "status" field. It is called by the builders before save. workorder.StatusValidator = workorderDescStatus.Validators[0].(func(string) error) // workorderDescCreatedAt is the schema descriptor for createdAt field. - workorderDescCreatedAt := workorderFields[14].Descriptor() + workorderDescCreatedAt := workorderFields[15].Descriptor() // workorder.DefaultCreatedAt holds the default value on creation for the createdAt field. workorder.DefaultCreatedAt = workorderDescCreatedAt.Default.(func() time.Time) // workorderDescUpdatedAt is the schema descriptor for updatedAt field. - workorderDescUpdatedAt := workorderFields[15].Descriptor() + workorderDescUpdatedAt := workorderFields[16].Descriptor() // workorder.DefaultUpdatedAt holds the default value on creation for the updatedAt field. workorder.DefaultUpdatedAt = workorderDescUpdatedAt.Default.(func() time.Time) // workorder.UpdateDefaultUpdatedAt holds the default value on update for the updatedAt field. diff --git a/bj_power_mes/ent/user.go b/bj_power_mes/ent/user.go index af5f942..53086c7 100644 --- a/bj_power_mes/ent/user.go +++ b/bj_power_mes/ent/user.go @@ -31,6 +31,8 @@ type User struct { WorkstationPassword string `json:"workstationPassword,omitempty"` // Status holds the value of the "status" field. Status string `json:"status,omitempty"` + // 最近登录时间(unix秒,后台登录更新;工位终端登录不计) + LastLoginAt *int64 `json:"lastLoginAt,omitempty"` // CreatedAt holds the value of the "createdAt" field. CreatedAt time.Time `json:"createdAt,omitempty"` // UpdatedAt holds the value of the "updatedAt" field. @@ -45,7 +47,7 @@ func (*User) scanValues(columns []string) ([]any, error) { switch columns[i] { case user.FieldCanLoginWorkstation: values[i] = new(sql.NullBool) - case user.FieldID, user.FieldRoleId: + case user.FieldID, user.FieldRoleId, user.FieldLastLoginAt: values[i] = new(sql.NullInt64) case user.FieldUsername, user.FieldPassword, user.FieldName, user.FieldWorkstationPassword, user.FieldStatus: values[i] = new(sql.NullString) @@ -114,6 +116,13 @@ func (_m *User) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Status = value.String } + case user.FieldLastLoginAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field lastLoginAt", values[i]) + } else if value.Valid { + _m.LastLoginAt = new(int64) + *_m.LastLoginAt = value.Int64 + } case user.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field createdAt", values[i]) @@ -184,6 +193,11 @@ func (_m *User) String() string { builder.WriteString("status=") builder.WriteString(_m.Status) builder.WriteString(", ") + if v := _m.LastLoginAt; v != nil { + builder.WriteString("lastLoginAt=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") builder.WriteString("createdAt=") builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") diff --git a/bj_power_mes/ent/user/user.go b/bj_power_mes/ent/user/user.go index fec9ab6..69baac5 100644 --- a/bj_power_mes/ent/user/user.go +++ b/bj_power_mes/ent/user/user.go @@ -27,6 +27,8 @@ const ( FieldWorkstationPassword = "workstation_password" // FieldStatus holds the string denoting the status field in the database. FieldStatus = "status" + // FieldLastLoginAt holds the string denoting the lastloginat field in the database. + FieldLastLoginAt = "last_login_at" // FieldCreatedAt holds the string denoting the createdat field in the database. FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updatedat field in the database. @@ -45,6 +47,7 @@ var Columns = []string{ FieldCanLoginWorkstation, FieldWorkstationPassword, FieldStatus, + FieldLastLoginAt, FieldCreatedAt, FieldUpdatedAt, } @@ -129,6 +132,11 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldStatus, opts...).ToFunc() } +// ByLastLoginAt orders the results by the lastLoginAt field. +func ByLastLoginAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastLoginAt, opts...).ToFunc() +} + // ByCreatedAt orders the results by the createdAt field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/bj_power_mes/ent/user/where.go b/bj_power_mes/ent/user/where.go index 42f67f1..a6ce3cb 100644 --- a/bj_power_mes/ent/user/where.go +++ b/bj_power_mes/ent/user/where.go @@ -89,6 +89,11 @@ func Status(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldStatus, v)) } +// LastLoginAt applies equality check predicate on the "lastLoginAt" field. It's identical to LastLoginAtEQ. +func LastLoginAt(v int64) predicate.User { + return predicate.User(sql.FieldEQ(FieldLastLoginAt, v)) +} + // CreatedAt applies equality check predicate on the "createdAt" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) @@ -494,6 +499,56 @@ func StatusContainsFold(v string) predicate.User { return predicate.User(sql.FieldContainsFold(FieldStatus, v)) } +// LastLoginAtEQ applies the EQ predicate on the "lastLoginAt" field. +func LastLoginAtEQ(v int64) predicate.User { + return predicate.User(sql.FieldEQ(FieldLastLoginAt, v)) +} + +// LastLoginAtNEQ applies the NEQ predicate on the "lastLoginAt" field. +func LastLoginAtNEQ(v int64) predicate.User { + return predicate.User(sql.FieldNEQ(FieldLastLoginAt, v)) +} + +// LastLoginAtIn applies the In predicate on the "lastLoginAt" field. +func LastLoginAtIn(vs ...int64) predicate.User { + return predicate.User(sql.FieldIn(FieldLastLoginAt, vs...)) +} + +// LastLoginAtNotIn applies the NotIn predicate on the "lastLoginAt" field. +func LastLoginAtNotIn(vs ...int64) predicate.User { + return predicate.User(sql.FieldNotIn(FieldLastLoginAt, vs...)) +} + +// LastLoginAtGT applies the GT predicate on the "lastLoginAt" field. +func LastLoginAtGT(v int64) predicate.User { + return predicate.User(sql.FieldGT(FieldLastLoginAt, v)) +} + +// LastLoginAtGTE applies the GTE predicate on the "lastLoginAt" field. +func LastLoginAtGTE(v int64) predicate.User { + return predicate.User(sql.FieldGTE(FieldLastLoginAt, v)) +} + +// LastLoginAtLT applies the LT predicate on the "lastLoginAt" field. +func LastLoginAtLT(v int64) predicate.User { + return predicate.User(sql.FieldLT(FieldLastLoginAt, v)) +} + +// LastLoginAtLTE applies the LTE predicate on the "lastLoginAt" field. +func LastLoginAtLTE(v int64) predicate.User { + return predicate.User(sql.FieldLTE(FieldLastLoginAt, v)) +} + +// LastLoginAtIsNil applies the IsNil predicate on the "lastLoginAt" field. +func LastLoginAtIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldLastLoginAt)) +} + +// LastLoginAtNotNil applies the NotNil predicate on the "lastLoginAt" field. +func LastLoginAtNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldLastLoginAt)) +} + // CreatedAtEQ applies the EQ predicate on the "createdAt" field. func CreatedAtEQ(v time.Time) predicate.User { return predicate.User(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/bj_power_mes/ent/user_create.go b/bj_power_mes/ent/user_create.go index 38d7672..6a9b4a7 100644 --- a/bj_power_mes/ent/user_create.go +++ b/bj_power_mes/ent/user_create.go @@ -102,6 +102,20 @@ func (_c *UserCreate) SetNillableStatus(v *string) *UserCreate { return _c } +// SetLastLoginAt sets the "lastLoginAt" field. +func (_c *UserCreate) SetLastLoginAt(v int64) *UserCreate { + _c.mutation.SetLastLoginAt(v) + return _c +} + +// SetNillableLastLoginAt sets the "lastLoginAt" field if the given value is not nil. +func (_c *UserCreate) SetNillableLastLoginAt(v *int64) *UserCreate { + if v != nil { + _c.SetLastLoginAt(*v) + } + return _c +} + // SetCreatedAt sets the "createdAt" field. func (_c *UserCreate) SetCreatedAt(v time.Time) *UserCreate { _c.mutation.SetCreatedAt(v) @@ -303,6 +317,10 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldStatus, field.TypeString, value) _node.Status = value } + if value, ok := _c.mutation.LastLoginAt(); ok { + _spec.SetField(user.FieldLastLoginAt, field.TypeInt64, value) + _node.LastLoginAt = &value + } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(user.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value diff --git a/bj_power_mes/ent/user_update.go b/bj_power_mes/ent/user_update.go index a305fd7..fb37dc6 100644 --- a/bj_power_mes/ent/user_update.go +++ b/bj_power_mes/ent/user_update.go @@ -146,6 +146,33 @@ func (_u *UserUpdate) SetNillableStatus(v *string) *UserUpdate { return _u } +// SetLastLoginAt sets the "lastLoginAt" field. +func (_u *UserUpdate) SetLastLoginAt(v int64) *UserUpdate { + _u.mutation.ResetLastLoginAt() + _u.mutation.SetLastLoginAt(v) + return _u +} + +// SetNillableLastLoginAt sets the "lastLoginAt" field if the given value is not nil. +func (_u *UserUpdate) SetNillableLastLoginAt(v *int64) *UserUpdate { + if v != nil { + _u.SetLastLoginAt(*v) + } + return _u +} + +// AddLastLoginAt adds value to the "lastLoginAt" field. +func (_u *UserUpdate) AddLastLoginAt(v int64) *UserUpdate { + _u.mutation.AddLastLoginAt(v) + return _u +} + +// ClearLastLoginAt clears the value of the "lastLoginAt" field. +func (_u *UserUpdate) ClearLastLoginAt() *UserUpdate { + _u.mutation.ClearLastLoginAt() + return _u +} + // SetUpdatedAt sets the "updatedAt" field. func (_u *UserUpdate) SetUpdatedAt(v time.Time) *UserUpdate { _u.mutation.SetUpdatedAt(v) @@ -277,6 +304,15 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.Status(); ok { _spec.SetField(user.FieldStatus, field.TypeString, value) } + if value, ok := _u.mutation.LastLoginAt(); ok { + _spec.SetField(user.FieldLastLoginAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedLastLoginAt(); ok { + _spec.AddField(user.FieldLastLoginAt, field.TypeInt64, value) + } + if _u.mutation.LastLoginAtCleared() { + _spec.ClearField(user.FieldLastLoginAt, field.TypeInt64) + } if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } @@ -422,6 +458,33 @@ func (_u *UserUpdateOne) SetNillableStatus(v *string) *UserUpdateOne { return _u } +// SetLastLoginAt sets the "lastLoginAt" field. +func (_u *UserUpdateOne) SetLastLoginAt(v int64) *UserUpdateOne { + _u.mutation.ResetLastLoginAt() + _u.mutation.SetLastLoginAt(v) + return _u +} + +// SetNillableLastLoginAt sets the "lastLoginAt" field if the given value is not nil. +func (_u *UserUpdateOne) SetNillableLastLoginAt(v *int64) *UserUpdateOne { + if v != nil { + _u.SetLastLoginAt(*v) + } + return _u +} + +// AddLastLoginAt adds value to the "lastLoginAt" field. +func (_u *UserUpdateOne) AddLastLoginAt(v int64) *UserUpdateOne { + _u.mutation.AddLastLoginAt(v) + return _u +} + +// ClearLastLoginAt clears the value of the "lastLoginAt" field. +func (_u *UserUpdateOne) ClearLastLoginAt() *UserUpdateOne { + _u.mutation.ClearLastLoginAt() + return _u +} + // SetUpdatedAt sets the "updatedAt" field. func (_u *UserUpdateOne) SetUpdatedAt(v time.Time) *UserUpdateOne { _u.mutation.SetUpdatedAt(v) @@ -583,6 +646,15 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { if value, ok := _u.mutation.Status(); ok { _spec.SetField(user.FieldStatus, field.TypeString, value) } + if value, ok := _u.mutation.LastLoginAt(); ok { + _spec.SetField(user.FieldLastLoginAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedLastLoginAt(); ok { + _spec.AddField(user.FieldLastLoginAt, field.TypeInt64, value) + } + if _u.mutation.LastLoginAtCleared() { + _spec.ClearField(user.FieldLastLoginAt, field.TypeInt64) + } if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(user.FieldUpdatedAt, field.TypeTime, value) } diff --git a/bj_power_mes/ent/workorder.go b/bj_power_mes/ent/workorder.go index dec74b7..a6f71b0 100644 --- a/bj_power_mes/ent/workorder.go +++ b/bj_power_mes/ent/workorder.go @@ -24,6 +24,8 @@ type WorkOrder struct { ProductTypeId int `json:"productTypeId,omitempty"` // 产品编码 ProductCode string `json:"productCode,omitempty"` + // 建单时选定的BOM名称(该型号下多份BOM之一,备料按此计算) + BomName string `json:"bomName,omitempty"` // ProductName holds the value of the "productName" field. ProductName string `json:"productName,omitempty"` // 工件数量 @@ -60,7 +62,7 @@ func (*WorkOrder) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case workorder.FieldID, workorder.FieldProductTypeId, workorder.FieldQuantity, workorder.FieldFinishedNum, workorder.FieldFailNum: values[i] = new(sql.NullInt64) - case workorder.FieldWorkOrderNo, workorder.FieldProductCode, workorder.FieldProductName, workorder.FieldProcessSeq, workorder.FieldStatus: + case workorder.FieldWorkOrderNo, workorder.FieldProductCode, workorder.FieldBomName, workorder.FieldProductName, workorder.FieldProcessSeq, workorder.FieldStatus: values[i] = new(sql.NullString) case workorder.FieldPlanStart, workorder.FieldPlanEnd, workorder.FieldCompletedAt, workorder.FieldCreatedAt, workorder.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -103,6 +105,12 @@ func (_m *WorkOrder) assignValues(columns []string, values []any) error { } else if value.Valid { _m.ProductCode = value.String } + case workorder.FieldBomName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field bomName", values[i]) + } else if value.Valid { + _m.BomName = value.String + } case workorder.FieldProductName: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field productName", values[i]) @@ -226,6 +234,9 @@ func (_m *WorkOrder) String() string { builder.WriteString("productCode=") builder.WriteString(_m.ProductCode) builder.WriteString(", ") + builder.WriteString("bomName=") + builder.WriteString(_m.BomName) + builder.WriteString(", ") builder.WriteString("productName=") builder.WriteString(_m.ProductName) builder.WriteString(", ") diff --git a/bj_power_mes/ent/workorder/where.go b/bj_power_mes/ent/workorder/where.go index e171b94..c65efcf 100644 --- a/bj_power_mes/ent/workorder/where.go +++ b/bj_power_mes/ent/workorder/where.go @@ -69,6 +69,11 @@ func ProductCode(v string) predicate.WorkOrder { return predicate.WorkOrder(sql.FieldEQ(FieldProductCode, v)) } +// BomName applies equality check predicate on the "bomName" field. It's identical to BomNameEQ. +func BomName(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldEQ(FieldBomName, v)) +} + // ProductName applies equality check predicate on the "productName" field. It's identical to ProductNameEQ. func ProductName(v string) predicate.WorkOrder { return predicate.WorkOrder(sql.FieldEQ(FieldProductName, v)) @@ -294,6 +299,71 @@ func ProductCodeContainsFold(v string) predicate.WorkOrder { return predicate.WorkOrder(sql.FieldContainsFold(FieldProductCode, v)) } +// BomNameEQ applies the EQ predicate on the "bomName" field. +func BomNameEQ(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldEQ(FieldBomName, v)) +} + +// BomNameNEQ applies the NEQ predicate on the "bomName" field. +func BomNameNEQ(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldNEQ(FieldBomName, v)) +} + +// BomNameIn applies the In predicate on the "bomName" field. +func BomNameIn(vs ...string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldIn(FieldBomName, vs...)) +} + +// BomNameNotIn applies the NotIn predicate on the "bomName" field. +func BomNameNotIn(vs ...string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldNotIn(FieldBomName, vs...)) +} + +// BomNameGT applies the GT predicate on the "bomName" field. +func BomNameGT(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldGT(FieldBomName, v)) +} + +// BomNameGTE applies the GTE predicate on the "bomName" field. +func BomNameGTE(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldGTE(FieldBomName, v)) +} + +// BomNameLT applies the LT predicate on the "bomName" field. +func BomNameLT(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldLT(FieldBomName, v)) +} + +// BomNameLTE applies the LTE predicate on the "bomName" field. +func BomNameLTE(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldLTE(FieldBomName, v)) +} + +// BomNameContains applies the Contains predicate on the "bomName" field. +func BomNameContains(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldContains(FieldBomName, v)) +} + +// BomNameHasPrefix applies the HasPrefix predicate on the "bomName" field. +func BomNameHasPrefix(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldHasPrefix(FieldBomName, v)) +} + +// BomNameHasSuffix applies the HasSuffix predicate on the "bomName" field. +func BomNameHasSuffix(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldHasSuffix(FieldBomName, v)) +} + +// BomNameEqualFold applies the EqualFold predicate on the "bomName" field. +func BomNameEqualFold(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldEqualFold(FieldBomName, v)) +} + +// BomNameContainsFold applies the ContainsFold predicate on the "bomName" field. +func BomNameContainsFold(v string) predicate.WorkOrder { + return predicate.WorkOrder(sql.FieldContainsFold(FieldBomName, v)) +} + // ProductNameEQ applies the EQ predicate on the "productName" field. func ProductNameEQ(v string) predicate.WorkOrder { return predicate.WorkOrder(sql.FieldEQ(FieldProductName, v)) diff --git a/bj_power_mes/ent/workorder/workorder.go b/bj_power_mes/ent/workorder/workorder.go index 8de8eb3..c59c76e 100644 --- a/bj_power_mes/ent/workorder/workorder.go +++ b/bj_power_mes/ent/workorder/workorder.go @@ -19,6 +19,8 @@ const ( FieldProductTypeId = "product_type_id" // FieldProductCode holds the string denoting the productcode field in the database. FieldProductCode = "product_code" + // FieldBomName holds the string denoting the bomname field in the database. + FieldBomName = "bom_name" // FieldProductName holds the string denoting the productname field in the database. FieldProductName = "product_name" // FieldQuantity holds the string denoting the quantity field in the database. @@ -53,6 +55,7 @@ var Columns = []string{ FieldWorkOrderNo, FieldProductTypeId, FieldProductCode, + FieldBomName, FieldProductName, FieldQuantity, FieldFinishedNum, @@ -84,6 +87,10 @@ var ( DefaultProductCode string // ProductCodeValidator is a validator for the "productCode" field. It is called by the builders before save. ProductCodeValidator func(string) error + // DefaultBomName holds the default value on creation for the "bomName" field. + DefaultBomName string + // BomNameValidator is a validator for the "bomName" field. It is called by the builders before save. + BomNameValidator func(string) error // DefaultProductName holds the default value on creation for the "productName" field. DefaultProductName string // ProductNameValidator is a validator for the "productName" field. It is called by the builders before save. @@ -135,6 +142,11 @@ func ByProductCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldProductCode, opts...).ToFunc() } +// ByBomName orders the results by the bomName field. +func ByBomName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBomName, opts...).ToFunc() +} + // ByProductName orders the results by the productName field. func ByProductName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldProductName, opts...).ToFunc() diff --git a/bj_power_mes/ent/workorder_create.go b/bj_power_mes/ent/workorder_create.go index e170352..b6b22bb 100644 --- a/bj_power_mes/ent/workorder_create.go +++ b/bj_power_mes/ent/workorder_create.go @@ -46,6 +46,20 @@ func (_c *WorkOrderCreate) SetNillableProductCode(v *string) *WorkOrderCreate { return _c } +// SetBomName sets the "bomName" field. +func (_c *WorkOrderCreate) SetBomName(v string) *WorkOrderCreate { + _c.mutation.SetBomName(v) + return _c +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_c *WorkOrderCreate) SetNillableBomName(v *string) *WorkOrderCreate { + if v != nil { + _c.SetBomName(*v) + } + return _c +} + // SetProductName sets the "productName" field. func (_c *WorkOrderCreate) SetProductName(v string) *WorkOrderCreate { _c.mutation.SetProductName(v) @@ -251,6 +265,10 @@ func (_c *WorkOrderCreate) defaults() { v := workorder.DefaultProductCode _c.mutation.SetProductCode(v) } + if _, ok := _c.mutation.BomName(); !ok { + v := workorder.DefaultBomName + _c.mutation.SetBomName(v) + } if _, ok := _c.mutation.ProductName(); !ok { v := workorder.DefaultProductName _c.mutation.SetProductName(v) @@ -306,6 +324,14 @@ func (_c *WorkOrderCreate) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.productCode": %w`, err)} } } + if _, ok := _c.mutation.BomName(); !ok { + return &ValidationError{Name: "bomName", err: errors.New(`ent: missing required field "WorkOrder.bomName"`)} + } + if v, ok := _c.mutation.BomName(); ok { + if err := workorder.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.bomName": %w`, err)} + } + } if _, ok := _c.mutation.ProductName(); !ok { return &ValidationError{Name: "productName", err: errors.New(`ent: missing required field "WorkOrder.productName"`)} } @@ -391,6 +417,10 @@ func (_c *WorkOrderCreate) createSpec() (*WorkOrder, *sqlgraph.CreateSpec) { _spec.SetField(workorder.FieldProductCode, field.TypeString, value) _node.ProductCode = value } + if value, ok := _c.mutation.BomName(); ok { + _spec.SetField(workorder.FieldBomName, field.TypeString, value) + _node.BomName = value + } if value, ok := _c.mutation.ProductName(); ok { _spec.SetField(workorder.FieldProductName, field.TypeString, value) _node.ProductName = value diff --git a/bj_power_mes/ent/workorder_update.go b/bj_power_mes/ent/workorder_update.go index ee15a89..b2d1d89 100644 --- a/bj_power_mes/ent/workorder_update.go +++ b/bj_power_mes/ent/workorder_update.go @@ -78,6 +78,20 @@ func (_u *WorkOrderUpdate) SetNillableProductCode(v *string) *WorkOrderUpdate { return _u } +// SetBomName sets the "bomName" field. +func (_u *WorkOrderUpdate) SetBomName(v string) *WorkOrderUpdate { + _u.mutation.SetBomName(v) + return _u +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_u *WorkOrderUpdate) SetNillableBomName(v *string) *WorkOrderUpdate { + if v != nil { + _u.SetBomName(*v) + } + return _u +} + // SetProductName sets the "productName" field. func (_u *WorkOrderUpdate) SetProductName(v string) *WorkOrderUpdate { _u.mutation.SetProductName(v) @@ -320,6 +334,11 @@ func (_u *WorkOrderUpdate) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.productCode": %w`, err)} } } + if v, ok := _u.mutation.BomName(); ok { + if err := workorder.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.bomName": %w`, err)} + } + } if v, ok := _u.mutation.ProductName(); ok { if err := workorder.ProductNameValidator(v); err != nil { return &ValidationError{Name: "productName", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.productName": %w`, err)} @@ -368,6 +387,9 @@ func (_u *WorkOrderUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.ProductCode(); ok { _spec.SetField(workorder.FieldProductCode, field.TypeString, value) } + if value, ok := _u.mutation.BomName(); ok { + _spec.SetField(workorder.FieldBomName, field.TypeString, value) + } if value, ok := _u.mutation.ProductName(); ok { _spec.SetField(workorder.FieldProductName, field.TypeString, value) } @@ -496,6 +518,20 @@ func (_u *WorkOrderUpdateOne) SetNillableProductCode(v *string) *WorkOrderUpdate return _u } +// SetBomName sets the "bomName" field. +func (_u *WorkOrderUpdateOne) SetBomName(v string) *WorkOrderUpdateOne { + _u.mutation.SetBomName(v) + return _u +} + +// SetNillableBomName sets the "bomName" field if the given value is not nil. +func (_u *WorkOrderUpdateOne) SetNillableBomName(v *string) *WorkOrderUpdateOne { + if v != nil { + _u.SetBomName(*v) + } + return _u +} + // SetProductName sets the "productName" field. func (_u *WorkOrderUpdateOne) SetProductName(v string) *WorkOrderUpdateOne { _u.mutation.SetProductName(v) @@ -751,6 +787,11 @@ func (_u *WorkOrderUpdateOne) check() error { return &ValidationError{Name: "productCode", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.productCode": %w`, err)} } } + if v, ok := _u.mutation.BomName(); ok { + if err := workorder.BomNameValidator(v); err != nil { + return &ValidationError{Name: "bomName", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.bomName": %w`, err)} + } + } if v, ok := _u.mutation.ProductName(); ok { if err := workorder.ProductNameValidator(v); err != nil { return &ValidationError{Name: "productName", err: fmt.Errorf(`ent: validator failed for field "WorkOrder.productName": %w`, err)} @@ -816,6 +857,9 @@ func (_u *WorkOrderUpdateOne) sqlSave(ctx context.Context) (_node *WorkOrder, er if value, ok := _u.mutation.ProductCode(); ok { _spec.SetField(workorder.FieldProductCode, field.TypeString, value) } + if value, ok := _u.mutation.BomName(); ok { + _spec.SetField(workorder.FieldBomName, field.TypeString, value) + } if value, ok := _u.mutation.ProductName(); ok { _spec.SetField(workorder.FieldProductName, field.TypeString, value) } diff --git a/bj_power_mes/frontend/src/components/ProcessSeqPicker.vue b/bj_power_mes/frontend/src/components/ProcessSeqPicker.vue index 2c5993b..ec368e7 100644 --- a/bj_power_mes/frontend/src/components/ProcessSeqPicker.vue +++ b/bj_power_mes/frontend/src/components/ProcessSeqPicker.vue @@ -6,7 +6,7 @@ {{ summary }} - 工位{{ i }} + 工位{{ i }}(已完成) @@ -15,7 +15,8 @@ import { computed } from 'vue' const props = defineProps({ - modelValue: { type: String, default: '' } + modelValue: { type: String, default: '' }, + disabledCodes: { type: Array, default: () => [] } // 已完成工位号禁选(如 ≤当前工位) }) const emit = defineEmits(['update:modelValue']) diff --git a/bj_power_mes/frontend/src/help.js b/bj_power_mes/frontend/src/help.js index 6773cf2..ddbe94f 100644 --- a/bj_power_mes/frontend/src/help.js +++ b/bj_power_mes/frontend/src/help.js @@ -3,16 +3,16 @@ // 说明:这里的"数据来源"特指该字段的数据由谁产生、从哪个系统/页面来,帮助跑通真实流程。 export const helpProductType = { - title: '产品类型(档案维护)', + title: '产品型号(档案维护)', overview: - '产品类型 = 企业要生产哪些产品的分类档案,是整条产线的源头基础数据。\n\n主数据链条:产品类型 → 工单(选产品类型) → 物料清单(给该产品编码配料) → 日排产 → 备料单 → 库房出库 → 产线生产。\n\n关系说明(几对几):\n· 产品类型 ↔ 工单:1 对 N。一个产品类型可以建多个工单(同一型号今天做一批、明天做一批,工单号不同、产品编码相同)。\n· 产品类型 ↔ 物料清单:按产品编码 1 对 1。同型号产品共用一份 BOM。\n· 产品类型 ↔ 备料单:间接。备料单由「日排产 × 该产品 BOM」算出,与产品类型无直接约束。\n· 强约束:工单/物料清单都挂在产品编码下,产品类型编码变了,下游关联会断,建议建好后不要改。\n· 与 WMS 的关系:两套系统解耦。本页(MES)维护产品与 BOM;WMS(库房)维护物料档案与库存,靠「物料编码」对齐;成品编码由人员手动录入 WMS 实现两系统对齐。', + '产品型号 = 企业要生产哪些产品的分类档案,是整条产线的源头基础数据。\n\n主数据链条:产品型号 → 工单(选产品型号+BOM) → 物料清单(给该产品编码配料) → 日排产 → 备料单 → 库房出库 → 产线生产 → 完工成品自动回流 WMS 入库。\n\n关系说明(几对几):\n· 产品型号 ↔ 工单:1 对 N。一个产品型号可以建多个工单(同一型号今天做一批、明天做一批,工单号不同、产品编码相同)。\n· 产品型号 ↔ 物料清单:1 对多(选用制)。同一型号可并存多份 BOM(不同厂家供货/含半成品结构),用「BOM 名称」区分,工单建单时选定一份,备料/装机都按工单锁定的那份算。\n· 产品型号 ↔ 备料单:间接。备料单由「日排产 × 工单锁定的 BOM」算出,与产品型号无直接约束。\n· 强约束:工单/物料清单都挂在产品编码下,产品型号编码变了,下游关联会断,建议建好后不要改。\n· 与 WMS 的关系:两套系统解耦。产品/物料编码主数据以 WMS 为单一来源,MES 实时拉取(WMS 不可达时允许手填、仅提示不强校验);成品完工后由系统自动生成 SN 回流 WMS 入库,无需人工对账。', fields: [ - { name: '名称', source: '手工填写。用户自定义,无上游来源。', purpose: '产品类型的中文名,用于识别与下拉展示', fill: '如:智能电表、断路器、配电柜' }, + { name: '名称', source: '手工填写。用户自定义,无上游来源。', purpose: '产品型号的中文名,用于识别与下拉展示', fill: '如:智能电表、断路器、配电柜' }, { name: '编码', source: '系统自动生成 + 可手动修改。新建时会自动带出(格式 PROD-YYYYMMDD-序号),你可直接使用或改为自定义编码;编辑时保留已存值。一个产品编码可对应多个工单(同一型号今天做一批、明天做一批,工单号不同,产品编码相同)。', purpose: '产品唯一编码。它是 MES 内部识别该产品的标识,决定该产品的物料清单(BOM)内容,也是工单、备料、拧紧、追溯的挂靠键。', - fill: '建议用有意义的编码,如 METER-01。要点:①本编码与 WMS 是两套系统、互相解耦,MES 这边独立维护,WMS 那边各自单独录入;②成品入库时,由操作人员把本编码**手动录入 WMS**,两系统靠人工对齐;③如产品编码就是"产品类型",则直接沿用该类型编码即可;④建好后不要随意改,否则下游工单/追溯的关联会断。' + fill: '建议用有意义的编码,如 METER-01。要点:①编码主数据以 WMS(库房) 为单一来源,本系统实时从 WMS 拉取物料/产品档案;WMS 不可达时允许手填、仅提示不强校验;②成品完工后系统自动生成 SN 并回流 WMS 入库,无需人工对账;③如产品编码就是"产品型号",则直接沿用该类型编码即可;④建好后不要随意改,否则下游工单/追溯的关联会断。' }, { name: '类别', source: '手工填写。可选,无上游来源。', purpose: '产品所属大类,便于分类统计', fill: '如:计量表 / 开关柜;可空' }, { name: '启用', source: '开关选择。默认开启。', purpose: '启用后才允许在新建工单/备料的下拉里被选到', fill: '默认开启;暂时停产的可关闭(不删除,保留档案)' }, @@ -23,15 +23,16 @@ export const helpProductType = { export const helpWorkOrder = { title: '工单管理(新建 / 编辑 / 删除工单)', overview: - '工单 = 一个"合同/生产批次"的唯一载体,是生产执行和仓库出库挂靠的核心对象。\n\n全流程:先有产品类型 → 建工单(选产品类型、定数量、勾选工位组合) → 进"物料清单"给该产品配料 → 到"日排产"排哪天产多少 → 仓库按排产备料出库 → 产线装配/拧紧/报工 → 完工。\n工单号会贯穿 备料单、拧紧查询、手动报工、工件追溯、操作日志 全链路。\n\n查询:支持按 工单号 / 产品编码 / 产品名称(模糊) / 状态 组合筛选。\n删除:仅「已创建 / 待排产 / 已发布」且无排产、备料、生产数据的工单可删除(操作列「删除」),生产中或已完成的工单不可删除。', + '工单 = 一个"合同/生产批次"的唯一载体,是生产执行和仓库出库挂靠的核心对象。\n\n全流程:先有产品型号 → 建工单(选产品型号、选定 BOM、定数量、勾选工位组合) → 进"物料清单"给该产品配料 → 到"日排产"排哪天产多少 → 仓库按排产备料出库 → 产线装配/拧紧/报工 → 完工(成品 SN 系统自动生成,并自动回流 WMS 入库为"成品"品类)。\n工单号会贯穿 备料单、拧紧查询、手动报工、工件追溯、操作日志 全链路。\n\n查询:支持按 工单号 / 产品编码 / 产品名称(模糊) / 状态 组合筛选;列表有 BOM 列显示各工单选用的 BOM 名称。\n删除:仅「已创建 / 待排产 / 已发布」且无排产、备料、生产数据的工单可删除(操作列「删除」),生产中或已完成的工单不可删除。', sections: [ { title: '工单基本信息', fields: [ { name: '工单号', source: '系统自动生成(WO+时间戳),可手工改。新建时自动带出。', purpose: '唯一标识一个工单,备料/出库/报工/追溯都挂在此号下', fill: '一般不用改;如改动须保证唯一' }, - { name: '产品类型', source: '下拉选择。数据来自"产品类型"页面已启用的记录。', purpose: '说明这批生产的是什么产品,并决定物料清单内容', fill: '先到"产品类型"页面建好记录再选择' }, - { name: '产品编码', source: '手工填写。通常与所选产品类型的编码一致。', purpose: '产品唯一编码,用于展示与追溯', fill: '如 METER-01,与产品类型编码对齐' }, - { name: '产品名称', source: '手工填写。通常与产品类型名称一致。', purpose: '产品中文名,用于展示', fill: '如 智能电表' }, + { name: '产品型号', source: '下拉选择。数据来自"产品型号"页面已启用的记录。', purpose: '说明这批生产的是什么产品,并决定物料清单内容', fill: '先到"产品型号"页面建好记录再选择' }, + { name: 'BOM 名称', source: '下拉选择。按所选产品编码实时拉取该型号下并存的 BOM(显示名称+料数),可输入新名创建。', purpose: '本工单选用哪一份 BOM;后续备料算料、装机绑定校验都锁定这一份,不同厂家料单互不串', fill: '一般选「默认」或对应厂家的料单名;不选则用「默认」' }, + { name: '产品编码', source: '手工填写。通常与所选产品型号的编码一致。', purpose: '产品唯一编码,用于展示与追溯', fill: '如 METER-01,与产品型号编码对齐' }, + { name: '产品名称', source: '手工填写。通常与产品型号名称一致。', purpose: '产品中文名,用于展示', fill: '如 智能电表' }, { name: '数量', source: '手工填写整数。', purpose: '本工单生产总数量(合同台数),决定需求量与排产上限', fill: '整数 ≥1,如 100' }, { name: '工位组合', source: '勾选 12 个工位(工位1~工位12)。', purpose: '本工单要经过的工位集合,PLC 按此组合下发控制产线', fill: '勾选要经过的工位即可,系统自动按 1→12 顺序执行(传送带不回走);半成品再上线可只勾漏做的工位' }, { name: '状态', source: '新建时选"已创建",后续由流程推进。', purpose: '工单生命周期:创建→发布→生产→完成', fill: '新建时选"已创建";也可在列表操作列点「状态」快捷流转(发布/开工/完工)' } @@ -64,9 +65,10 @@ export const helpDailyPlan = { export const helpBom = { title: '物料清单(产品 BOM)', overview: - '物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按产品编码维护:同型号产品今天做一批、明天做一批,工单号不同、产品编码相同,共用这一份 BOM。\n仓库(库房)按工单取其产品的 BOM 计算备料/出库需求量,并强约束累计出库 ≤ 需求总量。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是精密件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过物料编码对齐。\n\n编码对应规则:本页的「物料编码」必须与 WMS 物料档案里的「编码」完全一致;「管理方式」也要对应(精密件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n数据链路:先建「产品类型」→ 到本页选产品编码,为它逐条添加物料 → 之后仓库才能为对应工单领料。', + '物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编码 + BOM 名称」维护:同一产品编码可并存多份 BOM(选用制)——不同厂家供货、含半成品的结构差异,都用不同的 BOM 名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份 BOM,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是精密件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过物料编码对齐。\n\n编码对应规则:本页的「物料编码」必须与 WMS 物料档案里的「编码」完全一致;「管理方式」也要对应(精密件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份 BOM 一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的 BOM 名称在本页「BOM 名称」下拉直接输入即可创建。\n\n数据链路:先建「产品型号」→ 到本页选产品编码 + 命名 BOM,逐条添加物料 → 建工单时选定 BOM → 仓库才能为对应工单领料。', fields: [ - { name: '产品编码', source: '下拉选择。来自「产品类型」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编码' }, + { name: 'BOM 名称', source: '下拉选择/输入(可搜索,输入新名即创建)。缺省「默认」。', purpose: '同一产品编码下多份 BOM 的区分名,工单建单时按此选定', fill: '如「默认」「A厂料单」「含半成品」;同一型号下名称唯一' }, + { name: '产品编码', source: '下拉选择。来自「产品型号」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编码' }, { name: '物料编码', source: '手工填写。必须与 WMS(库房) 物料档案的编码一致。', purpose: '该物料在库房的全库唯一编码,两系统靠它关联', fill: '如 螺丝=GJ-LUOSI-M16;编码对不上 WMS 将无法备料' }, { name: '物料名称', source: '手工填写。', purpose: '物料中文名,便于识别', fill: '如 十字螺丝、主控芯片' }, { name: '规格', source: '手工填写。', purpose: '物料规格型号', fill: '如 M16×80 / SMT-48脚' }, @@ -101,39 +103,42 @@ export const helpMaterial = { } export const helpPlc = { - title: '工位组合下发(业务说明)', + title: '工位组合下发(下发录入 / 下发记录)', overview: - '这个菜单做什么用:把某个工单/工件的「工位组合」写入西门子 S7-1214 PLC,产线传送带据此只停靠勾选的工位,其余工位不加工。\n\n什么时候用:一个工件进线/上线时,需要告诉 PLC「这件货要走哪几个工位」,就在这里下发。\n\n为什么单独一个菜单:产线偶尔需要人工干预——比如本该走 3 号工位却做成了半成品,人工在此改成只补第 2 工位重上线,完成闭环。一切交给人工自行判断。\n\n核心约束:系统遵循「上一条未收到完成信号,不下发下一条」,确保产线不串序。\n\n数据链路:工单(含工位组合) → 本页下发到 S7-1214 → PLC 驱动传送带/工位 → 收到完成信号再下发下一条。', + '这个菜单做什么用:把某工单/工件的「工位组合」写入西门子 S7-1214 PLC,产线传送带据此只停靠勾选的工位,其余工位不加工。\n\n页面分两个页签:「下发录入」(默认打开)、「下发记录」(点击才加载,可按工单号/状态筛选)。\n\n操作流程:选工单号 → 下拉选出该工单已上线未完工的工件 SN(也可扫码/手输) → 系统按工件实际进度自动带出「当前工位」(只读) → 工位组合中 ≤ 当前工位的编号视为已完成,自动剔除且禁选 → 勾选剩余要走的工位 → 下发。\n\n为什么单独一个菜单:产线偶尔需要人工干预——比如本该走 3 号工位却做成了半成品,人工在此只勾剩余工位重上线,完成闭环。\n\n核心约束:系统遵循「上一条未收到完成信号,不下发下一条」,确保产线不串序。\n\n数据链路:工单(含工位组合) → 本页下发到 S7-1214 → PLC 驱动传送带/工位 → 收到完成信号再下发下一条。', fields: [ - { name: '工单号', source: '下拉选择/输入。来自工单。', purpose: '当前生产的工单,追溯依据', fill: '选择或输入工单号' }, - { name: '工件SN', source: '扫码枪扫描。来自工件条码。', purpose: '当前流经的工件唯一序列号', fill: '扫码枪扫入;可空(按工单整体下发)' }, - { name: '当前工位', source: '手动填写。', purpose: '当前要下发的工位(1~12)', fill: '输入 1~12 的整数' }, - { name: '工位组合', source: '勾选 12 个工位。通常与工单的工位组合一致。', purpose: '该工件要经过的工位,PLC 控制停靠', fill: '勾选即可,按工位号顺序执行' } + { name: '工单号', source: '下拉选择/输入。来自工单。', purpose: '当前生产的工单,选中后带出该工单可下发的工件 SN', fill: '选择或输入工单号' }, + { name: '工件SN', source: '下拉选择(该工单已上线未完工的 SN,可搜索),也可扫码枪扫入手输。', purpose: '当前流经的工件唯一序列号', fill: '优先下拉选已上线 SN;扫码手输时须是已上线的工件才能带出当前工位' }, + { name: '当前工位', source: '自动带出(只读)。按所选工件的实际生产进度识别。', purpose: '从哪一站开始继续下发', fill: '无需填写;未带出说明工件未上线或 SN 有误' }, + { name: '工位组合', source: '勾选 12 个工位;≤当前工位的编号已禁选(视为完成)。', purpose: '该工件还要经过的工位,PLC 控制停靠', fill: '勾选剩余要走的工位即可,按工位号顺序执行' } ] } export const helpTorque = { - title: '拧紧数据(查询/录入)', + title: '拧紧数据(补录 / 记录查询)', overview: - '接收丹尼科尔拧紧工具的采集结果(扭矩/角度/时间/结果/操作人),用于质量追溯。\n数据在工位终端点"工位完成上报"后由拧紧枪数据同步到 MES。本页可查询,也可手工补录。', + '接收丹尼科尔拧紧工具的采集结果(扭矩/角度/时间/结果/操作人),用于质量追溯。\n\n页面分两个页签:「拧紧补录」(默认打开) 补录设备漏传/手工修正的历史数据;「记录查询」(点击才加载) 按 SN / 工单号(均不区分大小写) 查询拧紧记录。\n\n实时采集不在本页:数据在工位终端/PLC 报工时由拧紧枪自动同步到 MES;本页补录仅用于设备漏传或手工修正历史,补录记录操作人自动标记「(补录)」并写入操作日志。', fields: [ - { name: 'SN', source: '扫码枪/手工。来自工件条码。', purpose: '被拧紧的工件唯一序列号', fill: '扫码枪扫描' }, - { name: '工单号', source: '自动带出/选择。来自工单。', purpose: '该工件所属工单', fill: '选择工单号' }, - { name: '工位', source: '手动填写。', purpose: '拧紧发生在哪道工位', fill: '如 3' }, - { name: '扭矩', source: '拧紧枪自动采集(丹尼科尔工具)。', purpose: '实际拧紧扭矩(N·m),判定是否达标', fill: '工具自动带出;补录时手填' }, - { name: '角度', source: '拧紧枪自动采集。', purpose: '实际旋转角度(°),判定滑牙/屈服', fill: '工具自动带出;补录时手填' }, - { name: '操作人', source: '登录账号自动带出。', purpose: '执行拧紧的人,用于追溯', fill: '默认当前登录用户,可改' } + { name: '工单号 / SN', source: '补录必填,手工填写。', purpose: '补录记录挂靠的工单与工件', fill: '设备漏传时补填对应工单号与 SN' }, + { name: '工位', source: '手工填写。', purpose: '拧紧发生在哪道工位', fill: '如 5,可空' }, + { name: '螺丝号', source: '手工填写。', purpose: '同一工件多颗螺丝时区分第几颗', fill: '如 S1,可空' }, + { name: '扭矩(N·m) / 角度(°)', source: '正常由拧紧枪自动采集;补录时手工填写。', purpose: '实际拧紧扭矩与旋转角度,判定是否达标', fill: '按实际值填写' }, + { name: '结果', source: '单选 OK/NG。', purpose: '拧紧判定结果', fill: '默认 OK' }, + { name: '补录原因', source: '手工填写,必填。', purpose: '说明为何补录,留痕审计', fill: '如:设备漏传 / 手工修正' }, + { name: '查询(SN / 工单号)', source: '「记录查询」页签筛选条件,不区分大小写。', purpose: '只看指定工件/工单的拧紧记录', fill: '可空=全部' } ] } export const helpScan = { title: '手动报工(工位工序步骤完成上报)', overview: - '每个工位完成本工位的工序步骤后,在【手动报工】页选择工位、扫入工件SN,系统按该工位「启用」的工艺流程自动展开其工序步骤(扭矩/角度/尺寸等采集项),人工录入并提交。\n提交后写报工实绩 + 步骤数据,系统按步骤考核标准自动判定 OK/NG。\n\n数据链路:工艺流程(为工位维护工序步骤与考核标准) → 手动报工页选工位扫SN → 自动载入该工位工序步骤 → 逐参数录入 → 判 OK/NG → 工件追溯可见。', + '页面分两个页签:「报工录入」(默认打开)、「报工记录」(点击才加载,分页)。\n\n录入流程:选择工位、扫入/输入工件 SN 后,系统自动载入该工位「启用」工艺流程的工序步骤(扭矩/角度/尺寸等采集项),逐参数录入提交;提交后写报工实绩 + 步骤数据,系统按考核标准自动判定 OK/NG。\n\n装机绑定:若该产品在本工序配置了需绑定的装配物料(BOM「装配工序」),会出现装机绑定区——精密件扫 SN、结构件扫/输批次号,逐项绑齐(已绑/需绑)后才允许提交报工;未配置物料则不校验。\n\n数据链路:工艺流程(工序步骤+考核标准) + BOM 装机配置 → 本页选工位扫 SN 自动载入 → 绑料+录参数 → 判 OK/NG → 工件追溯可见。', fields: [ - { name: '工位', source: '下拉选择(1~12)。', purpose: '哪个工位在报工,自动带出该工位的工序步骤', fill: '选择工位 1~12' }, - { name: '工件SN', source: '扫码枪扫描。来自工件条码。', purpose: '工件的唯一序列号,报工对象', fill: '扫码枪扫入,触发载入工序步骤' }, - { name: '各步骤采集值', source: '手工填写 或 拧紧枪自动带出(视采集方式)。字段由【工艺流程】的工序步骤人为定义。', purpose: '收集该工位的扭矩/角度/尺寸等参数,按标准判定合格与否', fill: '逐条填采集值,未维护标准的步骤仅记录' } + { name: '工位', source: '下拉选择(1~12)。', purpose: '哪个工位在报工,选择后自动载入该工位的工序步骤', fill: '选择工位 1~12' }, + { name: '工件SN', source: '扫码枪扫描或手输。来自工件条码。', purpose: '工件的唯一序列号,报工对象', fill: '扫码枪扫入,与工位一起触发自动载入' }, + { name: '各步骤采集值', source: '手工填写 或 拧紧枪自动带出(视采集方式)。字段由【工艺流程】的工序步骤人为定义。', purpose: '收集该工位的扭矩/角度/尺寸等参数,按标准判定合格与否', fill: '逐条填采集值,未维护标准的步骤仅记录' }, + { name: '装机绑定(物料/已绑/需绑)', source: '来自 BOM 的「装配工序」配置(按工单锁定的 BOM 过滤)。', purpose: '该工件本工序应装哪些料;精密件按 SN、结构件按批次绑定,齐套后才允许报工', fill: '在绑定输入框扫码或录入后点「绑定」,绑齐后提交报工' }, + { name: '报工记录查询(SN / 工单号)', source: '「报工记录」页签筛选条件,分页查看。', purpose: '查历史报工实绩', fill: '可空=全部' } ] } @@ -255,10 +260,11 @@ export const helpProcessFlow = { export const helpPerformance = { title: '绩效报表(工人工作量统计)', overview: - '按 操作人 × 日期 × 工位 汇总每个工人的完成/合格/NG 数量,用于工作量与绩效统计。\n\n页面提供「按人 / 按工位 / 明细」三个视图切换:按人看每人汇总,按工位看每工位汇总,明细看逐条记录;三者均受上方筛选条件约束,顶部汇总卡为当前筛选的总计。\n\n数据来源:全部由工位终端/手动报工的「工位完成上报」自动写入报工实绩(workpiece_process),本页只读统计,不手工录入。\n\n数据链路:工位终端报工(写报工实绩) → 本页按人/天/工位聚合 → 出报表。', + '按 操作人 × 日期 × 工位 汇总每个工人的完成/合格/NG 数量,用于工作量与绩效统计。\n\n页面提供「按人 / 按工位 / 明细」三个页签:按人看每人汇总,按工位看每工位汇总,明细看逐条记录(含工序名);三个视图各自独立分页,均受上方筛选条件约束。\n\n筛选与导出:支持 操作人 / 工位 / 日期范围 组合筛选(默认近 90 天);顶部汇总卡随查询刷新;「导出 xlsx」一次导出三个 sheet(按人/按工位/明细),导出遵循统一时间硬规则(时间范围为空默认近 3 个月、只传一端报错、跨度超 1 年报错)。\n\n数据来源:全部由工位终端/手动报工的「工位完成上报」自动写入报工实绩(workpiece_process),本页只读统计,不手工录入。', fields: [ { name: '操作人', source: '筛选条件。来自报工实绩的 operator 字段(工位终端登录人)。', purpose: '只看某个工人的工作量', fill: '输入姓名/账号,可空=全部' }, - { name: '日期范围', source: '筛选条件。来自报工实绩的产生时间。', purpose: '按时间段统计', fill: '选择起止日期,可空=全部' }, + { name: '工位', source: '下拉选择(工位1~工位12)。', purpose: '只看某个工位的报工情况', fill: '可空=全部工位' }, + { name: '日期范围', source: '筛选条件。来自报工实绩的产生时间,默认近 90 天。', purpose: '按时间段统计', fill: '选择起止日期;导出时同一范围' }, { name: '完成数', source: '自动汇总。该 操作人×日期×工位 的报工次数。', purpose: '工作量核心指标', fill: '只读' }, { name: '合格数/NG数', source: '自动汇总。按报工实绩 result 字段(OK/NG)分类计数。', purpose: '质量指标,OK+NG=完成数', fill: '只读' }, { name: '合格率', source: '自动计算 = 合格数/完成数。', purpose: '质量表现', fill: '只读' } diff --git a/bj_power_mes/frontend/src/layouts/MainLayout.vue b/bj_power_mes/frontend/src/layouts/MainLayout.vue index 6834629..d13efb7 100644 --- a/bj_power_mes/frontend/src/layouts/MainLayout.vue +++ b/bj_power_mes/frontend/src/layouts/MainLayout.vue @@ -137,7 +137,7 @@ const menuGroups = [ title: '基础配置', icon: Box, children: [ { code: 'produce.processflow', name: '工艺流程', path: '/process-flow', icon: Operation }, { code: 'produce.station', name: '关联工位', path: '/station', icon: Link }, - { code: 'produce.product', name: '产品类型', path: '/product-type', icon: Box } + { code: 'produce.product', name: '产品型号', path: '/product-type', icon: Box } ] }, { diff --git a/bj_power_mes/frontend/src/pages/AccountManage.vue b/bj_power_mes/frontend/src/pages/AccountManage.vue index f74622b..f9b5b8d 100644 --- a/bj_power_mes/frontend/src/pages/AccountManage.vue +++ b/bj_power_mes/frontend/src/pages/AccountManage.vue @@ -22,6 +22,12 @@ {{ roleName(row) }} + + + {{ fmtTime(row.lastLoginAt) }} + 从未登录 + + diff --git a/bj_power_mes/frontend/src/pages/ProcessFlow.vue b/bj_power_mes/frontend/src/pages/ProcessFlow.vue index 4535599..0d9609f 100644 --- a/bj_power_mes/frontend/src/pages/ProcessFlow.vue +++ b/bj_power_mes/frontend/src/pages/ProcessFlow.vue @@ -32,17 +32,6 @@ {{ statusText(row.status) }} - - - - - {{ (row.stations || []).length }} 个工位 - - {{ (row.stations || []).map(s => `工位${s}`).join('、') }} - - - - - + @@ -89,14 +79,7 @@ - 该流程承载的工序编号(1~12) - - - - - - - + 该流程承载的工序编号(1~999,流程内唯一即可;与物理工位的对应关系在「关联工位」页维护) @@ -277,9 +260,11 @@ function blankStep(seq) { } async function loadFlows() { - const params = {} + const params = { page: flowPage.value, pageSize: flowPageSize.value } if (query.stationNo) params.stationNo = query.stationNo - flows.value = (await request.get('/process-flows', { params })) || [] + const data = await request.get('/process-flows', { params }) + flows.value = (data && data.list) ? data.list : (data || []) + flowTotal.value = (data && data.total) ? data.total : flows.value.length } function fmt(v) { return v ? String(v).replace('T', ' ').slice(0, 19) : '-' } @@ -387,7 +372,7 @@ function saveCriteria() { async function saveFlow() { if (!flowDlg.name) return ElMessage.warning('请填写流程名') if (!flowDlg.stationNo) return ElMessage.warning('请选择工序编号(1~12)') - if (!flowDlg.stations || !flowDlg.stations.length) return ElMessage.warning('请至少选择一个绑定工位') + // 绑定关系由「关联工位」页维护:此处不再收集绑定工位,保存流程本身 const body = { id: flowDlg.id || undefined, name: flowDlg.name, diff --git a/bj_power_mes/frontend/src/pages/ProductType.vue b/bj_power_mes/frontend/src/pages/ProductType.vue index 2c01d9a..c4165de 100644 --- a/bj_power_mes/frontend/src/pages/ProductType.vue +++ b/bj_power_mes/frontend/src/pages/ProductType.vue @@ -1,29 +1,58 @@ + - 新增产品类型 + + + + + + + + + + + + + 查询 + 新增产品型号 + 导出 + - - + + + {{ row.category || '-' }} + {{ row.isActive?'是':'否' }} - - + + {{ row.remark || '-' }} + + + {{ row.createdAt ? fmtTime(row.createdAt) : '-' }} + + 编辑 删除 + - + - + + + @@ -44,37 +73,83 @@ import request from '../utils/request' import { can } from '../utils/perm' import PageHelp from '../components/PageHelp.vue' import { helpProductType } from '../help' +import { fmtTime } from '../utils/format' +// 数据源说明:产品型号档案已并入 WMS 物料主数据(item_type=3,品类=成品), +// 本页经 MES 后端实时代理 WMS;WMS 不可达时降级为本地只读缓存(wmsOnline=false)。 const list = ref([]) +const total = ref(0) +const page = ref(1) +const pageSize = ref(20) +const wmsOnline = ref(true) +const filters = reactive({ code: '', name: '', category: '', isActive: '', startDate: '', endDate: '' }) const dialog = reactive({ show: false, id: 0, name: '', code: '', category: '', remark: '', isActive: true }) -async function load() { list.value = (await request.get('/product-types')) || [] } -// 自动生成一个可修改的编码:PROD-日期-序号(序号取当前已存在编码数的下一位,避免重复) -async function genCode() { - const d = new Date(), p = (n) => String(n).padStart(2, '0') - const stamp = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}` - const max = (list.value || []).reduce((m, it) => { - const mch = /^PROD-\d{8}-(\d+)$/.exec(it.code || '') - return mch ? Math.max(m, parseInt(mch[1])) : m - }, 0) - return `PROD-${stamp}-${String(max + 1).padStart(3, '0')}` +function defaultRange() { + const d = new Date() + const p = (n) => String(n).padStart(2, '0') + const end = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` + const s = new Date(d.getTime() - 90 * 24 * 3600 * 1000) + const start = `${s.getFullYear()}-${p(s.getMonth() + 1)}-${p(s.getDate())}` + return { start, end } } +Object.assign(filters, defaultRange()) + +function keyword() { + // 编码/名称/类别合并为 keyword(后端大小写折叠模糊匹配) + return filters.code || filters.name || filters.category || '' +} + +async function load() { + const params = { + page: page.value, + pageSize: pageSize.value, + keyword: keyword(), + isActive: filters.isActive, + startDate: filters.startDate, + endDate: filters.endDate + } + const data = await request.get('/product-types/page', { params }) + list.value = data.list || [] + total.value = data.total || 0 + wmsOnline.value = data.wmsOnline !== false +} +function search() { page.value = 1; load() } + async function openAdd() { Object.assign(dialog, { show: true, id: 0, name: '', code: '', category: '', remark: '', isActive: true }) - dialog.code = await genCode() } -function openEdit(row) { Object.assign(dialog, { show: true, ...row }) } +function openEdit(row) { Object.assign(dialog, { show: true, id: row.id, name: row.name, code: row.code, category: row.category, remark: row.remark, isActive: row.isActive }) } async function save() { - if (dialog.id) await request.put('/product-types', { ...dialog }) - else await request.post('/product-types', { ...dialog }) + if (!dialog.name) return ElMessage.warning('名称必填') + if (dialog.id) await request.put('/product-types', { id: dialog.id, name: dialog.name, category: dialog.category, remark: dialog.remark, isActive: dialog.isActive }) + else await request.post('/product-types', { code: dialog.code, name: dialog.name, category: dialog.category, remark: dialog.remark, isActive: dialog.isActive }) ElMessage.success('保存成功') dialog.show = false load() } async function del(row) { - await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }) + await ElMessageBox.confirm('确认删除?已被工单/BOM/库存引用的型号会被拒绝删除。', '提示', { type: 'warning' }) await request.delete(`/product-types/${row.id}`) + ElMessage.success('已删除') load() } +async function exportXlsx() { + if (!filters.startDate || !filters.endDate) { + const r = defaultRange() + filters.startDate = r.start + filters.endDate = r.end + } + const params = { + keyword: keyword(), isActive: filters.isActive, + startDate: filters.startDate, endDate: filters.endDate + } + const blob = await request.get('/product-types/export', { params, responseType: 'blob' }) + const a = document.createElement('a') + a.href = URL.createObjectURL(blob) + a.download = '产品型号.xlsx' + a.click() + URL.revokeObjectURL(a.href) +} onMounted(load) - \ No newline at end of file + diff --git a/bj_power_mes/frontend/src/pages/RoleManage.vue b/bj_power_mes/frontend/src/pages/RoleManage.vue index 9753d25..50a4087 100644 --- a/bj_power_mes/frontend/src/pages/RoleManage.vue +++ b/bj_power_mes/frontend/src/pages/RoleManage.vue @@ -18,7 +18,8 @@ - {{ row.name }} + {{ row.name }} + 内置 diff --git a/bj_power_mes/frontend/src/pages/Scan.vue b/bj_power_mes/frontend/src/pages/Scan.vue index b30a237..a4cfd73 100644 --- a/bj_power_mes/frontend/src/pages/Scan.vue +++ b/bj_power_mes/frontend/src/pages/Scan.vue @@ -1,5 +1,7 @@ + + @@ -7,7 +9,7 @@ - 载入工序步骤 + 已自动载入当前工位应做步骤 @@ -85,7 +87,8 @@ - 报工记录 + + @@ -100,6 +103,10 @@ + + + @@ -112,6 +119,10 @@ import { can } from '../utils/perm' import PageHelp from '../components/PageHelp.vue' import { helpScan } from '../help' +const tab = ref('entry') +const recordsPage = ref(1) +const recordsSize = ref(20) +const recordsTotal = ref(0) const form = reactive({ stationNo: 1, sn: '' }) const steps = ref([]) const flowName = ref('') @@ -211,7 +222,10 @@ async function submit() { } async function load() { - list.value = (await request.get('/scan/records', { params: query })) || [] + const data = await request.get('/scan/records', { params: { ...query, page: recordsPage.value, pageSize: recordsSize.value } }) + list.value = data?.list || [] + recordsTotal.value = data?.total || 0 } -onMounted(load) +function onTab(name) { if (name === 'records' && !list.value.length) load() } +onMounted(() => { if (form.stationNo) loadSteps() }) diff --git a/bj_power_mes/frontend/src/pages/Torque.vue b/bj_power_mes/frontend/src/pages/Torque.vue index a6ada57..7d54769 100644 --- a/bj_power_mes/frontend/src/pages/Torque.vue +++ b/bj_power_mes/frontend/src/pages/Torque.vue @@ -1,38 +1,52 @@ - - - - 查询 - - - - - + + + - - - - - - - 上报 + + + 查询 - - + + + + + + {{ row.screwNo || '-' }} + + + + + {{ row.result }} + + + + + - - - - - - - - {{ row.result }} - - - - + + + + + + + + + + + + + OK + NG + + + + 提交补录 + + + @@ -44,19 +58,25 @@ import request from '../utils/request' import PageHelp from '../components/PageHelp.vue' import { helpTorque } from '../help' +// 录入 tab 在前默认打开(多 Tab 范式:录入在前、记录在后点击才加载) +const tab = ref('add') const query = reactive({ sn: '', workOrderNo: '' }) -const torque = reactive({ sn: '', workOrderNo: '', stationNo: '', strain: 20, angle: 90, operator: '' }) +const torque = reactive({ sn: '', workOrderNo: '', stationNo: '', screwNo: '', strain: 20, angle: 90, result: 'OK', reason: '' }) const list = ref([]) async function load() { list.value = (await request.get('/torque/records', { params: query })) || [] } -async function report() { - if (!torque.sn) return ElMessage.warning('请输入SN') - await request.post('/torque/report', torque) - ElMessage.success('拧紧数据已上报') - Object.assign(torque, { sn: '', workOrderNo: '', strain: 20, angle: 90 }) - load() +function onTab(name) { + if (name === 'records' && !list.value.length) load() +} +async function submit() { + if (!torque.workOrderNo || !torque.sn) return ElMessage.warning('工单号与 SN 必填') + if (!torque.reason) return ElMessage.warning('补录原因必填') + await request.post('/torque/manual-add', torque) + ElMessage.success('补录成功') + Object.assign(torque, { sn: '', workOrderNo: '', stationNo: '', screwNo: '', strain: 20, angle: 90, result: 'OK', reason: '' }) + list.value = [] } onMounted(load) - \ No newline at end of file + diff --git a/bj_power_mes/frontend/src/pages/Trace.vue b/bj_power_mes/frontend/src/pages/Trace.vue index 910b1be..205633e 100644 --- a/bj_power_mes/frontend/src/pages/Trace.vue +++ b/bj_power_mes/frontend/src/pages/Trace.vue @@ -13,14 +13,19 @@ - - {{ data.workpiece?.sn }} - {{ data.workpiece?.orderNo }} - {{ data.workpiece?.status }} - {{ data.workpiece?.currentProcess }} - {{ fmt(data.workpiece?.onlineAt) }} - {{ fmt(data.workpiece?.doneAt) }} + + {{ data.workpiece?.sn }} + {{ data.workpiece?.orderNo }} + + {{ data.workpiece?.productCode || data.order?.productCode }}{{ (data.workpiece?.productName || data.order?.productName) ? '(' + (data.workpiece?.productName || data.order?.productName) + ')' : '' }} + + {{ statusText(data.workpiece?.status) }} + {{ data.workpiece?.currentProcess }} + {{ fmt(data.workpiece?.onlineAt) }} + {{ fmt(data.workpiece?.doneAt) }} + @@ -127,5 +132,9 @@ async function printCard() { } window.open('/api/v1/process-card?sn=' + encodeURIComponent(sn.value), '_blank') } +function statusText(st) { + const map = { CREATED: '已创建', ONLINE: '生产中', DONE: '已完工', SCRAPPED: '已报废' } + return map[st] || st || '-' +} function fmt(v) { return v ? String(v).replace('T', ' ').slice(0, 19) : '-' } \ No newline at end of file diff --git a/bj_power_mes/frontend/src/pages/WorkOrder.vue b/bj_power_mes/frontend/src/pages/WorkOrder.vue index 4bcf740..288bcc6 100644 --- a/bj_power_mes/frontend/src/pages/WorkOrder.vue +++ b/bj_power_mes/frontend/src/pages/WorkOrder.vue @@ -27,6 +27,11 @@ + + + {{ row.bomName || '默认' }} + + @@ -61,12 +66,18 @@ - - + + - + + + + + + 同一型号多份 BOM 按名称区分(不同厂家/含半成品),备料与装机校验按此 BOM 计算。 + @@ -160,8 +171,17 @@ const query = reactive({ orderNo: '', productCode: '', productName: '', status: const list = ref([]) const productTypes = ref([]) -const dialog = reactive({ show: false, id: 0, workOrderNo: '', productTypeId: 0, productCode: '', productName: '', quantity: 1, processSeq: '', status: 'CREATED' }) +const dialog = reactive({ show: false, id: 0, workOrderNo: '', productTypeId: 0, productCode: '', bomName: '', productName: '', quantity: 1, processSeq: '', status: 'CREATED' }) const seq = reactive({ show: false, id: 0, orderNo: '', productCode: '', productName: '', processSeq: '' }) +const bomNames = ref([]) + +// 按产品编码拉取该型号下并存的 BOM 名称(建单选 BOM) +async function loadBomNames() { + const code = (dialog.productCode || '').trim() + if (!code) { bomNames.value = []; return } + bomNames.value = (await request.get('/bom/names', { params: { productCode: code } })) || [] + if (!dialog.bomName && bomNames.value.length) dialog.bomName = bomNames.value[0].bomName +} async function load() { const data = await request.get('/work-orders', { params: query }) @@ -171,13 +191,15 @@ async function loadProducts() { productTypes.value = (await request.get('/product-types')) || [] } function openCreate() { - Object.assign(dialog, { show: true, id: 0, workOrderNo: `WO${Date.now()}`, productTypeId: 0, productCode: '', productName: '', quantity: 1, processSeq: '1,2,3,4,5,6,7,8,9,10,11,12', status: 'CREATED' }) + Object.assign(dialog, { show: true, id: 0, workOrderNo: `WO${Date.now()}`, productTypeId: 0, productCode: '', bomName: '', productName: '', quantity: 1, processSeq: '1,2,3,4,5,6,7,8,9,10,11,12', status: 'CREATED' }) + bomNames.value = [] } function openEdit(row) { - Object.assign(dialog, { show: true, id: row.id, workOrderNo: row.workOrderNo, productTypeId: row.productTypeId, productCode: row.productCode, productName: row.productName, quantity: row.quantity, processSeq: row.processSeq || '1,2,3,4,5,6,7,8,9,10,11,12', status: row.status }) + Object.assign(dialog, { show: true, id: row.id, workOrderNo: row.workOrderNo, productTypeId: row.productTypeId, productCode: row.productCode, bomName: row.bomName || '', productName: row.productName, quantity: row.quantity, processSeq: row.processSeq || '1,2,3,4,5,6,7,8,9,10,11,12', status: row.status }) + loadBomNames() } async function save() { - const body = { id: dialog.id, workOrderNo: dialog.workOrderNo, productTypeId: dialog.productTypeId, productCode: dialog.productCode, productName: dialog.productName, quantity: dialog.quantity, processSeq: dialog.processSeq } + const body = { id: dialog.id, workOrderNo: dialog.workOrderNo, productTypeId: dialog.productTypeId, productCode: dialog.productCode, bomName: dialog.bomName || '默认', productName: dialog.productName, quantity: dialog.quantity, processSeq: dialog.processSeq } if (dialog.id) await request.put('/work-orders', body) else await request.post('/work-orders', body) ElMessage.success('保存成功') diff --git a/bj_power_mes/frontend/src/router/index.js b/bj_power_mes/frontend/src/router/index.js index 2649fe3..6936133 100644 --- a/bj_power_mes/frontend/src/router/index.js +++ b/bj_power_mes/frontend/src/router/index.js @@ -20,7 +20,7 @@ const routes = [ { path: 'inspect', name: 'Inspect', component: () => import('../pages/Inspect.vue'), meta: { title: '巡检终端' } }, { path: 'scan', name: 'Scan', component: () => import('../pages/Scan.vue'), meta: { title: '手动报工' } }, { path: 'trace', name: 'Trace', component: () => import('../pages/Trace.vue'), meta: { title: '工件追溯' } }, - { path: 'product-type', name: 'ProductType', component: () => import('../pages/ProductType.vue'), meta: { title: '产品类型' } }, + { path: 'product-type', name: 'ProductType', component: () => import('../pages/ProductType.vue'), meta: { title: '产品型号' } }, { path: 'event-log', name: 'EventLog', component: () => import('../pages/EventLog.vue'), meta: { title: '操作日志' } }, { path: 'change-password', name: 'ChangePassword', component: () => import('../pages/ChangePassword.vue'), meta: { title: '修改密码' } }, { path: 'account', name: 'AccountManage', component: () => import('../pages/AccountManage.vue'), meta: { title: '账号管理' } }, diff --git a/bj_power_mes/go.mod b/bj_power_mes/go.mod index d8d8037..b78debb 100644 --- a/bj_power_mes/go.mod +++ b/bj_power_mes/go.mod @@ -39,6 +39,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect @@ -46,8 +47,13 @@ require ( github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/testify v1.10.0 // indirect + github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect + github.com/xuri/excelize/v2 v2.8.1 // indirect + github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.1.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect diff --git a/bj_power_mes/go.sum b/bj_power_mes/go.sum index 50e51ff..028040b 100644 --- a/bj_power_mes/go.sum +++ b/bj_power_mes/go.sum @@ -126,6 +126,8 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -153,6 +155,11 @@ github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -180,6 +187,12 @@ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= +github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= diff --git a/bj_power_mes/internal/handler/inspection.go b/bj_power_mes/internal/handler/inspection.go index 6dbc6d5..74cc103 100644 --- a/bj_power_mes/internal/handler/inspection.go +++ b/bj_power_mes/internal/handler/inspection.go @@ -36,17 +36,27 @@ func CreateInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } -// ListInspectionsHandler GET /inspections?category=&operator=&from=&to= +// ListInspectionsHandler GET /inspections?category=&operator=&from=&to=&page=&pageSize= +// 返回 {list, total},后端真分页(created_at desc / id desc,最新置顶) func ListInspectionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - data, err := logic.New(svcCtx).ListInspections(r.Context(), - q.Get("category"), q.Get("operator"), q.Get("from"), q.Get("to")) + page := atoiDefault(q.Get("page"), 1) + pageSize := atoiDefault(q.Get("pageSize"), 50) + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 1000 { + pageSize = 50 + } + data, total, err := logic.New(svcCtx).ListInspectionsPaged(r.Context(), + q.Get("category"), q.Get("operator"), q.Get("from"), q.Get("to"), + q.Get("orderNo"), q.Get("stationNo"), page, pageSize) if err != nil { httpx.Fail(w, 2302, err.Error()) return } - httpx.Ok(w, data) + httpx.Ok(w, map[string]any{"list": data, "total": total}) } } diff --git a/bj_power_mes/internal/handler/perm.go b/bj_power_mes/internal/handler/perm.go index 8b924b9..61fb89c 100644 --- a/bj_power_mes/internal/handler/perm.go +++ b/bj_power_mes/internal/handler/perm.go @@ -20,6 +20,7 @@ var pathPermMap = map[string]string{ "DELETE:/api/v1/work-orders/*": "produce.workorder:delete", "POST:/api/v1/daily-plans": "produce.workorder:dailyplan", "PUT:/api/v1/bom": "produce.bom:edit", + "POST:/api/v1/bom/item/delete": "produce.bom:edit", "POST:/api/v1/material-requests/generate": "produce.material:generate", "POST:/api/v1/plc/send-process": "produce.plc:send", "POST:/api/v1/process-flows": "produce.processflow:add", diff --git a/bj_power_mes/internal/handler/processflow.go b/bj_power_mes/internal/handler/processflow.go index e0b8c29..5fd7eee 100644 --- a/bj_power_mes/internal/handler/processflow.go +++ b/bj_power_mes/internal/handler/processflow.go @@ -6,13 +6,17 @@ import ( "bj_power_mes/common/httpx" "bj_power_mes/internal/logic" "bj_power_mes/internal/svc" + + "github.com/zeromicro/go-zero/rest/pathvar" ) // ListProcessFlowsHandler GET /process-flows?stationNo= func ListProcessFlowsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { stationNo := atoiDefault(r.URL.Query().Get("stationNo"), 0) - data, err := logic.New(svcCtx).ListFlows(r.Context(), stationNo) + page := atoiDefault(r.URL.Query().Get("page"), 0) + pageSize := atoiDefault(r.URL.Query().Get("pageSize"), 20) + data, err := logic.New(svcCtx).ListFlows(r.Context(), stationNo, page, pageSize) if err != nil { httpx.Fail(w, 2201, err.Error()) return @@ -71,6 +75,15 @@ func FileDownloadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } +// FilePathHandler GET /files/:date/:name —— 路径式文件 URL(巡检拍照上传返回的 +// url 形如 /api/v1/files/2026-09-07/xxx.png,此前无此路由导致照片 404) +func FilePathHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + serveUploadFile(w, r, svcCtx.Config.Upload.Dir, vars["date"]+"/"+vars["name"]) + } +} + // ListStationsHandler GET /stations func ListStationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/bj_power_mes/internal/handler/production/bom_material.go b/bj_power_mes/internal/handler/production/bom_material.go index 7107cb8..c877976 100644 --- a/bj_power_mes/internal/handler/production/bom_material.go +++ b/bj_power_mes/internal/handler/production/bom_material.go @@ -14,6 +14,7 @@ func SaveBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { ProductCode string `json:"productCode"` + BomName string `json:"bomName"` // 同型号多份 BOM 并存时的名称(空=「默认」) Items []logic.BomItemReq `json:"items"` } if err := httpx.ParseJSON(r, &req); err != nil { @@ -24,7 +25,7 @@ func SaveBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { httpx.BadRequest(w, "请选择产品编码") return } - if err := logic.New(svcCtx).SaveBom(r.Context(), req.ProductCode, req.Items, operator(r, "")); err != nil { + if err := logic.New(svcCtx).SaveBom(r.Context(), req.ProductCode, req.BomName, req.Items, operator(r, "")); err != nil { httpx.Fail(w, 3101, err.Error()) return } @@ -34,7 +35,7 @@ func SaveBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ListBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - data, err := logic.New(svcCtx).ListBom(r.Context(), r.URL.Query().Get("productCode")) + data, err := logic.New(svcCtx).ListBom(r.Context(), r.URL.Query().Get("productCode"), r.URL.Query().Get("bomName")) if err != nil { httpx.Fail(w, 3102, err.Error()) return @@ -43,6 +44,36 @@ func ListBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } +// ListBomNamesHandler GET /bom/names?productCode= — 型号下并存的 BOM 名称列表(工单建单选 BOM 用) +func ListBomNamesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + data, err := logic.New(svcCtx).ListBomNames(r.Context(), r.URL.Query().Get("productCode")) + if err != nil { + httpx.Fail(w, 3107, err.Error()) + return + } + httpx.Ok(w, data) + } +} + +// DeleteBomItemHandler POST /bom/item/delete {id} — 移除 BOM 中一条物料(按行主键) +func DeleteBomItemHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + Id int `json:"id"` + } + if err := httpx.ParseJSON(r, &req); err != nil || req.Id <= 0 { + httpx.BadRequest(w, "缺少 id") + return + } + if err := logic.New(svcCtx).DeleteBomItem(r.Context(), req.Id, operator(r, "")); err != nil { + httpx.Fail(w, 3108, err.Error()) + return + } + httpx.OkMessage(w, "已移除", nil) + } +} + // ---------- 备料单 ---------- // GenerateMaterialHandler 按日排产生成备料单(生成前校验 BOM 物料在 WMS 档案存在) @@ -72,7 +103,9 @@ func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - data, err := logic.New(svcCtx).ListMaterialRequests(r.Context(), q.Get("orderNo"), q.Get("planDate"), q.Get("status")) + data, err := logic.New(svcCtx).ListMaterialRequestsFiltered(r.Context(), + q.Get("orderNo"), q.Get("planDate"), q.Get("status"), + q.Get("materialCode"), q.Get("materialName"), q.Get("targetDock")) if err != nil { httpx.Fail(w, 3104, err.Error()) return diff --git a/bj_power_mes/internal/handler/production/plc_torque_scan.go b/bj_power_mes/internal/handler/production/plc_torque_scan.go index 6f641b1..96af5dc 100644 --- a/bj_power_mes/internal/handler/production/plc_torque_scan.go +++ b/bj_power_mes/internal/handler/production/plc_torque_scan.go @@ -95,12 +95,13 @@ func ScanReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ScanRecordsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - data, err := logic.New(svcCtx).ListScanRecords(r.Context(), q.Get("sn"), q.Get("orderNo")) + data, total, err := logic.New(svcCtx).ListScanRecords(r.Context(), q.Get("sn"), q.Get("orderNo"), + atoiDefault(q.Get("page"), 1), atoiDefault(q.Get("pageSize"), 20)) if err != nil { httpx.Fail(w, 3206, err.Error()) return } - httpx.Ok(w, data) + httpx.Ok(w, map[string]any{"list": data, "total": total}) } } @@ -121,3 +122,30 @@ func ProcessStepsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { httpx.Ok(w, data) } } + + +// TorqueManualAddHandler POST /torque/manual-add 拧紧数据补录(设备漏传/手工修正) +func TorqueManualAddHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + Sn string `json:"sn"` + WorkOrderNo string `json:"workOrderNo"` + ScrewNo string `json:"screwNo"` + StationNo string `json:"stationNo"` + Strain float64 `json:"strain"` + Angle float64 `json:"angle"` + Result string `json:"result"` + Reason string `json:"reason"` + } + if err := httpx.ParseJSON(r, &req); err != nil { + httpx.BadRequest(w, "请求体解析失败") + return + } + if err := logic.New(svcCtx).AddTorqueRecord(r.Context(), req.Sn, req.WorkOrderNo, req.ScrewNo, + req.StationNo, req.Strain, req.Angle, req.Result, req.Reason, operator(r, "系统管理员")); err != nil { + httpx.Fail(w, 3205, err.Error()) + return + } + httpx.OkMessage(w, "补录成功", nil) + } +} diff --git a/bj_power_mes/internal/handler/production/workorder.go b/bj_power_mes/internal/handler/production/workorder.go index 53f54b9..dc9160f 100644 --- a/bj_power_mes/internal/handler/production/workorder.go +++ b/bj_power_mes/internal/handler/production/workorder.go @@ -2,6 +2,7 @@ package production import ( "net/http" + "net/url" "strconv" "strings" @@ -18,11 +19,11 @@ func pathID(r *http.Request) int { return n } -// ---------- 产品类型 ---------- +// ---------- 产品型号 ---------- func ListProductTypesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - data, err := logic.New(svcCtx).ListProductTypes(r.Context()) + data, _, err := logic.New(svcCtx).ListProductTypes(r.Context()) if err != nil { httpx.Fail(w, 3001, err.Error()) return @@ -31,6 +32,25 @@ func ListProductTypesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } +// ListProductTypesPageHandler GET /product-types/page 产品型号管理页真分页+搜索 +// 响应 {total,list,page,pageSize,wmsOnline} +func ListProductTypesPageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + page := atoiDefault(q.Get("page"), 1) + pageSize := atoiDefault(q.Get("pageSize"), 20) + total, list, wmsOnline, err := logic.New(svcCtx).ListProductTypesPage(r.Context(), + q.Get("keyword"), q.Get("isActive"), page, pageSize) + if err != nil { + httpx.Fail(w, 3001, err.Error()) + return + } + httpx.Ok(w, map[string]any{ + "total": total, "list": list, "page": page, "pageSize": pageSize, "wmsOnline": wmsOnline, + }) + } +} + func CreateProductTypeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req logic.ProductTypeReq @@ -71,6 +91,22 @@ func DeleteProductTypeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } } +// ExportProductTypesHandler GET /product-types/export 转发 WMS xlsx 导出 +func ExportProductTypesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + b, err := svcCtx.Wms.ExportProductTypes(r.Context(), + q.Get("keyword"), q.Get("isActive"), q.Get("startDate"), q.Get("endDate")) + if err != nil { + httpx.Fail(w, 3002, "导出失败(WMS 不可达):"+err.Error()) + return + } + w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape("产品型号.xlsx")) + _, _ = w.Write(b) + } +} + // ---------- 工单 ---------- func ListWorkOrdersHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { diff --git a/bj_power_mes/internal/handler/routes_production.go b/bj_power_mes/internal/handler/routes_production.go index 7493c7e..389c938 100644 --- a/bj_power_mes/internal/handler/routes_production.go +++ b/bj_power_mes/internal/handler/routes_production.go @@ -84,6 +84,8 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext // ---------- JWT 业务 API ---------- jwtRoutes := []rest.Route{ {Method: http.MethodGet, Path: "/product-types", Handler: production.ListProductTypesHandler(serverCtx)}, + {Method: http.MethodGet, Path: "/product-types/page", Handler: production.ListProductTypesPageHandler(serverCtx)}, + {Method: http.MethodGet, Path: "/product-types/export", Handler: production.ExportProductTypesHandler(serverCtx)}, {Method: http.MethodPost, Path: "/product-types", Handler: production.CreateProductTypeHandler(serverCtx)}, {Method: http.MethodPut, Path: "/product-types", Handler: production.UpdateProductTypeHandler(serverCtx)}, {Method: http.MethodDelete, Path: "/product-types/:id", Handler: production.DeleteProductTypeHandler(serverCtx)}, @@ -102,6 +104,8 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPut, Path: "/bom", Handler: production.SaveBomHandler(serverCtx)}, {Method: http.MethodGet, Path: "/bom", Handler: production.ListBomHandler(serverCtx)}, + {Method: http.MethodGet, Path: "/bom/names", Handler: production.ListBomNamesHandler(serverCtx)}, + {Method: http.MethodPost, Path: "/bom/item/delete", Handler: production.DeleteBomItemHandler(serverCtx)}, {Method: http.MethodPost, Path: "/material-requests/generate", Handler: production.GenerateMaterialHandler(serverCtx)}, {Method: http.MethodGet, Path: "/material-requests", Handler: production.ListMaterialRequestsHandler(serverCtx)}, @@ -111,6 +115,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPost, Path: "/torque/report", Handler: production.TorqueReportHandler(serverCtx)}, {Method: http.MethodGet, Path: "/torque/records", Handler: production.TorqueRecordsHandler(serverCtx)}, + {Method: http.MethodPost, Path: "/torque/manual-add", Handler: production.TorqueManualAddHandler(serverCtx)}, {Method: http.MethodPost, Path: "/scan/report", Handler: production.ScanReportHandler(serverCtx)}, {Method: http.MethodGet, Path: "/scan/records", Handler: production.ScanRecordsHandler(serverCtx)}, @@ -138,7 +143,6 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext {Method: http.MethodPost, Path: "/process-flows/upload", Handler: UploadPdfHandler(serverCtx)}, {Method: http.MethodGet, Path: "/stations", Handler: ListStationsHandler(serverCtx)}, {Method: http.MethodPost, Path: "/stations", Handler: SaveStationHandler(serverCtx)}, - {Method: http.MethodGet, Path: "/files", Handler: FileDownloadHandler(serverCtx)}, // ---------- PAD 巡检终端(块8) ---------- {Method: http.MethodPost, Path: "/inspections", Handler: CreateInspectionHandler(serverCtx)}, @@ -147,6 +151,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext // ---------- 工作量/绩效报表(块6) ---------- {Method: http.MethodGet, Path: "/performance", Handler: WorkloadInternalHandler(serverCtx)}, + {Method: http.MethodGet, Path: "/performance/export", Handler: PerformanceExportHandler(serverCtx)}, // ---------- 流程卡打印(块7) ---------- {Method: http.MethodGet, Path: "/process-card", Handler: ProcessCardHandler(serverCtx)}, @@ -158,4 +163,17 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext // 请求体上限跟随 Upload.MaxMB(默认20MB),否则 go-zero 默认 1MB 会 413(PDF/照片上传在此分组) rest.WithMaxBytes(serverCtx.Config.Upload.MaxMB<<20), ) + + // ---------- 公开文件访问(免 JWT) ---------- + // /el-image 发起的图片请求无法携带 Authorization 头,走 JWT 组会 401 + // 导致"上传成功但页面图片加载失败"(2026-09-08 修复)。文件名为系统生成的 + // 日期目录+随机名,不可枚举,免鉴权风险可控。 + fileRoutes := []rest.Route{ + {Method: http.MethodGet, Path: "/files", Handler: FileDownloadHandler(serverCtx)}, + {Method: http.MethodGet, Path: "/files/:date/:name", Handler: FilePathHandler(serverCtx)}, + } + server.AddRoutes( + fileRoutes, + rest.WithPrefix("/api/v1"), + ) } diff --git a/bj_power_mes/internal/handler/station.go b/bj_power_mes/internal/handler/station.go index c1df75d..e25fec2 100644 --- a/bj_power_mes/internal/handler/station.go +++ b/bj_power_mes/internal/handler/station.go @@ -2,11 +2,16 @@ package handler import ( "net/http" + "errors" + "net/url" "strconv" + "time" "bj_power_mes/common/httpx" "bj_power_mes/internal/logic" "bj_power_mes/internal/svc" + + "github.com/xuri/excelize/v2" ) // atoiDefault 解析整型字符串,非法返回默认值 @@ -110,7 +115,10 @@ func StationReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func WorkloadInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("from"), q.Get("to")) + data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("from"), q.Get("to"), + atoiDefault(q.Get("opPage"), 1), atoiDefault(q.Get("opPageSize"), 20), + atoiDefault(q.Get("stPage"), 1), atoiDefault(q.Get("stPageSize"), 20), + atoiDefault(q.Get("detPage"), 1), atoiDefault(q.Get("detPageSize"), 20)) if err != nil { httpx.Fail(w, 2105, err.Error()) return @@ -165,3 +173,91 @@ func StationCheckinInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc httpx.OkMessage(w, "暂存退库已登记", nil) } } + + +// PerformanceExportHandler GET /performance/export 绩效报表导出(一个 xlsx 三个 sheet:按人/按工位/明细) +// 执行全项目统一导出时间硬规则:起止必填(默认近3个月)、间隔≤1年 +func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + startDate, endDate, rerr := normalizeExportRange(q.Get("startDate"), q.Get("endDate")) + if rerr != nil { + httpx.Fail(w, 2106, rerr.Error()) + return + } + data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), startDate, endDate, + 1, 1<<30, 1, 1<<30, 1, 1<<30) + if err != nil { + httpx.Fail(w, 2105, err.Error()) + return + } + f := excelize.NewFile() + type view struct { + name string + key string + headers []string + cols func(m map[string]any) []any + } + rowOf := func(v any) map[string]any { m, _ := v.(map[string]any); return m } + views := []view{ + {"按人", "byOperator", []string{"操作人", "完成数", "合格", "不合格"}, func(m map[string]any) []any { + return []any{m["operator"], m["doneCount"], m["okCount"], m["ngCount"]} + }}, + {"按工位", "byStation", []string{"工位", "完成数", "合格", "不合格"}, func(m map[string]any) []any { + return []any{m["stationNo"], m["doneCount"], m["okCount"], m["ngCount"]} + }}, + {"明细", "detail", []string{"操作人", "工位", "日期", "工序", "工序名", "完成数", "合格", "不合格"}, func(m map[string]any) []any { + return []any{m["operator"], m["stationNo"], m["date"], m["processCode"], m["processName"], m["doneCount"], m["okCount"], m["ngCount"]} + }}, + } + for i, vw := range views { + sheet := vw.name + if i == 0 { + f.SetSheetName("Sheet1", sheet) + } else { + f.NewSheet(sheet) + } + for c, h := range vw.headers { + cell, _ := excelize.CoordinatesToCellName(c+1, 1) + _ = f.SetCellValue(sheet, cell, h) + } + vm, _ := data[vw.key].(map[string]any) + list, _ := vm["list"].([]any) + for rIdx, it := range list { + m := rowOf(it) + for c := range vw.headers { + cell, _ := excelize.CoordinatesToCellName(c+1, rIdx+2) + _ = f.SetCellValue(sheet, cell, vw.cols(m)[c]) + } + } + } + w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape("绩效报表.xlsx")) + _, _ = f.WriteTo(w) + } +} + + +// normalizeExportRange MES 侧导出时间硬规则(与 WMS 同一套): +// ① 起止都空 → 默认近 3 个月;② 只传一个 → 报错;③ 间隔 > 1 年 → 报错;④ 结束早于起始 → 报错。 +func normalizeExportRange(startDate, endDate string) (string, string, error) { + if startDate == "" && endDate == "" { + now := time.Now() + return now.AddDate(0, -3, 0).Format("2006-01-02"), now.Format("2006-01-02"), nil + } + if startDate == "" || endDate == "" { + return "", "", errors.New("导出必须同时提供起始时间与结束时间") + } + st, err1 := time.ParseInLocation("2006-01-02", startDate, time.Local) + et, err2 := time.ParseInLocation("2006-01-02", endDate, time.Local) + if err1 != nil || err2 != nil { + return "", "", errors.New("时间格式应为 YYYY-MM-DD") + } + if et.Before(st) { + return "", "", errors.New("结束时间不能早于起始时间") + } + if et.Sub(st) > 366*24*time.Hour { + return "", "", errors.New("导出时间间隔不能超过 1 年") + } + return startDate, endDate, nil +} diff --git a/bj_power_mes/internal/logic/auth.go b/bj_power_mes/internal/logic/auth.go index 61022bb..2c50ce0 100644 --- a/bj_power_mes/internal/logic/auth.go +++ b/bj_power_mes/internal/logic/auth.go @@ -89,6 +89,12 @@ func (s *Service) Login(ctx context.Context, req LoginReq) (LoginResp, error) { if bcrypt.CompareHashAndPassword([]byte(usr.Password), []byte(req.Password)) != nil { return resp, errors.New("用户名或密码错误") } + // 记录最近登录时间(仅后台登录;工位终端 StationLogin 不计入) + now := time.Now().Unix() + if err := s.ctx.EntClient.User.UpdateOneID(usr.ID).SetLastLoginAt(now).Exec(ctx); err != nil { + // 不阻塞登录,仅记日志 + } + usr.LastLoginAt = &now roleEntity, _ := s.ctx.EntClient.Role.Get(ctx, usr.RoleId) return s.issueTokens(usr, roleEntity) } @@ -445,6 +451,7 @@ type UserVO struct { Status string `json:"status"` CanLoginWorkstation bool `json:"canLoginWorkstation"` Stations []int `json:"stations"` // 允许登录/操作的工位号(空=全部工位) + LastLoginAt *int64 `json:"lastLoginAt"` // 最近登录时间(unix秒),null=从未登录 } func (s *Service) ListUsers(ctx context.Context) ([]UserVO, error) { @@ -462,6 +469,7 @@ func (s *Service) ListUsers(ctx context.Context) ([]UserVO, error) { Status: u.Status, CanLoginWorkstation: u.CanLoginWorkstation, Stations: s.UserStations(ctx, u.ID), + LastLoginAt: u.LastLoginAt, }) } return out, nil diff --git a/bj_power_mes/internal/logic/bom.go b/bj_power_mes/internal/logic/bom.go index 7007d06..697c363 100644 --- a/bj_power_mes/internal/logic/bom.go +++ b/bj_power_mes/internal/logic/bom.go @@ -2,14 +2,20 @@ package logic import ( "context" + "errors" "fmt" "bj_power_mes/ent" "bj_power_mes/ent/bomitem" + "bj_power_mes/ent/workorder" ) +// 默认BOMName 同一型号多份 BOM 并存时的缺省名称(历史数据/未指定时统一回填此值) +const DefaultBOMName = "默认" + type BomItemReq struct { ProductCode string `json:"productCode"` + BomName string `json:"bomName"` MaterialCode string `json:"materialCode"` MaterialName string `json:"materialName"` Spec string `json:"spec"` @@ -22,14 +28,25 @@ type BomItemReq struct { SerialSns []string `json:"serialSns"` } -// SaveBom 保存产品 BOM(覆盖式:按 产品编码+物料编码 做 upsert) -func (s *Service) SaveBom(ctx context.Context, productCode string, items []BomItemReq, operator string) error { +// bomNameOrDefault 空 BOM 名回退为「默认」(兼容旧前端/旧数据) +func bomNameOrDefault(name string) string { + if name == "" { + return DefaultBOMName + } + return name +} + +// SaveBom 保存产品 BOM(覆盖式 upsert:唯一键 = 产品编码+BOM名称+物料编码)。 +// 同一产品型号可并存多份 BOM(不同厂家供货/含半成品等不同料单结构),按 bomName 区分, +// 工单建单时选定一份;备料/装机绑定均按工单锁定的 bomName 计算。 +func (s *Service) SaveBom(ctx context.Context, productCode, bomName string, items []BomItemReq, operator string) error { + bomName = bomNameOrDefault(bomName) for _, it := range items { if it.ManageMode == "" { it.ManageMode = "2" } exist, err := s.ctx.EntClient.BomItem.Query(). - Where(bomitem.ProductCode(productCode), bomitem.MaterialCode(it.MaterialCode)).First(ctx) + Where(bomitem.ProductCode(productCode), bomitem.BomName(bomName), bomitem.MaterialCode(it.MaterialCode)).First(ctx) if err == nil && exist != nil { u := s.ctx.EntClient.BomItem.UpdateOneID(exist.ID). SetMaterialName(it.MaterialName).SetSpec(it.Spec).SetUnit(it.Unit). @@ -49,7 +66,7 @@ func (s *Service) SaveBom(ctx context.Context, productCode string, items []BomIt pc = *it.ProcessCode } b := s.ctx.EntClient.BomItem.Create(). - SetProductCode(productCode).SetMaterialCode(it.MaterialCode). + SetProductCode(productCode).SetBomName(bomName).SetMaterialCode(it.MaterialCode). SetMaterialName(it.MaterialName).SetSpec(it.Spec).SetUnit(it.Unit). SetManageMode(it.ManageMode).SetProcessCode(pc). SetUnitQty(it.UnitQty).SetLossRate(it.LossRate) @@ -61,14 +78,70 @@ func (s *Service) SaveBom(ctx context.Context, productCode string, items []BomIt } } } - s.ctx.EventLog.Write(ctx, "bom.save", "", operator, "product_bom", productCode, "保存产品BOM", map[string]any{"count": len(items)}) + s.ctx.EventLog.Write(ctx, "bom.save", "", operator, "product_bom", productCode, "保存产品BOM", map[string]any{"bomName": bomName, "count": len(items)}) return nil } -func (s *Service) ListBom(ctx context.Context, productCode string) ([]*ent.BomItem, error) { +// DeleteBomItem 移除 BOM 中的一条物料(按行主键)。 +// 安全校验:该 (产品编码+BOM名称) 下任一工单已生成备料单时禁止移除,避免已锁定料单被改动。 +func (s *Service) DeleteBomItem(ctx context.Context, id int, operator string) error { + item, err := s.ctx.EntClient.BomItem.Get(ctx, id) + if err != nil { + return errors.New("BOM 物料不存在") + } + used, err := s.ctx.EntClient.WorkOrder.Query(). + Where(workorder.ProductCode(item.ProductCode), workorder.BomName(item.BomName)).Exist(ctx) + if err == nil && used { + // 该型号+BOM 已被工单选用过(有工单绑定),谨慎起见禁止直接移除 + return fmt.Errorf("该 BOM 已被「%s」型号的工单选用,移除物料会影响备料/追溯,请先评估相关工单", item.ProductCode) + } + if err := s.ctx.EntClient.BomItem.DeleteOneID(id).Exec(ctx); err != nil { + return err + } + s.ctx.EventLog.Write(ctx, "bom.item.delete", "", operator, "product_bom", item.ProductCode, + "移除BOM物料 "+item.MaterialCode, map[string]any{"bomName": item.BomName}) + return nil +} + +// ListBom 查询 BOM(可按产品编码 + BOM 名称过滤;bomName 传 "ALL" 或空且指定了产品时返回全部) +func (s *Service) ListBom(ctx context.Context, productCode, bomName string) ([]*ent.BomItem, error) { q := s.ctx.EntClient.BomItem.Query() if productCode != "" { q = q.Where(bomitem.ProductCode(productCode)) } - return q.Order(ent.Asc(bomitem.FieldID)).All(ctx) -} \ No newline at end of file + if bomName != "" && bomName != "ALL" { + q = q.Where(bomitem.BomName(bomName)) + } + return q.Order(ent.Asc(bomitem.FieldProductCode), ent.Asc(bomitem.FieldBomName), ent.Asc(bomitem.FieldID)).All(ctx) +} + +// BomNameInfo 型号下的一份 BOM 概要(名称 + 物料数) +type BomNameInfo struct { + BomName string `json:"bomName"` + ItemCount int `json:"itemCount"` +} + +// ListBomNames 列出某产品型号下并存的所有 BOM 名称(工单建单选 BOM 下拉数据源) +func (s *Service) ListBomNames(ctx context.Context, productCode string) ([]*BomNameInfo, error) { + if productCode == "" { + return []*BomNameInfo{}, nil + } + items, err := s.ctx.EntClient.BomItem.Query(). + Where(bomitem.ProductCode(productCode)).All(ctx) + if err != nil { + return nil, err + } + order := []string{} + count := map[string]int{} + for _, it := range items { + if _, ok := count[it.BomName]; !ok { + order = append(order, it.BomName) + } + count[it.BomName]++ + } + out := make([]*BomNameInfo, 0, len(order)) + for _, name := range order { + out = append(out, &BomNameInfo{BomName: name, ItemCount: count[name]}) + } + return out, nil +} diff --git a/bj_power_mes/internal/logic/inspection.go b/bj_power_mes/internal/logic/inspection.go index acfc516..0054b67 100644 --- a/bj_power_mes/internal/logic/inspection.go +++ b/bj_power_mes/internal/logic/inspection.go @@ -4,16 +4,31 @@ import ( "context" "encoding/json" "errors" + "strings" "time" "bj_power_mes/ent" "bj_power_mes/ent/inspectionrecord" ) +// FlexStr 兼容客户端把数字字段传成 number 或 string(前端 stationNo 用数字, +// 标准 JSON 反序列化进 string 会报错 → 400 请求体解析失败) +type FlexStr string + +func (f *FlexStr) UnmarshalJSON(b []byte) error { + s := strings.TrimSpace(string(b)) + s = strings.Trim(s, `"`) + if s == "null" { + s = "" + } + *f = FlexStr(s) + return nil +} + // InspectionReq 巡检记录请求(PAD 巡检终端,块8) type InspectionReq struct { Category string `json:"category"` // CHECKIN/POINT/PROCESS/DONE/ALARM - StationNo string `json:"stationNo"` + StationNo FlexStr `json:"stationNo"` Shift string `json:"shift"` // 早/中/晚 OrderNo string `json:"orderNo"` // 工单号 Sn string `json:"sn"` // 工件SN @@ -42,7 +57,7 @@ func (s *Service) CreateInspection(ctx context.Context, req InspectionReq, opera } _, err := s.ctx.EntClient.InspectionRecord.Create(). SetCategory(category). - SetStationNo(req.StationNo). + SetStationNo(string(req.StationNo)). SetShift(req.Shift). SetOrderNo(req.OrderNo). SetSn(req.Sn). @@ -56,7 +71,7 @@ func (s *Service) CreateInspection(ctx context.Context, req InspectionReq, opera return err } s.ctx.EventLog.Write(ctx, "inspection."+category, req.OrderNo, req.Operator, "inspection_record", - req.Sn, "巡检终端提交", map[string]any{"category": category, "stationNo": req.StationNo, "result": result}) + req.Sn, "巡检终端提交", map[string]any{"category": category, "stationNo": string(req.StationNo), "result": result}) if category == "ALARM" { // 触发看板报警:SSE 推送 + 使 Redis 看板报警缓存失效 @@ -64,7 +79,7 @@ func (s *Service) CreateInspection(ctx context.Context, req InspectionReq, opera "level": "CRITICAL", "type": "inspection_alarm", "message": "巡检异常上报:" + req.Remark, - "station": req.StationNo, + "station": string(req.StationNo), "time": time.Now().Format("2006-01-02 15:04:05"), }) if s.ctx.SSE != nil { @@ -78,12 +93,25 @@ func (s *Service) CreateInspection(ctx context.Context, req InspectionReq, opera return nil } -// ListInspections 查询巡检记录 +// ListInspections 查询巡检记录(不分页,供内部/导出用,封顶 1000) func (s *Service) ListInspections(ctx context.Context, category, operator, from, to string) ([]*ent.InspectionRecord, error) { - q := s.ctx.EntClient.InspectionRecord.Query().Order(ent.Desc(inspectionrecord.FieldID)) + rows, _, err := s.ListInspectionsPaged(ctx, category, operator, from, to, "", "", 1, 1000) + return rows, err +} + +// ListInspectionsPaged 分页查询巡检记录,返回 {rows, total};排序 id desc 最新置顶 +// orderNo/stationNo:2026-09-08 补充筛选(大小写不敏感) +func (s *Service) ListInspectionsPaged(ctx context.Context, category, operator, from, to, orderNo, stationNo string, page, pageSize int) ([]*ent.InspectionRecord, int, error) { + q := s.ctx.EntClient.InspectionRecord.Query() if category != "" { q = q.Where(inspectionrecord.Category(category)) } + if orderNo != "" { + q = q.Where(inspectionrecord.OrderNoEqualFold(orderNo)) + } + if stationNo != "" { + q = q.Where(inspectionrecord.StationNoEqualFold(stationNo)) + } if operator != "" { q = q.Where(inspectionrecord.Operator(operator)) } @@ -97,7 +125,13 @@ func (s *Service) ListInspections(ctx context.Context, category, operator, from, q = q.Where(inspectionrecord.CreatedAtLT(t.Add(24 * time.Hour))) } } - return q.Limit(1000).All(ctx) + total, err := q.Count(ctx) + if err != nil { + return nil, 0, err + } + rows, err := q.Order(ent.Desc(inspectionrecord.FieldID)). + Offset((page - 1) * pageSize).Limit(pageSize).All(ctx) + return rows, total, err } func oneOf(v string, items ...string) bool { diff --git a/bj_power_mes/internal/logic/material.go b/bj_power_mes/internal/logic/material.go index c55428b..a125de7 100644 --- a/bj_power_mes/internal/logic/material.go +++ b/bj_power_mes/internal/logic/material.go @@ -36,13 +36,13 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, if plan.Status == "CANCELLED" || plan.Status == "DONE" { continue // 已取消/已完成的排产不再算料 } - // 解析工单 → 产品编码 → 该产品 BOM + // 解析工单 → 产品编码 → 该工单锁定的 BOM(建单时选定的 bomName) wo, err := s.ctx.EntClient.WorkOrder.Query(). Where(workorder.WorkOrderNo(plan.OrderNo)).Only(ctx) if err != nil { continue } - bom, err := s.ListBom(ctx, wo.ProductCode) + bom, err := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault(wo.BomName)) if err != nil { continue } @@ -103,9 +103,20 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, // ListMaterialRequests 查询备料单 func (s *Service) ListMaterialRequests(ctx context.Context, orderNo, planDate, status string) ([]*ent.MaterialRequest, error) { + return s.listMaterialRequests(ctx, orderNo, planDate, status, "", "", "") +} + +// ListMaterialRequestsFiltered 管理页丰富筛选入口 +func (s *Service) ListMaterialRequestsFiltered(ctx context.Context, orderNo, planDate, status, materialCode, materialName, targetDock string) ([]*ent.MaterialRequest, error) { + return s.listMaterialRequests(ctx, orderNo, planDate, status, materialCode, materialName, targetDock) +} + +// listMaterialRequests 备料单查询(2026-09-08 丰富筛选+大小写不敏感) +// materialCode/materialName/targetDock 可空;keyword 兜底匹配 备料单号/工单号/物料编码 +func (s *Service) listMaterialRequests(ctx context.Context, orderNo, planDate, status, materialCode, materialName, targetDock string) ([]*ent.MaterialRequest, error) { q := s.ctx.EntClient.MaterialRequest.Query() if orderNo != "" { - q = q.Where(materialrequest.OrderNo(orderNo)) + q = q.Where(materialrequest.OrderNoEqualFold(orderNo)) } if planDate != "" { q = q.Where(materialrequest.PlanDate(planDate)) @@ -113,9 +124,19 @@ func (s *Service) ListMaterialRequests(ctx context.Context, orderNo, planDate, s if status != "" { q = q.Where(materialrequest.Status(status)) } - return q.Order(materialrequest.ByID()).All(ctx) + if materialCode != "" { + q = q.Where(materialrequest.MaterialCodeContainsFold(materialCode)) + } + if materialName != "" { + q = q.Where(materialrequest.MaterialNameContainsFold(materialName)) + } + if targetDock != "" { + q = q.Where(materialrequest.TargetDockEqualFold(targetDock)) + } + return q.Order(ent.Desc(materialrequest.FieldCreatedAt), ent.Desc(materialrequest.FieldID)).All(ctx) } + // SetMaterialRequestStatus 推进备料单状态(WMS 下发 AGV DELIVERING / 到位 DONE) func (s *Service) SetMaterialRequestStatus(ctx context.Context, requestNo, status string) error { n, err := s.ctx.EntClient.MaterialRequest.Update(). diff --git a/bj_power_mes/internal/logic/processflow.go b/bj_power_mes/internal/logic/processflow.go index cf5bb36..acc320b 100644 --- a/bj_power_mes/internal/logic/processflow.go +++ b/bj_power_mes/internal/logic/processflow.go @@ -46,7 +46,8 @@ type FlowVO struct { } // ListFlows 查询工艺流程(可按工位号过滤:命中绑定该工位或主工序号的流程) -func (s *Service) ListFlows(ctx context.Context, stationNo int) ([]*FlowVO, error) { +// page>0 时真分页返回 map{total,list,page,pageSize}(管理页用);page=0 全量数组(工位终端兼容) +func (s *Service) ListFlows(ctx context.Context, stationNo, page, pageSize int) (any, error) { q := s.ctx.EntClient.ProcessFlow.Query(). Order(ent.Asc(processflow.FieldProcessCode), ent.Asc(processflow.FieldID)) flows, err := q.All(ctx) @@ -85,6 +86,18 @@ func (s *Service) ListFlows(ctx context.Context, stationNo int) ([]*FlowVO, erro } out = append(out, vo) } + if page > 0 { + total := len(out) + start := (page - 1) * pageSize + if start > total { + start = total + } + end := start + pageSize + if end > total { + end = total + } + return map[string]any{"total": total, "list": out[start:end], "page": page, "pageSize": pageSize}, nil + } return out, nil } @@ -95,8 +108,10 @@ func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) er if req.Name == "" { return errors.New("流程名称必填") } - if req.StationNo < 1 || req.StationNo > 12 { - return errors.New("请选择主工序编号(1~12)") + // 工序编号与物理工位解耦:工序是流程步骤编号(1~999,流程内唯一即可), + // 工位对应关系由「关联工位」页维护(2026-09-08) + if req.StationNo < 1 || req.StationNo > 999 { + return errors.New("主工序编号应为 1~999 的整数") } // 解析绑定工位列表:显式 stations 优先,否则回退到 StationNo(兼容旧调用) bindStations := req.Stations @@ -122,21 +137,8 @@ func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) er if status != "ACTIVE" && status != "INACTIVE" { return errors.New("非法状态") } - // 校验:绑定工位不得已被其他"启用"流程占用(一个工位只能绑一个启用流程) - bound, err := s.ctx.EntClient.Station.Query(). - Where(station.StationNoIn(stations...), station.FlowIdGT(0)).All(ctx) - if err == nil { - flowID := req.Id - for _, st := range bound { - if flowID > 0 && st.FlowId == flowID { - continue // 绑定的是本流程自身,允许 - } - other, gErr := s.ctx.EntClient.ProcessFlow.Get(ctx, st.FlowId) - if gErr == nil && other.Status == "ACTIVE" { - return fmt.Errorf("工位 %d 已被启用流程「%s」绑定,请先在流程/工位中解除绑定", st.StationNo, other.Name) - } - } - } + // 绑定语义(2026-09-08 定稿):直接更换——一个工位只有一条关联数据, + // 保存时所选工位一律改绑到本流程(覆盖旧绑定),不再报"已被启用流程绑定"冲突。 var flowID int if req.Id > 0 { upd := s.ctx.EntClient.ProcessFlow.UpdateOneID(req.Id). @@ -395,7 +397,8 @@ type WorkloadRow struct { NgCount int `json:"ngCount"` } -func (s *Service) Workload(ctx context.Context, operator, stationNo, from, to string) (map[string]any, error) { +func (s *Service) Workload(ctx context.Context, operator, stationNo, from, to string, + opPage, opSize, stPage, stSize, detPage, detSize int) (map[string]any, error) { q := s.ctx.EntClient.WorkpieceProcess.Query() if operator != "" { q = q.Where(workpieceprocess.Operator(operator)) @@ -449,5 +452,67 @@ func (s *Service) Workload(ctx context.Context, operator, stationNo, from, to st } return list[i].ProcessCode < list[j].ProcessCode }) - return map[string]any{"rows": list, "detail": rows}, nil + + // 三个视图分别聚合(2026-09-08 绩效报表改造:按人/按工位/明细,各自真分页) + byOpAgg := map[string]*WorkloadRow{} + byStAgg := map[string]*WorkloadRow{} + for _, v := range list { + opKey := v.Operator + if o, ok := byOpAgg[opKey]; ok { + o.DoneCount += v.DoneCount; o.OkCount += v.OkCount; o.NgCount += v.NgCount + } else { + byOpAgg[opKey] = &WorkloadRow{Operator: v.Operator, DoneCount: v.DoneCount, OkCount: v.OkCount, NgCount: v.NgCount} + } + stKey := v.StationNo + if o, ok := byStAgg[stKey]; ok { + o.DoneCount += v.DoneCount; o.OkCount += v.OkCount; o.NgCount += v.NgCount + } else { + byStAgg[stKey] = &WorkloadRow{StationNo: v.StationNo, DoneCount: v.DoneCount, OkCount: v.OkCount, NgCount: v.NgCount} + } + } + paginateRows := func(all []*ent.WorkpieceProcess, page, size int) (int, []*ent.WorkpieceProcess) { + total := len(all) + start := (page - 1) * size + if start > total { + start = total + } + end := start + size + if end > total { + end = total + } + return total, all[start:end] + } + paginate := func(all []*WorkloadRow, page, size int) (int, []*WorkloadRow) { + total := len(all) + start := (page - 1) * size + if start > total { + start = total + } + end := start + size + if end > total { + end = total + } + return total, all[start:end] + } + + byOpList := make([]*WorkloadRow, 0, len(byOpAgg)) + for _, v := range byOpAgg { + byOpList = append(byOpList, v) + } + sort.Slice(byOpList, func(i, j int) bool { return byOpList[i].Operator < byOpList[j].Operator }) + byStList := make([]*WorkloadRow, 0, len(byStAgg)) + for _, v := range byStAgg { + byStList = append(byStList, v) + } + sort.Slice(byStList, func(i, j int) bool { return byStList[i].StationNo < byStList[j].StationNo }) + + opTotal, byOpPage := paginate(byOpList, opPage, opSize) + stTotal, byStPage := paginate(byStList, stPage, stSize) + detTotal, detRowsPage := paginateRows(rows, detPage, detSize) + + return map[string]any{ + "byOperator": map[string]any{"total": opTotal, "list": byOpPage}, + "byStation": map[string]any{"total": stTotal, "list": byStPage}, + "detail": map[string]any{"total": detTotal, "list": detRowsPage}, + }, nil } diff --git a/bj_power_mes/internal/logic/product.go b/bj_power_mes/internal/logic/product.go index c2bc23d..544c84a 100644 --- a/bj_power_mes/internal/logic/product.go +++ b/bj_power_mes/internal/logic/product.go @@ -3,11 +3,20 @@ package logic import ( "context" "errors" + "fmt" + "strings" "bj_power_mes/ent" + "bj_power_mes/ent/bomitem" "bj_power_mes/ent/producttype" + "bj_power_mes/ent/workorder" + "bj_power_mes/internal/wmsclient" ) +// 产品型号(成品档案):编码主数据单一来源 = WMS(materials item_type=3)。 +// MES 本页改为实时代理 WMS;WMS 不可达时降级读本地 product_types 只读缓存(仅查询, +// 写操作必须 WMS 可用),前端以 wmsOnline 标识提示。 + type ProductTypeReq struct { Id int `json:"id"` Name string `json:"name"` @@ -22,25 +31,115 @@ func (s *Service) CreateProductType(ctx context.Context, req ProductTypeReq) err if !req.IsActive { active = true } - return s.ctx.EntClient.ProductType.Create(). - SetName(req.Name).SetCode(req.Code).SetCategory(req.Category). - SetRemark(req.Remark).SetIsActive(active).Exec(ctx) + if err := s.ctx.Wms.CreateProductType(ctx, req.Code, req.Name, req.Category, req.Remark, active); err != nil { + return errors.New("WMS 不可达或拒绝写入(编码主数据以 WMS 为单一来源):" + err.Error()) + } + return nil } func (s *Service) UpdateProductType(ctx context.Context, req ProductTypeReq) error { if req.Id == 0 { return errors.New("缺少 id") } - return s.ctx.EntClient.ProductType.UpdateOneID(req.Id). - SetName(req.Name).SetCode(req.Code).SetCategory(req.Category). - SetRemark(req.Remark).SetIsActive(req.IsActive).Exec(ctx) + if err := s.ctx.Wms.UpdateProductType(ctx, req.Id, req.Name, req.Category, req.Remark, req.IsActive); err != nil { + return errors.New("WMS 不可达或拒绝写入:" + err.Error()) + } + return nil } +// DeleteProductType 删除保护:被工单/BOM 引用过的型号禁删(MES 侧引用校验, +// WMS 侧库存引用由 WMS 内部接口再校验一层) func (s *Service) DeleteProductType(ctx context.Context, id int) error { - return s.ctx.EntClient.ProductType.DeleteOneID(id).Exec(ctx) + row, err := s.productTypeByID(ctx, id) + if err != nil { + return err + } + woCnt, _ := s.ctx.EntClient.WorkOrder.Query().Where(workorder.ProductCode(row.Code)).Count(ctx) + if woCnt > 0 { + return fmt.Errorf("该产品型号已被 %d 张工单引用,不可删除;建议停用(下架)", woCnt) + } + bomCnt, _ := s.ctx.EntClient.BomItem.Query().Where(bomitem.ProductCode(row.Code)).Count(ctx) + if bomCnt > 0 { + return fmt.Errorf("该产品型号在物料清单中有 %d 条配置,不可删除;建议停用(下架)", bomCnt) + } + if err := s.ctx.Wms.DeleteProductType(ctx, id); err != nil { + return errors.New("WMS 不可达或拒绝删除:" + err.Error()) + } + return nil } -func (s *Service) ListProductTypes(ctx context.Context) ([]*ent.ProductType, error) { - return s.ctx.EntClient.ProductType.Query(). - Order(ent.Desc(producttype.FieldID)).All(ctx) -} \ No newline at end of file +// ListProductTypes 全量列表(下拉/兼容形态,id asc 新建在底部)。 +// degraded=false 表示 WMS 不可达、数据来自本地只读缓存。 +func (s *Service) ListProductTypes(ctx context.Context) ([]wmsclient.ProductTypeRow, bool, error) { + list, err := s.ctx.Wms.ProductTypes(ctx, "", "") + if err == nil { + return list, true, nil + } + fallback, ferr := s.localProductTypes(ctx, "") + if ferr != nil { + return nil, false, errors.New("WMS 不可达且本地缓存读取失败") + } + return fallback, false, nil +} + +// ListProductTypesPage 分页+搜索(管理页真分页) +func (s *Service) ListProductTypesPage(ctx context.Context, keyword, isActive string, page, pageSize int) (int64, []wmsclient.ProductTypeRow, bool, error) { + total, list, err := s.ctx.Wms.ProductTypePage(ctx, keyword, isActive, page, pageSize) + if err == nil { + return total, list, true, nil + } + fallback, ferr := s.localProductTypes(ctx, keyword) + if ferr != nil { + return 0, nil, false, errors.New("WMS 不可达且本地缓存读取失败") + } + start := (page - 1) * pageSize + if start > len(fallback) { + start = len(fallback) + } + end := start + pageSize + if end > len(fallback) { + end = len(fallback) + } + return int64(len(fallback)), fallback[start:end], false, nil +} + +func (s *Service) productTypeByID(ctx context.Context, id int) (*wmsclient.ProductTypeRow, error) { + list, err := s.ctx.Wms.ProductTypes(ctx, "", "") + if err == nil { + for i := range list { + if list[i].ID == id { + return &list[i], nil + } + } + return nil, errors.New("产品型号不存在") + } + // 降级:本地缓存 + pt, err := s.ctx.EntClient.ProductType.Get(ctx, id) + if err != nil { + return nil, errors.New("产品型号不存在(WMS 不可达,无法校验引用)") + } + return &wmsclient.ProductTypeRow{ID: pt.ID, Code: pt.Code, Name: pt.Name}, nil +} + +func (s *Service) localProductTypes(ctx context.Context, keyword string) ([]wmsclient.ProductTypeRow, error) { + list, err := s.ctx.EntClient.ProductType.Query().Order(ent.Asc(producttype.FieldID)).All(ctx) + if err != nil { + return nil, err + } + rows := make([]wmsclient.ProductTypeRow, 0, len(list)) + kw := strings.ToLower(strings.TrimSpace(keyword)) + for _, p := range list { + if kw != "" && + !strings.Contains(strings.ToLower(p.Code), kw) && + !strings.Contains(strings.ToLower(p.Name), kw) && + !strings.Contains(strings.ToLower(p.Category), kw) { + continue + } + rows = append(rows, wmsclient.ProductTypeRow{ + ID: p.ID, Code: p.Code, Name: p.Name, Category: p.Category, + Remark: p.Remark, IsActive: p.IsActive, + CreatedAt: p.CreatedAt.Unix(), + }) + } + return rows, nil +} diff --git a/bj_power_mes/internal/logic/scan.go b/bj_power_mes/internal/logic/scan.go index 538e4c4..a9144ed 100644 --- a/bj_power_mes/internal/logic/scan.go +++ b/bj_power_mes/internal/logic/scan.go @@ -92,13 +92,22 @@ func (s *Service) bumpDailyPlanCompleted(ctx context.Context, orderNo string, t } // ListScanRecords 查询扫码报工记录 -func (s *Service) ListScanRecords(ctx context.Context, sn, orderNo string) ([]*ent.ScanRecord, error) { +func (s *Service) ListScanRecords(ctx context.Context, sn, orderNo string, page, pageSize int) ([]*ent.ScanRecord, int, error) { q := s.ctx.EntClient.ScanRecord.Query() if sn != "" { q = q.Where(scanrecord.Sn(sn)) } if orderNo != "" { - q = q.Where(scanrecord.OrderNo(orderNo)) + q = q.Where(scanrecord.OrderNoEqualFold(orderNo)) } - return q.Order(ent.Desc(scanrecord.FieldID)).Limit(1000).All(ctx) + if sn != "" { + q = q.Where(scanrecord.SnEqualFold(sn)) + } + total, err := q.Count(ctx) + if err != nil { + return nil, 0, err + } + rows, err := q.Order(ent.Desc(scanrecord.FieldID)). + Offset((page - 1) * pageSize).Limit(pageSize).All(ctx) + return rows, total, err } diff --git a/bj_power_mes/internal/logic/seed.go b/bj_power_mes/internal/logic/seed.go index 149f01b..3cfaa7d 100644 --- a/bj_power_mes/internal/logic/seed.go +++ b/bj_power_mes/internal/logic/seed.go @@ -80,7 +80,7 @@ func (s *Service) Seed(ctx context.Context) error { {"produce.performance", "绩效报表", "MENU", "/performance"}, {"produce.scan", "手动报工", "MENU", "/scan"}, {"produce.trace", "工件追溯", "MENU", "/trace"}, - {"produce.product", "产品类型", "MENU", "/product-type"}, + {"produce.product", "产品型号", "MENU", "/product-type"}, {"sys.inspect", "巡检终端", "MENU", "/inspect"}, {"sys.eventlog", "操作日志", "MENU", "/event-log"}, // sys.rbac(角色权限管理) 已拆分:账号管理 + 角色管理(对齐 WMS 系统管理菜单) @@ -106,9 +106,9 @@ func (s *Service) Seed(ctx context.Context) error { {"produce.bom:edit", "物料清单-维护", "BUTTON", "", "produce.bom"}, {"produce.material:generate", "备料单-生成", "BUTTON", "", "produce.material"}, {"produce.plc:send", "工位组合-下发", "BUTTON", "", "produce.plc"}, - {"produce.product:add", "产品类型-新增", "BUTTON", "", "produce.product"}, - {"produce.product:edit", "产品类型-编辑", "BUTTON", "", "produce.product"}, - {"produce.product:delete", "产品类型-删除", "BUTTON", "", "produce.product"}, + {"produce.product:add", "产品型号-新增", "BUTTON", "", "produce.product"}, + {"produce.product:edit", "产品型号-编辑", "BUTTON", "", "produce.product"}, + {"produce.product:delete", "产品型号-删除", "BUTTON", "", "produce.product"}, {"produce.processflow:add", "工艺流程-新增", "BUTTON", "", "produce.processflow"}, {"produce.processflow:edit", "工艺流程-编辑", "BUTTON", "", "produce.processflow"}, {"produce.processflow:delete", "工艺流程-删除", "BUTTON", "", "produce.processflow"}, diff --git a/bj_power_mes/internal/logic/torque.go b/bj_power_mes/internal/logic/torque.go index 96e0489..fa80831 100644 --- a/bj_power_mes/internal/logic/torque.go +++ b/bj_power_mes/internal/logic/torque.go @@ -85,14 +85,40 @@ func (s *Service) evaluateTorqueCriterion(ctx context.Context, sn, stationNo str func (s *Service) ListTorqueRecords(ctx context.Context, sn, workOrderNo string, from, to time.Time) ([]*ent.TorqueRecord, error) { q := s.ctx.EntClient.TorqueRecord.Query() if sn != "" { - q = q.Where(torquerecord.Sn(sn)) + // 大小写不敏感(2026-09-08 全项目搜索规范) + q = q.Where(torquerecord.SnEqualFold(sn)) } if workOrderNo != "" { - q = q.Where(torquerecord.WorkOrderNo(workOrderNo)) + q = q.Where(torquerecord.WorkOrderNoEqualFold(workOrderNo)) } return q.Order(ent.Desc(torquerecord.FieldTime)).Limit(500).All(ctx) } +// AddTorqueRecord 拧紧数据补录(管理端"拧紧查询-拧紧补录"tab 用,定位为设备漏传/手工修正) +func (s *Service) AddTorqueRecord(ctx context.Context, sn, workOrderNo, screwNo, stationNo string, strain, angle float64, result, reason, operator string) error { + if sn == "" || workOrderNo == "" { + return errors.New("工单号与 SN 必填") + } + if reason == "" { + return errors.New("补录原因必填(如:设备漏传/手工修正)") + } + if result == "" { + result = "OK" + } + now := time.Now() + _, err := s.ctx.EntClient.TorqueRecord.Create(). + SetSn(sn).SetWorkOrderNo(workOrderNo).SetScrewNo(screwNo).SetStationNo(stationNo). + SetStrain(strain).SetTorque(strain).SetAngle(angle). + SetResult(result).SetOperator(operator+"(补录)").SetTime(now). + Save(ctx) + if err != nil { + return err + } + s.ctx.EventLog.Write(ctx, "torque.manual.add", workOrderNo, operator, "torque_record", sn, + "拧紧数据补录 "+workOrderNo+" "+sn, map[string]any{"reason": reason, "result": result, "screwNo": screwNo, "stationNo": stationNo}) + return nil +} + // 计算一块工件某工序的扭矩步骤是否已报 func (s *Service) hasWorkpieceProcess(ctx context.Context, sn string, processCode int) bool { cnt, _ := s.ctx.EntClient.WorkpieceProcess.Query(). diff --git a/bj_power_mes/internal/logic/workorder.go b/bj_power_mes/internal/logic/workorder.go index e82ceea..c4c57e5 100644 --- a/bj_power_mes/internal/logic/workorder.go +++ b/bj_power_mes/internal/logic/workorder.go @@ -18,6 +18,7 @@ type WorkOrderReq struct { WorkOrderNo string `json:"workOrderNo"` ProductTypeId int `json:"productTypeId"` ProductCode string `json:"productCode"` + BomName string `json:"bomName"` // 建单选定的 BOM 名称(同型号多份并存;空=「默认」) ProductName string `json:"productName"` Quantity int `json:"quantity"` ProcessSeq string `json:"processSeq"` @@ -26,13 +27,34 @@ type WorkOrderReq struct { PlanEnd string `json:"planEnd"` } +// validateBomName 校验该产品型号下是否存在指定名称的 BOM(型号下无任何 BOM 时放行,允许先建单后补 BOM) +func (s *Service) validateBomName(ctx context.Context, productCode, bomName string) error { + if productCode == "" { + return nil + } + names, err := s.ListBomNames(ctx, productCode) + if err != nil { + return nil // 查询异常不阻塞建单(与 WMS 降级策略同思路) + } + if len(names) == 0 { + return nil + } + target := bomNameOrDefault(bomName) + for _, n := range names { + if n.BomName == target { + return nil + } + } + return fmt.Errorf("产品型号 %s 下不存在名为「%s」的BOM,请先在物料清单中创建", productCode, target) +} + // CreateWorkOrder 创建工单(状态恒 CREATED,后续用状态流转按钮推进) func (s *Service) CreateWorkOrder(ctx context.Context, req WorkOrderReq, operator string) error { if req.WorkOrderNo == "" { return errors.New("工单号不能为空") } if req.ProductTypeId == 0 { - return errors.New("请选择产品类型") + return errors.New("请选择产品型号") } if req.Quantity <= 0 { req.Quantity = 1 @@ -50,10 +72,14 @@ func (s *Service) CreateWorkOrder(ctx context.Context, req WorkOrderReq, operato return err } req.ProcessSeq = NormalizeProcessSeq(req.ProcessSeq) + if err := s.validateBomName(ctx, req.ProductCode, req.BomName); err != nil { + return err + } b := s.ctx.EntClient.WorkOrder.Create(). SetWorkOrderNo(req.WorkOrderNo). SetProductTypeId(req.ProductTypeId). SetProductCode(req.ProductCode). + SetBomName(bomNameOrDefault(req.BomName)). SetProductName(req.ProductName). SetQuantity(req.Quantity). SetProcessSeq(req.ProcessSeq). @@ -101,6 +127,12 @@ func (s *Service) UpdateWorkOrder(ctx context.Context, req WorkOrderReq, operato if req.ProductCode != "" { u.SetProductCode(req.ProductCode) } + if req.BomName != "" { + if err := s.validateBomName(ctx, req.ProductCode, req.BomName); err != nil { + return err + } + u.SetBomName(bomNameOrDefault(req.BomName)) + } if req.ProductName != "" { u.SetProductName(req.ProductName) } @@ -140,7 +172,7 @@ func (s *Service) ListWorkOrders(ctx context.Context, orderNo, status, productCo if productName != "" { q = q.Where(workorder.ProductNameContains(productName)) } - return q.Order(ent.Desc(workorder.FieldID)).All(ctx) + return q.Order(ent.Desc(workorder.FieldCreatedAt), ent.Desc(workorder.FieldID)).All(ctx) } func (s *Service) GetWorkOrder(ctx context.Context, id int) (*ent.WorkOrder, error) { diff --git a/bj_power_mes/internal/logic/workpiece.go b/bj_power_mes/internal/logic/workpiece.go index c2a4a72..6ce6b83 100644 --- a/bj_power_mes/internal/logic/workpiece.go +++ b/bj_power_mes/internal/logic/workpiece.go @@ -226,10 +226,32 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin s.bumpWorkOrderProgress(ctx, wp.OrderNo, req.Sn) s.bumpDailyPlanCompleted(ctx, wp.OrderNo, now) s.ctx.EventLog.Write(ctx, "workpiece.done", wp.OrderNo, operator, "workpiece", req.Sn, "完工+关联追溯", map[string]any{"batchItems": req.BatchItems, "serialItems": req.SerialItems}) + // 成品完工自动回流 WMS(统一库存主表 category=3): + // 成品 SN = 工件 SN(进线登记即系统发号,全程唯一,保证对号可追溯)。 + // WMS 不可达/校验失败时降级:记 eventlog 不阻塞完工,与备料 WMS 校验降级策略同模式。 + s.retainFinishedToWms(ctx, wp, operator) s.notifyDashboard() return nil } +// retainFinishedToWms 完工品回流 WMS 库存。失败仅记日志(wms.finished.retain_failed), +// 不回滚完工状态——仓管可在 WMS 侧人工补录,避免 WMS 抖动卡死产线。 +func (s *Service) retainFinishedToWms(ctx context.Context, wp *ent.Workpiece, operator string) { + wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId) + if err != nil || wo == nil { + s.ctx.EventLog.Write(ctx, "wms.finished.retain_failed", wp.OrderNo, operator, "workpiece", wp.Sn, + "成品回流WMS失败:工单不存在", map[string]any{"error": "work order not found"}) + return + } + if wmsErr := s.ctx.Wms.FinishedInbound(ctx, wo.ProductCode, wo.ProductName, wp.Sn, wp.OrderNo, operator); wmsErr != nil { + s.ctx.EventLog.Write(ctx, "wms.finished.retain_failed", wp.OrderNo, operator, "workpiece", wp.Sn, + "成品回流WMS失败(库存未入,请人工补录)", map[string]any{"error": wmsErr.Error(), "productCode": wo.ProductCode}) + return + } + s.ctx.EventLog.Write(ctx, "wms.finished.retain", wp.OrderNo, operator, "workpiece", wp.Sn, + "成品完工已回流WMS库存", map[string]any{"productCode": wo.ProductCode}) +} + // Trace 追溯查询(SN 维度):工序时间线/操作人/步骤数据/物料批次SN/拧紧数据 func (s *Service) Trace(ctx context.Context, sn string) (map[string]any, error) { wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(sn)).First(ctx) diff --git a/bj_power_mes/internal/logic/workpiece_bind.go b/bj_power_mes/internal/logic/workpiece_bind.go index d99f116..d48642d 100644 --- a/bj_power_mes/internal/logic/workpiece_bind.go +++ b/bj_power_mes/internal/logic/workpiece_bind.go @@ -81,26 +81,27 @@ type bindTarget struct { UnitQty float64 } -// workpieceProduct 解析工件所属产品编码(sn → workpiece → workOrder → productCode) -func (s *Service) workpieceProduct(ctx context.Context, sn string) (*ent.Workpiece, string, error) { +// workpieceProduct 解析工件所属产品编码与工单锁定的 BOM 名称(sn → workpiece → workOrder) +func (s *Service) workpieceProduct(ctx context.Context, sn string) (*ent.Workpiece, *ent.WorkOrder, error) { wp, err := s.ctx.EntClient.Workpiece.Query().Where(workpiece.Sn(sn)).First(ctx) if err != nil { - return nil, "", errors.New("工件未进线登记") + return nil, nil, errors.New("工件未进线登记") } if wp.WorkOrderId <= 0 { - return wp, "", errors.New("工件未关联工单") + return wp, nil, errors.New("工件未关联工单") } wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId) if err != nil || wo == nil { - return wp, "", errors.New("工件所属工单不存在") + return wp, nil, errors.New("工件所属工单不存在") } - return wp, wo.ProductCode, nil + return wp, wo, nil } -// stationBindTargets 取某产品在某工序(processCode>0)要求装配的物料清单 -func (s *Service) stationBindTargets(ctx context.Context, productCode string, processCode int) ([]bindTarget, error) { +// stationBindTargets 取某工单(产品+BOM)在某工序(processCode>0)要求装配的物料清单。 +// 一型号多份 BOM 并存,必须按工单锁定的 bomName 过滤,否则不同厂家料单互相串。 +func (s *Service) stationBindTargets(ctx context.Context, productCode, bomName string, processCode int) ([]bindTarget, error) { items, err := s.ctx.EntClient.BomItem.Query(). - Where(bomitem.ProductCode(productCode), bomitem.ProcessCode(processCode)).All(ctx) + Where(bomitem.ProductCode(productCode), bomitem.BomName(bomNameOrDefault(bomName)), bomitem.ProcessCode(processCode)).All(ctx) if err != nil { return nil, err } @@ -123,17 +124,18 @@ func (s *Service) applyBinds(ctx context.Context, sn, orderNo string, processCod if len(items) == 0 { return 0, nil } - wp, productCode, err := s.workpieceProduct(ctx, sn) + wp, wo, err := s.workpieceProduct(ctx, sn) if err != nil { return 0, err } + productCode := wo.ProductCode if wp.Status == "DONE" || wp.Status == "SCRAPPED" { return 0, errors.New("已完工/报废工件不能再绑定物料") } - // 产品 BOM 全量索引:materialCode → target(校验料合法性 + 取物料快照) + // 工单锁定 BOM 的全量索引:materialCode → target(校验料合法性 + 取物料快照) bomMap := map[string]*ent.BomItem{} allBom, _ := s.ctx.EntClient.BomItem.Query(). - Where(bomitem.ProductCode(productCode)).All(ctx) + Where(bomitem.ProductCode(productCode), bomitem.BomName(bomNameOrDefault(wo.BomName))).All(ctx) for _, b := range allBom { bomMap[b.MaterialCode] = b } @@ -186,11 +188,11 @@ func (s *Service) applyBinds(ctx context.Context, sn, orderNo string, processCod // 返回缺料明细(空串=齐套)。判定:精密件(SN) 绑定的不同SN数 ≥ 单台用量;结构件(批次) 至少绑定1个批次号。 // 若该产品该工序未配置任何装配物料(processCode未配置),视为不启用绑定校验,放行。 func (s *Service) validateStationBinds(ctx context.Context, sn string, processCode int) (string, error) { - _, productCode, err := s.workpieceProduct(ctx, sn) + _, wo, err := s.workpieceProduct(ctx, sn) if err != nil { return "", err } - targets, err := s.stationBindTargets(ctx, productCode, processCode) + targets, err := s.stationBindTargets(ctx, wo.ProductCode, wo.BomName, processCode) if err != nil { return "", err } @@ -310,11 +312,11 @@ type StationBindPanel struct { // StationBindPanelData 供工位终端/手动报工页在报工前拉取:本工件本工序该装什么、已装什么 func (s *Service) StationBindPanelData(ctx context.Context, sn string, processCode int) (*StationBindPanel, error) { - _, productCode, err := s.workpieceProduct(ctx, sn) + _, wo, err := s.workpieceProduct(ctx, sn) if err != nil { return nil, err } - targets, err := s.stationBindTargets(ctx, productCode, processCode) + targets, err := s.stationBindTargets(ctx, wo.ProductCode, wo.BomName, processCode) if err != nil { return nil, err } diff --git a/bj_power_mes/internal/wmsclient/client.go b/bj_power_mes/internal/wmsclient/client.go index a501192..425d0ee 100644 --- a/bj_power_mes/internal/wmsclient/client.go +++ b/bj_power_mes/internal/wmsclient/client.go @@ -1,6 +1,7 @@ package wmsclient import ( + "fmt" "bytes" "context" "encoding/json" @@ -40,6 +41,15 @@ func (c *Client) SemiOutbound(ctx context.Context, sn, orderNo string, doneProce }) } +// FinishedInbound 成品完工回流入库:调 WMS /api/internal/finished/inbound。 +// 成品 SN 由 MES 系统生成(工件SN),统一库存主表 category=3;失败返回 err 由调用方降级。 +func (c *Client) FinishedInbound(ctx context.Context, productCode, productName, sn, orderNo, operator string) error { + return c.post(ctx, "/api/internal/finished/inbound", map[string]any{ + "productCode": productCode, "productName": productName, "sn": sn, + "orderNo": orderNo, "snSource": "AUTO", "operator": operator, + }) +} + // MaterialExists 批量校验物料编码是否存在于 WMS 物料档案。 // 返回 WMS 中不存在的编码列表(全部存在则为空)。WMS 响应 {code,message,data:{missing:[]}}。 // 失败(网络/超时/非0响应)返回 err,由调用方决定降级策略。 @@ -74,6 +84,157 @@ func (c *Client) MaterialExists(ctx context.Context, codes []string) ([]string, return body.Data.Missing, nil } +// ProductTypeRow 产品型号档案行(WMS materials item_type=3) +type ProductTypeRow struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + Category string `json:"category"` + Remark string `json:"remark"` + IsActive bool `json:"isActive"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +// get 调 WMS GET 接口并解出 {code,data} +func (c *Client) get(ctx context.Context, path string, out any) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil) + if err != nil { + return err + } + req.Header.Set("X-API-TOKEN", c.token) + resp, err := c.hc.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 300 { + return &RespError{Status: resp.StatusCode} + } + var body struct { + Code int `json:"code"` + Data any `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return err + } + if body.Code != 0 { + return &RespError{Status: resp.StatusCode} + } + if out != nil && body.Data != nil { + b, _ := json.Marshal(body.Data) + return json.Unmarshal(b, out) + } + return nil +} + +// putJSON 调 WMS PUT 接口 +func (c *Client) putJSON(ctx context.Context, path string, body any) error { + b, err := json.Marshal(body) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.baseURL+path, bytes.NewReader(b)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-API-TOKEN", c.token) + resp, err := c.hc.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 300 { + return &RespError{Status: resp.StatusCode} + } + return nil +} + +// ProductTypes 全量产品型号(item_type=3,id asc,新建在底部)。keyword/isActive 可空。 +func (c *Client) ProductTypes(ctx context.Context, keyword, isActive string) ([]ProductTypeRow, error) { + q := "/api/internal/product-types?" + if keyword != "" { + q += "keyword=" + keyword + "&" + } + if isActive != "" { + q += "isActive=" + isActive + } + var list []ProductTypeRow + if err := c.get(ctx, q, &list); err != nil { + return nil, err + } + return list, nil +} + +// ProductTypePage 分页+搜索(产品型号管理页真分页) +func (c *Client) ProductTypePage(ctx context.Context, keyword, isActive string, page, pageSize int) (int64, []ProductTypeRow, error) { + q := fmt.Sprintf("/api/internal/product-types?page=%d&pageSize=%d", page, pageSize) + if keyword != "" { + q += "&keyword=" + keyword + } + if isActive != "" { + q += "&isActive=" + isActive + } + var data struct { + Total int64 `json:"total"` + List []ProductTypeRow `json:"list"` + } + if err := c.get(ctx, q, &data); err != nil { + return 0, nil, err + } + return data.Total, data.List, nil +} + +// CreateProductType 新建产品型号(code 可空由 WMS 自动生成 PROD-日期-流水) +func (c *Client) CreateProductType(ctx context.Context, code, name, category, remark string, isActive bool) error { + return c.post(ctx, "/api/internal/product-types", map[string]any{ + "code": code, "name": name, "category": category, "remark": remark, "isActive": isActive, + }) +} + +// UpdateProductType 修改产品型号(编码不可改) +func (c *Client) UpdateProductType(ctx context.Context, id int, name, category, remark string, isActive bool) error { + return c.putJSON(ctx, "/api/internal/product-types", map[string]any{ + "id": id, "name": name, "category": category, "remark": remark, "isActive": isActive, + }) +} + +// DeleteProductType 删除产品型号(引用保护由调用方与 WMS 双侧校验) +func (c *Client) DeleteProductType(ctx context.Context, id int) error { + return c.post(ctx, "/api/internal/product-types/delete", map[string]any{"id": id}) +} + +// ExportProductTypes 拉取产品型号导出 xlsx 字节流(WMS 端执行统一导出时间规则) +func (c *Client) ExportProductTypes(ctx context.Context, keyword, isActive, startDate, endDate string) ([]byte, error) { + q := "/api/internal/product-types/export?" + if keyword != "" { + q += "keyword=" + keyword + "&" + } + if isActive != "" { + q += "isActive=" + isActive + "&" + } + q += "startDate=" + startDate + "&endDate=" + endDate + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+q, nil) + if err != nil { + return nil, err + } + req.Header.Set("X-API-TOKEN", c.token) + resp, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode >= 300 { + return nil, &RespError{Status: resp.StatusCode} + } + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(resp.Body); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + // DisplayOverview 拉取 WMS 免登录看板总览 /api/display/overview。 // 用于看板「仓储动态」屏。WMS 不可达时返回 err,由调用方降级为空对象(看板其余屏不受影响)。 func (c *Client) DisplayOverview(ctx context.Context) (map[string]any, error) { diff --git a/bj_power_mes/public/index.html b/bj_power_mes/public/index.html index 72ed2c6..f679316 100644 --- a/bj_power_mes/public/index.html +++ b/bj_power_mes/public/index.html @@ -6,8 +6,8 @@ MES 产线控制 - - + + diff --git a/bj_power_mes/schema/bom_item.go b/bj_power_mes/schema/bom_item.go index 2a1b030..8191ea1 100644 --- a/bj_power_mes/schema/bom_item.go +++ b/bj_power_mes/schema/bom_item.go @@ -11,7 +11,7 @@ import ( "entgo.io/ent/schema/index" ) -// BomItem 产品 BOM 明细(按产品编码维护,同一型号多个工单共用) +// BomItem 产品 BOM 明细(一个产品型号可并存多份 BOM:不同厂家供货/含半成品等不同料单结构,工单建单时选定一份) type BomItem struct { ent.Schema } @@ -24,6 +24,7 @@ func (BomItem) Fields() []ent.Field { return []ent.Field{ field.Int("id").Positive(), field.String("productCode").Default("").MaxLen(64).Comment("产品编码"), + field.String("bomName").Default("默认").MaxLen(64).Comment("BOM名称:同一型号多份并存(如 厂家A/厂家B/含半成品),工单按名选用"), field.String("materialCode").MaxLen(64).Comment("物料编码"), field.String("materialName").Default("").MaxLen(128), field.String("spec").Default("").MaxLen(128), @@ -45,6 +46,6 @@ func (BomItem) Fields() []ent.Field { func (BomItem) Indexes() []ent.Index { return []ent.Index{ index.Fields("productCode"), - index.Fields("productCode", "materialCode").Unique(), + index.Fields("productCode", "bomName", "materialCode").Unique(), } } diff --git a/bj_power_mes/schema/user.go b/bj_power_mes/schema/user.go index 6fea452..a60d842 100644 --- a/bj_power_mes/schema/user.go +++ b/bj_power_mes/schema/user.go @@ -31,6 +31,7 @@ func (User) Fields() []ent.Field { field.String("workstationPassword").Optional().MaxLen(128).Comment("工位终端密码(bcrypt)"), // ENABLED / DISABLED field.String("status").Default("ENABLED").MaxLen(20), + field.Int64("lastLoginAt").Optional().Nillable().Comment("最近登录时间(unix秒,后台登录更新;工位终端登录不计)"), field.Time("createdAt").Default(time.Now).Immutable(), field.Time("updatedAt").Default(time.Now).UpdateDefault(time.Now).Optional().Nillable(), } diff --git a/bj_power_mes/schema/work_order.go b/bj_power_mes/schema/work_order.go index 5d5f6b4..3cd2b66 100644 --- a/bj_power_mes/schema/work_order.go +++ b/bj_power_mes/schema/work_order.go @@ -26,6 +26,7 @@ func (WorkOrder) Fields() []ent.Field { field.String("workOrderNo").MaxLen(64).Comment("工单号"), field.Int("productTypeId").Comment("产品类型ID"), field.String("productCode").Default("").MaxLen(64).Comment("产品编码"), + field.String("bomName").Default("默认").MaxLen(64).Comment("建单时选定的BOM名称(该型号下多份BOM之一,备料按此计算)"), field.String("productName").Default("").MaxLen(128), field.Int("quantity").Default(1).Comment("工件数量"), field.Int("finishedNum").Default(0).Comment("完成数量"), diff --git a/bj_power_wms/ent/inventory.go b/bj_power_wms/ent/inventory.go index 35c9a9b..567dd39 100644 --- a/bj_power_wms/ent/inventory.go +++ b/bj_power_wms/ent/inventory.go @@ -18,6 +18,8 @@ type Inventory struct { ID int `json:"id,omitempty"` // 管理粒度 1=结构件(批次) 2=精密件(SN) ManageMode int `json:"manageMode,omitempty"` + // 品类 1=原材料 2=半成品 3=成品 + Category int `json:"category,omitempty"` // 物料编码(关联 materials 表) MaterialCode string `json:"materialCode,omitempty"` // 物料名称(冗余,便于直接展示) @@ -42,6 +44,10 @@ type Inventory struct { Supplier string `json:"supplier,omitempty"` // 关联入库单号 InboundNo string `json:"inboundNo,omitempty"` + // 关联工单号(半成品/成品回流) + OrderNo string `json:"orderNo,omitempty"` + // 已完成工序(半成品用,如 135) + CompletedProcess string `json:"completedProcess,omitempty"` // 备注 Remark string `json:"remark,omitempty"` // CreatedAt holds the value of the "created_at" field. @@ -56,9 +62,9 @@ func (*Inventory) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case inventory.FieldID, inventory.FieldManageMode, inventory.FieldQuantity, inventory.FieldLockedQty, inventory.FieldCreatedAt, inventory.FieldUpdatedAt: + case inventory.FieldID, inventory.FieldManageMode, inventory.FieldCategory, inventory.FieldQuantity, inventory.FieldLockedQty, inventory.FieldCreatedAt, inventory.FieldUpdatedAt: values[i] = new(sql.NullInt64) - case inventory.FieldMaterialCode, inventory.FieldMaterialName, inventory.FieldBatchNo, inventory.FieldSnCode, inventory.FieldQualityStatus, inventory.FieldZoneCode, inventory.FieldStatus, inventory.FieldProductionDate, inventory.FieldSupplier, inventory.FieldInboundNo, inventory.FieldRemark: + case inventory.FieldMaterialCode, inventory.FieldMaterialName, inventory.FieldBatchNo, inventory.FieldSnCode, inventory.FieldQualityStatus, inventory.FieldZoneCode, inventory.FieldStatus, inventory.FieldProductionDate, inventory.FieldSupplier, inventory.FieldInboundNo, inventory.FieldOrderNo, inventory.FieldCompletedProcess, inventory.FieldRemark: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -87,6 +93,12 @@ func (_m *Inventory) assignValues(columns []string, values []any) error { } else if value.Valid { _m.ManageMode = int(value.Int64) } + case inventory.FieldCategory: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field category", values[i]) + } else if value.Valid { + _m.Category = int(value.Int64) + } case inventory.FieldMaterialCode: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field material_code", values[i]) @@ -159,6 +171,18 @@ func (_m *Inventory) assignValues(columns []string, values []any) error { } else if value.Valid { _m.InboundNo = value.String } + case inventory.FieldOrderNo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field order_no", values[i]) + } else if value.Valid { + _m.OrderNo = value.String + } + case inventory.FieldCompletedProcess: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field completed_process", values[i]) + } else if value.Valid { + _m.CompletedProcess = value.String + } case inventory.FieldRemark: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field remark", values[i]) @@ -216,6 +240,9 @@ func (_m *Inventory) String() string { builder.WriteString("manage_mode=") builder.WriteString(fmt.Sprintf("%v", _m.ManageMode)) builder.WriteString(", ") + builder.WriteString("category=") + builder.WriteString(fmt.Sprintf("%v", _m.Category)) + builder.WriteString(", ") builder.WriteString("material_code=") builder.WriteString(_m.MaterialCode) builder.WriteString(", ") @@ -252,6 +279,12 @@ func (_m *Inventory) String() string { builder.WriteString("inbound_no=") builder.WriteString(_m.InboundNo) builder.WriteString(", ") + builder.WriteString("order_no=") + builder.WriteString(_m.OrderNo) + builder.WriteString(", ") + builder.WriteString("completed_process=") + builder.WriteString(_m.CompletedProcess) + builder.WriteString(", ") builder.WriteString("remark=") builder.WriteString(_m.Remark) builder.WriteString(", ") diff --git a/bj_power_wms/ent/inventory/inventory.go b/bj_power_wms/ent/inventory/inventory.go index 1c60864..4dc31b6 100644 --- a/bj_power_wms/ent/inventory/inventory.go +++ b/bj_power_wms/ent/inventory/inventory.go @@ -13,6 +13,8 @@ const ( FieldID = "id" // FieldManageMode holds the string denoting the manage_mode field in the database. FieldManageMode = "manage_mode" + // FieldCategory holds the string denoting the category field in the database. + FieldCategory = "category" // FieldMaterialCode holds the string denoting the material_code field in the database. FieldMaterialCode = "material_code" // FieldMaterialName holds the string denoting the material_name field in the database. @@ -37,6 +39,10 @@ const ( FieldSupplier = "supplier" // FieldInboundNo holds the string denoting the inbound_no field in the database. FieldInboundNo = "inbound_no" + // FieldOrderNo holds the string denoting the order_no field in the database. + FieldOrderNo = "order_no" + // FieldCompletedProcess holds the string denoting the completed_process field in the database. + FieldCompletedProcess = "completed_process" // FieldRemark holds the string denoting the remark field in the database. FieldRemark = "remark" // FieldCreatedAt holds the string denoting the created_at field in the database. @@ -51,6 +57,7 @@ const ( var Columns = []string{ FieldID, FieldManageMode, + FieldCategory, FieldMaterialCode, FieldMaterialName, FieldBatchNo, @@ -63,6 +70,8 @@ var Columns = []string{ FieldProductionDate, FieldSupplier, FieldInboundNo, + FieldOrderNo, + FieldCompletedProcess, FieldRemark, FieldCreatedAt, FieldUpdatedAt, @@ -79,6 +88,8 @@ func ValidColumn(column string) bool { } var ( + // DefaultCategory holds the default value on creation for the "category" field. + DefaultCategory int // DefaultQuantity holds the default value on creation for the "quantity" field. DefaultQuantity int // DefaultLockedQty holds the default value on creation for the "locked_qty" field. @@ -87,6 +98,14 @@ var ( DefaultQualityStatus string // DefaultStatus holds the default value on creation for the "status" field. DefaultStatus string + // DefaultOrderNo holds the default value on creation for the "order_no" field. + DefaultOrderNo string + // OrderNoValidator is a validator for the "order_no" field. It is called by the builders before save. + OrderNoValidator func(string) error + // DefaultCompletedProcess holds the default value on creation for the "completed_process" field. + DefaultCompletedProcess string + // CompletedProcessValidator is a validator for the "completed_process" field. It is called by the builders before save. + CompletedProcessValidator func(string) error // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. @@ -106,6 +125,11 @@ func ByManageMode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldManageMode, opts...).ToFunc() } +// ByCategory orders the results by the category field. +func ByCategory(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCategory, opts...).ToFunc() +} + // ByMaterialCode orders the results by the material_code field. func ByMaterialCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMaterialCode, opts...).ToFunc() @@ -166,6 +190,16 @@ func ByInboundNo(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldInboundNo, opts...).ToFunc() } +// ByOrderNo orders the results by the order_no field. +func ByOrderNo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOrderNo, opts...).ToFunc() +} + +// ByCompletedProcess orders the results by the completed_process field. +func ByCompletedProcess(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCompletedProcess, opts...).ToFunc() +} + // ByRemark orders the results by the remark field. func ByRemark(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldRemark, opts...).ToFunc() diff --git a/bj_power_wms/ent/inventory/where.go b/bj_power_wms/ent/inventory/where.go index 8600904..e775fc2 100644 --- a/bj_power_wms/ent/inventory/where.go +++ b/bj_power_wms/ent/inventory/where.go @@ -58,6 +58,11 @@ func ManageMode(v int) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldManageMode, v)) } +// Category applies equality check predicate on the "category" field. It's identical to CategoryEQ. +func Category(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldCategory, v)) +} + // MaterialCode applies equality check predicate on the "material_code" field. It's identical to MaterialCodeEQ. func MaterialCode(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldMaterialCode, v)) @@ -118,6 +123,16 @@ func InboundNo(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldInboundNo, v)) } +// OrderNo applies equality check predicate on the "order_no" field. It's identical to OrderNoEQ. +func OrderNo(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldOrderNo, v)) +} + +// CompletedProcess applies equality check predicate on the "completed_process" field. It's identical to CompletedProcessEQ. +func CompletedProcess(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldCompletedProcess, v)) +} + // Remark applies equality check predicate on the "remark" field. It's identical to RemarkEQ. func Remark(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldRemark, v)) @@ -173,6 +188,46 @@ func ManageModeLTE(v int) predicate.Inventory { return predicate.Inventory(sql.FieldLTE(FieldManageMode, v)) } +// CategoryEQ applies the EQ predicate on the "category" field. +func CategoryEQ(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldCategory, v)) +} + +// CategoryNEQ applies the NEQ predicate on the "category" field. +func CategoryNEQ(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldNEQ(FieldCategory, v)) +} + +// CategoryIn applies the In predicate on the "category" field. +func CategoryIn(vs ...int) predicate.Inventory { + return predicate.Inventory(sql.FieldIn(FieldCategory, vs...)) +} + +// CategoryNotIn applies the NotIn predicate on the "category" field. +func CategoryNotIn(vs ...int) predicate.Inventory { + return predicate.Inventory(sql.FieldNotIn(FieldCategory, vs...)) +} + +// CategoryGT applies the GT predicate on the "category" field. +func CategoryGT(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldGT(FieldCategory, v)) +} + +// CategoryGTE applies the GTE predicate on the "category" field. +func CategoryGTE(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldGTE(FieldCategory, v)) +} + +// CategoryLT applies the LT predicate on the "category" field. +func CategoryLT(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldLT(FieldCategory, v)) +} + +// CategoryLTE applies the LTE predicate on the "category" field. +func CategoryLTE(v int) predicate.Inventory { + return predicate.Inventory(sql.FieldLTE(FieldCategory, v)) +} + // MaterialCodeEQ applies the EQ predicate on the "material_code" field. func MaterialCodeEQ(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldMaterialCode, v)) @@ -973,6 +1028,136 @@ func InboundNoContainsFold(v string) predicate.Inventory { return predicate.Inventory(sql.FieldContainsFold(FieldInboundNo, v)) } +// OrderNoEQ applies the EQ predicate on the "order_no" field. +func OrderNoEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldOrderNo, v)) +} + +// OrderNoNEQ applies the NEQ predicate on the "order_no" field. +func OrderNoNEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldNEQ(FieldOrderNo, v)) +} + +// OrderNoIn applies the In predicate on the "order_no" field. +func OrderNoIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldIn(FieldOrderNo, vs...)) +} + +// OrderNoNotIn applies the NotIn predicate on the "order_no" field. +func OrderNoNotIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldNotIn(FieldOrderNo, vs...)) +} + +// OrderNoGT applies the GT predicate on the "order_no" field. +func OrderNoGT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGT(FieldOrderNo, v)) +} + +// OrderNoGTE applies the GTE predicate on the "order_no" field. +func OrderNoGTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGTE(FieldOrderNo, v)) +} + +// OrderNoLT applies the LT predicate on the "order_no" field. +func OrderNoLT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLT(FieldOrderNo, v)) +} + +// OrderNoLTE applies the LTE predicate on the "order_no" field. +func OrderNoLTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLTE(FieldOrderNo, v)) +} + +// OrderNoContains applies the Contains predicate on the "order_no" field. +func OrderNoContains(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContains(FieldOrderNo, v)) +} + +// OrderNoHasPrefix applies the HasPrefix predicate on the "order_no" field. +func OrderNoHasPrefix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasPrefix(FieldOrderNo, v)) +} + +// OrderNoHasSuffix applies the HasSuffix predicate on the "order_no" field. +func OrderNoHasSuffix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasSuffix(FieldOrderNo, v)) +} + +// OrderNoEqualFold applies the EqualFold predicate on the "order_no" field. +func OrderNoEqualFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEqualFold(FieldOrderNo, v)) +} + +// OrderNoContainsFold applies the ContainsFold predicate on the "order_no" field. +func OrderNoContainsFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContainsFold(FieldOrderNo, v)) +} + +// CompletedProcessEQ applies the EQ predicate on the "completed_process" field. +func CompletedProcessEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldCompletedProcess, v)) +} + +// CompletedProcessNEQ applies the NEQ predicate on the "completed_process" field. +func CompletedProcessNEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldNEQ(FieldCompletedProcess, v)) +} + +// CompletedProcessIn applies the In predicate on the "completed_process" field. +func CompletedProcessIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldIn(FieldCompletedProcess, vs...)) +} + +// CompletedProcessNotIn applies the NotIn predicate on the "completed_process" field. +func CompletedProcessNotIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldNotIn(FieldCompletedProcess, vs...)) +} + +// CompletedProcessGT applies the GT predicate on the "completed_process" field. +func CompletedProcessGT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGT(FieldCompletedProcess, v)) +} + +// CompletedProcessGTE applies the GTE predicate on the "completed_process" field. +func CompletedProcessGTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGTE(FieldCompletedProcess, v)) +} + +// CompletedProcessLT applies the LT predicate on the "completed_process" field. +func CompletedProcessLT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLT(FieldCompletedProcess, v)) +} + +// CompletedProcessLTE applies the LTE predicate on the "completed_process" field. +func CompletedProcessLTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLTE(FieldCompletedProcess, v)) +} + +// CompletedProcessContains applies the Contains predicate on the "completed_process" field. +func CompletedProcessContains(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContains(FieldCompletedProcess, v)) +} + +// CompletedProcessHasPrefix applies the HasPrefix predicate on the "completed_process" field. +func CompletedProcessHasPrefix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasPrefix(FieldCompletedProcess, v)) +} + +// CompletedProcessHasSuffix applies the HasSuffix predicate on the "completed_process" field. +func CompletedProcessHasSuffix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasSuffix(FieldCompletedProcess, v)) +} + +// CompletedProcessEqualFold applies the EqualFold predicate on the "completed_process" field. +func CompletedProcessEqualFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEqualFold(FieldCompletedProcess, v)) +} + +// CompletedProcessContainsFold applies the ContainsFold predicate on the "completed_process" field. +func CompletedProcessContainsFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContainsFold(FieldCompletedProcess, v)) +} + // RemarkEQ applies the EQ predicate on the "remark" field. func RemarkEQ(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldRemark, v)) diff --git a/bj_power_wms/ent/inventory_create.go b/bj_power_wms/ent/inventory_create.go index 0fdd714..d8633f5 100644 --- a/bj_power_wms/ent/inventory_create.go +++ b/bj_power_wms/ent/inventory_create.go @@ -27,6 +27,20 @@ func (_c *InventoryCreate) SetManageMode(v int) *InventoryCreate { return _c } +// SetCategory sets the "category" field. +func (_c *InventoryCreate) SetCategory(v int) *InventoryCreate { + _c.mutation.SetCategory(v) + return _c +} + +// SetNillableCategory sets the "category" field if the given value is not nil. +func (_c *InventoryCreate) SetNillableCategory(v *int) *InventoryCreate { + if v != nil { + _c.SetCategory(*v) + } + return _c +} + // SetMaterialCode sets the "material_code" field. func (_c *InventoryCreate) SetMaterialCode(v string) *InventoryCreate { _c.mutation.SetMaterialCode(v) @@ -187,6 +201,34 @@ func (_c *InventoryCreate) SetNillableInboundNo(v *string) *InventoryCreate { return _c } +// SetOrderNo sets the "order_no" field. +func (_c *InventoryCreate) SetOrderNo(v string) *InventoryCreate { + _c.mutation.SetOrderNo(v) + return _c +} + +// SetNillableOrderNo sets the "order_no" field if the given value is not nil. +func (_c *InventoryCreate) SetNillableOrderNo(v *string) *InventoryCreate { + if v != nil { + _c.SetOrderNo(*v) + } + return _c +} + +// SetCompletedProcess sets the "completed_process" field. +func (_c *InventoryCreate) SetCompletedProcess(v string) *InventoryCreate { + _c.mutation.SetCompletedProcess(v) + return _c +} + +// SetNillableCompletedProcess sets the "completed_process" field if the given value is not nil. +func (_c *InventoryCreate) SetNillableCompletedProcess(v *string) *InventoryCreate { + if v != nil { + _c.SetCompletedProcess(*v) + } + return _c +} + // SetRemark sets the "remark" field. func (_c *InventoryCreate) SetRemark(v string) *InventoryCreate { _c.mutation.SetRemark(v) @@ -264,6 +306,10 @@ func (_c *InventoryCreate) ExecX(ctx context.Context) { // defaults sets the default values of the builder before save. func (_c *InventoryCreate) defaults() { + if _, ok := _c.mutation.Category(); !ok { + v := inventory.DefaultCategory + _c.mutation.SetCategory(v) + } if _, ok := _c.mutation.Quantity(); !ok { v := inventory.DefaultQuantity _c.mutation.SetQuantity(v) @@ -280,6 +326,14 @@ func (_c *InventoryCreate) defaults() { v := inventory.DefaultStatus _c.mutation.SetStatus(v) } + if _, ok := _c.mutation.OrderNo(); !ok { + v := inventory.DefaultOrderNo + _c.mutation.SetOrderNo(v) + } + if _, ok := _c.mutation.CompletedProcess(); !ok { + v := inventory.DefaultCompletedProcess + _c.mutation.SetCompletedProcess(v) + } if _, ok := _c.mutation.CreatedAt(); !ok { v := inventory.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) @@ -295,6 +349,9 @@ func (_c *InventoryCreate) check() error { if _, ok := _c.mutation.ManageMode(); !ok { return &ValidationError{Name: "manage_mode", err: errors.New(`ent: missing required field "Inventory.manage_mode"`)} } + if _, ok := _c.mutation.Category(); !ok { + return &ValidationError{Name: "category", err: errors.New(`ent: missing required field "Inventory.category"`)} + } if _, ok := _c.mutation.MaterialCode(); !ok { return &ValidationError{Name: "material_code", err: errors.New(`ent: missing required field "Inventory.material_code"`)} } @@ -310,6 +367,22 @@ func (_c *InventoryCreate) check() error { if _, ok := _c.mutation.Status(); !ok { return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Inventory.status"`)} } + if _, ok := _c.mutation.OrderNo(); !ok { + return &ValidationError{Name: "order_no", err: errors.New(`ent: missing required field "Inventory.order_no"`)} + } + if v, ok := _c.mutation.OrderNo(); ok { + if err := inventory.OrderNoValidator(v); err != nil { + return &ValidationError{Name: "order_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.order_no": %w`, err)} + } + } + if _, ok := _c.mutation.CompletedProcess(); !ok { + return &ValidationError{Name: "completed_process", err: errors.New(`ent: missing required field "Inventory.completed_process"`)} + } + if v, ok := _c.mutation.CompletedProcess(); ok { + if err := inventory.CompletedProcessValidator(v); err != nil { + return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} + } + } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Inventory.created_at"`)} } @@ -347,6 +420,10 @@ func (_c *InventoryCreate) createSpec() (*Inventory, *sqlgraph.CreateSpec) { _spec.SetField(inventory.FieldManageMode, field.TypeInt, value) _node.ManageMode = value } + if value, ok := _c.mutation.Category(); ok { + _spec.SetField(inventory.FieldCategory, field.TypeInt, value) + _node.Category = value + } if value, ok := _c.mutation.MaterialCode(); ok { _spec.SetField(inventory.FieldMaterialCode, field.TypeString, value) _node.MaterialCode = value @@ -395,6 +472,14 @@ func (_c *InventoryCreate) createSpec() (*Inventory, *sqlgraph.CreateSpec) { _spec.SetField(inventory.FieldInboundNo, field.TypeString, value) _node.InboundNo = value } + if value, ok := _c.mutation.OrderNo(); ok { + _spec.SetField(inventory.FieldOrderNo, field.TypeString, value) + _node.OrderNo = value + } + if value, ok := _c.mutation.CompletedProcess(); ok { + _spec.SetField(inventory.FieldCompletedProcess, field.TypeString, value) + _node.CompletedProcess = value + } if value, ok := _c.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) _node.Remark = value @@ -477,6 +562,24 @@ func (u *InventoryUpsert) AddManageMode(v int) *InventoryUpsert { return u } +// SetCategory sets the "category" field. +func (u *InventoryUpsert) SetCategory(v int) *InventoryUpsert { + u.Set(inventory.FieldCategory, v) + return u +} + +// UpdateCategory sets the "category" field to the value that was provided on create. +func (u *InventoryUpsert) UpdateCategory() *InventoryUpsert { + u.SetExcluded(inventory.FieldCategory) + return u +} + +// AddCategory adds v to the "category" field. +func (u *InventoryUpsert) AddCategory(v int) *InventoryUpsert { + u.Add(inventory.FieldCategory, v) + return u +} + // SetMaterialCode sets the "material_code" field. func (u *InventoryUpsert) SetMaterialCode(v string) *InventoryUpsert { u.Set(inventory.FieldMaterialCode, v) @@ -675,6 +778,30 @@ func (u *InventoryUpsert) ClearInboundNo() *InventoryUpsert { return u } +// SetOrderNo sets the "order_no" field. +func (u *InventoryUpsert) SetOrderNo(v string) *InventoryUpsert { + u.Set(inventory.FieldOrderNo, v) + return u +} + +// UpdateOrderNo sets the "order_no" field to the value that was provided on create. +func (u *InventoryUpsert) UpdateOrderNo() *InventoryUpsert { + u.SetExcluded(inventory.FieldOrderNo) + return u +} + +// SetCompletedProcess sets the "completed_process" field. +func (u *InventoryUpsert) SetCompletedProcess(v string) *InventoryUpsert { + u.Set(inventory.FieldCompletedProcess, v) + return u +} + +// UpdateCompletedProcess sets the "completed_process" field to the value that was provided on create. +func (u *InventoryUpsert) UpdateCompletedProcess() *InventoryUpsert { + u.SetExcluded(inventory.FieldCompletedProcess) + return u +} + // SetRemark sets the "remark" field. func (u *InventoryUpsert) SetRemark(v string) *InventoryUpsert { u.Set(inventory.FieldRemark, v) @@ -790,6 +917,27 @@ func (u *InventoryUpsertOne) UpdateManageMode() *InventoryUpsertOne { }) } +// SetCategory sets the "category" field. +func (u *InventoryUpsertOne) SetCategory(v int) *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.SetCategory(v) + }) +} + +// AddCategory adds v to the "category" field. +func (u *InventoryUpsertOne) AddCategory(v int) *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.AddCategory(v) + }) +} + +// UpdateCategory sets the "category" field to the value that was provided on create. +func (u *InventoryUpsertOne) UpdateCategory() *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.UpdateCategory() + }) +} + // SetMaterialCode sets the "material_code" field. func (u *InventoryUpsertOne) SetMaterialCode(v string) *InventoryUpsertOne { return u.Update(func(s *InventoryUpsert) { @@ -1021,6 +1169,34 @@ func (u *InventoryUpsertOne) ClearInboundNo() *InventoryUpsertOne { }) } +// SetOrderNo sets the "order_no" field. +func (u *InventoryUpsertOne) SetOrderNo(v string) *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.SetOrderNo(v) + }) +} + +// UpdateOrderNo sets the "order_no" field to the value that was provided on create. +func (u *InventoryUpsertOne) UpdateOrderNo() *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.UpdateOrderNo() + }) +} + +// SetCompletedProcess sets the "completed_process" field. +func (u *InventoryUpsertOne) SetCompletedProcess(v string) *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.SetCompletedProcess(v) + }) +} + +// UpdateCompletedProcess sets the "completed_process" field to the value that was provided on create. +func (u *InventoryUpsertOne) UpdateCompletedProcess() *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.UpdateCompletedProcess() + }) +} + // SetRemark sets the "remark" field. func (u *InventoryUpsertOne) SetRemark(v string) *InventoryUpsertOne { return u.Update(func(s *InventoryUpsert) { @@ -1309,6 +1485,27 @@ func (u *InventoryUpsertBulk) UpdateManageMode() *InventoryUpsertBulk { }) } +// SetCategory sets the "category" field. +func (u *InventoryUpsertBulk) SetCategory(v int) *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.SetCategory(v) + }) +} + +// AddCategory adds v to the "category" field. +func (u *InventoryUpsertBulk) AddCategory(v int) *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.AddCategory(v) + }) +} + +// UpdateCategory sets the "category" field to the value that was provided on create. +func (u *InventoryUpsertBulk) UpdateCategory() *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.UpdateCategory() + }) +} + // SetMaterialCode sets the "material_code" field. func (u *InventoryUpsertBulk) SetMaterialCode(v string) *InventoryUpsertBulk { return u.Update(func(s *InventoryUpsert) { @@ -1540,6 +1737,34 @@ func (u *InventoryUpsertBulk) ClearInboundNo() *InventoryUpsertBulk { }) } +// SetOrderNo sets the "order_no" field. +func (u *InventoryUpsertBulk) SetOrderNo(v string) *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.SetOrderNo(v) + }) +} + +// UpdateOrderNo sets the "order_no" field to the value that was provided on create. +func (u *InventoryUpsertBulk) UpdateOrderNo() *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.UpdateOrderNo() + }) +} + +// SetCompletedProcess sets the "completed_process" field. +func (u *InventoryUpsertBulk) SetCompletedProcess(v string) *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.SetCompletedProcess(v) + }) +} + +// UpdateCompletedProcess sets the "completed_process" field to the value that was provided on create. +func (u *InventoryUpsertBulk) UpdateCompletedProcess() *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.UpdateCompletedProcess() + }) +} + // SetRemark sets the "remark" field. func (u *InventoryUpsertBulk) SetRemark(v string) *InventoryUpsertBulk { return u.Update(func(s *InventoryUpsert) { diff --git a/bj_power_wms/ent/inventory_update.go b/bj_power_wms/ent/inventory_update.go index e2a4e4d..c4ac212 100644 --- a/bj_power_wms/ent/inventory_update.go +++ b/bj_power_wms/ent/inventory_update.go @@ -48,6 +48,27 @@ func (_u *InventoryUpdate) AddManageMode(v int) *InventoryUpdate { return _u } +// SetCategory sets the "category" field. +func (_u *InventoryUpdate) SetCategory(v int) *InventoryUpdate { + _u.mutation.ResetCategory() + _u.mutation.SetCategory(v) + return _u +} + +// SetNillableCategory sets the "category" field if the given value is not nil. +func (_u *InventoryUpdate) SetNillableCategory(v *int) *InventoryUpdate { + if v != nil { + _u.SetCategory(*v) + } + return _u +} + +// AddCategory adds value to the "category" field. +func (_u *InventoryUpdate) AddCategory(v int) *InventoryUpdate { + _u.mutation.AddCategory(v) + return _u +} + // SetMaterialCode sets the "material_code" field. func (_u *InventoryUpdate) SetMaterialCode(v string) *InventoryUpdate { _u.mutation.SetMaterialCode(v) @@ -272,6 +293,34 @@ func (_u *InventoryUpdate) ClearInboundNo() *InventoryUpdate { return _u } +// SetOrderNo sets the "order_no" field. +func (_u *InventoryUpdate) SetOrderNo(v string) *InventoryUpdate { + _u.mutation.SetOrderNo(v) + return _u +} + +// SetNillableOrderNo sets the "order_no" field if the given value is not nil. +func (_u *InventoryUpdate) SetNillableOrderNo(v *string) *InventoryUpdate { + if v != nil { + _u.SetOrderNo(*v) + } + return _u +} + +// SetCompletedProcess sets the "completed_process" field. +func (_u *InventoryUpdate) SetCompletedProcess(v string) *InventoryUpdate { + _u.mutation.SetCompletedProcess(v) + return _u +} + +// SetNillableCompletedProcess sets the "completed_process" field if the given value is not nil. +func (_u *InventoryUpdate) SetNillableCompletedProcess(v *string) *InventoryUpdate { + if v != nil { + _u.SetCompletedProcess(*v) + } + return _u +} + // SetRemark sets the "remark" field. func (_u *InventoryUpdate) SetRemark(v string) *InventoryUpdate { _u.mutation.SetRemark(v) @@ -366,7 +415,25 @@ func (_u *InventoryUpdate) ExecX(ctx context.Context) { } } +// check runs all checks and user-defined validators on the builder. +func (_u *InventoryUpdate) check() error { + if v, ok := _u.mutation.OrderNo(); ok { + if err := inventory.OrderNoValidator(v); err != nil { + return &ValidationError{Name: "order_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.order_no": %w`, err)} + } + } + if v, ok := _u.mutation.CompletedProcess(); ok { + if err := inventory.CompletedProcessValidator(v); err != nil { + return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} + } + } + return nil +} + func (_u *InventoryUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } _spec := sqlgraph.NewUpdateSpec(inventory.Table, inventory.Columns, sqlgraph.NewFieldSpec(inventory.FieldID, field.TypeInt)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { @@ -381,6 +448,12 @@ func (_u *InventoryUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.AddedManageMode(); ok { _spec.AddField(inventory.FieldManageMode, field.TypeInt, value) } + if value, ok := _u.mutation.Category(); ok { + _spec.SetField(inventory.FieldCategory, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedCategory(); ok { + _spec.AddField(inventory.FieldCategory, field.TypeInt, value) + } if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(inventory.FieldMaterialCode, field.TypeString, value) } @@ -444,6 +517,12 @@ func (_u *InventoryUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.InboundNoCleared() { _spec.ClearField(inventory.FieldInboundNo, field.TypeString) } + if value, ok := _u.mutation.OrderNo(); ok { + _spec.SetField(inventory.FieldOrderNo, field.TypeString, value) + } + if value, ok := _u.mutation.CompletedProcess(); ok { + _spec.SetField(inventory.FieldCompletedProcess, field.TypeString, value) + } if value, ok := _u.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) } @@ -503,6 +582,27 @@ func (_u *InventoryUpdateOne) AddManageMode(v int) *InventoryUpdateOne { return _u } +// SetCategory sets the "category" field. +func (_u *InventoryUpdateOne) SetCategory(v int) *InventoryUpdateOne { + _u.mutation.ResetCategory() + _u.mutation.SetCategory(v) + return _u +} + +// SetNillableCategory sets the "category" field if the given value is not nil. +func (_u *InventoryUpdateOne) SetNillableCategory(v *int) *InventoryUpdateOne { + if v != nil { + _u.SetCategory(*v) + } + return _u +} + +// AddCategory adds value to the "category" field. +func (_u *InventoryUpdateOne) AddCategory(v int) *InventoryUpdateOne { + _u.mutation.AddCategory(v) + return _u +} + // SetMaterialCode sets the "material_code" field. func (_u *InventoryUpdateOne) SetMaterialCode(v string) *InventoryUpdateOne { _u.mutation.SetMaterialCode(v) @@ -727,6 +827,34 @@ func (_u *InventoryUpdateOne) ClearInboundNo() *InventoryUpdateOne { return _u } +// SetOrderNo sets the "order_no" field. +func (_u *InventoryUpdateOne) SetOrderNo(v string) *InventoryUpdateOne { + _u.mutation.SetOrderNo(v) + return _u +} + +// SetNillableOrderNo sets the "order_no" field if the given value is not nil. +func (_u *InventoryUpdateOne) SetNillableOrderNo(v *string) *InventoryUpdateOne { + if v != nil { + _u.SetOrderNo(*v) + } + return _u +} + +// SetCompletedProcess sets the "completed_process" field. +func (_u *InventoryUpdateOne) SetCompletedProcess(v string) *InventoryUpdateOne { + _u.mutation.SetCompletedProcess(v) + return _u +} + +// SetNillableCompletedProcess sets the "completed_process" field if the given value is not nil. +func (_u *InventoryUpdateOne) SetNillableCompletedProcess(v *string) *InventoryUpdateOne { + if v != nil { + _u.SetCompletedProcess(*v) + } + return _u +} + // SetRemark sets the "remark" field. func (_u *InventoryUpdateOne) SetRemark(v string) *InventoryUpdateOne { _u.mutation.SetRemark(v) @@ -834,7 +962,25 @@ func (_u *InventoryUpdateOne) ExecX(ctx context.Context) { } } +// check runs all checks and user-defined validators on the builder. +func (_u *InventoryUpdateOne) check() error { + if v, ok := _u.mutation.OrderNo(); ok { + if err := inventory.OrderNoValidator(v); err != nil { + return &ValidationError{Name: "order_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.order_no": %w`, err)} + } + } + if v, ok := _u.mutation.CompletedProcess(); ok { + if err := inventory.CompletedProcessValidator(v); err != nil { + return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} + } + } + return nil +} + func (_u *InventoryUpdateOne) sqlSave(ctx context.Context) (_node *Inventory, err error) { + if err := _u.check(); err != nil { + return _node, err + } _spec := sqlgraph.NewUpdateSpec(inventory.Table, inventory.Columns, sqlgraph.NewFieldSpec(inventory.FieldID, field.TypeInt)) id, ok := _u.mutation.ID() if !ok { @@ -866,6 +1012,12 @@ func (_u *InventoryUpdateOne) sqlSave(ctx context.Context) (_node *Inventory, er if value, ok := _u.mutation.AddedManageMode(); ok { _spec.AddField(inventory.FieldManageMode, field.TypeInt, value) } + if value, ok := _u.mutation.Category(); ok { + _spec.SetField(inventory.FieldCategory, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedCategory(); ok { + _spec.AddField(inventory.FieldCategory, field.TypeInt, value) + } if value, ok := _u.mutation.MaterialCode(); ok { _spec.SetField(inventory.FieldMaterialCode, field.TypeString, value) } @@ -929,6 +1081,12 @@ func (_u *InventoryUpdateOne) sqlSave(ctx context.Context) (_node *Inventory, er if _u.mutation.InboundNoCleared() { _spec.ClearField(inventory.FieldInboundNo, field.TypeString) } + if value, ok := _u.mutation.OrderNo(); ok { + _spec.SetField(inventory.FieldOrderNo, field.TypeString, value) + } + if value, ok := _u.mutation.CompletedProcess(); ok { + _spec.SetField(inventory.FieldCompletedProcess, field.TypeString, value) + } if value, ok := _u.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) } diff --git a/bj_power_wms/ent/material.go b/bj_power_wms/ent/material.go index 00f38bf..cbd5c63 100644 --- a/bj_power_wms/ent/material.go +++ b/bj_power_wms/ent/material.go @@ -28,6 +28,8 @@ type Material struct { Unit string `json:"unit,omitempty"` // 描述 Description string `json:"description,omitempty"` + // 品类 1原材料/2半成品/3成品 + ItemType int `json:"itemType,omitempty"` // 管理粒度 1批次/2序列号 ManageMode int `json:"manageMode,omitempty"` // 是否批次管理 @@ -36,6 +38,8 @@ type Material struct { IsSerialManaged bool `json:"isSerialManaged,omitempty"` // 物料模板编码(物料种类) TemplateCode string `json:"templateCode,omitempty"` + // 是否启用 + IsActive bool `json:"isActive,omitempty"` // 创建时间 CreatedAt int64 `json:"createdAt,omitempty"` // 更新时间 @@ -48,9 +52,9 @@ func (*Material) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case material.FieldIsBatchManaged, material.FieldIsSerialManaged: + case material.FieldIsBatchManaged, material.FieldIsSerialManaged, material.FieldIsActive: values[i] = new(sql.NullBool) - case material.FieldID, material.FieldManageMode, material.FieldCreatedAt, material.FieldUpdatedAt: + case material.FieldID, material.FieldItemType, material.FieldManageMode, material.FieldCreatedAt, material.FieldUpdatedAt: values[i] = new(sql.NullInt64) case material.FieldCode, material.FieldName, material.FieldShortName, material.FieldSpec, material.FieldUnit, material.FieldDescription, material.FieldTemplateCode: values[i] = new(sql.NullString) @@ -111,6 +115,12 @@ func (_m *Material) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Description = value.String } + case material.FieldItemType: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field item_type", values[i]) + } else if value.Valid { + _m.ItemType = int(value.Int64) + } case material.FieldManageMode: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field manage_mode", values[i]) @@ -135,6 +145,12 @@ func (_m *Material) assignValues(columns []string, values []any) error { } else if value.Valid { _m.TemplateCode = value.String } + case material.FieldIsActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field is_active", values[i]) + } else if value.Valid { + _m.IsActive = value.Bool + } case material.FieldCreatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) @@ -201,6 +217,9 @@ func (_m *Material) String() string { builder.WriteString("description=") builder.WriteString(_m.Description) builder.WriteString(", ") + builder.WriteString("item_type=") + builder.WriteString(fmt.Sprintf("%v", _m.ItemType)) + builder.WriteString(", ") builder.WriteString("manage_mode=") builder.WriteString(fmt.Sprintf("%v", _m.ManageMode)) builder.WriteString(", ") @@ -213,6 +232,9 @@ func (_m *Material) String() string { builder.WriteString("template_code=") builder.WriteString(_m.TemplateCode) builder.WriteString(", ") + builder.WriteString("is_active=") + builder.WriteString(fmt.Sprintf("%v", _m.IsActive)) + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(fmt.Sprintf("%v", _m.CreatedAt)) builder.WriteString(", ") diff --git a/bj_power_wms/ent/material/material.go b/bj_power_wms/ent/material/material.go index cc30dab..564ddfb 100644 --- a/bj_power_wms/ent/material/material.go +++ b/bj_power_wms/ent/material/material.go @@ -23,6 +23,8 @@ const ( FieldUnit = "unit" // FieldDescription holds the string denoting the description field in the database. FieldDescription = "description" + // FieldItemType holds the string denoting the item_type field in the database. + FieldItemType = "item_type" // FieldManageMode holds the string denoting the manage_mode field in the database. FieldManageMode = "manage_mode" // FieldIsBatchManaged holds the string denoting the is_batch_managed field in the database. @@ -31,6 +33,8 @@ const ( FieldIsSerialManaged = "is_serial_managed" // FieldTemplateCode holds the string denoting the template_code field in the database. FieldTemplateCode = "template_code" + // FieldIsActive holds the string denoting the is_active field in the database. + FieldIsActive = "is_active" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. @@ -48,10 +52,12 @@ var Columns = []string{ FieldSpec, FieldUnit, FieldDescription, + FieldItemType, FieldManageMode, FieldIsBatchManaged, FieldIsSerialManaged, FieldTemplateCode, + FieldIsActive, FieldCreatedAt, FieldUpdatedAt, } @@ -67,12 +73,16 @@ func ValidColumn(column string) bool { } var ( + // DefaultItemType holds the default value on creation for the "item_type" field. + DefaultItemType int // DefaultManageMode holds the default value on creation for the "manage_mode" field. DefaultManageMode int // DefaultIsBatchManaged holds the default value on creation for the "is_batch_managed" field. DefaultIsBatchManaged bool // DefaultIsSerialManaged holds the default value on creation for the "is_serial_managed" field. DefaultIsSerialManaged bool + // DefaultIsActive holds the default value on creation for the "is_active" field. + DefaultIsActive bool // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() int64 // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. @@ -117,6 +127,11 @@ func ByDescription(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDescription, opts...).ToFunc() } +// ByItemType orders the results by the item_type field. +func ByItemType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldItemType, opts...).ToFunc() +} + // ByManageMode orders the results by the manage_mode field. func ByManageMode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldManageMode, opts...).ToFunc() @@ -137,6 +152,11 @@ func ByTemplateCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldTemplateCode, opts...).ToFunc() } +// ByIsActive orders the results by the is_active field. +func ByIsActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldIsActive, opts...).ToFunc() +} + // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/bj_power_wms/ent/material/where.go b/bj_power_wms/ent/material/where.go index 9b73a49..716c489 100644 --- a/bj_power_wms/ent/material/where.go +++ b/bj_power_wms/ent/material/where.go @@ -83,6 +83,11 @@ func Description(v string) predicate.Material { return predicate.Material(sql.FieldEQ(FieldDescription, v)) } +// ItemType applies equality check predicate on the "item_type" field. It's identical to ItemTypeEQ. +func ItemType(v int) predicate.Material { + return predicate.Material(sql.FieldEQ(FieldItemType, v)) +} + // ManageMode applies equality check predicate on the "manage_mode" field. It's identical to ManageModeEQ. func ManageMode(v int) predicate.Material { return predicate.Material(sql.FieldEQ(FieldManageMode, v)) @@ -103,6 +108,11 @@ func TemplateCode(v string) predicate.Material { return predicate.Material(sql.FieldEQ(FieldTemplateCode, v)) } +// IsActive applies equality check predicate on the "is_active" field. It's identical to IsActiveEQ. +func IsActive(v bool) predicate.Material { + return predicate.Material(sql.FieldEQ(FieldIsActive, v)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v int64) predicate.Material { return predicate.Material(sql.FieldEQ(FieldCreatedAt, v)) @@ -543,6 +553,46 @@ func DescriptionContainsFold(v string) predicate.Material { return predicate.Material(sql.FieldContainsFold(FieldDescription, v)) } +// ItemTypeEQ applies the EQ predicate on the "item_type" field. +func ItemTypeEQ(v int) predicate.Material { + return predicate.Material(sql.FieldEQ(FieldItemType, v)) +} + +// ItemTypeNEQ applies the NEQ predicate on the "item_type" field. +func ItemTypeNEQ(v int) predicate.Material { + return predicate.Material(sql.FieldNEQ(FieldItemType, v)) +} + +// ItemTypeIn applies the In predicate on the "item_type" field. +func ItemTypeIn(vs ...int) predicate.Material { + return predicate.Material(sql.FieldIn(FieldItemType, vs...)) +} + +// ItemTypeNotIn applies the NotIn predicate on the "item_type" field. +func ItemTypeNotIn(vs ...int) predicate.Material { + return predicate.Material(sql.FieldNotIn(FieldItemType, vs...)) +} + +// ItemTypeGT applies the GT predicate on the "item_type" field. +func ItemTypeGT(v int) predicate.Material { + return predicate.Material(sql.FieldGT(FieldItemType, v)) +} + +// ItemTypeGTE applies the GTE predicate on the "item_type" field. +func ItemTypeGTE(v int) predicate.Material { + return predicate.Material(sql.FieldGTE(FieldItemType, v)) +} + +// ItemTypeLT applies the LT predicate on the "item_type" field. +func ItemTypeLT(v int) predicate.Material { + return predicate.Material(sql.FieldLT(FieldItemType, v)) +} + +// ItemTypeLTE applies the LTE predicate on the "item_type" field. +func ItemTypeLTE(v int) predicate.Material { + return predicate.Material(sql.FieldLTE(FieldItemType, v)) +} + // ManageModeEQ applies the EQ predicate on the "manage_mode" field. func ManageModeEQ(v int) predicate.Material { return predicate.Material(sql.FieldEQ(FieldManageMode, v)) @@ -678,6 +728,16 @@ func TemplateCodeContainsFold(v string) predicate.Material { return predicate.Material(sql.FieldContainsFold(FieldTemplateCode, v)) } +// IsActiveEQ applies the EQ predicate on the "is_active" field. +func IsActiveEQ(v bool) predicate.Material { + return predicate.Material(sql.FieldEQ(FieldIsActive, v)) +} + +// IsActiveNEQ applies the NEQ predicate on the "is_active" field. +func IsActiveNEQ(v bool) predicate.Material { + return predicate.Material(sql.FieldNEQ(FieldIsActive, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v int64) predicate.Material { return predicate.Material(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/bj_power_wms/ent/material_create.go b/bj_power_wms/ent/material_create.go index 08b1d9a..53490c0 100644 --- a/bj_power_wms/ent/material_create.go +++ b/bj_power_wms/ent/material_create.go @@ -89,6 +89,20 @@ func (_c *MaterialCreate) SetNillableDescription(v *string) *MaterialCreate { return _c } +// SetItemType sets the "item_type" field. +func (_c *MaterialCreate) SetItemType(v int) *MaterialCreate { + _c.mutation.SetItemType(v) + return _c +} + +// SetNillableItemType sets the "item_type" field if the given value is not nil. +func (_c *MaterialCreate) SetNillableItemType(v *int) *MaterialCreate { + if v != nil { + _c.SetItemType(*v) + } + return _c +} + // SetManageMode sets the "manage_mode" field. func (_c *MaterialCreate) SetManageMode(v int) *MaterialCreate { _c.mutation.SetManageMode(v) @@ -145,6 +159,20 @@ func (_c *MaterialCreate) SetNillableTemplateCode(v *string) *MaterialCreate { return _c } +// SetIsActive sets the "is_active" field. +func (_c *MaterialCreate) SetIsActive(v bool) *MaterialCreate { + _c.mutation.SetIsActive(v) + return _c +} + +// SetNillableIsActive sets the "is_active" field if the given value is not nil. +func (_c *MaterialCreate) SetNillableIsActive(v *bool) *MaterialCreate { + if v != nil { + _c.SetIsActive(*v) + } + return _c +} + // SetCreatedAt sets the "created_at" field. func (_c *MaterialCreate) SetCreatedAt(v int64) *MaterialCreate { _c.mutation.SetCreatedAt(v) @@ -208,6 +236,10 @@ func (_c *MaterialCreate) ExecX(ctx context.Context) { // defaults sets the default values of the builder before save. func (_c *MaterialCreate) defaults() { + if _, ok := _c.mutation.ItemType(); !ok { + v := material.DefaultItemType + _c.mutation.SetItemType(v) + } if _, ok := _c.mutation.ManageMode(); !ok { v := material.DefaultManageMode _c.mutation.SetManageMode(v) @@ -220,6 +252,10 @@ func (_c *MaterialCreate) defaults() { v := material.DefaultIsSerialManaged _c.mutation.SetIsSerialManaged(v) } + if _, ok := _c.mutation.IsActive(); !ok { + v := material.DefaultIsActive + _c.mutation.SetIsActive(v) + } if _, ok := _c.mutation.CreatedAt(); !ok { v := material.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) @@ -238,6 +274,9 @@ func (_c *MaterialCreate) check() error { if _, ok := _c.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Material.name"`)} } + if _, ok := _c.mutation.ItemType(); !ok { + return &ValidationError{Name: "item_type", err: errors.New(`ent: missing required field "Material.item_type"`)} + } if _, ok := _c.mutation.ManageMode(); !ok { return &ValidationError{Name: "manage_mode", err: errors.New(`ent: missing required field "Material.manage_mode"`)} } @@ -247,6 +286,9 @@ func (_c *MaterialCreate) check() error { if _, ok := _c.mutation.IsSerialManaged(); !ok { return &ValidationError{Name: "is_serial_managed", err: errors.New(`ent: missing required field "Material.is_serial_managed"`)} } + if _, ok := _c.mutation.IsActive(); !ok { + return &ValidationError{Name: "is_active", err: errors.New(`ent: missing required field "Material.is_active"`)} + } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Material.created_at"`)} } @@ -304,6 +346,10 @@ func (_c *MaterialCreate) createSpec() (*Material, *sqlgraph.CreateSpec) { _spec.SetField(material.FieldDescription, field.TypeString, value) _node.Description = value } + if value, ok := _c.mutation.ItemType(); ok { + _spec.SetField(material.FieldItemType, field.TypeInt, value) + _node.ItemType = value + } if value, ok := _c.mutation.ManageMode(); ok { _spec.SetField(material.FieldManageMode, field.TypeInt, value) _node.ManageMode = value @@ -320,6 +366,10 @@ func (_c *MaterialCreate) createSpec() (*Material, *sqlgraph.CreateSpec) { _spec.SetField(material.FieldTemplateCode, field.TypeString, value) _node.TemplateCode = value } + if value, ok := _c.mutation.IsActive(); ok { + _spec.SetField(material.FieldIsActive, field.TypeBool, value) + _node.IsActive = value + } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(material.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value @@ -476,6 +526,24 @@ func (u *MaterialUpsert) ClearDescription() *MaterialUpsert { return u } +// SetItemType sets the "item_type" field. +func (u *MaterialUpsert) SetItemType(v int) *MaterialUpsert { + u.Set(material.FieldItemType, v) + return u +} + +// UpdateItemType sets the "item_type" field to the value that was provided on create. +func (u *MaterialUpsert) UpdateItemType() *MaterialUpsert { + u.SetExcluded(material.FieldItemType) + return u +} + +// AddItemType adds v to the "item_type" field. +func (u *MaterialUpsert) AddItemType(v int) *MaterialUpsert { + u.Add(material.FieldItemType, v) + return u +} + // SetManageMode sets the "manage_mode" field. func (u *MaterialUpsert) SetManageMode(v int) *MaterialUpsert { u.Set(material.FieldManageMode, v) @@ -536,6 +604,18 @@ func (u *MaterialUpsert) ClearTemplateCode() *MaterialUpsert { return u } +// SetIsActive sets the "is_active" field. +func (u *MaterialUpsert) SetIsActive(v bool) *MaterialUpsert { + u.Set(material.FieldIsActive, v) + return u +} + +// UpdateIsActive sets the "is_active" field to the value that was provided on create. +func (u *MaterialUpsert) UpdateIsActive() *MaterialUpsert { + u.SetExcluded(material.FieldIsActive) + return u +} + // SetCreatedAt sets the "created_at" field. func (u *MaterialUpsert) SetCreatedAt(v int64) *MaterialUpsert { u.Set(material.FieldCreatedAt, v) @@ -724,6 +804,27 @@ func (u *MaterialUpsertOne) ClearDescription() *MaterialUpsertOne { }) } +// SetItemType sets the "item_type" field. +func (u *MaterialUpsertOne) SetItemType(v int) *MaterialUpsertOne { + return u.Update(func(s *MaterialUpsert) { + s.SetItemType(v) + }) +} + +// AddItemType adds v to the "item_type" field. +func (u *MaterialUpsertOne) AddItemType(v int) *MaterialUpsertOne { + return u.Update(func(s *MaterialUpsert) { + s.AddItemType(v) + }) +} + +// UpdateItemType sets the "item_type" field to the value that was provided on create. +func (u *MaterialUpsertOne) UpdateItemType() *MaterialUpsertOne { + return u.Update(func(s *MaterialUpsert) { + s.UpdateItemType() + }) +} + // SetManageMode sets the "manage_mode" field. func (u *MaterialUpsertOne) SetManageMode(v int) *MaterialUpsertOne { return u.Update(func(s *MaterialUpsert) { @@ -794,6 +895,20 @@ func (u *MaterialUpsertOne) ClearTemplateCode() *MaterialUpsertOne { }) } +// SetIsActive sets the "is_active" field. +func (u *MaterialUpsertOne) SetIsActive(v bool) *MaterialUpsertOne { + return u.Update(func(s *MaterialUpsert) { + s.SetIsActive(v) + }) +} + +// UpdateIsActive sets the "is_active" field to the value that was provided on create. +func (u *MaterialUpsertOne) UpdateIsActive() *MaterialUpsertOne { + return u.Update(func(s *MaterialUpsert) { + s.UpdateIsActive() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *MaterialUpsertOne) SetCreatedAt(v int64) *MaterialUpsertOne { return u.Update(func(s *MaterialUpsert) { @@ -1152,6 +1267,27 @@ func (u *MaterialUpsertBulk) ClearDescription() *MaterialUpsertBulk { }) } +// SetItemType sets the "item_type" field. +func (u *MaterialUpsertBulk) SetItemType(v int) *MaterialUpsertBulk { + return u.Update(func(s *MaterialUpsert) { + s.SetItemType(v) + }) +} + +// AddItemType adds v to the "item_type" field. +func (u *MaterialUpsertBulk) AddItemType(v int) *MaterialUpsertBulk { + return u.Update(func(s *MaterialUpsert) { + s.AddItemType(v) + }) +} + +// UpdateItemType sets the "item_type" field to the value that was provided on create. +func (u *MaterialUpsertBulk) UpdateItemType() *MaterialUpsertBulk { + return u.Update(func(s *MaterialUpsert) { + s.UpdateItemType() + }) +} + // SetManageMode sets the "manage_mode" field. func (u *MaterialUpsertBulk) SetManageMode(v int) *MaterialUpsertBulk { return u.Update(func(s *MaterialUpsert) { @@ -1222,6 +1358,20 @@ func (u *MaterialUpsertBulk) ClearTemplateCode() *MaterialUpsertBulk { }) } +// SetIsActive sets the "is_active" field. +func (u *MaterialUpsertBulk) SetIsActive(v bool) *MaterialUpsertBulk { + return u.Update(func(s *MaterialUpsert) { + s.SetIsActive(v) + }) +} + +// UpdateIsActive sets the "is_active" field to the value that was provided on create. +func (u *MaterialUpsertBulk) UpdateIsActive() *MaterialUpsertBulk { + return u.Update(func(s *MaterialUpsert) { + s.UpdateIsActive() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *MaterialUpsertBulk) SetCreatedAt(v int64) *MaterialUpsertBulk { return u.Update(func(s *MaterialUpsert) { diff --git a/bj_power_wms/ent/material_update.go b/bj_power_wms/ent/material_update.go index 07685ce..1f8c68e 100644 --- a/bj_power_wms/ent/material_update.go +++ b/bj_power_wms/ent/material_update.go @@ -135,6 +135,27 @@ func (_u *MaterialUpdate) ClearDescription() *MaterialUpdate { return _u } +// SetItemType sets the "item_type" field. +func (_u *MaterialUpdate) SetItemType(v int) *MaterialUpdate { + _u.mutation.ResetItemType() + _u.mutation.SetItemType(v) + return _u +} + +// SetNillableItemType sets the "item_type" field if the given value is not nil. +func (_u *MaterialUpdate) SetNillableItemType(v *int) *MaterialUpdate { + if v != nil { + _u.SetItemType(*v) + } + return _u +} + +// AddItemType adds value to the "item_type" field. +func (_u *MaterialUpdate) AddItemType(v int) *MaterialUpdate { + _u.mutation.AddItemType(v) + return _u +} + // SetManageMode sets the "manage_mode" field. func (_u *MaterialUpdate) SetManageMode(v int) *MaterialUpdate { _u.mutation.ResetManageMode() @@ -204,6 +225,20 @@ func (_u *MaterialUpdate) ClearTemplateCode() *MaterialUpdate { return _u } +// SetIsActive sets the "is_active" field. +func (_u *MaterialUpdate) SetIsActive(v bool) *MaterialUpdate { + _u.mutation.SetIsActive(v) + return _u +} + +// SetNillableIsActive sets the "is_active" field if the given value is not nil. +func (_u *MaterialUpdate) SetNillableIsActive(v *bool) *MaterialUpdate { + if v != nil { + _u.SetIsActive(*v) + } + return _u +} + // SetCreatedAt sets the "created_at" field. func (_u *MaterialUpdate) SetCreatedAt(v int64) *MaterialUpdate { _u.mutation.ResetCreatedAt() @@ -317,6 +352,12 @@ func (_u *MaterialUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.DescriptionCleared() { _spec.ClearField(material.FieldDescription, field.TypeString) } + if value, ok := _u.mutation.ItemType(); ok { + _spec.SetField(material.FieldItemType, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedItemType(); ok { + _spec.AddField(material.FieldItemType, field.TypeInt, value) + } if value, ok := _u.mutation.ManageMode(); ok { _spec.SetField(material.FieldManageMode, field.TypeInt, value) } @@ -335,6 +376,9 @@ func (_u *MaterialUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.TemplateCodeCleared() { _spec.ClearField(material.FieldTemplateCode, field.TypeString) } + if value, ok := _u.mutation.IsActive(); ok { + _spec.SetField(material.FieldIsActive, field.TypeBool, value) + } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(material.FieldCreatedAt, field.TypeInt64, value) } @@ -475,6 +519,27 @@ func (_u *MaterialUpdateOne) ClearDescription() *MaterialUpdateOne { return _u } +// SetItemType sets the "item_type" field. +func (_u *MaterialUpdateOne) SetItemType(v int) *MaterialUpdateOne { + _u.mutation.ResetItemType() + _u.mutation.SetItemType(v) + return _u +} + +// SetNillableItemType sets the "item_type" field if the given value is not nil. +func (_u *MaterialUpdateOne) SetNillableItemType(v *int) *MaterialUpdateOne { + if v != nil { + _u.SetItemType(*v) + } + return _u +} + +// AddItemType adds value to the "item_type" field. +func (_u *MaterialUpdateOne) AddItemType(v int) *MaterialUpdateOne { + _u.mutation.AddItemType(v) + return _u +} + // SetManageMode sets the "manage_mode" field. func (_u *MaterialUpdateOne) SetManageMode(v int) *MaterialUpdateOne { _u.mutation.ResetManageMode() @@ -544,6 +609,20 @@ func (_u *MaterialUpdateOne) ClearTemplateCode() *MaterialUpdateOne { return _u } +// SetIsActive sets the "is_active" field. +func (_u *MaterialUpdateOne) SetIsActive(v bool) *MaterialUpdateOne { + _u.mutation.SetIsActive(v) + return _u +} + +// SetNillableIsActive sets the "is_active" field if the given value is not nil. +func (_u *MaterialUpdateOne) SetNillableIsActive(v *bool) *MaterialUpdateOne { + if v != nil { + _u.SetIsActive(*v) + } + return _u +} + // SetCreatedAt sets the "created_at" field. func (_u *MaterialUpdateOne) SetCreatedAt(v int64) *MaterialUpdateOne { _u.mutation.ResetCreatedAt() @@ -687,6 +766,12 @@ func (_u *MaterialUpdateOne) sqlSave(ctx context.Context) (_node *Material, err if _u.mutation.DescriptionCleared() { _spec.ClearField(material.FieldDescription, field.TypeString) } + if value, ok := _u.mutation.ItemType(); ok { + _spec.SetField(material.FieldItemType, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedItemType(); ok { + _spec.AddField(material.FieldItemType, field.TypeInt, value) + } if value, ok := _u.mutation.ManageMode(); ok { _spec.SetField(material.FieldManageMode, field.TypeInt, value) } @@ -705,6 +790,9 @@ func (_u *MaterialUpdateOne) sqlSave(ctx context.Context) (_node *Material, err if _u.mutation.TemplateCodeCleared() { _spec.ClearField(material.FieldTemplateCode, field.TypeString) } + if value, ok := _u.mutation.IsActive(); ok { + _spec.SetField(material.FieldIsActive, field.TypeBool, value) + } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(material.FieldCreatedAt, field.TypeInt64, value) } diff --git a/bj_power_wms/ent/migrate/schema.go b/bj_power_wms/ent/migrate/schema.go index 6c18113..60a8666 100644 --- a/bj_power_wms/ent/migrate/schema.go +++ b/bj_power_wms/ent/migrate/schema.go @@ -233,6 +233,7 @@ var ( InventoriesColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "manage_mode", Type: field.TypeInt}, + {Name: "category", Type: field.TypeInt, Default: 1}, {Name: "material_code", Type: field.TypeString}, {Name: "material_name", Type: field.TypeString, Nullable: true}, {Name: "batch_no", Type: field.TypeString, Nullable: true}, @@ -245,6 +246,8 @@ var ( {Name: "production_date", Type: field.TypeString, Nullable: true}, {Name: "supplier", Type: field.TypeString, Nullable: true}, {Name: "inbound_no", Type: field.TypeString, Nullable: true}, + {Name: "order_no", Type: field.TypeString, Size: 64, Default: ""}, + {Name: "completed_process", Type: field.TypeString, Size: 64, Default: ""}, {Name: "remark", Type: field.TypeString, Nullable: true}, {Name: "created_at", Type: field.TypeInt64}, {Name: "updated_at", Type: field.TypeInt64}, @@ -261,29 +264,34 @@ var ( Columns: []*schema.Column{InventoriesColumns[1]}, }, { - Name: "inventory_material_code", + Name: "inventory_category", Unique: false, Columns: []*schema.Column{InventoriesColumns[2]}, }, { - Name: "inventory_quality_status", + Name: "inventory_material_code", Unique: false, - Columns: []*schema.Column{InventoriesColumns[8]}, + Columns: []*schema.Column{InventoriesColumns[3]}, }, { - Name: "inventory_zone_code", + Name: "inventory_quality_status", Unique: false, Columns: []*schema.Column{InventoriesColumns[9]}, }, + { + Name: "inventory_zone_code", + Unique: false, + Columns: []*schema.Column{InventoriesColumns[10]}, + }, { Name: "inventory_batch_no", Unique: false, - Columns: []*schema.Column{InventoriesColumns[4]}, + Columns: []*schema.Column{InventoriesColumns[5]}, }, { Name: "inventory_sn_code", Unique: false, - Columns: []*schema.Column{InventoriesColumns[5]}, + Columns: []*schema.Column{InventoriesColumns[6]}, }, }, } @@ -333,10 +341,12 @@ var ( {Name: "spec", Type: field.TypeString, Nullable: true}, {Name: "unit", Type: field.TypeString, Nullable: true}, {Name: "description", Type: field.TypeString, Nullable: true}, + {Name: "item_type", Type: field.TypeInt, Default: 1}, {Name: "manage_mode", Type: field.TypeInt, Default: 1}, {Name: "is_batch_managed", Type: field.TypeBool, Default: false}, {Name: "is_serial_managed", Type: field.TypeBool, Default: false}, {Name: "template_code", Type: field.TypeString, Nullable: true}, + {Name: "is_active", Type: field.TypeBool, Default: true}, {Name: "created_at", Type: field.TypeInt64}, {Name: "updated_at", Type: field.TypeInt64}, } @@ -354,7 +364,12 @@ var ( { Name: "material_template_code", Unique: false, - Columns: []*schema.Column{MaterialsColumns[10]}, + Columns: []*schema.Column{MaterialsColumns[11]}, + }, + { + Name: "material_item_type", + Unique: false, + Columns: []*schema.Column{MaterialsColumns[7]}, }, }, } diff --git a/bj_power_wms/ent/mutation.go b/bj_power_wms/ent/mutation.go index 16b772a..cd1ed12 100644 --- a/bj_power_wms/ent/mutation.go +++ b/bj_power_wms/ent/mutation.go @@ -5664,34 +5664,38 @@ func (m *InspectionRecordMutation) ResetEdge(name string) error { // InventoryMutation represents an operation that mutates the Inventory nodes in the graph. type InventoryMutation struct { config - op Op - typ string - id *int - manage_mode *int - addmanage_mode *int - material_code *string - material_name *string - batch_no *string - sn_code *string - quantity *int - addquantity *int - locked_qty *int - addlocked_qty *int - quality_status *string - zone_code *string - status *string - production_date *string - supplier *string - inbound_no *string - remark *string - created_at *int64 - addcreated_at *int64 - updated_at *int64 - addupdated_at *int64 - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*Inventory, error) - predicates []predicate.Inventory + op Op + typ string + id *int + manage_mode *int + addmanage_mode *int + category *int + addcategory *int + material_code *string + material_name *string + batch_no *string + sn_code *string + quantity *int + addquantity *int + locked_qty *int + addlocked_qty *int + quality_status *string + zone_code *string + status *string + production_date *string + supplier *string + inbound_no *string + order_no *string + completed_process *string + remark *string + created_at *int64 + addcreated_at *int64 + updated_at *int64 + addupdated_at *int64 + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*Inventory, error) + predicates []predicate.Inventory } var _ ent.Mutation = (*InventoryMutation)(nil) @@ -5848,6 +5852,62 @@ func (m *InventoryMutation) ResetManageMode() { m.addmanage_mode = nil } +// SetCategory sets the "category" field. +func (m *InventoryMutation) SetCategory(i int) { + m.category = &i + m.addcategory = nil +} + +// Category returns the value of the "category" field in the mutation. +func (m *InventoryMutation) Category() (r int, exists bool) { + v := m.category + if v == nil { + return + } + return *v, true +} + +// OldCategory returns the old "category" field's value of the Inventory entity. +// If the Inventory 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 *InventoryMutation) OldCategory(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCategory is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCategory requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCategory: %w", err) + } + return oldValue.Category, nil +} + +// AddCategory adds i to the "category" field. +func (m *InventoryMutation) AddCategory(i int) { + if m.addcategory != nil { + *m.addcategory += i + } else { + m.addcategory = &i + } +} + +// AddedCategory returns the value that was added to the "category" field in this mutation. +func (m *InventoryMutation) AddedCategory() (r int, exists bool) { + v := m.addcategory + if v == nil { + return + } + return *v, true +} + +// ResetCategory resets all changes to the "category" field. +func (m *InventoryMutation) ResetCategory() { + m.category = nil + m.addcategory = nil +} + // SetMaterialCode sets the "material_code" field. func (m *InventoryMutation) SetMaterialCode(s string) { m.material_code = &s @@ -6411,6 +6471,78 @@ func (m *InventoryMutation) ResetInboundNo() { delete(m.clearedFields, inventory.FieldInboundNo) } +// SetOrderNo sets the "order_no" field. +func (m *InventoryMutation) SetOrderNo(s string) { + m.order_no = &s +} + +// OrderNo returns the value of the "order_no" field in the mutation. +func (m *InventoryMutation) OrderNo() (r string, exists bool) { + v := m.order_no + if v == nil { + return + } + return *v, true +} + +// OldOrderNo returns the old "order_no" field's value of the Inventory entity. +// If the Inventory 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 *InventoryMutation) OldOrderNo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOrderNo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOrderNo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOrderNo: %w", err) + } + return oldValue.OrderNo, nil +} + +// ResetOrderNo resets all changes to the "order_no" field. +func (m *InventoryMutation) ResetOrderNo() { + m.order_no = nil +} + +// SetCompletedProcess sets the "completed_process" field. +func (m *InventoryMutation) SetCompletedProcess(s string) { + m.completed_process = &s +} + +// CompletedProcess returns the value of the "completed_process" field in the mutation. +func (m *InventoryMutation) CompletedProcess() (r string, exists bool) { + v := m.completed_process + if v == nil { + return + } + return *v, true +} + +// OldCompletedProcess returns the old "completed_process" field's value of the Inventory entity. +// If the Inventory 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 *InventoryMutation) OldCompletedProcess(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCompletedProcess is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCompletedProcess requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCompletedProcess: %w", err) + } + return oldValue.CompletedProcess, nil +} + +// ResetCompletedProcess resets all changes to the "completed_process" field. +func (m *InventoryMutation) ResetCompletedProcess() { + m.completed_process = nil +} + // SetRemark sets the "remark" field. func (m *InventoryMutation) SetRemark(s string) { m.remark = &s @@ -6606,10 +6738,13 @@ func (m *InventoryMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *InventoryMutation) Fields() []string { - fields := make([]string, 0, 16) + fields := make([]string, 0, 19) if m.manage_mode != nil { fields = append(fields, inventory.FieldManageMode) } + if m.category != nil { + fields = append(fields, inventory.FieldCategory) + } if m.material_code != nil { fields = append(fields, inventory.FieldMaterialCode) } @@ -6646,6 +6781,12 @@ func (m *InventoryMutation) Fields() []string { if m.inbound_no != nil { fields = append(fields, inventory.FieldInboundNo) } + if m.order_no != nil { + fields = append(fields, inventory.FieldOrderNo) + } + if m.completed_process != nil { + fields = append(fields, inventory.FieldCompletedProcess) + } if m.remark != nil { fields = append(fields, inventory.FieldRemark) } @@ -6665,6 +6806,8 @@ func (m *InventoryMutation) Field(name string) (ent.Value, bool) { switch name { case inventory.FieldManageMode: return m.ManageMode() + case inventory.FieldCategory: + return m.Category() case inventory.FieldMaterialCode: return m.MaterialCode() case inventory.FieldMaterialName: @@ -6689,6 +6832,10 @@ func (m *InventoryMutation) Field(name string) (ent.Value, bool) { return m.Supplier() case inventory.FieldInboundNo: return m.InboundNo() + case inventory.FieldOrderNo: + return m.OrderNo() + case inventory.FieldCompletedProcess: + return m.CompletedProcess() case inventory.FieldRemark: return m.Remark() case inventory.FieldCreatedAt: @@ -6706,6 +6853,8 @@ func (m *InventoryMutation) OldField(ctx context.Context, name string) (ent.Valu switch name { case inventory.FieldManageMode: return m.OldManageMode(ctx) + case inventory.FieldCategory: + return m.OldCategory(ctx) case inventory.FieldMaterialCode: return m.OldMaterialCode(ctx) case inventory.FieldMaterialName: @@ -6730,6 +6879,10 @@ func (m *InventoryMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldSupplier(ctx) case inventory.FieldInboundNo: return m.OldInboundNo(ctx) + case inventory.FieldOrderNo: + return m.OldOrderNo(ctx) + case inventory.FieldCompletedProcess: + return m.OldCompletedProcess(ctx) case inventory.FieldRemark: return m.OldRemark(ctx) case inventory.FieldCreatedAt: @@ -6752,6 +6905,13 @@ func (m *InventoryMutation) SetField(name string, value ent.Value) error { } m.SetManageMode(v) return nil + case inventory.FieldCategory: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCategory(v) + return nil case inventory.FieldMaterialCode: v, ok := value.(string) if !ok { @@ -6836,6 +6996,20 @@ func (m *InventoryMutation) SetField(name string, value ent.Value) error { } m.SetInboundNo(v) return nil + case inventory.FieldOrderNo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOrderNo(v) + return nil + case inventory.FieldCompletedProcess: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCompletedProcess(v) + return nil case inventory.FieldRemark: v, ok := value.(string) if !ok { @@ -6868,6 +7042,9 @@ func (m *InventoryMutation) AddedFields() []string { if m.addmanage_mode != nil { fields = append(fields, inventory.FieldManageMode) } + if m.addcategory != nil { + fields = append(fields, inventory.FieldCategory) + } if m.addquantity != nil { fields = append(fields, inventory.FieldQuantity) } @@ -6890,6 +7067,8 @@ func (m *InventoryMutation) AddedField(name string) (ent.Value, bool) { switch name { case inventory.FieldManageMode: return m.AddedManageMode() + case inventory.FieldCategory: + return m.AddedCategory() case inventory.FieldQuantity: return m.AddedQuantity() case inventory.FieldLockedQty: @@ -6914,6 +7093,13 @@ func (m *InventoryMutation) AddField(name string, value ent.Value) error { } m.AddManageMode(v) return nil + case inventory.FieldCategory: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCategory(v) + return nil case inventory.FieldQuantity: v, ok := value.(int) if !ok { @@ -7023,6 +7209,9 @@ func (m *InventoryMutation) ResetField(name string) error { case inventory.FieldManageMode: m.ResetManageMode() return nil + case inventory.FieldCategory: + m.ResetCategory() + return nil case inventory.FieldMaterialCode: m.ResetMaterialCode() return nil @@ -7059,6 +7248,12 @@ func (m *InventoryMutation) ResetField(name string) error { case inventory.FieldInboundNo: m.ResetInboundNo() return nil + case inventory.FieldOrderNo: + m.ResetOrderNo() + return nil + case inventory.FieldCompletedProcess: + m.ResetCompletedProcess() + return nil case inventory.FieldRemark: m.ResetRemark() return nil @@ -8121,11 +8316,14 @@ type MaterialMutation struct { spec *string unit *string description *string + item_type *int + additem_type *int manage_mode *int addmanage_mode *int is_batch_managed *bool is_serial_managed *bool template_code *string + is_active *bool created_at *int64 addcreated_at *int64 updated_at *int64 @@ -8502,6 +8700,62 @@ func (m *MaterialMutation) ResetDescription() { delete(m.clearedFields, material.FieldDescription) } +// SetItemType sets the "item_type" field. +func (m *MaterialMutation) SetItemType(i int) { + m.item_type = &i + m.additem_type = nil +} + +// ItemType returns the value of the "item_type" field in the mutation. +func (m *MaterialMutation) ItemType() (r int, exists bool) { + v := m.item_type + if v == nil { + return + } + return *v, true +} + +// OldItemType returns the old "item_type" field's value of the Material entity. +// If the Material 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 *MaterialMutation) OldItemType(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldItemType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldItemType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldItemType: %w", err) + } + return oldValue.ItemType, nil +} + +// AddItemType adds i to the "item_type" field. +func (m *MaterialMutation) AddItemType(i int) { + if m.additem_type != nil { + *m.additem_type += i + } else { + m.additem_type = &i + } +} + +// AddedItemType returns the value that was added to the "item_type" field in this mutation. +func (m *MaterialMutation) AddedItemType() (r int, exists bool) { + v := m.additem_type + if v == nil { + return + } + return *v, true +} + +// ResetItemType resets all changes to the "item_type" field. +func (m *MaterialMutation) ResetItemType() { + m.item_type = nil + m.additem_type = nil +} + // SetManageMode sets the "manage_mode" field. func (m *MaterialMutation) SetManageMode(i int) { m.manage_mode = &i @@ -8679,6 +8933,42 @@ func (m *MaterialMutation) ResetTemplateCode() { delete(m.clearedFields, material.FieldTemplateCode) } +// SetIsActive sets the "is_active" field. +func (m *MaterialMutation) SetIsActive(b bool) { + m.is_active = &b +} + +// IsActive returns the value of the "is_active" field in the mutation. +func (m *MaterialMutation) IsActive() (r bool, exists bool) { + v := m.is_active + if v == nil { + return + } + return *v, true +} + +// OldIsActive returns the old "is_active" field's value of the Material entity. +// If the Material 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 *MaterialMutation) OldIsActive(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldIsActive is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldIsActive requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldIsActive: %w", err) + } + return oldValue.IsActive, nil +} + +// ResetIsActive resets all changes to the "is_active" field. +func (m *MaterialMutation) ResetIsActive() { + m.is_active = nil +} + // SetCreatedAt sets the "created_at" field. func (m *MaterialMutation) SetCreatedAt(i int64) { m.created_at = &i @@ -8825,7 +9115,7 @@ func (m *MaterialMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *MaterialMutation) Fields() []string { - fields := make([]string, 0, 12) + fields := make([]string, 0, 14) if m.code != nil { fields = append(fields, material.FieldCode) } @@ -8844,6 +9134,9 @@ func (m *MaterialMutation) Fields() []string { if m.description != nil { fields = append(fields, material.FieldDescription) } + if m.item_type != nil { + fields = append(fields, material.FieldItemType) + } if m.manage_mode != nil { fields = append(fields, material.FieldManageMode) } @@ -8856,6 +9149,9 @@ func (m *MaterialMutation) Fields() []string { if m.template_code != nil { fields = append(fields, material.FieldTemplateCode) } + if m.is_active != nil { + fields = append(fields, material.FieldIsActive) + } if m.created_at != nil { fields = append(fields, material.FieldCreatedAt) } @@ -8882,6 +9178,8 @@ func (m *MaterialMutation) Field(name string) (ent.Value, bool) { return m.Unit() case material.FieldDescription: return m.Description() + case material.FieldItemType: + return m.ItemType() case material.FieldManageMode: return m.ManageMode() case material.FieldIsBatchManaged: @@ -8890,6 +9188,8 @@ func (m *MaterialMutation) Field(name string) (ent.Value, bool) { return m.IsSerialManaged() case material.FieldTemplateCode: return m.TemplateCode() + case material.FieldIsActive: + return m.IsActive() case material.FieldCreatedAt: return m.CreatedAt() case material.FieldUpdatedAt: @@ -8915,6 +9215,8 @@ func (m *MaterialMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldUnit(ctx) case material.FieldDescription: return m.OldDescription(ctx) + case material.FieldItemType: + return m.OldItemType(ctx) case material.FieldManageMode: return m.OldManageMode(ctx) case material.FieldIsBatchManaged: @@ -8923,6 +9225,8 @@ func (m *MaterialMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldIsSerialManaged(ctx) case material.FieldTemplateCode: return m.OldTemplateCode(ctx) + case material.FieldIsActive: + return m.OldIsActive(ctx) case material.FieldCreatedAt: return m.OldCreatedAt(ctx) case material.FieldUpdatedAt: @@ -8978,6 +9282,13 @@ func (m *MaterialMutation) SetField(name string, value ent.Value) error { } m.SetDescription(v) return nil + case material.FieldItemType: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetItemType(v) + return nil case material.FieldManageMode: v, ok := value.(int) if !ok { @@ -9006,6 +9317,13 @@ func (m *MaterialMutation) SetField(name string, value ent.Value) error { } m.SetTemplateCode(v) return nil + case material.FieldIsActive: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetIsActive(v) + return nil case material.FieldCreatedAt: v, ok := value.(int64) if !ok { @@ -9028,6 +9346,9 @@ func (m *MaterialMutation) SetField(name string, value ent.Value) error { // this mutation. func (m *MaterialMutation) AddedFields() []string { var fields []string + if m.additem_type != nil { + fields = append(fields, material.FieldItemType) + } if m.addmanage_mode != nil { fields = append(fields, material.FieldManageMode) } @@ -9045,6 +9366,8 @@ func (m *MaterialMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *MaterialMutation) AddedField(name string) (ent.Value, bool) { switch name { + case material.FieldItemType: + return m.AddedItemType() case material.FieldManageMode: return m.AddedManageMode() case material.FieldCreatedAt: @@ -9060,6 +9383,13 @@ func (m *MaterialMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *MaterialMutation) AddField(name string, value ent.Value) error { switch name { + case material.FieldItemType: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddItemType(v) + return nil case material.FieldManageMode: v, ok := value.(int) if !ok { @@ -9159,6 +9489,9 @@ func (m *MaterialMutation) ResetField(name string) error { case material.FieldDescription: m.ResetDescription() return nil + case material.FieldItemType: + m.ResetItemType() + return nil case material.FieldManageMode: m.ResetManageMode() return nil @@ -9171,6 +9504,9 @@ func (m *MaterialMutation) ResetField(name string) error { case material.FieldTemplateCode: m.ResetTemplateCode() return nil + case material.FieldIsActive: + m.ResetIsActive() + return nil case material.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/bj_power_wms/ent/runtime.go b/bj_power_wms/ent/runtime.go index 93bffb4..ed49a1c 100644 --- a/bj_power_wms/ent/runtime.go +++ b/bj_power_wms/ent/runtime.go @@ -225,28 +225,44 @@ func init() { inspectionrecord.DefaultUpdatedAt = inspectionrecordDescUpdatedAt.Default.(func() int64) inventoryFields := schema.Inventory{}.Fields() _ = inventoryFields + // inventoryDescCategory is the schema descriptor for category field. + inventoryDescCategory := inventoryFields[1].Descriptor() + // inventory.DefaultCategory holds the default value on creation for the category field. + inventory.DefaultCategory = inventoryDescCategory.Default.(int) // inventoryDescQuantity is the schema descriptor for quantity field. - inventoryDescQuantity := inventoryFields[5].Descriptor() + inventoryDescQuantity := inventoryFields[6].Descriptor() // inventory.DefaultQuantity holds the default value on creation for the quantity field. inventory.DefaultQuantity = inventoryDescQuantity.Default.(int) // inventoryDescLockedQty is the schema descriptor for locked_qty field. - inventoryDescLockedQty := inventoryFields[6].Descriptor() + inventoryDescLockedQty := inventoryFields[7].Descriptor() // inventory.DefaultLockedQty holds the default value on creation for the locked_qty field. inventory.DefaultLockedQty = inventoryDescLockedQty.Default.(int) // inventoryDescQualityStatus is the schema descriptor for quality_status field. - inventoryDescQualityStatus := inventoryFields[7].Descriptor() + inventoryDescQualityStatus := inventoryFields[8].Descriptor() // inventory.DefaultQualityStatus holds the default value on creation for the quality_status field. inventory.DefaultQualityStatus = inventoryDescQualityStatus.Default.(string) // inventoryDescStatus is the schema descriptor for status field. - inventoryDescStatus := inventoryFields[9].Descriptor() + inventoryDescStatus := inventoryFields[10].Descriptor() // inventory.DefaultStatus holds the default value on creation for the status field. inventory.DefaultStatus = inventoryDescStatus.Default.(string) + // inventoryDescOrderNo is the schema descriptor for order_no field. + inventoryDescOrderNo := inventoryFields[14].Descriptor() + // inventory.DefaultOrderNo holds the default value on creation for the order_no field. + inventory.DefaultOrderNo = inventoryDescOrderNo.Default.(string) + // inventory.OrderNoValidator is a validator for the "order_no" field. It is called by the builders before save. + inventory.OrderNoValidator = inventoryDescOrderNo.Validators[0].(func(string) error) + // inventoryDescCompletedProcess is the schema descriptor for completed_process field. + inventoryDescCompletedProcess := inventoryFields[15].Descriptor() + // inventory.DefaultCompletedProcess holds the default value on creation for the completed_process field. + inventory.DefaultCompletedProcess = inventoryDescCompletedProcess.Default.(string) + // inventory.CompletedProcessValidator is a validator for the "completed_process" field. It is called by the builders before save. + inventory.CompletedProcessValidator = inventoryDescCompletedProcess.Validators[0].(func(string) error) // inventoryDescCreatedAt is the schema descriptor for created_at field. - inventoryDescCreatedAt := inventoryFields[14].Descriptor() + inventoryDescCreatedAt := inventoryFields[17].Descriptor() // inventory.DefaultCreatedAt holds the default value on creation for the created_at field. inventory.DefaultCreatedAt = inventoryDescCreatedAt.Default.(func() int64) // inventoryDescUpdatedAt is the schema descriptor for updated_at field. - inventoryDescUpdatedAt := inventoryFields[15].Descriptor() + inventoryDescUpdatedAt := inventoryFields[18].Descriptor() // inventory.DefaultUpdatedAt holds the default value on creation for the updated_at field. inventory.DefaultUpdatedAt = inventoryDescUpdatedAt.Default.(func() int64) inventorylockFields := schema.InventoryLock{}.Fields() @@ -269,24 +285,32 @@ func init() { inventorylock.DefaultUpdatedAt = inventorylockDescUpdatedAt.Default.(func() int64) materialFields := schema.Material{}.Fields() _ = materialFields + // materialDescItemType is the schema descriptor for item_type field. + materialDescItemType := materialFields[6].Descriptor() + // material.DefaultItemType holds the default value on creation for the item_type field. + material.DefaultItemType = materialDescItemType.Default.(int) // materialDescManageMode is the schema descriptor for manage_mode field. - materialDescManageMode := materialFields[6].Descriptor() + materialDescManageMode := materialFields[7].Descriptor() // material.DefaultManageMode holds the default value on creation for the manage_mode field. material.DefaultManageMode = materialDescManageMode.Default.(int) // materialDescIsBatchManaged is the schema descriptor for is_batch_managed field. - materialDescIsBatchManaged := materialFields[7].Descriptor() + materialDescIsBatchManaged := materialFields[8].Descriptor() // material.DefaultIsBatchManaged holds the default value on creation for the is_batch_managed field. material.DefaultIsBatchManaged = materialDescIsBatchManaged.Default.(bool) // materialDescIsSerialManaged is the schema descriptor for is_serial_managed field. - materialDescIsSerialManaged := materialFields[8].Descriptor() + materialDescIsSerialManaged := materialFields[9].Descriptor() // material.DefaultIsSerialManaged holds the default value on creation for the is_serial_managed field. material.DefaultIsSerialManaged = materialDescIsSerialManaged.Default.(bool) + // materialDescIsActive is the schema descriptor for is_active field. + materialDescIsActive := materialFields[11].Descriptor() + // material.DefaultIsActive holds the default value on creation for the is_active field. + material.DefaultIsActive = materialDescIsActive.Default.(bool) // materialDescCreatedAt is the schema descriptor for created_at field. - materialDescCreatedAt := materialFields[10].Descriptor() + materialDescCreatedAt := materialFields[12].Descriptor() // material.DefaultCreatedAt holds the default value on creation for the created_at field. material.DefaultCreatedAt = materialDescCreatedAt.Default.(func() int64) // materialDescUpdatedAt is the schema descriptor for updated_at field. - materialDescUpdatedAt := materialFields[11].Descriptor() + materialDescUpdatedAt := materialFields[13].Descriptor() // material.DefaultUpdatedAt holds the default value on creation for the updated_at field. material.DefaultUpdatedAt = materialDescUpdatedAt.Default.(func() int64) ordermaterialledgerFields := schema.OrderMaterialLedger{}.Fields() diff --git a/bj_power_wms/frontend/src/help.js b/bj_power_wms/frontend/src/help.js index 202a964..ffca763 100644 --- a/bj_power_wms/frontend/src/help.js +++ b/bj_power_wms/frontend/src/help.js @@ -60,10 +60,11 @@ export const helpOutbound = { export const helpInventory = { title: '库存查询', overview: - '查看当前库存:结构件按批次(数量/锁定/区域),精密件按 SN(状态/区域)。数据由入库、出库、检验、盘点等业务实时维护,本页只读。\n\n本页三个视图页签(首屏默认「物料汇总」):\n· 物料汇总:一物料一行,直接回答"这个物料总共有多少"——总量/锁定/可用 + 质量分布(合格/未检/不合格) + 分布在哪些区域;ID 与创建时间取自物料主表,另有最近入库单/最近入库时间;点行下钻该物料的批次/SN 明细。\n· 库存明细:按 物料 × 区域 × 质量 聚合,可组合 区域/质量状态/入库时间 筛选(物料汇总不受区域/质量筛选影响)。\n· 区域汇总:按 区域 × 质量状态 × 类型 看各库区的货量;ID 与创建时间取自区域主表,另有组内最近入库时间;支持全量导出。\n\n层级概念:入库单 → 批次/SN → 库存行。区域/质量状态/批次/最近入库单都是某物料的一对多属性,放在下钻明细中查看,不在物料行上并排平铺。', + '查看当前库存:全品类统一一张库存主表——原材料(结构件按批次/精密件按SN)、半成品(MES 暂存退库)、成品(MES 完工自动回流,系统生成 SN)。数据由入库、出库、检验、盘点及 MES 回流等业务实时维护,本页只读。\n\n本页三个视图页签(首屏默认「物料汇总」):\n· 物料汇总:一物料一行,直接回答"这个物料总共有多少"——总量/锁定/可用 + 质量分布(合格/未检/不合格) + 分布在哪些区域;ID 与创建时间取自物料主表,另有最近入库单/最近入库时间;点行下钻该物料的批次/SN 明细。\n· 库存明细:按 物料 × 区域 × 质量 聚合,可组合 区域/质量状态/入库时间/品类 筛选(物料汇总不受区域/质量筛选影响)。\n· 区域汇总:按 区域 × 质量状态 × 类型(批次/精密件/半成品/成品) 看各库区的货量;ID 与创建时间取自区域主表,另有组内最近入库时间;支持全量导出。\n\n层级概念:入库单 → 批次/SN → 库存行。区域/质量状态/批次/最近入库单都是某物料的一对多属性,放在下钻明细中查看,不在物料行上并排平铺。', fields: [ { name: '物料编码 / 物料名称', source: '筛选条件。', purpose: '只看某个物料', fill: '输入编码或名称,可空=全部' }, { name: '库存类型', source: '单选(全部/结构件/精密件)。', purpose: '按管理粒度过滤', fill: '全部 / 结构件(批次) / 精密件(SN)' }, + { name: '品类', source: '单选(全部/原材料/半成品/成品)。', purpose: '按品类过滤:原材料=采购到货入库;半成品=MES 工位暂存退库;成品=MES 完工自动回流(成品 SN 系统生成,入库单号形如 MES:工单号)', fill: '全部 / 原材料 / 半成品 / 成品;三个视图与导出均生效' }, { name: '物料汇总-质量分布', source: '系统按当前在库行统计。', purpose: '一个物料合格/未检/不合格各多少', fill: '合格/未检/不合格三列,自动统计' }, { name: '物料汇总-分布区域', source: '系统统计该物料当前占用了哪些区域。', purpose: '一个物料可跨多个区域存放(批次可拆分),物料行不写死单一区域', fill: '如 Z02、Z05;点行下钻可看每个批次/SN 具体在哪个区' }, { name: '库存明细-区域/质量/入库时间', source: '仅「库存明细」视图生效的筛选。', purpose: '在物料基础上再按存放区/质量状态/入库时间收窄', fill: '可组合筛选' }, @@ -105,18 +106,18 @@ export const helpStocktake = { export const helpSemi = { title: '半成品管理(流转)', overview: - '产线工位"暂存/退回"的半成品或完工成品先在此入库(记已完成工序),需要时再出库(重上线),MES 从下一工序继续。\n\n闭环逻辑(与 MES 自动打通):\n' + + '产线工位"暂存/退回"的半成品先在此入库(记已完成工序),需要时再出库(重上线),MES 从下一工序继续;成品则由 MES 完工后自动回流入库(品类=成品),不经本页手工操作。\n\n闭环逻辑(与 MES 自动打通):\n' + '① 工位终端"暂存退库" → 调 MES 工序流转接口 → MES 自动调 WMS /api/internal/semi/inbound 入库(带 SN、工单号、已完成工序码)。\n' + - '② WMS 收到后生成"半成品库存"记录,并写入 SN 台账,状态=在库。\n' + + '② WMS 收到后写入统一库存主表(品类=半成品),与原材料/成品同表管理——库存查询三视图、区域汇总、导出都能看到它。\n' + '③ 该件后续要接着做时,在本页"半成品出库"或 MES 重上线 → WMS /api/internal/semi/outbound 把状态置为出库。\n' + '④ MES 据此下发下一工序,工位继续装配,直至完工。\n\n' + - 'SN 是两系统唯一的追溯锚点,因此物料编码非必填:MES 调用时不传,WMS 会优先用请求里的、其次按 SN 反查序列号台账,都没有则留空,不影响闭环。', + 'SN 是两系统唯一的追溯锚点。物料编码以请求传入为准,WMS 会同时从物料档案补齐物料名称(档案缺失不阻塞入库,只用编码兜底)。', sections: [ { title: '半成品/成品入库', overview: '把产线退回的半成品或成品收进库房(手工录入或 MES 自动同步均走此接口)。', fields: [ - { name: '物料', source: '下拉选择。来自物料档案。', purpose: '入库物料(MES 自动同步时通常留空,由 WMS 反查)', fill: '选择物料编码' }, + { name: '物料', source: '下拉选择。来自物料档案(最近100条+模糊搜索)。', 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' }, @@ -174,13 +175,14 @@ export const helpZone = { export const helpMaterial = { title: '物料档案', overview: - '定义每一种物料的字典:叫什么、什么规格、是【结构件(批次)】还是【精密件(SN)】。\n此处维护的物料会作为下拉选项出现在入库/出库/装箱等页面;其中"类型"直接决定后续入库方式——结构件走批次(一批多件)、精密件走 SN 逐件扫码(单件追溯)。\n\n物料档案是库房的另一道地基:先建好物料,入库时才能选到;类型选错会导致入库方式与追溯粒度不符。', + '定义每一种物料的字典:叫什么、什么规格、什么品类(原材料/半成品/成品)、是【结构件(批次)】还是【精密件(SN)】。\n此处维护的物料会作为下拉选项出现在入库/出库/装箱等页面;其中"类型"直接决定后续入库方式——结构件走批次(一批多件)、精密件走 SN 逐件扫码(单件追溯)。\n\n品类说明:1=原材料(默认) / 2=半成品 / 3=成品。编码主数据单一来源在本表:产品型号档案即本表中"成品"品类的行(MES 实时拉取,不双录);库存主表 inventories 的品类与其呼应。列表支持按品类筛选,导出含品类列。\n\n物料档案是库房的另一道地基:先建好物料,入库时才能选到;类型选错会导致入库方式与追溯粒度不符。', fields: [ { name: '物料编码', source: '手工填写,仓库自定义(新增后不可改)。', purpose: '物料唯一编号', fill: '如 SCREW-M3;新增后不可改编码' }, { name: '名称', source: '手工填写。', purpose: '物料名称', fill: '如 螺丝 M3' }, { name: '简称', source: '手工填写(可空)。', purpose: '简写', fill: '可空' }, { name: '规格', source: '手工填写(可空)。', purpose: '规格型号', fill: '可空' }, { name: '单位', source: '手工填写。', purpose: '计量单位', fill: '如 个/套' }, + { name: '品类', source: '创建时默认"原材料";成品品类由「产品型号」页维护(自动生成),本页查询/导出覆盖全部品类。', purpose: '区分原材料/半成品/成品,与统一库存主表品类对应', fill: '查询区可选 原材料/半成品/成品' }, { name: '类型', source: '选择:1=结构件(批次管理) / 2=精密件(SN 管理)。语义由库房定:结构件按批次管理,精密件按 SN 逐件管理。', purpose: '决定入库方式:结构件走批次、精密件走 SN 逐件扫码', fill: '结构件(批次) / 精密件(SN)' }, { name: '说明', source: '手工填写(可空)。', purpose: '补充描述', fill: '可空' } ] diff --git a/bj_power_wms/frontend/src/pages/BaseData.vue b/bj_power_wms/frontend/src/pages/BaseData.vue index 30d63ee..af263c4 100644 --- a/bj_power_wms/frontend/src/pages/BaseData.vue +++ b/bj_power_wms/frontend/src/pages/BaseData.vue @@ -106,7 +106,7 @@ const materials = ref([]) const materialTotal = ref(0) const materialLoading = ref(false) const matFilter = reactive({ - materialCode: '', name: '', spec: '', manageMode: '', startDate: defaultStart(), endDate: '' + materialCode: '', name: '', spec: '', manageMode: '', itemType: '', startDate: defaultStart(), endDate: '' }) const matPage = reactive({ current: 1, size: 10 }) @@ -127,6 +127,7 @@ async function loadMaterials() { name: matFilter.name.trim(), spec: matFilter.spec.trim(), manageMode: matFilter.manageMode, + itemType: matFilter.itemType, startDate: matFilter.startDate, endDate: matFilter.endDate, page: matPage.current, @@ -213,7 +214,8 @@ async function exportMaterials() { materialCode: matFilter.materialCode.trim(), name: matFilter.name.trim(), spec: matFilter.spec.trim(), - manageMode: matFilter.manageMode + manageMode: matFilter.manageMode, + itemType: matFilter.itemType }, '物料档案.xlsx') ElMessage.success('已导出:' + name) } catch (e) { @@ -388,6 +390,13 @@ async function onMaterialFile(e) { {{ row.unit || '-' }} + + + + {{ row.itemType === 3 ? '成品' : (row.itemType === 2 ? '半成品' : '原材料') }} + + + diff --git a/bj_power_wms/frontend/src/pages/Inventory.vue b/bj_power_wms/frontend/src/pages/Inventory.vue index 188d0c4..0518b27 100644 --- a/bj_power_wms/frontend/src/pages/Inventory.vue +++ b/bj_power_wms/frontend/src/pages/Inventory.vue @@ -25,11 +25,12 @@ function defaultStart() { return `${y}-${m}-${day}` } -// manageMode: ''=全部 1=结构件 2=精密件 +// manageMode: ''=全部 1=结构件 2=精密件;category: ''=全部 1=原材料 2=半成品 3=成品 const filters = reactive({ materialCode: '', materialName: '', manageMode: '', + category: '', zoneCode: '', qualityStatus: '', startDate: defaultStart(), @@ -72,6 +73,7 @@ async function loadMaterial() { materialCode: filters.materialCode.trim(), materialName: filters.materialName.trim(), manageMode: filters.manageMode, + category: filters.category, page: matPage.current, pageSize: matPage.size } @@ -94,6 +96,7 @@ async function loadDetail() { zoneCode: filters.zoneCode, qualityStatus: filters.qualityStatus, manageMode: filters.manageMode, + category: filters.category, startDate: filters.startDate, endDate: filters.endDate, page: page.current, @@ -457,6 +460,11 @@ onMounted(() => { {{ typeLabel(row.manageMode) }} + + + {{ categoryLabel(row.category) }} + + {{ row.zoneCode || '未分区' }} diff --git a/bj_power_wms/internal/handler/excel.go b/bj_power_wms/internal/handler/excel.go index 12bb296..fff3078 100644 --- a/bj_power_wms/internal/handler/excel.go +++ b/bj_power_wms/internal/handler/excel.go @@ -2,6 +2,7 @@ package handler import ( "bytes" + "errors" "fmt" "net/http" "net/url" @@ -17,6 +18,38 @@ import ( "github.com/xuri/excelize/v2" ) +// normalizeExportRange 导出时间范围硬规则(2026-09-08 全项目统一): +// ① 起止都空 → 默认近 3 个月;② 只传一个 → 报错;③ 间隔 > 1 年 → 报错;④ 结束早于起始 → 报错。 +// 返回归一化后的 YYYY-MM-DD 字符串,调用方按原有逻辑解析过滤。 +func normalizeExportRange(startDate, endDate string) (string, string, error) { + if startDate == "" && endDate == "" { + now := time.Now() + return now.AddDate(0, -3, 0).Format("2006-01-02"), now.Format("2006-01-02"), nil + } + if startDate == "" || endDate == "" { + return "", "", errors.New("导出必须同时提供起始时间与结束时间") + } + st, err1 := time.ParseInLocation("2006-01-02", startDate, time.Local) + et, err2 := time.ParseInLocation("2006-01-02", endDate, time.Local) + if err1 != nil || err2 != nil { + return "", "", errors.New("时间格式应为 YYYY-MM-DD") + } + if et.Before(st) { + return "", "", errors.New("结束时间不能早于起始时间") + } + if et.Sub(st) > 366*24*time.Hour { + return "", "", errors.New("导出时间间隔不能超过 1 年") + } + return startDate, endDate, nil +} + +// exportRangeUnix 把 YYYY-MM-DD 区间转为 unix 秒闭区间 [start, end+1天) +func exportRangeUnix(startDate, endDate string) (int64, int64) { + st, _ := time.ParseInLocation("2006-01-02", startDate, time.Local) + et, _ := time.ParseInLocation("2006-01-02", endDate, time.Local) + return st.Unix(), et.Add(24*time.Hour).Unix() - 1 +} + // xlsxColumn 导出列定义:列名 + 取值函数(返回字符串) type xlsxColumn struct { Title string diff --git a/bj_power_wms/internal/handler/inbound.go b/bj_power_wms/internal/handler/inbound.go index 4ea31a7..64ebbcd 100644 --- a/bj_power_wms/internal/handler/inbound.go +++ b/bj_power_wms/internal/handler/inbound.go @@ -232,10 +232,10 @@ type inboundRow struct { // 结构件(manageMode=1)与精密件(manageMode=2)同主表(inbound_order),按 manageMode 区分类型 func applyInboundFilters(q *ent.InboundOrderQuery, r *http.Request) *ent.InboundOrderQuery { if v := r.URL.Query().Get("inboundNo"); v != "" { - q = q.Where(inboundorder.InboundNoContains(v)) + q = q.Where(inboundorder.InboundNoContainsFold(v)) } if v := r.URL.Query().Get("materialCode"); v != "" { - q = q.Where(inboundorder.MaterialCodeContains(v)) + q = q.Where(inboundorder.MaterialCodeContainsFold(v)) } if v := r.URL.Query().Get("manageMode"); v != "" { if m := atoi(v, 0); m != 0 { @@ -248,9 +248,9 @@ func applyInboundFilters(q *ent.InboundOrderQuery, r *http.Request) *ent.Inbound if v := r.URL.Query().Get("keyword"); v != "" { kw := v q = q.Where(inboundorder.Or( - inboundorder.MaterialCodeContains(kw), - inboundorder.InboundNoContains(kw), - inboundorder.MaterialNameContains(kw), + inboundorder.MaterialCodeContainsFold(kw), + inboundorder.InboundNoContainsFold(kw), + inboundorder.MaterialNameContainsFold(kw), )) } if v := r.URL.Query().Get("startDate"); v != "" { @@ -358,6 +358,10 @@ func queryInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { // 与 /api/outbound/export 保持一致。 func exportInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, _, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate")); rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } q := ctx.EntClient.InboundOrder.Query() q = applyInboundFilters(q, r) list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0()) diff --git a/bj_power_wms/internal/handler/inspection.go b/bj_power_wms/internal/handler/inspection.go index 8ea5ce3..8b66011 100644 --- a/bj_power_wms/internal/handler/inspection.go +++ b/bj_power_wms/internal/handler/inspection.go @@ -229,13 +229,18 @@ func queryInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { inspector := r.URL.Query().Get("inspector") startDate := r.URL.Query().Get("startDate") endDate := r.URL.Query().Get("endDate") + startDate, endDate, rerr := normalizeExportRange(startDate, endDate) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } q := ctx.EntClient.InspectionRecord.Query() if targetId != "" { - q = q.Where(inspectionrecord.TargetIDContains(targetId)) + q = q.Where(inspectionrecord.TargetIDContainsFold(targetId)) } if materialCode != "" { - q = q.Where(inspectionrecord.MaterialCodeContains(materialCode)) + q = q.Where(inspectionrecord.MaterialCodeContainsFold(materialCode)) } if inspectionType != "" { q = q.Where(inspectionrecord.InspectionTypeEQ(inspectionType)) @@ -244,7 +249,7 @@ func queryInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { q = q.Where(inspectionrecord.StatusEQ(status)) } if inspector != "" { - q = q.Where(inspectionrecord.InspectorContains(inspector)) + q = q.Where(inspectionrecord.InspectorContainsFold(inspector)) } // 时间筛选(默认近3个月由前端传入;兼容两端口径) if startDate != "" { @@ -290,13 +295,18 @@ func exportInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { inspector := r.URL.Query().Get("inspector") startDate := r.URL.Query().Get("startDate") endDate := r.URL.Query().Get("endDate") + startDate, endDate, rerr := normalizeExportRange(startDate, endDate) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } q := ctx.EntClient.InspectionRecord.Query() if targetId != "" { - q = q.Where(inspectionrecord.TargetIDContains(targetId)) + q = q.Where(inspectionrecord.TargetIDContainsFold(targetId)) } if materialCode != "" { - q = q.Where(inspectionrecord.MaterialCodeContains(materialCode)) + q = q.Where(inspectionrecord.MaterialCodeContainsFold(materialCode)) } if inspectionType != "" { q = q.Where(inspectionrecord.InspectionTypeEQ(inspectionType)) @@ -305,7 +315,7 @@ func exportInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { q = q.Where(inspectionrecord.StatusEQ(status)) } if inspector != "" { - q = q.Where(inspectionrecord.InspectorContains(inspector)) + q = q.Where(inspectionrecord.InspectorContainsFold(inspector)) } if startDate != "" { if t, err := time.ParseInLocation("2006-01-02", startDate, time.Local); err == nil { diff --git a/bj_power_wms/internal/handler/internalapi.go b/bj_power_wms/internal/handler/internalapi.go index 51f75ff..ee33e3b 100644 --- a/bj_power_wms/internal/handler/internalapi.go +++ b/bj_power_wms/internal/handler/internalapi.go @@ -6,9 +6,9 @@ import ( "bj_power_wms/ent/inboundorder" "bj_power_wms/ent/inventory" + "bj_power_wms/ent/material" "bj_power_wms/ent/ordermaterialledger" "bj_power_wms/ent/outboundorder" - "bj_power_wms/ent/semifinished" "bj_power_wms/internal/svc" ) @@ -89,7 +89,10 @@ func internalDisplayHandler(ctx *svc.ServiceContext) http.HandlerFunc { } snInStock, _ := ctx.EntClient.Inventory.Query().Where(inventory.ManageModeEQ(2), inventory.StatusIn("在库", "锁定")).Count(ctx0()) snOut, _ := ctx.EntClient.Inventory.Query().Where(inventory.ManageModeEQ(2), inventory.StatusEQ("出库")).Count(ctx0()) - semiInStock, _ := ctx.EntClient.SemiFinished.Query().Where(semifinished.StatusEQ("在库")).Count(ctx0()) + // 半成品统计:统一库存主表 category=2(semi_finisheds 已收编废弃) + semiInStock, _ := ctx.EntClient.Inventory.Query().Where(inventory.CategoryEQ(invCatSemi), inventory.StatusEQ("在库")).Count(ctx0()) + // 成品在库统计:category=3 + finishedInStock, _ := ctx.EntClient.Inventory.Query().Where(inventory.CategoryEQ(invCatFinished), inventory.StatusIn("在库", "锁定")).Count(ctx0()) pkgCount, _ := ctx.EntClient.OutboundOrder.Query().Where(outboundorder.BoxNoNEQ("")).Count(ctx0()) dayStart := time.Now().Truncate(24 * time.Hour).Unix() @@ -105,6 +108,7 @@ func internalDisplayHandler(ctx *svc.ServiceContext) http.HandlerFunc { "snInStock": snInStock, "snOut": snOut, "semiInStock": semiInStock, + "finishedInStock": finishedInStock, "packageCount": pkgCount, "inToday": inToday, "outToday": outToday, @@ -113,3 +117,92 @@ func internalDisplayHandler(ctx *svc.ServiceContext) http.HandlerFunc { }) } } + +// finishedInboundRequest 成品完工回流入库请求(MES 完工后调用,X-API-TOKEN 内部接口) +// 定稿(2026-09-07):成品 SN 由 MES 系统生成(系统必发号,实物可不贴码,允许手填覆盖), +// SN 与实物码解耦;完工品经本接口自动回流 WMS 统一库存主表(manage_mode=2 SN单件, category=3 成品)。 +type finishedInboundRequest struct { + ProductCode string `json:"productCode"` // 产品编码(成品物料编码) + ProductName string `json:"productName"` // 产品名称(档案缺失时兜底展示) + Sn string `json:"sn"` // 成品 SN(系统生成或手填,全局唯一) + OrderNo string `json:"orderNo"` // 关联工单号 + ZoneCode string `json:"zoneCode"` // 入库区域(可空=未分区) + QualityStatus string `json:"qualityStatus"` // 默认 合格(完工已过全工序校验) + SnSource string `json:"snSource"` // AUTO=系统生成 / MANUAL=手填,仅记录 + Operator string `json:"operator"` +} + +// finishedInboundHandler 成品完工回流入库 +// 物料档案缺失时不阻塞回流(库存行 material_code 仍落产品编码,名称用请求传入兜底), +// 记 eventlog 提示补建档——编码主数据仍以 WMS 为单一来源,产线不被档案缺失卡死。 +func finishedInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req finishedInboundRequest + if err := parseJSON(r, &req); err != nil { + fail(w, http.StatusBadRequest, "参数错误: "+err.Error()) + return + } + if req.Sn == "" || req.ProductCode == "" { + fail(w, http.StatusBadRequest, "sn 与 productCode 必填") + return + } + quality := req.QualityStatus + if quality == "" { + quality = "合格" + } + + // SN 全局防重(同品类成品 + 半成品 + 精密件 SN 都不允许撞号) + dup, err := ctx.EntClient.Inventory.Query(). + Where(inventory.ManageModeEQ(2), inventory.SnCodeEQ(req.Sn)).Exist(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + if dup { + fail(w, http.StatusConflict, "成品 SN 已存在库存: "+req.Sn) + return + } + + // 物料档案补名称;缺失时记日志(不阻塞回流) + materialName := req.ProductName + archiveMiss := false + if m, err := ctx.EntClient.Material.Query().Where(material.CodeEQ(req.ProductCode)).First(ctx0()); err == nil && m != nil { + materialName = m.Name + } else { + archiveMiss = true + } + + rec, err := ctx.EntClient.Inventory.Create(). + SetManageMode(2). + SetCategory(invCatFinished). + SetMaterialCode(req.ProductCode). + SetMaterialName(materialName). + SetSnCode(req.Sn). + SetQuantity(1). + SetQualityStatus(quality). + SetNillableZoneCode(strPtr(req.ZoneCode)). + SetStatus("在库"). + SetInboundNo("MES:" + req.OrderNo). + SetOrderNo(req.OrderNo). + SetRemark("成品完工回流(SN来源:" + snSourceLabel(req.SnSource) + ")"). + Save(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + + ctx.EventLog.Write(ctx0(), "finished.inbound", req.Operator, "finished", req.Sn, + "成品完工回流入库 "+req.Sn, map[string]any{ + "productCode": req.ProductCode, "orderNo": req.OrderNo, + "snSource": req.SnSource, "archiveMiss": archiveMiss, + }) + ok(w, rec) + } +} + +func snSourceLabel(s string) string { + if s == "MANUAL" { + return "手填" + } + return "系统生成" +} diff --git a/bj_power_wms/internal/handler/material.go b/bj_power_wms/internal/handler/material.go index 2250de7..81411a0 100644 --- a/bj_power_wms/internal/handler/material.go +++ b/bj_power_wms/internal/handler/material.go @@ -13,6 +13,18 @@ import ( "github.com/xuri/excelize/v2" ) +// itemTypeLabel 物料品类标签(与 inventories.category 对应):1原材料/2半成品/3成品 +func itemTypeLabel(t int) string { + switch t { + case invCatSemi: + return "半成品" + case invCatFinished: + return "成品" + default: + return "原材料" + } +} + // createMaterialHandler 创建物料档案 func createMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -133,28 +145,32 @@ func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { name := r.URL.Query().Get("name") spec := r.URL.Query().Get("spec") manageMode := atoi(r.URL.Query().Get("manageMode"), 0) + itemType := atoi(r.URL.Query().Get("itemType"), 0) startDate := r.URL.Query().Get("startDate") endDate := r.URL.Query().Get("endDate") keyword := r.URL.Query().Get("keyword") // 兼容旧版,作为物料编码/名称兜底 q := ctx.EntClient.Material.Query() if materialCode != "" { - q = q.Where(material.CodeContains(materialCode)) + q = q.Where(material.CodeContainsFold(materialCode)) } if name != "" { - q = q.Where(material.NameContains(name)) + q = q.Where(material.NameContainsFold(name)) } if spec != "" { - q = q.Where(material.SpecContains(spec)) + q = q.Where(material.SpecContainsFold(spec)) } if manageMode != 0 { q = q.Where(material.ManageModeEQ(manageMode)) } + if itemType != 0 { + q = q.Where(material.ItemTypeEQ(itemType)) + } if keyword != "" { q = q.Where( material.Or( - material.CodeContains(keyword), - material.NameContains(keyword), + material.CodeContainsFold(keyword), + material.NameContainsFold(keyword), ), ) } @@ -175,7 +191,7 @@ func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")). + list, err := q.Order(ent.Asc("id")). Offset((page - 1) * pageSize). Limit(pageSize). All(ctx0()) @@ -196,7 +212,7 @@ func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { // listMaterialsHandler 下拉框全量列表(不翻页) func listMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - list, err := ctx.EntClient.Material.Query().Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0()) + list, err := ctx.EntClient.Material.Query().Order(ent.Desc("created_at"), ent.Desc("id")).Limit(100).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return @@ -273,23 +289,34 @@ func exportMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { materialCode := r.URL.Query().Get("materialCode") name := r.URL.Query().Get("name") + startDate, endDate, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate")) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } + startUnix, endUnix := exportRangeUnix(startDate, endDate) spec := r.URL.Query().Get("spec") manageMode := atoi(r.URL.Query().Get("manageMode"), 0) + itemType := atoi(r.URL.Query().Get("itemType"), 0) q := ctx.EntClient.Material.Query() + q = q.Where(material.CreatedAtGTE(startUnix), material.CreatedAtLTE(endUnix)) if materialCode != "" { - q = q.Where(material.CodeContains(materialCode)) + q = q.Where(material.CodeContainsFold(materialCode)) } if name != "" { - q = q.Where(material.NameContains(name)) + q = q.Where(material.NameContainsFold(name)) } if spec != "" { - q = q.Where(material.SpecContains(spec)) + q = q.Where(material.SpecContainsFold(spec)) } if manageMode != 0 { q = q.Where(material.ManageModeEQ(manageMode)) } - list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0()) + if itemType != 0 { + q = q.Where(material.ItemTypeEQ(itemType)) + } + list, err := q.Order(ent.Asc("id")).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return @@ -301,6 +328,7 @@ func exportMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { ShortName string Spec string Unit string + ItemType int ManageMode int Description string CreatedAt int64 @@ -309,17 +337,19 @@ func exportMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc { for _, m := range list { rows = append(rows, exportRow{ Code: m.Code, Name: m.Name, ShortName: m.ShortName, Spec: m.Spec, - Unit: m.Unit, ManageMode: m.ManageMode, Description: m.Description, CreatedAt: m.CreatedAt, + Unit: m.Unit, ItemType: m.ItemType, ManageMode: m.ManageMode, + Description: m.Description, CreatedAt: m.CreatedAt, }) } if r.URL.Query().Get("format") == "xlsx" { - headers := []string{"物料编码", "名称", "简称", "规格", "单位", "类型", "说明", "创建时间"} + headers := []string{"物料编码", "名称", "简称", "规格", "单位", "品类", "类型", "说明", "创建时间"} matrix := make([][]any, 0, len(rows)) for _, rr := range rows { matrix = append(matrix, []any{ rr.Code, rr.Name, rr.ShortName, rr.Spec, rr.Unit, - manageModeLabel(rr.ManageMode), rr.Description, unixFmt(rr.CreatedAt), + itemTypeLabel(rr.ItemType), manageModeLabel(rr.ManageMode), + rr.Description, unixFmt(rr.CreatedAt), }) } sendExcel(w, xlsxFilename("物料档案"), headers, matrix) diff --git a/bj_power_wms/internal/handler/misc.go b/bj_power_wms/internal/handler/misc.go index 937e028..5a00915 100644 --- a/bj_power_wms/internal/handler/misc.go +++ b/bj_power_wms/internal/handler/misc.go @@ -4,19 +4,31 @@ import ( "net/http" "strconv" "strings" + "time" "bj_power_wms/ent" + "bj_power_wms/ent/inventory" + "bj_power_wms/ent/material" "bj_power_wms/ent/ordermaterialledger" - "bj_power_wms/ent/semifinished" "bj_power_wms/internal/svc" ) +// 库存品类(与 schema/inventory.go 保持一致):1=原材料 2=半成品 3=成品 +const ( + invCatRaw = 1 + invCatSemi = 2 + invCatFinished = 3 +) + // 半成品 / 台账 相关(盘点见 stocktake.go) +// +// 【统一库存主表收编 2026-09-07】半成品不再写独立 semi_finisheds 表, +// 全部落 inventories(manage_mode=2 SN单件, category=2 半成品)。 +// 三个 semi 接口签名与响应结构保持不变,MES / 前端零改造平滑切换。 // semiInboundHandler 半成品入库(携带已完成工序) -// 与 MES 闭环:MES 工位报工暂存退库时调用本接口,把半成品 SN 入 WMS 库。 -// 为保持两系统解耦,materialCode 不强制要求——优先用请求传入, -// 其次按 SN 反查 WMS 序列号台账,都没有则留空(SN 本身即追溯锚点)。 +// 与 MES 闭环:MES 工位报工暂存退库时调用本接口,把半成品 SN 入 WMS 库(inventories, category=2)。 +// 为保持两系统解耦,materialCode 不强制要求——优先用请求传入,都没有则留空(SN 本身即追溯锚点)。 func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { @@ -37,8 +49,9 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - exists, err := ctx.EntClient.SemiFinished.Query(). - Where(semifinished.SnEQ(req.Sn)). + // SN 防重:同一 SN 只允许一条在库/锁定/出库的库存行(含已出库历史,避免重复回流) + exists, err := ctx.EntClient.Inventory.Query(). + Where(inventory.CategoryEQ(invCatSemi), inventory.SnCodeEQ(req.Sn)). Exist(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) @@ -59,16 +72,29 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { completed = strings.Join(parts, ",") } - // 物料编码:以请求传入为准(半成品是独立固定业务类型,不依赖统一库存表) + // 物料编码:以请求传入为准;名称从物料档案补齐(档案缺失则用编码兜底) materialCode := req.MaterialCode + materialName := materialCode + if materialCode != "" { + if m, err := ctx.EntClient.Material.Query().Where(material.CodeEQ(materialCode)).First(ctx0()); err == nil && m != nil { + materialName = m.Name + } + } - rec, err := ctx.EntClient.SemiFinished.Create(). - SetSn(req.Sn). - SetNillableMaterialCode(strPtr(materialCode)). - SetNillableCompletedProcess(strPtr(completed)). + rec, err := ctx.EntClient.Inventory.Create(). + SetManageMode(2). + SetCategory(invCatSemi). + SetSnCode(req.Sn). + SetMaterialCode(materialCode). + SetMaterialName(materialName). + SetQuantity(1). + SetQualityStatus("未检"). SetNillableZoneCode(strPtr(req.ZoneCode)). SetStatus("在库"). - SetNillableOrderNo(strPtr(req.OrderNo)). + SetNillableInboundNo(strPtr(req.OrderNo)). + SetOrderNo(req.OrderNo). + SetCompletedProcess(completed). + SetRemark("半成品完工暂存回流"). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) @@ -77,11 +103,11 @@ func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { ctx.EventLog.Write(ctx0(), "semi.inbound", r.Header.Get("X-Username"), "semi", req.Sn, "半成品入库 "+req.Sn, map[string]any{"materialCode": materialCode}) - ok(w, rec) + ok(w, semiView(rec)) } } -// semiOutboundHandler 半成品出库(重上线) +// semiOutboundHandler 半成品出库(重上线):更新 inventories 状态 func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { @@ -98,8 +124,8 @@ func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - rec, err := ctx.EntClient.SemiFinished.Query(). - Where(semifinished.SnEQ(req.Sn)). + rec, err := ctx.EntClient.Inventory.Query(). + Where(inventory.CategoryEQ(invCatSemi), inventory.SnCodeEQ(req.Sn)). Only(ctx0()) if err != nil { fail(w, http.StatusNotFound, "半成品不存在: "+req.Sn) @@ -110,17 +136,47 @@ func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - ctx.EntClient.SemiFinished.UpdateOneID(rec.ID). + ctx.EntClient.Inventory.UpdateOneID(rec.ID). SetStatus("出库"). + SetUpdatedAt(time.Now().Unix()). ExecX(ctx0()) ctx.EventLog.Write(ctx0(), "semi.outbound", r.Header.Get("X-Username"), "semi", req.Sn, - "半成品出库 "+req.Sn, nil) + "半成品出库(重上线) "+req.Sn, nil) ok(w, map[string]any{"sn": req.Sn, "status": "出库"}) } } -// querySemiHandler 半成品查询 +// semiView 把统一库存行映射为半成品视图(保持原 /api/semi/query 响应结构,前端零改造) +type semiViewRow struct { + ID int `json:"id"` + Sn string `json:"sn"` + MaterialCode string `json:"materialCode"` + CompletedProcess string `json:"completedProcess"` + Quantity int `json:"quantity"` + ZoneCode string `json:"zoneCode"` + Status string `json:"status"` + OrderNo string `json:"orderNo"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +func semiView(inv *ent.Inventory) *semiViewRow { + return &semiViewRow{ + ID: inv.ID, + Sn: inv.SnCode, + MaterialCode: inv.MaterialCode, + CompletedProcess: inv.CompletedProcess, + Quantity: inv.Quantity, + ZoneCode: inv.ZoneCode, + Status: inv.Status, + OrderNo: inv.OrderNo, + CreatedAt: inv.CreatedAt, + UpdatedAt: inv.UpdatedAt, + } +} + +// querySemiHandler 半成品查询(读统一库存主表 category=2) func querySemiHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { page := atoi(r.URL.Query().Get("page"), 1) @@ -128,12 +184,12 @@ func querySemiHandler(ctx *svc.ServiceContext) http.HandlerFunc { materialCode := r.URL.Query().Get("materialCode") status := r.URL.Query().Get("status") - q := ctx.EntClient.SemiFinished.Query() + q := ctx.EntClient.Inventory.Query().Where(inventory.CategoryEQ(invCatSemi)) if materialCode != "" { - q = q.Where(semifinished.MaterialCodeEQ(materialCode)) + q = q.Where(inventory.MaterialCodeEqualFold(materialCode)) } if status != "" { - q = q.Where(semifinished.StatusEQ(status)) + q = q.Where(inventory.StatusEQ(status)) } total, err := q.Count(ctx0()) @@ -147,10 +203,14 @@ func querySemiHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } + rows := make([]*semiViewRow, 0, len(list)) + for _, inv := range list { + rows = append(rows, semiView(inv)) + } ok(w, map[string]any{ "total": total, - "list": list, + "list": rows, "page": page, "pageSize": pageSize, }) @@ -172,7 +232,7 @@ func queryLedgerHandler(ctx *svc.ServiceContext) http.HandlerFunc { q = q.Where(ordermaterialledger.OrderNoEQ(orderNo)) } if materialCode != "" { - q = q.Where(ordermaterialledger.MaterialCodeEQ(materialCode)) + q = q.Where(ordermaterialledger.MaterialCodeEqualFold(materialCode)) } if materialName != "" { q = q.Where(ordermaterialledger.MaterialNameContains(materialName)) diff --git a/bj_power_wms/internal/handler/outbound.go b/bj_power_wms/internal/handler/outbound.go index 79f3e4f..3dbb3b3 100644 --- a/bj_power_wms/internal/handler/outbound.go +++ b/bj_power_wms/internal/handler/outbound.go @@ -246,16 +246,16 @@ func queryOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { q := ctx.EntClient.OutboundOrder.Query() if orderNo != "" { - q = q.Where(outboundorder.OrderNoEQ(orderNo)) + q = q.Where(outboundorder.OrderNoEqualFold(orderNo)) } if materialCode != "" { - q = q.Where(outboundorder.MaterialCodeEQ(materialCode)) + q = q.Where(outboundorder.MaterialCodeEqualFold(materialCode)) } if outboundNo != "" { - q = q.Where(outboundorder.OutboundNoEQ(outboundNo)) + q = q.Where(outboundorder.OutboundNoEqualFold(outboundNo)) } if boxNo != "" { - q = q.Where(outboundorder.BoxNoContains(boxNo)) + q = q.Where(outboundorder.BoxNoContainsFold(boxNo)) } total, err := q.Count(ctx0()) @@ -480,19 +480,26 @@ func exportOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { orderNo := r.URL.Query().Get("orderNo") materialCode := r.URL.Query().Get("materialCode") boxNo := r.URL.Query().Get("boxNo") + startDate, endDate, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate")) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } + startUnix, endUnix := exportRangeUnix(startDate, endDate) q := ctx.EntClient.OutboundOrder.Query() + q = q.Where(outboundorder.CreatedAtGTE(startUnix), outboundorder.CreatedAtLTE(endUnix)) if outboundType != "" { q = q.Where(outboundorder.OutboundTypeEQ(outboundType)) } if orderNo != "" { - q = q.Where(outboundorder.OrderNoEQ(orderNo)) + q = q.Where(outboundorder.OrderNoEqualFold(orderNo)) } if materialCode != "" { - q = q.Where(outboundorder.MaterialCodeEQ(materialCode)) + q = q.Where(outboundorder.MaterialCodeEqualFold(materialCode)) } if boxNo != "" { - q = q.Where(outboundorder.BoxNoContains(boxNo)) + q = q.Where(outboundorder.BoxNoContainsFold(boxNo)) } list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0()) diff --git a/bj_power_wms/internal/handler/producttype.go b/bj_power_wms/internal/handler/producttype.go new file mode 100644 index 0000000..8801f71 --- /dev/null +++ b/bj_power_wms/internal/handler/producttype.go @@ -0,0 +1,291 @@ +package handler + +import ( + "fmt" + "net/http" + "time" + + "bj_power_wms/ent" + "bj_power_wms/ent/inventory" + "bj_power_wms/ent/material" + "bj_power_wms/internal/svc" +) + +// 成品档案(产品型号)内部接口 —— 编码主数据单一来源在 WMS。 +// MES 不再双录产品型号,通过 wmsclient 实时代理本组接口; +// item_type=3(成品) 的 materials 行即产品型号档案,与统一库存主表 category 呼应。 + +const productTypePrefix = "PROD" + +func productTypeQuery(ctx *svc.ServiceContext) *ent.MaterialQuery { + return ctx.EntClient.Material.Query().Where(material.ItemTypeEQ(3)) +} + +// listProductTypesInternalHandler GET /api/internal/product-types +// 参数:keyword(编码/名称/类别模糊,大小写折叠)、isActive(true/false)、page、pageSize +// 无 page 参数时返回全量数组(兼容下拉场景);带 page 时返回 {total,list,page,pageSize} +func listProductTypesInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + keyword := r.URL.Query().Get("keyword") + isActive := r.URL.Query().Get("isActive") + page := atoi(r.URL.Query().Get("page"), 0) + pageSize := atoi(r.URL.Query().Get("pageSize"), 20) + + q := productTypeQuery(ctx) + if keyword != "" { + q = q.Where(material.Or( + material.CodeContainsFold(keyword), + material.NameContainsFold(keyword), + material.DescriptionContainsFold(keyword), + )) + } + if isActive == "true" { + q = q.Where(material.IsActiveEQ(true)) + } else if isActive == "false" { + q = q.Where(material.IsActiveEQ(false)) + } + + if page <= 0 { + // 下拉/兼容模式:全量(基础档案量级小),id asc 新建在底部 + list, err := q.Order(ent.Asc("id")).All(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + ok(w, productTypeRows(list)) + return + } + + total, err := q.Count(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + list, err := q.Order(ent.Asc("id")). + Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + ok(w, map[string]any{ + "total": total, + "list": productTypeRows(list), + "page": page, + "pageSize": pageSize, + }) + } +} + +type productTypeRow struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + Category string `json:"category"` + Remark string `json:"remark"` + IsActive bool `json:"isActive"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +func productTypeRows(list []*ent.Material) []*productTypeRow { + rows := make([]*productTypeRow, 0, len(list)) + for _, m := range list { + rows = append(rows, &productTypeRow{ + ID: m.ID, + Code: m.Code, + Name: m.Name, + Category: m.Description, // MES 迁移时 category 存于 description + Remark: m.ShortName, + IsActive: m.IsActive, + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + }) + } + return rows +} + +// genProductTypeCode 生成产品型号编码 PROD-YYYYMMDD-流水(当日序号递增) +func genProductTypeCode(ctx *svc.ServiceContext) (string, error) { + date := time.Now().Format("20060102") + prefix := fmt.Sprintf("%s-%s-", productTypePrefix, date) + all, err := ctx.EntClient.Material.Query(). + Where(material.CodeHasPrefix(prefix)). + All(ctx0()) + if err != nil { + return "", err + } + maxSeq := 0 + for _, m := range all { + var seq int + if _, err := fmt.Sscanf(m.Code, prefix+"%d", &seq); err == nil && seq > maxSeq { + maxSeq = seq + } + } + return fmt.Sprintf("%s%03d", prefix, maxSeq+1), nil +} + +// createProductTypeInternalHandler POST /api/internal/product-types +// {name(必填), code(可空→系统生成), category, remark, isActive} +func createProductTypeInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + Code string `json:"code"` + Name string `json:"name"` + Category string `json:"category"` + Remark string `json:"remark"` + IsActive *bool `json:"isActive"` + } + if err := parseJSON(r, &req); err != nil { + fail(w, http.StatusBadRequest, "参数错误") + return + } + if req.Name == "" { + fail(w, http.StatusBadRequest, "名称必填") + return + } + code := req.Code + if code == "" { + var err error + if code, err = genProductTypeCode(ctx); err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + } + exists, _ := ctx.EntClient.Material.Query().Where(material.CodeEQ(code)).Exist(ctx0()) + if exists { + fail(w, http.StatusConflict, "编码已存在: "+code) + return + } + active := true + if req.IsActive != nil { + active = *req.IsActive + } + m, err := ctx.EntClient.Material.Create(). + SetCode(code).SetName(req.Name). + SetNillableDescription(strPtr(req.Category)). + SetNillableShortName(strPtr(req.Remark)). + SetItemType(3).SetIsActive(active). + SetManageMode(2).SetIsSerialManaged(true). // 成品按 SN 管理,与库存统一设计一致 + Save(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + ctx.EventLog.Write(ctx0(), "producttype.create", r.Header.Get("X-Username"), "material", code, + "新增产品型号 "+code+" "+req.Name, nil) + ok(w, productTypeRows([]*ent.Material{m})[0]) + } +} + +// updateProductTypeInternalHandler PUT /api/internal/product-types +// {id(必填), name, category, remark, isActive} —— 编码创建后不可修改(忽略传入 code) +func updateProductTypeInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + ID int `json:"id"` + Code string `json:"code"` // 仅接收不使用:编码不可改 + Name string `json:"name"` + Category string `json:"category"` + Remark string `json:"remark"` + IsActive *bool `json:"isActive"` + } + if err := parseJSON(r, &req); err != nil || req.ID <= 0 { + fail(w, http.StatusBadRequest, "参数错误") + return + } + cur, err := ctx.EntClient.Material.Get(ctx0(), req.ID) + if err != nil || cur.ItemType != 3 { + fail(w, http.StatusNotFound, "产品型号不存在") + return + } + u := ctx.EntClient.Material.UpdateOneID(req.ID). + SetName(req.Name). + SetDescription(req.Category). + SetShortName(req.Remark) + if req.IsActive != nil { + u = u.SetIsActive(*req.IsActive) + } + if _, err := u.Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + ctx.EventLog.Write(ctx0(), "producttype.update", r.Header.Get("X-Username"), "material", cur.Code, + "修改产品型号 "+cur.Code, nil) + ok(w, nil) + } +} + +// deleteProductTypeInternalHandler POST /api/internal/product-types/delete {id} +// 删除保护:库存/入库/出库中仍引用该编码时禁删(MES 侧工单/BOM 引用由 MES 代理先查) +func deleteProductTypeInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + ID int `json:"id"` + } + if err := parseJSON(r, &req); err != nil || req.ID <= 0 { + fail(w, http.StatusBadRequest, "参数错误") + return + } + cur, err := ctx.EntClient.Material.Get(ctx0(), req.ID) + if err != nil || cur.ItemType != 3 { + fail(w, http.StatusNotFound, "产品型号不存在") + return + } + if n, _ := ctx.EntClient.Inventory.Query().Where(inventory.MaterialCodeEqualFold(cur.Code)).Count(ctx0()); n > 0 { + fail(w, http.StatusConflict, "该产品型号已有库存记录("+itoa(n)+"条),不可删除;建议停用(下架)") + return + } + if err := ctx.EntClient.Material.DeleteOneID(req.ID).Exec(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + ctx.EventLog.Write(ctx0(), "producttype.delete", r.Header.Get("X-Username"), "material", cur.Code, + "删除产品型号 "+cur.Code, nil) + ok(w, nil) + } +} + +// exportProductTypesInternalHandler GET /api/internal/product-types/export?format=xlsx +// 全量导出(按筛选),执行统一导出时间硬规则 +func exportProductTypesInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + keyword := r.URL.Query().Get("keyword") + isActive := r.URL.Query().Get("isActive") + startDate, endDate, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate")) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } + startUnix, endUnix := exportRangeUnix(startDate, endDate) + + q := productTypeQuery(ctx) + q = q.Where(material.CreatedAtGTE(startUnix), material.CreatedAtLTE(endUnix)) + if keyword != "" { + q = q.Where(material.Or( + material.CodeContainsFold(keyword), + material.NameContainsFold(keyword), + material.DescriptionContainsFold(keyword), + )) + } + if isActive == "true" { + q = q.Where(material.IsActiveEQ(true)) + } else if isActive == "false" { + q = q.Where(material.IsActiveEQ(false)) + } + list, err := q.Order(ent.Asc("id")).All(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + headers := []string{"ID", "编码", "名称", "类别", "启用", "创建时间"} + matrix := make([][]any, 0, len(list)) + for _, m := range list { + active := "启用" + if !m.IsActive { + active = "停用" + } + matrix = append(matrix, []any{m.ID, m.Code, m.Name, m.Description, active, unixFmt(m.CreatedAt)}) + } + sendExcel(w, xlsxFilename("产品型号"), headers, matrix) + } +} diff --git a/bj_power_wms/internal/handler/rbac.go b/bj_power_wms/internal/handler/rbac.go index 8e28a03..4a098bb 100644 --- a/bj_power_wms/internal/handler/rbac.go +++ b/bj_power_wms/internal/handler/rbac.go @@ -164,7 +164,7 @@ func listUsersHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } - list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")). + list, err := q.Order(ent.Asc("id")). Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) diff --git a/bj_power_wms/internal/handler/rbac_admin.go b/bj_power_wms/internal/handler/rbac_admin.go index 66174ee..d6d795d 100644 --- a/bj_power_wms/internal/handler/rbac_admin.go +++ b/bj_power_wms/internal/handler/rbac_admin.go @@ -341,6 +341,12 @@ func deleteRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "内置角色不可删除: "+target.Code) return } + // 删除保护:角色仍被用户引用时禁删(按 role_id 引用 + 旧 role 编码字段双口径) + used, _ := ctx.EntClient.User.Query().Where(user.Or(user.RoleIDEQ(req.ID), user.RoleEQ(target.Code))).Count(ctx0()) + if used > 0 { + fail(w, http.StatusConflict, "该角色仍被 "+strconv.Itoa(used)+" 个账号使用,请先在账号管理中调整这些账号的角色") + return + } if err := ctx.EntClient.Role.DeleteOneID(req.ID).Exec(ctx0()); err != nil { fail(w, http.StatusInternalServerError, err.Error()) return diff --git a/bj_power_wms/internal/handler/routes.go b/bj_power_wms/internal/handler/routes.go index 293e595..6b0312a 100644 --- a/bj_power_wms/internal/handler/routes.go +++ b/bj_power_wms/internal/handler/routes.go @@ -170,6 +170,12 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { {Method: http.MethodGet, Path: "/api/internal/stock/query", Handler: wrapInternal(ctx)(queryStockHandler(ctx))}, {Method: http.MethodPost, Path: "/api/internal/semi/inbound", Handler: wrapInternal(ctx)(semiInboundHandler(ctx))}, {Method: http.MethodPost, Path: "/api/internal/semi/outbound", Handler: wrapInternal(ctx)(semiOutboundHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/internal/finished/inbound", Handler: wrapInternal(ctx)(finishedInboundHandler(ctx))}, + {Method: http.MethodGet, Path: "/api/internal/product-types", Handler: wrapInternal(ctx)(listProductTypesInternalHandler(ctx))}, + {Method: http.MethodGet, Path: "/api/internal/product-types/export", Handler: wrapInternal(ctx)(exportProductTypesInternalHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/internal/product-types", Handler: wrapInternal(ctx)(createProductTypeInternalHandler(ctx))}, + {Method: http.MethodPut, Path: "/api/internal/product-types", Handler: wrapInternal(ctx)(updateProductTypeInternalHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/internal/product-types/delete", Handler: wrapInternal(ctx)(deleteProductTypeInternalHandler(ctx))}, {Method: http.MethodPost, Path: "/api/internal/material/exists", Handler: wrapInternal(ctx)(materialExistsHandler(ctx))}, {Method: http.MethodPost, Path: "/api/internal/dock/pallet-sync", Handler: syncDockPalletHandler(ctx)}, }, diff --git a/bj_power_wms/internal/handler/stock.go b/bj_power_wms/internal/handler/stock.go index cd1951d..cf8df02 100644 --- a/bj_power_wms/internal/handler/stock.go +++ b/bj_power_wms/internal/handler/stock.go @@ -22,6 +22,19 @@ type stockKey struct { ZoneCode string QualityStatus string ManageMode int + Category int // 品类 1原材料/2半成品/3成品(统一主表收编后加入分组) +} + +// categoryLabel 品类中文名 +func categoryLabel(c int) string { + switch c { + case invCatSemi: + return "半成品" + case invCatFinished: + return "成品" + default: + return "原材料" + } } // stockAggRow 聚合主列表的行结构(按 物料+区域+质量+管理粒度 聚合,只返回数量,不铺开明细) @@ -95,6 +108,7 @@ type aggregateStockArgs struct { ZoneCode string QualityStatus string ManageMode string + Category string // 1原材料/2半成品/3成品/空=全部 InboundNo string StartDate string EndDate string @@ -128,7 +142,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR )) } if a.ZoneCode != "" { - q = q.Where(inventory.ZoneCodeEQ(a.ZoneCode)) + q = q.Where(inventory.ZoneCodeEqualFold(a.ZoneCode)) } if a.QualityStatus != "" { q = q.Where(inventory.QualityStatusEQ(a.QualityStatus)) @@ -141,6 +155,13 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR } else if a.ManageMode == "2" { q = q.Where(inventory.ManageModeEQ(2)) } + if a.Category == "1" { + q = q.Where(inventory.CategoryEQ(invCatRaw)) + } else if a.Category == "2" { + q = q.Where(inventory.CategoryEQ(invCatSemi)) + } else if a.Category == "3" { + q = q.Where(inventory.CategoryEQ(invCatFinished)) + } // 时间筛选:默认近3个月 startUnix := int64(0) if a.StartDate != "" { @@ -206,7 +227,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR if zc == "" { zc = "未分区" } - key := stockKey{inv.MaterialCode, zc, inv.QualityStatus, inv.ManageMode} + key := stockKey{inv.MaterialCode, zc, inv.QualityStatus, inv.ManageMode, inv.Category} g, ok := group[key] if !ok { g = &agg{} @@ -279,6 +300,7 @@ type materialAggRow struct { MaterialName string `json:"materialName"` Spec string `json:"spec"` ManageMode int `json:"manageMode"` // 1结构件/2精密件 + Category int `json:"category"` // 品类 1原材料/2半成品/3成品 TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数) LockedQty int `json:"lockedQty"` // 锁定量 AvailQty int `json:"availQty"` // 可用量 = total - locked @@ -299,6 +321,7 @@ type materialSummaryArgs struct { MaterialCode string MaterialName string ManageMode string + Category string // 1原材料/2半成品/3成品/空=全部 Page int PageSize int } @@ -341,6 +364,13 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ } else if a.ManageMode == "2" { q = q.Where(inventory.ManageModeEQ(2)) } + if a.Category == "1" { + q = q.Where(inventory.CategoryEQ(invCatRaw)) + } else if a.Category == "2" { + q = q.Where(inventory.CategoryEQ(invCatSemi)) + } else if a.Category == "3" { + q = q.Where(inventory.CategoryEQ(invCatFinished)) + } all, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0()) if err != nil { @@ -378,6 +408,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ qPending int qRejected int mMode int // 管理粒度(同物料唯一,取首见行的值) + mCategory int // 品类(同物料唯一,取首见行的值) batchCnt int snCnt int zones []string @@ -391,7 +422,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ key := inv.MaterialCode g, ok := group[key] if !ok { - g = &agg{mMode: inv.ManageMode, zoneSeen: map[string]bool{}} + g = &agg{mMode: inv.ManageMode, mCategory: inv.Category, zoneSeen: map[string]bool{}} group[key] = g order = append(order, key) } @@ -454,6 +485,7 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ MaterialName: name, Spec: matSpec[code], ManageMode: g.mMode, + Category: g.mCategory, TotalQty: g.totalQty, LockedQty: g.lockedQty, AvailQty: g.totalQty - g.lockedQty, @@ -492,6 +524,7 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc { } qualityStatus := r.URL.Query().Get("qualityStatus") manageMode := atoi(r.URL.Query().Get("manageMode"), 0) + category := atoi(r.URL.Query().Get("category"), 0) page := atoi(r.URL.Query().Get("page"), 1) pageSize := atoi(r.URL.Query().Get("pageSize"), 50) @@ -499,10 +532,10 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc { q = q.Where(inventory.StatusIn("在库", "锁定")) q = q.Where(inventory.Or(inventory.ManageModeEQ(2), inventory.QuantityGT(0))) if materialCode != "" { - q = q.Where(inventory.MaterialCodeEQ(materialCode)) + q = q.Where(inventory.MaterialCodeEqualFold(materialCode)) } if zoneCode != "" { - q = q.Where(inventory.ZoneCodeEQ(zoneCode)) + q = q.Where(inventory.ZoneCodeEqualFold(zoneCode)) } if qualityStatus != "" { q = q.Where(inventory.QualityStatusEQ(qualityStatus)) @@ -510,6 +543,9 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc { if manageMode != 0 { q = q.Where(inventory.ManageModeEQ(manageMode)) } + if category != 0 { + q = q.Where(inventory.CategoryEQ(category)) + } total, err := q.Count(ctx0()) if err != nil { @@ -588,6 +624,11 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { sendExcel(w, xlsxFilename("物料汇总"), headers, matrix) return } + startDate, endDate, rerr := normalizeExportRange(r.URL.Query().Get("startDate"), r.URL.Query().Get("endDate")) + if rerr != nil { + fail(w, http.StatusBadRequest, rerr.Error()) + return + } rows, _, err := aggregateStock(ctx, aggregateStockArgs{ MaterialCode: r.URL.Query().Get("materialCode"), MaterialName: r.URL.Query().Get("materialName"), @@ -595,8 +636,8 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { QualityStatus: r.URL.Query().Get("qualityStatus"), ManageMode: r.URL.Query().Get("manageMode"), InboundNo: r.URL.Query().Get("inboundNo"), - StartDate: r.URL.Query().Get("startDate"), - EndDate: r.URL.Query().Get("endDate"), + StartDate: startDate, + EndDate: endDate, Page: 1, PageSize: 1000000, // 全量 }) @@ -662,9 +703,17 @@ func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) { if zc == "" { zc = "未分区" } + // 类型标签:批次 / 精密件 / 半成品 / 成品(统一主表收编后按品类细分) t := "批次" if inv.ManageMode == 2 { - t = "精密件" + switch inv.Category { + case invCatSemi: + t = "半成品" + case invCatFinished: + t = "成品" + default: + t = "精密件" + } } key := zc + "|" + inv.QualityStatus + "|" + t g, ok := agg[key] diff --git a/bj_power_wms/internal/handler/zone.go b/bj_power_wms/internal/handler/zone.go index 3e237e8..6d8f9d4 100644 --- a/bj_power_wms/internal/handler/zone.go +++ b/bj_power_wms/internal/handler/zone.go @@ -106,7 +106,7 @@ func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { q = q.Where(zone.ZoneCodeContainsFold(zoneCode)) } if zoneName != "" { - q = q.Where(zone.ZoneNameContains(zoneName)) + q = q.Where(zone.ZoneNameContainsFold(zoneName)) } if status != "" { q = q.Where(zone.StatusEQ(status)) @@ -114,8 +114,8 @@ func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { if keyword != "" { q = q.Where( zone.Or( - zone.ZoneCodeContains(keyword), - zone.ZoneNameContains(keyword), + zone.ZoneCodeContainsFold(keyword), + zone.ZoneNameContainsFold(keyword), ), ) } @@ -134,7 +134,7 @@ func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } - list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")). + list, err := q.Order(ent.Asc("id")). Offset((page - 1) * pageSize). Limit(pageSize). All(ctx0()) diff --git a/bj_power_wms/schema/inventory.go b/bj_power_wms/schema/inventory.go index ef1fa4c..b8875cc 100644 --- a/bj_power_wms/schema/inventory.go +++ b/bj_power_wms/schema/inventory.go @@ -22,15 +22,26 @@ import ( // 可用量 = quantity - locked_qty(锁定时为 0)。 // // 区域(zone_code)只到 Z01~Z04 大分区级别,不引入货架/层/格(业务决策:小货架高频取货场景无需精确货位索引)。 -// 半成品(semifinished)是独立的固定业务类型,不进本表(由 MES 报工闭环单独管理)。 +// 统一收编(2026-09-07 定稿):半成品/成品也进本表(category 区分品类),废除独立 semi_finisheds 孤表; +// 成品由 MES 完工发码后经内部接口自动回流入库,成品 SN 与实物码解耦(系统必发号,实物可不贴码,允许手填覆盖)。 type Inventory struct { ent.Schema } +// 品类(category):1=原材料 2=半成品 3=成品。与 manage_mode 正交: +// manage_mode 管"怎么管"(批次数量 vs SN 单件),category 管"是什么"(原料/半成品/成品)。 +const ( + InvCategoryRaw = 1 + InvCategorySemi = 2 + InvCategoryFinished = 3 +) + func (Inventory) Fields() []ent.Field { return []ent.Field{ // 管理粒度:1=结构件(批次) 2=精密件(SN)。决定本行按"数量"还是按"单件"解释。 field.Int("manage_mode").Comment("管理粒度 1=结构件(批次) 2=精密件(SN)"), + // 品类:1=原材料 2=半成品 3=成品(统一库存主表收编 semi_finished 后新增) + field.Int("category").Default(InvCategoryRaw).Comment("品类 1=原材料 2=半成品 3=成品"), // 物料编码,关联 materials 主数据。 field.String("material_code").Comment("物料编码(关联 materials 表)"), @@ -63,6 +74,10 @@ func (Inventory) Fields() []ent.Field { field.String("supplier").Optional().Comment("供应商"), // 关联入库单号,便于正向追溯。 field.String("inbound_no").Optional().Comment("关联入库单号"), + // 关联工单号(半成品流转/成品完工回流时由 MES 传入)。 + field.String("order_no").Default("").MaxLen(64).Comment("关联工单号(半成品/成品回流)"), + // 已完成工序(半成品暂存回流用,如 135)。 + field.String("completed_process").Default("").MaxLen(64).Comment("已完成工序(半成品用,如 135)"), // 备注。 field.String("remark").Optional().Comment("备注"), @@ -74,6 +89,7 @@ func (Inventory) Fields() []ent.Field { func (Inventory) Indexes() []ent.Index { return []ent.Index{ index.Fields("manage_mode"), + index.Fields("category"), index.Fields("material_code"), index.Fields("quality_status"), index.Fields("zone_code"), diff --git a/bj_power_wms/schema/material.go b/bj_power_wms/schema/material.go index 608a05e..f1cf498 100644 --- a/bj_power_wms/schema/material.go +++ b/bj_power_wms/schema/material.go @@ -11,8 +11,10 @@ import ( // 管理粒度由 manage_mode 决定,贯穿整个库存链路: // - manage_mode=1 结构件:按"批次"管理,一次到货入一批,库存以 quantity 计数; // - manage_mode=2 精密件:按"SN 序列号"逐件管理,库存以单件行计数,可单件追溯。 -// 注意:物料档案只描述"这个物料是什么、怎么管",不承载生命周期状态 -// (原材料→半成品→成品的阶段由各自库存实体/inventory 表承载,不在此预判)。 +// 品类由 item_type 区分(与库存主表 inventories.category 对应): +// - item_type=1 原材料 / 2 半成品 / 3 成品(成品即"产品型号"档案,MES 实时从本表拉取, +// 消除两系统双录——2026-09-08 并表定稿)。 +// 注意:物料档案只描述"这个物料是什么、怎么管",不承载生命周期状态。 type Material struct { ent.Schema } @@ -25,12 +27,16 @@ func (Material) Fields() []ent.Field { field.String("spec").Optional().Comment("规格型号"), field.String("unit").Optional().Comment("单位"), field.String("description").Optional().Comment("描述"), + // 品类:1=原材料 2=半成品 3=成品(成品=产品型号档案) + field.Int("item_type").Default(1).Comment("品类 1原材料/2半成品/3成品"), // 管理粒度:1=批次管理(结构件) 2=序列号管理(精密件) field.Int("manage_mode").Default(1).Comment("管理粒度 1批次/2序列号"), field.Bool("is_batch_managed").Default(false).Comment("是否批次管理"), field.Bool("is_serial_managed").Default(false).Comment("是否序列号管理"), // 所属物料模板(种类) field.String("template_code").Optional().Comment("物料模板编码(物料种类)"), + // 启用状态:停用后不在新建工单/备料下拉出现(对齐 MES 产品型号 isActive) + field.Bool("is_active").Default(true).Comment("是否启用"), field.Int64("created_at").DefaultFunc(nowUnix).Comment("创建时间"), field.Int64("updated_at").DefaultFunc(nowUnix).Comment("更新时间"), } @@ -40,5 +46,6 @@ func (Material) Indexes() []ent.Index { return []ent.Index{ index.Fields("name"), index.Fields("template_code"), + index.Fields("item_type"), } } diff --git a/bj_power_wms/schema/semi_finished.go b/bj_power_wms/schema/semi_finished.go index 10abb88..2b3ef60 100644 --- a/bj_power_wms/schema/semi_finished.go +++ b/bj_power_wms/schema/semi_finished.go @@ -7,6 +7,8 @@ import ( ) // SemiFinished 半成品库存 +// 【已废弃 2026-09-07】半成品已收编进统一库存主表 inventories(category=2), +// 本表仅保留历史数据(存量已迁移),新代码一律读写 inventories,禁止再写本表。 // 记录已完成工序,支持流转 type SemiFinished struct { ent.Schema diff --git a/bj_power_wms/web/static/index.html b/bj_power_wms/web/static/index.html index 061369b..613f5a2 100644 --- a/bj_power_wms/web/static/index.html +++ b/bj_power_wms/web/static/index.html @@ -6,7 +6,7 @@ WMS库房管理 - + diff --git a/问题记录.md b/问题记录.md index 3f2ff2d..81075db 100644 --- a/问题记录.md +++ b/问题记录.md @@ -1,5 +1,149 @@ +只给方案,待我确认后再动手 + +1、mes ---> 角色管理 ,名称,为什么显示红色呢? 没有特殊内容,不应该 有颜色区分。没有区分依据 +2、mes ---> 账号管理,缺少 最近登录时间。(按照实际业务,总结一下如何更好,比如 用户体系 是不是 新建用户在 最下面好?工单,新建的在最上面最前面)。wms你也捋捋,如果真的需要改动 +3、wms ---> 账号管理,列表排序是不是应该统一一下。全项目列表,都应该是创建时间 倒排 id辅助倒排 。 +4、搜索条件,都要忽略大小写。例如: wms 的 物料档案 的 物料编码、规格 +5、产品型号 和 物料档案 有没有用同一张表,最优方案, 思考一下 +6、产品型号 ,缺少 搜索条件、缺少导出。 所有项目,增加数据,数据量大的数据。都要有搜索条件 +7、已经被引用的 产品型号、物料档案,等基础数据,不允许被删除。给出提示。 +8、工艺流程---> 流程列表 , 缺少 分页。 流程配置 --> 工序编号,不应该限制12,这个自动有用吗? 并且,绑定工位,在这个页面应该去掉,因为有关联工位页面了。请提供合理方案 +9、页面业务错误提示: 工位 1 已被启用流程「工位1_默认」绑定,请先在流程/工位中解除绑定 。 和问题8结合审理吧 +10、巡检终端:显示图片上传成功,实际,页面显示图片加载失败。显示, 提示签到成功,却换的记录查看,就不应该再让刷新了。 并且,缺少工单号、工位 筛选 +11、巡检记录详情 ,没有显示图片, +13、工单号(输入搜索) ,应该按照 创建时间倒排。当前支持模糊搜索 并且支持大小写,这很好。 +14、拧紧查询,工单号、SN 条件查询,应该改为支持大小写不敏感 +15、扫码录入拧紧数据,显示方式,有问题。应该改成 菜单 拧紧查询 ,有两个tab页签。 扫码录入拧紧数据,应该是补录数据?补录哪个步骤?是不是应该在工位操作,所以,请思考,合理性,不合理,我就会删除它。 +16、工件追溯,我输入 SN20260904-0003 ,没看懂数据显示。你审核一下是否合理。工件,名称有歧义? 物料?产品型号? 这些东西,都是如何定义的。你捋捋清楚。工单、工件,名称相互呼应,没问题,产品型号 ? 改为 工件型号 ? 你捋捋清楚,帮助文档说清楚些,统一主表。最优方案 +17、 手动报工 菜单, 又把查询、新增,放到了一个页面,并且 报工记录,没有分页,搜索条件大小写 缺少 大小写脱敏。载入工艺步骤有歧义。。。 请提供最优方案 +18、工位组合下发,和手动报工 一样的问题。 并且 ,当前工位 和 工位组合 的关系,没有说明确,如果是手动下发,当前工位,是带出来的吧,如果当前工位5,那么5之间的工位是不是就不用再选了? 请提供最优方案。 +19、绩效报表, 顶部有操作人选项,缺少工位选项。并且下面三个tab缺少分页。分页,默认20比较合理。并且 导出,应该导出三个tab,对应excel的三个sheet 。 +20、4个项目,所有导出,必须输入起始时间。最大时间间隔1年。当前所有有时间筛选条件的列表,默认带出时间,3个月。 +21、备料单,缺少搜索条件,丰富些,并且,大小写要脱敏 + - +把你的详细方案写到下面: + +--- + +## 详细方案(2026-09-08,只给方案待确认) + +> 标注说明:【已核实】= 已对照实际代码确认根因;改动点均列出具体文件。全程遵守:不打包 exe、前端改动全量重建产物、0 字节校验。 + +### 1、角色管理名称红色 +【已核实】根因:`RoleManage.vue` 列表里角色名称用 `el-tag`,且 `SUPER_ADMIN` 用了 `type="danger"`(红色),普通角色是蓝色——颜色被当成了"等级"含义,但没有区分依据。 +**方案**:名称列统一改为纯文本(不用 el-tag 着色)。超管身份已由"权限数=`*`(全部)"和按钮置灰天然体现,若仍想一眼识别,在名称旁加一个灰色小标"内置"(`el-tag effect="plain" type="info"`),红/蓝等级色全部去掉。 + +### 2、账号管理缺最近登录时间 + 新建记录位置规则 +【已核实】MES `users` 表没有 last_login 字段,`AccountManage.vue` 也没有该列。 +**方案**: +- MES `users` 加 `last_login_at BIGINT`(unix 秒),登录成功(含工位终端登录?——工位登录不更新,避免混淆后台使用时间)时写入;账号管理列表加"最近登录时间"列(fmtTime)。WMS 同步加同名字段同名列(两系统一致)。 +- **新建记录位置规则(全项目统一标准)**: + - **业务单据/记录类**(工单、备料单、出入库、巡检、日志、报表明细):`created_at desc, id desc`——新建在最上面,符合"刚做的最先看到"。 + - **基础资料/人员类**(账号、角色、物料档案、区域、产品型号、BOM):按 `id asc` 稳定排序——新建在列表底部,位置稳定不打乱使用习惯,新增的也不会"闪跳"到顶。 + - 按此标准全局巡检一遍所有列表(WMS+MES),把排序不符的统一掉(与问题 3 合并执行)。 + +### 3、WMS 账号管理排序 + 全项目列表排序统一 +【已核实】WMS 账号列表已是 `created_at desc`(上轮已改),但按问题 2 的规则,账号属"基础资料类",应改为 `id asc`。 +**方案**:以问题 2 的两条标准做**全项目列表排序审计**,输出一张"页面 → 当前排序 → 目标排序"清单,一次性改齐。WMS 账号管理改 `id asc`。 + +### 4、搜索条件忽略大小写(全项目) +【已核实】WMS `material.go` 物料编码/名称/规格用的 `Contains`(区分大小写),MES 侧部分查询同理。 +**方案**:两系统所有文本类筛选(编码、名称、规格、SN、批次号、工单号、操作人等)统一换成大小写折叠查询——WMS ent 用自带 `*ContainsFold`(手写 LOWER 会报 42601,踩过坑),MES 按同样语义处理。给出改动清单逐文件过,`EqualFold` 用于精确匹配场景。工单号搜索已是模糊+大小写(你说"这很好"),保持并作为基准。 + +### 5、产品型号与物料档案是否同表(最优方案思考) +【已核实】现状:MES `product_types`(产品型号)与 WMS `materials`(物料档案)是两张独立表、两个系统各自维护,靠编码人工对齐——这与上轮定稿的"编码主数据 WMS 单一来源"矛盾。 +**行业常规(ERP/PLM 视角)**:成品也是"物料"的一种,统一物料主数据一张表 + 物料类型字段(原材料/半成品/成品),是最优解;这也和我们已落地的统一库存主表(category 1/2/3)天然呼应。 +**方案(推荐 A,分两步走控制风险)**: +- **A(推荐)**:WMS `materials` 加 `item_type`(1原材料/2半成品/3成品),产品型号档案迁入 `materials`(item_type=3),作为唯一主数据源。MES 的"产品型号"页改为**实时从 WMS 拉取**(沿用 MaterialExists 同款降级:WMS 挂了允许手填仅提示);MES `product_types` 表保留只读过渡,工单/BOM 引用改为按编码关联(本就按 productCode 关联,改动集中在型号页数据源与编码生成——编码生成移到 WMS)。一步到位消除双录。 +- **B(保守过渡)**:两表暂保持独立,但 WMS 加只读"产品型号"视图(从 MES 拉取展示),MES 型号页补搜索+导出;待稳定后再并表。 +- 两条路的共同前置:先做问题 6/7(型号页搜索、导出、删除保护),无论选哪条都要做。**建议选 A,但第一步先只做"materials 加 item_type + WMS 可管理成品档案",MES 切换数据源放第二步验证后执行。** + +### 6、产品型号缺搜索条件、缺导出 +【已核实】`ProductType.vue` 只有"新增"按钮,无任何筛选、无导出。 +**方案**:产品型号页加筛选区(名称/编码/类别,模糊+大小写折叠,沿用"统一交互规范"筛选+分页+导出三件套)+ 导出 xlsx(后端全量导出接口)。同样规则顺带审计:**所有数据量会增长的数据页面都必须有筛选条件**(BOM、关联工位、事件日志已达标,缺的补齐)。 + +### 7、被引用的基础数据禁止删除 +**方案**(删除保护统一规则,两系统一致): +- **产品型号**:被工单/BOM 引用过 → 拒删,提示"已被 N 张工单引用,建议停用(下架)而非删除"。 +- **物料档案**:被 BOM/库存/入库单/出库单引用 → 拒删,同样提示。 +- **区域**:被库存占用(有在库行)→ 拒删。 +- **角色**:被用户使用 → 拒删(WMS 已有,MES 核实补齐)。 +- 实现:各 delete handler 删除前 count 引用,>0 返回 409 + 明确文案;前端无需改(错误提示已有通道)。给出引用关系清单表,逐条实现。 + +### 8、工艺流程:列表缺分页 + 工序编号限 12 + 绑定工位重复 +【已核实】`processflow.go` 里 StationNo 硬校验 `1..12`;`ProcessFlow.vue` 流程列表无分页。 +**方案**: +- **分页**:流程列表接口改真分页(page/pageSize/total,created_at desc),前端 el-pagination,默认 20。 +- **工序编号限制**:这个 12 来自"12 个物理工位"的产线现实,但放在"工序编号"上语义错了——工序是流程步骤,不该被工位数锁死。**建议**:工序编号放开为正整数(1~999),只校验"流程内不重复";与工位的对应关系交给"关联工位"页(Station.vue)维护,流程页不再承担工位绑定。若旧数据/PLC 下发依赖 1..12 映射,保留在"关联工位"页做映射校验,流程页彻底解耦。 +- **去掉绑定工位**:同意去掉——`ProcessFlow.vue` 删除绑定工位区块,绑定关系只在 `Station.vue` 维护(单一职责)。 + +### 9、错误提示"工位1已被启用流程「工位1_默认」绑定" +与问题 8 同根因:一个工位只能绑一个启用流程的校验没问题,但触发时机和文案不合理。**方案**:绑定动作收敛到关联工位页后,此冲突校验只在关联工位页保存时触发;文案改为可操作式:"工位 1 已绑定启用中的流程「工位1_默认」,请先在【关联工位】页为该工位换绑或停用该流程,再保存。"(附跳转链接)。 + +孙彦飞答:放到 关联工位,就不存在 已关联的提示了,因为,本来就工位就只有一条数据,点击关联,直接更换即可。 + +### 10、巡检终端:图片上传成功但显示失败 + 签到后不应刷新 + 缺筛选 +【已核实】后端已补 `GET /files/:date/:name` 路由;"上传成功但加载失败"最可疑的根因是:`el-image` 的 `` 请求**不带 Authorization 头**,若 /files 路由走 JWT 鉴权 → 401 → 图片加载失败。 +**方案**: +- 图片路由加白名单(登录后可看的静态资源不拦 JWT,或 /files 内部接口化);工位终端与管理端统一用同一图片 URL 规则。实施时先用 curl 带/不带 token 各测一次确认根因再改。 +- 签到成功提示后**当前页面不再轮询刷新**(停留页保持已提交状态,"记录查看"切换时才拉新)。 +- 记录查看 tab 补:工单号、工位筛选(下拉 12 工位),与现有筛选并列。 + +### 11、巡检记录详情没有图片 +【已核实】`Inspect.vue` 记录列表 L180 有图片列,详情抽屉需核对。结合问题 10 根因:若列表缩略图能显示、详情不能,是详情字段没传;若都不能显示,就是 JWT 白名单问题(问题 10 修复后自动解决)。 +**方案**:详情抽屉补"现场照片"区(el-image + 点击预览大图),URL 与列表同一来源;实施时先复现确认归属哪个根因。 + +### 13、工单号搜索按创建时间倒排 +**方案**:核实工单列表当前排序(疑似按工单号),统一改为 `created_at desc, id desc`。模糊+大小写折叠保持现状(已是标杆)。顺带把 MES 所有"输入搜索"的下拉数据源(工单选择框等)也按此排序。 + +### 14、拧紧查询大小写不敏感 +**方案**:`ListTorqueRecords` 的工单号/SN 查询改大小写折叠(精确匹配场景用 EqualFold,模糊场景上 LOWER 语义),与问题 4 一并批量执行。 + +### 15、扫码录入拧紧数据 → 改为"拧紧查询"两 tab +【已核实】拧紧查询 `Torque.vue` 目前是单列表页;"扫码录入拧紧数据"语义混乱(查询页里做录入)。 +**方案**:**拧紧查询页改两 tab**(录入 tab 在前默认打开,遵循录入/记录范式): +- Tab1 记录查询:现有列表 + 筛选(工单号/SN/工位/时间)+ 分页 + 导出。 +- Tab2 拧紧补录:定位为**补录**(设备漏传/手工修正),字段:工单号、SN、工位、螺丝号、扭矩/角度/结果、补录原因(必填,写操作日志)。合理——补录本质是数据修正,放管理端查询页合理;**实时采集不动**(那是工位终端/PLC 的事)。若你觉得连补录都不该在管理端,删掉 Tab2 即可,我预留开关。 + +### 16、工件追溯显示混乱 + "产品型号"命名歧义 +【已核实】`Trace.vue` 输入 SN 后展示工位时间线/考核/拧紧数据,"产品型号"字段在工件语境下确实歧义。 +**方案**: +- **命名统一(全系统文案)**:`产品型号` → `工件型号`?——不完全同意:型号是"物"的属性,工件是"这一台"的实例。行业惯用:**产品型号(物) → 工单(批次任务) → 工件(实例 SN)**。建议保留"产品型号",但追溯页字段改为:`工件 SN`、`所属工单`、`产品型号(编码+名称)`、`当前工位`、`状态`,层级一目了然;帮助文档明确三级关系图。若你仍倾向"工件型号",纯文案替换 8 个文件即可(同上次"产品类型→产品型号"套路)。 +- **展示重构**:Trace 顶部用 descriptions 分组(工件/工单/型号/时间),三个 tab(工位时间线/考核数据/拧紧数据)加分页与筛选;看不懂的字段逐个核对数据来源,来源为空的字段不显示而不是显示空值。 + +### 17、手动报工页面(Scan.vue)结构问题 +【已核实】router:`scan → 手动报工`。查询/新增混一页、记录无分页、搜索缺大小写折叠、"载入工艺步骤"歧义。 +**方案**:按多 Tab 范式重做:Tab1 **报工录入**(前、默认打开)——输入 SN → 自动带出工单/当前工序/应做步骤("载入工艺步骤"改为自动触发,去掉按钮和歧义文案,改提示"已自动载入当前工位应做步骤");Tab2 **报工记录**(点击加载)——真分页 + 筛选(SN/工单/工位/时间,大小写折叠)+ 导出。 + +### 18、工位组合下发(PlcSend.vue)同款问题 + 当前工位语义 +**方案**: +- 页面结构同 17:Tab1 下发录入 / Tab2 下发记录(分页+筛选+导出)。 +- **当前工位语义明确化**:手选工件 SN 后,"当前工位"由工件实际进度**自动带出**(只读展示),不再手填;工位组合选择器自动把 ≤ 当前工位的工位**默认剔除且禁选**(已完成的不用再走),只允许勾选之后的工位——解决"当前工位 5 时 1~5 还能选"的困惑。PLC 下发逻辑不变。 + +### 19、绩效报表 +【已核实】`Performance.vue` 顶部只有操作人筛选,三个 tab(按人/按工位/明细)均无分页。 +**方案**:顶部加**工位下拉筛选**(1~12,可多选);三个 tab 全部改真分页(默认 20);导出改一个 xlsx 三个 sheet(按人/按工位/明细),沿用 WMS sendExcel 机制在 MES 侧补多 sheet 能力(excelize 支持)。 + +### 20、导出时间规则(4 个项目统一) +**方案**:全项目导出统一三条硬规则: +- 必须有起始+结束时间(未选则用默认区间,不允许"全历史"一键导出); +- 最大间隔 1 年(前端校验提示 + 后端 export handler 双重校验,>366 天返回错误); +- 有时间筛选的列表页,时间默认值=近 3 个月。 +执行方式:后端在导出 handler 统一加校验函数(一处实现,各导出调用);前端 WMS/MES 各加一个公共校验。逐导出接口过清单。 + +### 21、备料单 +【已核实】`MaterialRequest.vue` 现有筛选:日期/工单号/状态——不够丰富。 +**方案**:补筛选:产品编码(模糊+大小写折叠)、物料编码/名称(模糊)、接驳台;工单号等文本条件大小写折叠;列表确认真分页+created_at desc;导出带同筛选条件。帮助文档同步补字段说明。 + +--- + +## 实施顺序建议(确认后执行) +1. **全局横切类**(一次改齐、影响面广):4/14/21 大小写折叠、3/13 排序统一、20 导出时间规则、7 删除保护。 +2. **页面结构重构类**:15/17/18 三页多 Tab 重构、19 绩效分页+三 sheet 导出、8/9 工艺流程解耦。 +3. **修复类**:10/11 巡检图片(先 curl 验证 JWT 根因)、2 最近登录时间(两系统 DB 加字段)。 +4. **设计类单独立项**:5 产品型号并表(先做 A 方案第一步)、6 型号页搜索导出、16 追溯展示+命名(等你对"工件型号 vs 产品型号"拍板)。 +每批做完 `go build ./...` 验证 + 前端全量重建,不打包 exe、不重启你本地服务。