diff --git a/bj_power_mes/internal/handler/production/bom_material.go b/bj_power_mes/internal/handler/production/bom_material.go index 4d850fc..541366f 100644 --- a/bj_power_mes/internal/handler/production/bom_material.go +++ b/bj_power_mes/internal/handler/production/bom_material.go @@ -251,7 +251,7 @@ func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { TargetDock: q.Get("targetDock"), StationNo: q.Get("stationNo"), Source: q.Get("source"), - Keyword: q.Get("keyword"), + RequestNo: q.Get("requestNo"), }) if err != nil { httpx.Fail(w, 3104, err.Error()) diff --git a/bj_power_mes/internal/logic/material.go b/bj_power_mes/internal/logic/material.go index 6839145..5acb057 100644 --- a/bj_power_mes/internal/logic/material.go +++ b/bj_power_mes/internal/logic/material.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "sort" "strconv" "strings" @@ -165,6 +166,19 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, } } s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产+工位派工自动生成工位级备料单", map[string]any{"planDate": planDate, "count": created}) + // 需求10:把每个工单的「工单级需求总量」同步给 WMS 台账(幂等,失败降级不阻塞)。 + // 必须先同步 total_qty,后续自动出库才能在台账上累加 out_qty 并判定缺口/领料完结。 + synced := map[string]bool{} + for _, g := range groups { + if synced[g.plan.OrderNo] { + continue + } + synced[g.plan.OrderNo] = true + if e := s.SyncOrderLedger(ctx, g.plan.OrderNo, operator); e != nil { + s.ctx.EventLog.Write(ctx, "material.ledger.sync_fail", "", operator, "material_request", g.plan.OrderNo, + "工单需求量同步 WMS 台账失败", map[string]any{"orderNo": g.plan.OrderNo, "error": e.Error()}) + } + } // 生成后立即自动出库(客户第1条:平时无需操作)。缺料/失败不阻塞生成,仅记预警等待补库存。 if _, short := s.AutoOutboundRequests(ctx, planDate, "", operator); len(short) > 0 { s.ctx.EventLog.Write(ctx, "material.outbound.short", "", operator, "material_request", planDate, @@ -174,6 +188,58 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, return created, nil, nil } +// SyncOrderLedger 计算并向 WMS 下发某工单的「工单级物料需求总量」(需求10)。 +// +// 需求量口径(临时.md §四 工单域):需求量 = 工单数量 × 单台用量 × (1+损耗率),向上取整。 +// 工单级而非日级——用日级会误卡跨天生产(临时.md L283-285)。 +// 多级 BOM 展开到叶子后按物料编码聚合(同编码合并),一个物料一行下发。 +// WMS 侧按 orderNo+materialCode 幂等 upsert total_qty,可重复调用。 +func (s *Service) SyncOrderLedger(ctx context.Context, orderNo, operator string) error { + wo, err := s.ctx.EntClient.WorkOrder.Query(). + Where(workorder.WorkOrderNo(orderNo)).Only(ctx) + if err != nil { + return err + } + leaves := s.expandBomToLeaf(ctx, wo.ProductCode, bomNameOrDefault(""), 1, 0, map[string]bool{}) + if len(leaves) == 0 { + return nil + } + type agg struct { + name string + qty float64 + } + m := map[string]*agg{} + order := make([]string, 0, len(leaves)) + for _, it := range leaves { + if it.materialCode == "" { + continue + } + need := float64(wo.Quantity) * it.unitQty * (1 + it.lossRate/100) + if need <= 0 { + continue + } + if a, ok := m[it.materialCode]; ok { + a.qty += need + } else { + m[it.materialCode] = &agg{name: it.materialName, qty: need} + order = append(order, it.materialCode) + } + } + items := make([]wmsclient.LedgerSyncItem, 0, len(order)) + for _, code := range order { + a := m[code] + items = append(items, wmsclient.LedgerSyncItem{ + MaterialCode: code, + MaterialName: a.name, + TotalQty: int(math.Ceil(a.qty - 1e-9)), + }) + } + if len(items) == 0 { + return nil + } + return s.ctx.Wms.LedgerSync(ctx, orderNo, wo.ProductCode, operator, items) +} + // AutoOutboundRequests 工单备料自动出库(客户第1条:备料出库根据 MES 排产自动生成,平时无需操作)。 // // 对指定排产日 / 工单下的备料单,按 FIFO 从 WMS「合格」在库库存自动领料: @@ -356,7 +422,7 @@ type MaterialRequestQuery struct { TargetDock string StationNo string Source string // PLAN / REFILL - Keyword string // 兜底匹配 备料单号/工单号/物料编码 + RequestNo string // 备料单号(独立模糊筛选;列表页禁用关键字混搜) } // ListMaterialRequests 备料单查询(大小写不敏感)。 @@ -383,12 +449,8 @@ func (s *Service) ListMaterialRequests(ctx context.Context, o MaterialRequestQue if o.Source != "" { q = q.Where(materialrequest.Source(o.Source)) } - if o.Keyword != "" { - q = q.Where(materialrequest.Or( - materialrequest.RequestNoContainsFold(o.Keyword), - materialrequest.OrderNoContainsFold(o.Keyword), - materialrequest.MaterialCodeContainsFold(o.Keyword), - )) + if o.RequestNo != "" { + q = q.Where(materialrequest.RequestNoContainsFold(o.RequestNo)) } if o.StationNo != "" { if n, e := strconv.Atoi(o.StationNo); e == nil { diff --git a/bj_power_mes/internal/wmsclient/client.go b/bj_power_mes/internal/wmsclient/client.go index 605aead..aa06b47 100644 --- a/bj_power_mes/internal/wmsclient/client.go +++ b/bj_power_mes/internal/wmsclient/client.go @@ -572,6 +572,57 @@ func (c *Client) LedgerProgress(ctx context.Context, orderNo, materialCode strin return out.List, out.Total, nil } +// LedgerSyncItem 工单物料台账同步项(工单级需求量,一个物料一行)。 +type LedgerSyncItem struct { + MaterialCode string `json:"materialCode"` + MaterialName string `json:"materialName"` + TotalQty int `json:"totalQty"` +} + +// LedgerSync 向 WMS 下发「该工单每种物料总共要多少料」(工单级需求量)。 +// +// 需求10根因修复:MES 生成备料单后必须把工单级需求量同步给 WMS, +// WMS 才知道 total_qty(台账需求量),否则出库/缺口/领料完结判定都无据可依。 +// WMS 侧按 orderNo+materialCode 幂等 upsert total_qty(详见 ledgerSyncHandler)。 +func (c *Client) LedgerSync(ctx context.Context, orderNo, productCode, operator string, items []LedgerSyncItem) error { + if orderNo == "" || len(items) == 0 { + return errors.New("orderNo 和 items 必填") + } + payload := map[string]any{ + "orderNo": orderNo, + "productCode": productCode, + "operator": operator, + "items": items, + } + b, err := json.Marshal(payload) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/ledger/sync", 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() + var body struct { + Code int `json:"code"` + Message string `json:"message"` + } + _ = json.NewDecoder(resp.Body).Decode(&body) + if resp.StatusCode >= 300 || body.Code != 0 { + if body.Message != "" { + return errors.New(body.Message) + } + return &RespError{Status: resp.StatusCode} + } + return nil +} + func (c *Client) post(ctx context.Context, path string, body any) error { b, err := json.Marshal(body) if err != nil { diff --git a/bj_power_wms/ent/inventory.go b/bj_power_wms/ent/inventory.go index 89e4404..96782f6 100644 --- a/bj_power_wms/ent/inventory.go +++ b/bj_power_wms/ent/inventory.go @@ -70,6 +70,8 @@ type Inventory struct { RecordedAt int64 `json:"recordedAt,omitempty"` // 出库原因(半成品/成品出库用) OutboundReason string `json:"outboundReason,omitempty"` + // 盘点冻结标记(进行中盘点单号;空=未冻结) + StocktakeNo string `json:"stocktakeNo,omitempty"` // 备注 Remark string `json:"remark,omitempty"` // CreatedAt holds the value of the "created_at" field. @@ -86,7 +88,7 @@ func (*Inventory) scanValues(columns []string) ([]any, error) { switch columns[i] { case inventory.FieldID, inventory.FieldManageMode, inventory.FieldCategory, inventory.FieldQuantity, inventory.FieldLockedQty, inventory.FieldRecordedAt, inventory.FieldCreatedAt, inventory.FieldUpdatedAt: values[i] = new(sql.NullInt64) - case inventory.FieldMaterialCode, inventory.FieldMaterialName, inventory.FieldBatchNo, inventory.FieldSnCode, inventory.FieldQualityStatus, inventory.FieldZoneCode, inventory.FieldShelfNo, inventory.FieldLayerNo, inventory.FieldPositionNo, inventory.FieldStatus, inventory.FieldProductionDate, inventory.FieldSupplier, inventory.FieldInboundNo, inventory.FieldOrderNo, inventory.FieldCompletedProcess, inventory.FieldOwnershipType, inventory.FieldOwnershipNo, inventory.FieldProductStatus, inventory.FieldRelatedStandard, inventory.FieldTestRecordNo, inventory.FieldRecordedBy, inventory.FieldOutboundReason, inventory.FieldRemark: + case inventory.FieldMaterialCode, inventory.FieldMaterialName, inventory.FieldBatchNo, inventory.FieldSnCode, inventory.FieldQualityStatus, inventory.FieldZoneCode, inventory.FieldShelfNo, inventory.FieldLayerNo, inventory.FieldPositionNo, inventory.FieldStatus, inventory.FieldProductionDate, inventory.FieldSupplier, inventory.FieldInboundNo, inventory.FieldOrderNo, inventory.FieldCompletedProcess, inventory.FieldOwnershipType, inventory.FieldOwnershipNo, inventory.FieldProductStatus, inventory.FieldRelatedStandard, inventory.FieldTestRecordNo, inventory.FieldRecordedBy, inventory.FieldOutboundReason, inventory.FieldStocktakeNo, inventory.FieldRemark: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -271,6 +273,12 @@ func (_m *Inventory) assignValues(columns []string, values []any) error { } else if value.Valid { _m.OutboundReason = value.String } + case inventory.FieldStocktakeNo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field stocktake_no", values[i]) + } else if value.Valid { + _m.StocktakeNo = value.String + } case inventory.FieldRemark: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field remark", values[i]) @@ -406,6 +414,9 @@ func (_m *Inventory) String() string { builder.WriteString("outbound_reason=") builder.WriteString(_m.OutboundReason) builder.WriteString(", ") + builder.WriteString("stocktake_no=") + builder.WriteString(_m.StocktakeNo) + 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 1cef188..5b680aa 100644 --- a/bj_power_wms/ent/inventory/inventory.go +++ b/bj_power_wms/ent/inventory/inventory.go @@ -65,6 +65,8 @@ const ( FieldRecordedAt = "recorded_at" // FieldOutboundReason holds the string denoting the outbound_reason field in the database. FieldOutboundReason = "outbound_reason" + // FieldStocktakeNo holds the string denoting the stocktake_no field in the database. + FieldStocktakeNo = "stocktake_no" // 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. @@ -105,6 +107,7 @@ var Columns = []string{ FieldRecordedBy, FieldRecordedAt, FieldOutboundReason, + FieldStocktakeNo, FieldRemark, FieldCreatedAt, FieldUpdatedAt, @@ -139,6 +142,10 @@ var ( DefaultCompletedProcess string // CompletedProcessValidator is a validator for the "completed_process" field. It is called by the builders before save. CompletedProcessValidator func(string) error + // DefaultStocktakeNo holds the default value on creation for the "stocktake_no" field. + DefaultStocktakeNo string + // StocktakeNoValidator is a validator for the "stocktake_no" field. It is called by the builders before save. + StocktakeNoValidator 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. @@ -288,6 +295,11 @@ func ByOutboundReason(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldOutboundReason, opts...).ToFunc() } +// ByStocktakeNo orders the results by the stocktake_no field. +func ByStocktakeNo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStocktakeNo, 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 ea63378..c38bb35 100644 --- a/bj_power_wms/ent/inventory/where.go +++ b/bj_power_wms/ent/inventory/where.go @@ -188,6 +188,11 @@ func OutboundReason(v string) predicate.Inventory { return predicate.Inventory(sql.FieldEQ(FieldOutboundReason, v)) } +// StocktakeNo applies equality check predicate on the "stocktake_no" field. It's identical to StocktakeNoEQ. +func StocktakeNo(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldStocktakeNo, 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)) @@ -2013,6 +2018,71 @@ func OutboundReasonContainsFold(v string) predicate.Inventory { return predicate.Inventory(sql.FieldContainsFold(FieldOutboundReason, v)) } +// StocktakeNoEQ applies the EQ predicate on the "stocktake_no" field. +func StocktakeNoEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEQ(FieldStocktakeNo, v)) +} + +// StocktakeNoNEQ applies the NEQ predicate on the "stocktake_no" field. +func StocktakeNoNEQ(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldNEQ(FieldStocktakeNo, v)) +} + +// StocktakeNoIn applies the In predicate on the "stocktake_no" field. +func StocktakeNoIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldIn(FieldStocktakeNo, vs...)) +} + +// StocktakeNoNotIn applies the NotIn predicate on the "stocktake_no" field. +func StocktakeNoNotIn(vs ...string) predicate.Inventory { + return predicate.Inventory(sql.FieldNotIn(FieldStocktakeNo, vs...)) +} + +// StocktakeNoGT applies the GT predicate on the "stocktake_no" field. +func StocktakeNoGT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGT(FieldStocktakeNo, v)) +} + +// StocktakeNoGTE applies the GTE predicate on the "stocktake_no" field. +func StocktakeNoGTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldGTE(FieldStocktakeNo, v)) +} + +// StocktakeNoLT applies the LT predicate on the "stocktake_no" field. +func StocktakeNoLT(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLT(FieldStocktakeNo, v)) +} + +// StocktakeNoLTE applies the LTE predicate on the "stocktake_no" field. +func StocktakeNoLTE(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldLTE(FieldStocktakeNo, v)) +} + +// StocktakeNoContains applies the Contains predicate on the "stocktake_no" field. +func StocktakeNoContains(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContains(FieldStocktakeNo, v)) +} + +// StocktakeNoHasPrefix applies the HasPrefix predicate on the "stocktake_no" field. +func StocktakeNoHasPrefix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasPrefix(FieldStocktakeNo, v)) +} + +// StocktakeNoHasSuffix applies the HasSuffix predicate on the "stocktake_no" field. +func StocktakeNoHasSuffix(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldHasSuffix(FieldStocktakeNo, v)) +} + +// StocktakeNoEqualFold applies the EqualFold predicate on the "stocktake_no" field. +func StocktakeNoEqualFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldEqualFold(FieldStocktakeNo, v)) +} + +// StocktakeNoContainsFold applies the ContainsFold predicate on the "stocktake_no" field. +func StocktakeNoContainsFold(v string) predicate.Inventory { + return predicate.Inventory(sql.FieldContainsFold(FieldStocktakeNo, 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 060d1f9..6882ae1 100644 --- a/bj_power_wms/ent/inventory_create.go +++ b/bj_power_wms/ent/inventory_create.go @@ -383,6 +383,20 @@ func (_c *InventoryCreate) SetNillableOutboundReason(v *string) *InventoryCreate return _c } +// SetStocktakeNo sets the "stocktake_no" field. +func (_c *InventoryCreate) SetStocktakeNo(v string) *InventoryCreate { + _c.mutation.SetStocktakeNo(v) + return _c +} + +// SetNillableStocktakeNo sets the "stocktake_no" field if the given value is not nil. +func (_c *InventoryCreate) SetNillableStocktakeNo(v *string) *InventoryCreate { + if v != nil { + _c.SetStocktakeNo(*v) + } + return _c +} + // SetRemark sets the "remark" field. func (_c *InventoryCreate) SetRemark(v string) *InventoryCreate { _c.mutation.SetRemark(v) @@ -488,6 +502,10 @@ func (_c *InventoryCreate) defaults() { v := inventory.DefaultCompletedProcess _c.mutation.SetCompletedProcess(v) } + if _, ok := _c.mutation.StocktakeNo(); !ok { + v := inventory.DefaultStocktakeNo + _c.mutation.SetStocktakeNo(v) + } if _, ok := _c.mutation.CreatedAt(); !ok { v := inventory.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) @@ -537,6 +555,14 @@ func (_c *InventoryCreate) check() error { return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} } } + if _, ok := _c.mutation.StocktakeNo(); !ok { + return &ValidationError{Name: "stocktake_no", err: errors.New(`ent: missing required field "Inventory.stocktake_no"`)} + } + if v, ok := _c.mutation.StocktakeNo(); ok { + if err := inventory.StocktakeNoValidator(v); err != nil { + return &ValidationError{Name: "stocktake_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.stocktake_no": %w`, err)} + } + } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Inventory.created_at"`)} } @@ -678,6 +704,10 @@ func (_c *InventoryCreate) createSpec() (*Inventory, *sqlgraph.CreateSpec) { _spec.SetField(inventory.FieldOutboundReason, field.TypeString, value) _node.OutboundReason = value } + if value, ok := _c.mutation.StocktakeNo(); ok { + _spec.SetField(inventory.FieldStocktakeNo, field.TypeString, value) + _node.StocktakeNo = value + } if value, ok := _c.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) _node.Remark = value @@ -1204,6 +1234,18 @@ func (u *InventoryUpsert) ClearOutboundReason() *InventoryUpsert { return u } +// SetStocktakeNo sets the "stocktake_no" field. +func (u *InventoryUpsert) SetStocktakeNo(v string) *InventoryUpsert { + u.Set(inventory.FieldStocktakeNo, v) + return u +} + +// UpdateStocktakeNo sets the "stocktake_no" field to the value that was provided on create. +func (u *InventoryUpsert) UpdateStocktakeNo() *InventoryUpsert { + u.SetExcluded(inventory.FieldStocktakeNo) + return u +} + // SetRemark sets the "remark" field. func (u *InventoryUpsert) SetRemark(v string) *InventoryUpsert { u.Set(inventory.FieldRemark, v) @@ -1837,6 +1879,20 @@ func (u *InventoryUpsertOne) ClearOutboundReason() *InventoryUpsertOne { }) } +// SetStocktakeNo sets the "stocktake_no" field. +func (u *InventoryUpsertOne) SetStocktakeNo(v string) *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.SetStocktakeNo(v) + }) +} + +// UpdateStocktakeNo sets the "stocktake_no" field to the value that was provided on create. +func (u *InventoryUpsertOne) UpdateStocktakeNo() *InventoryUpsertOne { + return u.Update(func(s *InventoryUpsert) { + s.UpdateStocktakeNo() + }) +} + // SetRemark sets the "remark" field. func (u *InventoryUpsertOne) SetRemark(v string) *InventoryUpsertOne { return u.Update(func(s *InventoryUpsert) { @@ -2643,6 +2699,20 @@ func (u *InventoryUpsertBulk) ClearOutboundReason() *InventoryUpsertBulk { }) } +// SetStocktakeNo sets the "stocktake_no" field. +func (u *InventoryUpsertBulk) SetStocktakeNo(v string) *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.SetStocktakeNo(v) + }) +} + +// UpdateStocktakeNo sets the "stocktake_no" field to the value that was provided on create. +func (u *InventoryUpsertBulk) UpdateStocktakeNo() *InventoryUpsertBulk { + return u.Update(func(s *InventoryUpsert) { + s.UpdateStocktakeNo() + }) +} + // 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 73dce55..46889e7 100644 --- a/bj_power_wms/ent/inventory_update.go +++ b/bj_power_wms/ent/inventory_update.go @@ -548,6 +548,20 @@ func (_u *InventoryUpdate) ClearOutboundReason() *InventoryUpdate { return _u } +// SetStocktakeNo sets the "stocktake_no" field. +func (_u *InventoryUpdate) SetStocktakeNo(v string) *InventoryUpdate { + _u.mutation.SetStocktakeNo(v) + return _u +} + +// SetNillableStocktakeNo sets the "stocktake_no" field if the given value is not nil. +func (_u *InventoryUpdate) SetNillableStocktakeNo(v *string) *InventoryUpdate { + if v != nil { + _u.SetStocktakeNo(*v) + } + return _u +} + // SetRemark sets the "remark" field. func (_u *InventoryUpdate) SetRemark(v string) *InventoryUpdate { _u.mutation.SetRemark(v) @@ -654,6 +668,11 @@ func (_u *InventoryUpdate) check() error { return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} } } + if v, ok := _u.mutation.StocktakeNo(); ok { + if err := inventory.StocktakeNoValidator(v); err != nil { + return &ValidationError{Name: "stocktake_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.stocktake_no": %w`, err)} + } + } return nil } @@ -819,6 +838,9 @@ func (_u *InventoryUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.OutboundReasonCleared() { _spec.ClearField(inventory.FieldOutboundReason, field.TypeString) } + if value, ok := _u.mutation.StocktakeNo(); ok { + _spec.SetField(inventory.FieldStocktakeNo, field.TypeString, value) + } if value, ok := _u.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) } @@ -1378,6 +1400,20 @@ func (_u *InventoryUpdateOne) ClearOutboundReason() *InventoryUpdateOne { return _u } +// SetStocktakeNo sets the "stocktake_no" field. +func (_u *InventoryUpdateOne) SetStocktakeNo(v string) *InventoryUpdateOne { + _u.mutation.SetStocktakeNo(v) + return _u +} + +// SetNillableStocktakeNo sets the "stocktake_no" field if the given value is not nil. +func (_u *InventoryUpdateOne) SetNillableStocktakeNo(v *string) *InventoryUpdateOne { + if v != nil { + _u.SetStocktakeNo(*v) + } + return _u +} + // SetRemark sets the "remark" field. func (_u *InventoryUpdateOne) SetRemark(v string) *InventoryUpdateOne { _u.mutation.SetRemark(v) @@ -1497,6 +1533,11 @@ func (_u *InventoryUpdateOne) check() error { return &ValidationError{Name: "completed_process", err: fmt.Errorf(`ent: validator failed for field "Inventory.completed_process": %w`, err)} } } + if v, ok := _u.mutation.StocktakeNo(); ok { + if err := inventory.StocktakeNoValidator(v); err != nil { + return &ValidationError{Name: "stocktake_no", err: fmt.Errorf(`ent: validator failed for field "Inventory.stocktake_no": %w`, err)} + } + } return nil } @@ -1679,6 +1720,9 @@ func (_u *InventoryUpdateOne) sqlSave(ctx context.Context) (_node *Inventory, er if _u.mutation.OutboundReasonCleared() { _spec.ClearField(inventory.FieldOutboundReason, field.TypeString) } + if value, ok := _u.mutation.StocktakeNo(); ok { + _spec.SetField(inventory.FieldStocktakeNo, field.TypeString, value) + } if value, ok := _u.mutation.Remark(); ok { _spec.SetField(inventory.FieldRemark, field.TypeString, value) } diff --git a/bj_power_wms/ent/migrate/schema.go b/bj_power_wms/ent/migrate/schema.go index 482b6f7..4ca4c98 100644 --- a/bj_power_wms/ent/migrate/schema.go +++ b/bj_power_wms/ent/migrate/schema.go @@ -327,6 +327,7 @@ var ( {Name: "recorded_by", Type: field.TypeString, Nullable: true}, {Name: "recorded_at", Type: field.TypeInt64, Nullable: true}, {Name: "outbound_reason", Type: field.TypeString, Nullable: true}, + {Name: "stocktake_no", 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}, @@ -759,6 +760,8 @@ var ( {Name: "scanned_qty", Type: field.TypeInt, Default: -1}, {Name: "diff_qty", Type: field.TypeInt, Default: 0}, {Name: "counted", Type: field.TypeBool, Default: false}, + {Name: "counted_by", Type: field.TypeString, Nullable: true}, + {Name: "counted_at", Type: field.TypeInt64, Nullable: true}, {Name: "updated_at", Type: field.TypeInt64}, } // StocktakeItemsTable holds the schema information for the "stocktake_items" table. @@ -786,6 +789,13 @@ var ( {Name: "status", Type: field.TypeString, Default: "进行中"}, {Name: "operator", Type: field.TypeString, Nullable: true}, {Name: "total_targets", Type: field.TypeInt, Default: 0}, + {Name: "diff_count", Type: field.TypeInt, Default: 0}, + {Name: "adjusted", Type: field.TypeBool, Default: false}, + {Name: "adjust_no", Type: field.TypeString, Nullable: true}, + {Name: "written_at", Type: field.TypeInt64, Nullable: true}, + {Name: "written_by", Type: field.TypeString, Nullable: true}, + {Name: "uncounted_mode", Type: field.TypeString, Nullable: true}, + {Name: "cancel_reason", Type: field.TypeString, Nullable: true}, {Name: "created_at", Type: field.TypeInt64}, {Name: "finished_at", Type: field.TypeInt64, Nullable: true}, } diff --git a/bj_power_wms/ent/mutation.go b/bj_power_wms/ent/mutation.go index 05d5095..fe43634 100644 --- a/bj_power_wms/ent/mutation.go +++ b/bj_power_wms/ent/mutation.go @@ -8553,6 +8553,7 @@ type InventoryMutation struct { recorded_at *int64 addrecorded_at *int64 outbound_reason *string + stocktake_no *string remark *string created_at *int64 addcreated_at *int64 @@ -9969,6 +9970,42 @@ func (m *InventoryMutation) ResetOutboundReason() { delete(m.clearedFields, inventory.FieldOutboundReason) } +// SetStocktakeNo sets the "stocktake_no" field. +func (m *InventoryMutation) SetStocktakeNo(s string) { + m.stocktake_no = &s +} + +// StocktakeNo returns the value of the "stocktake_no" field in the mutation. +func (m *InventoryMutation) StocktakeNo() (r string, exists bool) { + v := m.stocktake_no + if v == nil { + return + } + return *v, true +} + +// OldStocktakeNo returns the old "stocktake_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) OldStocktakeNo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStocktakeNo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStocktakeNo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStocktakeNo: %w", err) + } + return oldValue.StocktakeNo, nil +} + +// ResetStocktakeNo resets all changes to the "stocktake_no" field. +func (m *InventoryMutation) ResetStocktakeNo() { + m.stocktake_no = nil +} + // SetRemark sets the "remark" field. func (m *InventoryMutation) SetRemark(s string) { m.remark = &s @@ -10164,7 +10201,7 @@ 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, 30) + fields := make([]string, 0, 31) if m.manage_mode != nil { fields = append(fields, inventory.FieldManageMode) } @@ -10246,6 +10283,9 @@ func (m *InventoryMutation) Fields() []string { if m.outbound_reason != nil { fields = append(fields, inventory.FieldOutboundReason) } + if m.stocktake_no != nil { + fields = append(fields, inventory.FieldStocktakeNo) + } if m.remark != nil { fields = append(fields, inventory.FieldRemark) } @@ -10317,6 +10357,8 @@ func (m *InventoryMutation) Field(name string) (ent.Value, bool) { return m.RecordedAt() case inventory.FieldOutboundReason: return m.OutboundReason() + case inventory.FieldStocktakeNo: + return m.StocktakeNo() case inventory.FieldRemark: return m.Remark() case inventory.FieldCreatedAt: @@ -10386,6 +10428,8 @@ func (m *InventoryMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldRecordedAt(ctx) case inventory.FieldOutboundReason: return m.OldOutboundReason(ctx) + case inventory.FieldStocktakeNo: + return m.OldStocktakeNo(ctx) case inventory.FieldRemark: return m.OldRemark(ctx) case inventory.FieldCreatedAt: @@ -10590,6 +10634,13 @@ func (m *InventoryMutation) SetField(name string, value ent.Value) error { } m.SetOutboundReason(v) return nil + case inventory.FieldStocktakeNo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStocktakeNo(v) + return nil case inventory.FieldRemark: v, ok := value.(string) if !ok { @@ -10945,6 +10996,9 @@ func (m *InventoryMutation) ResetField(name string) error { case inventory.FieldOutboundReason: m.ResetOutboundReason() return nil + case inventory.FieldStocktakeNo: + m.ResetStocktakeNo() + return nil case inventory.FieldRemark: m.ResetRemark() return nil @@ -22710,6 +22764,9 @@ type StocktakeItemMutation struct { diff_qty *int adddiff_qty *int counted *bool + counted_by *string + counted_at *int64 + addcounted_at *int64 updated_at *int64 addupdated_at *int64 clearedFields map[string]struct{} @@ -23373,6 +23430,125 @@ func (m *StocktakeItemMutation) ResetCounted() { m.counted = nil } +// SetCountedBy sets the "counted_by" field. +func (m *StocktakeItemMutation) SetCountedBy(s string) { + m.counted_by = &s +} + +// CountedBy returns the value of the "counted_by" field in the mutation. +func (m *StocktakeItemMutation) CountedBy() (r string, exists bool) { + v := m.counted_by + if v == nil { + return + } + return *v, true +} + +// OldCountedBy returns the old "counted_by" field's value of the StocktakeItem entity. +// If the StocktakeItem 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 *StocktakeItemMutation) OldCountedBy(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCountedBy is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCountedBy requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCountedBy: %w", err) + } + return oldValue.CountedBy, nil +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (m *StocktakeItemMutation) ClearCountedBy() { + m.counted_by = nil + m.clearedFields[stocktakeitem.FieldCountedBy] = struct{}{} +} + +// CountedByCleared returns if the "counted_by" field was cleared in this mutation. +func (m *StocktakeItemMutation) CountedByCleared() bool { + _, ok := m.clearedFields[stocktakeitem.FieldCountedBy] + return ok +} + +// ResetCountedBy resets all changes to the "counted_by" field. +func (m *StocktakeItemMutation) ResetCountedBy() { + m.counted_by = nil + delete(m.clearedFields, stocktakeitem.FieldCountedBy) +} + +// SetCountedAt sets the "counted_at" field. +func (m *StocktakeItemMutation) SetCountedAt(i int64) { + m.counted_at = &i + m.addcounted_at = nil +} + +// CountedAt returns the value of the "counted_at" field in the mutation. +func (m *StocktakeItemMutation) CountedAt() (r int64, exists bool) { + v := m.counted_at + if v == nil { + return + } + return *v, true +} + +// OldCountedAt returns the old "counted_at" field's value of the StocktakeItem entity. +// If the StocktakeItem 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 *StocktakeItemMutation) OldCountedAt(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCountedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCountedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCountedAt: %w", err) + } + return oldValue.CountedAt, nil +} + +// AddCountedAt adds i to the "counted_at" field. +func (m *StocktakeItemMutation) AddCountedAt(i int64) { + if m.addcounted_at != nil { + *m.addcounted_at += i + } else { + m.addcounted_at = &i + } +} + +// AddedCountedAt returns the value that was added to the "counted_at" field in this mutation. +func (m *StocktakeItemMutation) AddedCountedAt() (r int64, exists bool) { + v := m.addcounted_at + if v == nil { + return + } + return *v, true +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (m *StocktakeItemMutation) ClearCountedAt() { + m.counted_at = nil + m.addcounted_at = nil + m.clearedFields[stocktakeitem.FieldCountedAt] = struct{}{} +} + +// CountedAtCleared returns if the "counted_at" field was cleared in this mutation. +func (m *StocktakeItemMutation) CountedAtCleared() bool { + _, ok := m.clearedFields[stocktakeitem.FieldCountedAt] + return ok +} + +// ResetCountedAt resets all changes to the "counted_at" field. +func (m *StocktakeItemMutation) ResetCountedAt() { + m.counted_at = nil + m.addcounted_at = nil + delete(m.clearedFields, stocktakeitem.FieldCountedAt) +} + // SetUpdatedAt sets the "updated_at" field. func (m *StocktakeItemMutation) SetUpdatedAt(i int64) { m.updated_at = &i @@ -23463,7 +23639,7 @@ func (m *StocktakeItemMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *StocktakeItemMutation) Fields() []string { - fields := make([]string, 0, 13) + fields := make([]string, 0, 15) if m.stocktake_no != nil { fields = append(fields, stocktakeitem.FieldStocktakeNo) } @@ -23500,6 +23676,12 @@ func (m *StocktakeItemMutation) Fields() []string { if m.counted != nil { fields = append(fields, stocktakeitem.FieldCounted) } + if m.counted_by != nil { + fields = append(fields, stocktakeitem.FieldCountedBy) + } + if m.counted_at != nil { + fields = append(fields, stocktakeitem.FieldCountedAt) + } if m.updated_at != nil { fields = append(fields, stocktakeitem.FieldUpdatedAt) } @@ -23535,6 +23717,10 @@ func (m *StocktakeItemMutation) Field(name string) (ent.Value, bool) { return m.DiffQty() case stocktakeitem.FieldCounted: return m.Counted() + case stocktakeitem.FieldCountedBy: + return m.CountedBy() + case stocktakeitem.FieldCountedAt: + return m.CountedAt() case stocktakeitem.FieldUpdatedAt: return m.UpdatedAt() } @@ -23570,6 +23756,10 @@ func (m *StocktakeItemMutation) OldField(ctx context.Context, name string) (ent. return m.OldDiffQty(ctx) case stocktakeitem.FieldCounted: return m.OldCounted(ctx) + case stocktakeitem.FieldCountedBy: + return m.OldCountedBy(ctx) + case stocktakeitem.FieldCountedAt: + return m.OldCountedAt(ctx) case stocktakeitem.FieldUpdatedAt: return m.OldUpdatedAt(ctx) } @@ -23665,6 +23855,20 @@ func (m *StocktakeItemMutation) SetField(name string, value ent.Value) error { } m.SetCounted(v) return nil + case stocktakeitem.FieldCountedBy: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCountedBy(v) + return nil + case stocktakeitem.FieldCountedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCountedAt(v) + return nil case stocktakeitem.FieldUpdatedAt: v, ok := value.(int64) if !ok { @@ -23689,6 +23893,9 @@ func (m *StocktakeItemMutation) AddedFields() []string { if m.adddiff_qty != nil { fields = append(fields, stocktakeitem.FieldDiffQty) } + if m.addcounted_at != nil { + fields = append(fields, stocktakeitem.FieldCountedAt) + } if m.addupdated_at != nil { fields = append(fields, stocktakeitem.FieldUpdatedAt) } @@ -23706,6 +23913,8 @@ func (m *StocktakeItemMutation) AddedField(name string) (ent.Value, bool) { return m.AddedScannedQty() case stocktakeitem.FieldDiffQty: return m.AddedDiffQty() + case stocktakeitem.FieldCountedAt: + return m.AddedCountedAt() case stocktakeitem.FieldUpdatedAt: return m.AddedUpdatedAt() } @@ -23738,6 +23947,13 @@ func (m *StocktakeItemMutation) AddField(name string, value ent.Value) error { } m.AddDiffQty(v) return nil + case stocktakeitem.FieldCountedAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddCountedAt(v) + return nil case stocktakeitem.FieldUpdatedAt: v, ok := value.(int64) if !ok { @@ -23768,6 +23984,12 @@ func (m *StocktakeItemMutation) ClearedFields() []string { if m.FieldCleared(stocktakeitem.FieldPositionNo) { fields = append(fields, stocktakeitem.FieldPositionNo) } + if m.FieldCleared(stocktakeitem.FieldCountedBy) { + fields = append(fields, stocktakeitem.FieldCountedBy) + } + if m.FieldCleared(stocktakeitem.FieldCountedAt) { + fields = append(fields, stocktakeitem.FieldCountedAt) + } return fields } @@ -23797,6 +24019,12 @@ func (m *StocktakeItemMutation) ClearField(name string) error { case stocktakeitem.FieldPositionNo: m.ClearPositionNo() return nil + case stocktakeitem.FieldCountedBy: + m.ClearCountedBy() + return nil + case stocktakeitem.FieldCountedAt: + m.ClearCountedAt() + return nil } return fmt.Errorf("unknown StocktakeItem nullable field %s", name) } @@ -23841,6 +24069,12 @@ func (m *StocktakeItemMutation) ResetField(name string) error { case stocktakeitem.FieldCounted: m.ResetCounted() return nil + case stocktakeitem.FieldCountedBy: + m.ResetCountedBy() + return nil + case stocktakeitem.FieldCountedAt: + m.ResetCountedAt() + return nil case stocktakeitem.FieldUpdatedAt: m.ResetUpdatedAt() return nil @@ -23907,6 +24141,15 @@ type StocktakeOrderMutation struct { operator *string total_targets *int addtotal_targets *int + diff_count *int + adddiff_count *int + adjusted *bool + adjust_no *string + written_at *int64 + addwritten_at *int64 + written_by *string + uncounted_mode *string + cancel_reason *string created_at *int64 addcreated_at *int64 finished_at *int64 @@ -24192,6 +24435,364 @@ func (m *StocktakeOrderMutation) ResetTotalTargets() { m.addtotal_targets = nil } +// SetDiffCount sets the "diff_count" field. +func (m *StocktakeOrderMutation) SetDiffCount(i int) { + m.diff_count = &i + m.adddiff_count = nil +} + +// DiffCount returns the value of the "diff_count" field in the mutation. +func (m *StocktakeOrderMutation) DiffCount() (r int, exists bool) { + v := m.diff_count + if v == nil { + return + } + return *v, true +} + +// OldDiffCount returns the old "diff_count" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldDiffCount(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDiffCount is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDiffCount requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDiffCount: %w", err) + } + return oldValue.DiffCount, nil +} + +// AddDiffCount adds i to the "diff_count" field. +func (m *StocktakeOrderMutation) AddDiffCount(i int) { + if m.adddiff_count != nil { + *m.adddiff_count += i + } else { + m.adddiff_count = &i + } +} + +// AddedDiffCount returns the value that was added to the "diff_count" field in this mutation. +func (m *StocktakeOrderMutation) AddedDiffCount() (r int, exists bool) { + v := m.adddiff_count + if v == nil { + return + } + return *v, true +} + +// ResetDiffCount resets all changes to the "diff_count" field. +func (m *StocktakeOrderMutation) ResetDiffCount() { + m.diff_count = nil + m.adddiff_count = nil +} + +// SetAdjusted sets the "adjusted" field. +func (m *StocktakeOrderMutation) SetAdjusted(b bool) { + m.adjusted = &b +} + +// Adjusted returns the value of the "adjusted" field in the mutation. +func (m *StocktakeOrderMutation) Adjusted() (r bool, exists bool) { + v := m.adjusted + if v == nil { + return + } + return *v, true +} + +// OldAdjusted returns the old "adjusted" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldAdjusted(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAdjusted is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAdjusted requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAdjusted: %w", err) + } + return oldValue.Adjusted, nil +} + +// ResetAdjusted resets all changes to the "adjusted" field. +func (m *StocktakeOrderMutation) ResetAdjusted() { + m.adjusted = nil +} + +// SetAdjustNo sets the "adjust_no" field. +func (m *StocktakeOrderMutation) SetAdjustNo(s string) { + m.adjust_no = &s +} + +// AdjustNo returns the value of the "adjust_no" field in the mutation. +func (m *StocktakeOrderMutation) AdjustNo() (r string, exists bool) { + v := m.adjust_no + if v == nil { + return + } + return *v, true +} + +// OldAdjustNo returns the old "adjust_no" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldAdjustNo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAdjustNo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAdjustNo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAdjustNo: %w", err) + } + return oldValue.AdjustNo, nil +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (m *StocktakeOrderMutation) ClearAdjustNo() { + m.adjust_no = nil + m.clearedFields[stocktakeorder.FieldAdjustNo] = struct{}{} +} + +// AdjustNoCleared returns if the "adjust_no" field was cleared in this mutation. +func (m *StocktakeOrderMutation) AdjustNoCleared() bool { + _, ok := m.clearedFields[stocktakeorder.FieldAdjustNo] + return ok +} + +// ResetAdjustNo resets all changes to the "adjust_no" field. +func (m *StocktakeOrderMutation) ResetAdjustNo() { + m.adjust_no = nil + delete(m.clearedFields, stocktakeorder.FieldAdjustNo) +} + +// SetWrittenAt sets the "written_at" field. +func (m *StocktakeOrderMutation) SetWrittenAt(i int64) { + m.written_at = &i + m.addwritten_at = nil +} + +// WrittenAt returns the value of the "written_at" field in the mutation. +func (m *StocktakeOrderMutation) WrittenAt() (r int64, exists bool) { + v := m.written_at + if v == nil { + return + } + return *v, true +} + +// OldWrittenAt returns the old "written_at" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldWrittenAt(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldWrittenAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldWrittenAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldWrittenAt: %w", err) + } + return oldValue.WrittenAt, nil +} + +// AddWrittenAt adds i to the "written_at" field. +func (m *StocktakeOrderMutation) AddWrittenAt(i int64) { + if m.addwritten_at != nil { + *m.addwritten_at += i + } else { + m.addwritten_at = &i + } +} + +// AddedWrittenAt returns the value that was added to the "written_at" field in this mutation. +func (m *StocktakeOrderMutation) AddedWrittenAt() (r int64, exists bool) { + v := m.addwritten_at + if v == nil { + return + } + return *v, true +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (m *StocktakeOrderMutation) ClearWrittenAt() { + m.written_at = nil + m.addwritten_at = nil + m.clearedFields[stocktakeorder.FieldWrittenAt] = struct{}{} +} + +// WrittenAtCleared returns if the "written_at" field was cleared in this mutation. +func (m *StocktakeOrderMutation) WrittenAtCleared() bool { + _, ok := m.clearedFields[stocktakeorder.FieldWrittenAt] + return ok +} + +// ResetWrittenAt resets all changes to the "written_at" field. +func (m *StocktakeOrderMutation) ResetWrittenAt() { + m.written_at = nil + m.addwritten_at = nil + delete(m.clearedFields, stocktakeorder.FieldWrittenAt) +} + +// SetWrittenBy sets the "written_by" field. +func (m *StocktakeOrderMutation) SetWrittenBy(s string) { + m.written_by = &s +} + +// WrittenBy returns the value of the "written_by" field in the mutation. +func (m *StocktakeOrderMutation) WrittenBy() (r string, exists bool) { + v := m.written_by + if v == nil { + return + } + return *v, true +} + +// OldWrittenBy returns the old "written_by" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldWrittenBy(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldWrittenBy is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldWrittenBy requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldWrittenBy: %w", err) + } + return oldValue.WrittenBy, nil +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (m *StocktakeOrderMutation) ClearWrittenBy() { + m.written_by = nil + m.clearedFields[stocktakeorder.FieldWrittenBy] = struct{}{} +} + +// WrittenByCleared returns if the "written_by" field was cleared in this mutation. +func (m *StocktakeOrderMutation) WrittenByCleared() bool { + _, ok := m.clearedFields[stocktakeorder.FieldWrittenBy] + return ok +} + +// ResetWrittenBy resets all changes to the "written_by" field. +func (m *StocktakeOrderMutation) ResetWrittenBy() { + m.written_by = nil + delete(m.clearedFields, stocktakeorder.FieldWrittenBy) +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (m *StocktakeOrderMutation) SetUncountedMode(s string) { + m.uncounted_mode = &s +} + +// UncountedMode returns the value of the "uncounted_mode" field in the mutation. +func (m *StocktakeOrderMutation) UncountedMode() (r string, exists bool) { + v := m.uncounted_mode + if v == nil { + return + } + return *v, true +} + +// OldUncountedMode returns the old "uncounted_mode" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldUncountedMode(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUncountedMode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUncountedMode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUncountedMode: %w", err) + } + return oldValue.UncountedMode, nil +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (m *StocktakeOrderMutation) ClearUncountedMode() { + m.uncounted_mode = nil + m.clearedFields[stocktakeorder.FieldUncountedMode] = struct{}{} +} + +// UncountedModeCleared returns if the "uncounted_mode" field was cleared in this mutation. +func (m *StocktakeOrderMutation) UncountedModeCleared() bool { + _, ok := m.clearedFields[stocktakeorder.FieldUncountedMode] + return ok +} + +// ResetUncountedMode resets all changes to the "uncounted_mode" field. +func (m *StocktakeOrderMutation) ResetUncountedMode() { + m.uncounted_mode = nil + delete(m.clearedFields, stocktakeorder.FieldUncountedMode) +} + +// SetCancelReason sets the "cancel_reason" field. +func (m *StocktakeOrderMutation) SetCancelReason(s string) { + m.cancel_reason = &s +} + +// CancelReason returns the value of the "cancel_reason" field in the mutation. +func (m *StocktakeOrderMutation) CancelReason() (r string, exists bool) { + v := m.cancel_reason + if v == nil { + return + } + return *v, true +} + +// OldCancelReason returns the old "cancel_reason" field's value of the StocktakeOrder entity. +// If the StocktakeOrder 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 *StocktakeOrderMutation) OldCancelReason(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCancelReason is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCancelReason requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCancelReason: %w", err) + } + return oldValue.CancelReason, nil +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (m *StocktakeOrderMutation) ClearCancelReason() { + m.cancel_reason = nil + m.clearedFields[stocktakeorder.FieldCancelReason] = struct{}{} +} + +// CancelReasonCleared returns if the "cancel_reason" field was cleared in this mutation. +func (m *StocktakeOrderMutation) CancelReasonCleared() bool { + _, ok := m.clearedFields[stocktakeorder.FieldCancelReason] + return ok +} + +// ResetCancelReason resets all changes to the "cancel_reason" field. +func (m *StocktakeOrderMutation) ResetCancelReason() { + m.cancel_reason = nil + delete(m.clearedFields, stocktakeorder.FieldCancelReason) +} + // SetCreatedAt sets the "created_at" field. func (m *StocktakeOrderMutation) SetCreatedAt(i int64) { m.created_at = &i @@ -24352,7 +24953,7 @@ func (m *StocktakeOrderMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *StocktakeOrderMutation) Fields() []string { - fields := make([]string, 0, 6) + fields := make([]string, 0, 13) if m.stocktake_no != nil { fields = append(fields, stocktakeorder.FieldStocktakeNo) } @@ -24365,6 +24966,27 @@ func (m *StocktakeOrderMutation) Fields() []string { if m.total_targets != nil { fields = append(fields, stocktakeorder.FieldTotalTargets) } + if m.diff_count != nil { + fields = append(fields, stocktakeorder.FieldDiffCount) + } + if m.adjusted != nil { + fields = append(fields, stocktakeorder.FieldAdjusted) + } + if m.adjust_no != nil { + fields = append(fields, stocktakeorder.FieldAdjustNo) + } + if m.written_at != nil { + fields = append(fields, stocktakeorder.FieldWrittenAt) + } + if m.written_by != nil { + fields = append(fields, stocktakeorder.FieldWrittenBy) + } + if m.uncounted_mode != nil { + fields = append(fields, stocktakeorder.FieldUncountedMode) + } + if m.cancel_reason != nil { + fields = append(fields, stocktakeorder.FieldCancelReason) + } if m.created_at != nil { fields = append(fields, stocktakeorder.FieldCreatedAt) } @@ -24387,6 +25009,20 @@ func (m *StocktakeOrderMutation) Field(name string) (ent.Value, bool) { return m.Operator() case stocktakeorder.FieldTotalTargets: return m.TotalTargets() + case stocktakeorder.FieldDiffCount: + return m.DiffCount() + case stocktakeorder.FieldAdjusted: + return m.Adjusted() + case stocktakeorder.FieldAdjustNo: + return m.AdjustNo() + case stocktakeorder.FieldWrittenAt: + return m.WrittenAt() + case stocktakeorder.FieldWrittenBy: + return m.WrittenBy() + case stocktakeorder.FieldUncountedMode: + return m.UncountedMode() + case stocktakeorder.FieldCancelReason: + return m.CancelReason() case stocktakeorder.FieldCreatedAt: return m.CreatedAt() case stocktakeorder.FieldFinishedAt: @@ -24408,6 +25044,20 @@ func (m *StocktakeOrderMutation) OldField(ctx context.Context, name string) (ent return m.OldOperator(ctx) case stocktakeorder.FieldTotalTargets: return m.OldTotalTargets(ctx) + case stocktakeorder.FieldDiffCount: + return m.OldDiffCount(ctx) + case stocktakeorder.FieldAdjusted: + return m.OldAdjusted(ctx) + case stocktakeorder.FieldAdjustNo: + return m.OldAdjustNo(ctx) + case stocktakeorder.FieldWrittenAt: + return m.OldWrittenAt(ctx) + case stocktakeorder.FieldWrittenBy: + return m.OldWrittenBy(ctx) + case stocktakeorder.FieldUncountedMode: + return m.OldUncountedMode(ctx) + case stocktakeorder.FieldCancelReason: + return m.OldCancelReason(ctx) case stocktakeorder.FieldCreatedAt: return m.OldCreatedAt(ctx) case stocktakeorder.FieldFinishedAt: @@ -24449,6 +25099,55 @@ func (m *StocktakeOrderMutation) SetField(name string, value ent.Value) error { } m.SetTotalTargets(v) return nil + case stocktakeorder.FieldDiffCount: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDiffCount(v) + return nil + case stocktakeorder.FieldAdjusted: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAdjusted(v) + return nil + case stocktakeorder.FieldAdjustNo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAdjustNo(v) + return nil + case stocktakeorder.FieldWrittenAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetWrittenAt(v) + return nil + case stocktakeorder.FieldWrittenBy: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetWrittenBy(v) + return nil + case stocktakeorder.FieldUncountedMode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUncountedMode(v) + return nil + case stocktakeorder.FieldCancelReason: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCancelReason(v) + return nil case stocktakeorder.FieldCreatedAt: v, ok := value.(int64) if !ok { @@ -24474,6 +25173,12 @@ func (m *StocktakeOrderMutation) AddedFields() []string { if m.addtotal_targets != nil { fields = append(fields, stocktakeorder.FieldTotalTargets) } + if m.adddiff_count != nil { + fields = append(fields, stocktakeorder.FieldDiffCount) + } + if m.addwritten_at != nil { + fields = append(fields, stocktakeorder.FieldWrittenAt) + } if m.addcreated_at != nil { fields = append(fields, stocktakeorder.FieldCreatedAt) } @@ -24490,6 +25195,10 @@ func (m *StocktakeOrderMutation) AddedField(name string) (ent.Value, bool) { switch name { case stocktakeorder.FieldTotalTargets: return m.AddedTotalTargets() + case stocktakeorder.FieldDiffCount: + return m.AddedDiffCount() + case stocktakeorder.FieldWrittenAt: + return m.AddedWrittenAt() case stocktakeorder.FieldCreatedAt: return m.AddedCreatedAt() case stocktakeorder.FieldFinishedAt: @@ -24510,6 +25219,20 @@ func (m *StocktakeOrderMutation) AddField(name string, value ent.Value) error { } m.AddTotalTargets(v) return nil + case stocktakeorder.FieldDiffCount: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddDiffCount(v) + return nil + case stocktakeorder.FieldWrittenAt: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddWrittenAt(v) + return nil case stocktakeorder.FieldCreatedAt: v, ok := value.(int64) if !ok { @@ -24535,6 +25258,21 @@ func (m *StocktakeOrderMutation) ClearedFields() []string { if m.FieldCleared(stocktakeorder.FieldOperator) { fields = append(fields, stocktakeorder.FieldOperator) } + if m.FieldCleared(stocktakeorder.FieldAdjustNo) { + fields = append(fields, stocktakeorder.FieldAdjustNo) + } + if m.FieldCleared(stocktakeorder.FieldWrittenAt) { + fields = append(fields, stocktakeorder.FieldWrittenAt) + } + if m.FieldCleared(stocktakeorder.FieldWrittenBy) { + fields = append(fields, stocktakeorder.FieldWrittenBy) + } + if m.FieldCleared(stocktakeorder.FieldUncountedMode) { + fields = append(fields, stocktakeorder.FieldUncountedMode) + } + if m.FieldCleared(stocktakeorder.FieldCancelReason) { + fields = append(fields, stocktakeorder.FieldCancelReason) + } if m.FieldCleared(stocktakeorder.FieldFinishedAt) { fields = append(fields, stocktakeorder.FieldFinishedAt) } @@ -24555,6 +25293,21 @@ func (m *StocktakeOrderMutation) ClearField(name string) error { case stocktakeorder.FieldOperator: m.ClearOperator() return nil + case stocktakeorder.FieldAdjustNo: + m.ClearAdjustNo() + return nil + case stocktakeorder.FieldWrittenAt: + m.ClearWrittenAt() + return nil + case stocktakeorder.FieldWrittenBy: + m.ClearWrittenBy() + return nil + case stocktakeorder.FieldUncountedMode: + m.ClearUncountedMode() + return nil + case stocktakeorder.FieldCancelReason: + m.ClearCancelReason() + return nil case stocktakeorder.FieldFinishedAt: m.ClearFinishedAt() return nil @@ -24578,6 +25331,27 @@ func (m *StocktakeOrderMutation) ResetField(name string) error { case stocktakeorder.FieldTotalTargets: m.ResetTotalTargets() return nil + case stocktakeorder.FieldDiffCount: + m.ResetDiffCount() + return nil + case stocktakeorder.FieldAdjusted: + m.ResetAdjusted() + return nil + case stocktakeorder.FieldAdjustNo: + m.ResetAdjustNo() + return nil + case stocktakeorder.FieldWrittenAt: + m.ResetWrittenAt() + return nil + case stocktakeorder.FieldWrittenBy: + m.ResetWrittenBy() + return nil + case stocktakeorder.FieldUncountedMode: + m.ResetUncountedMode() + return nil + case stocktakeorder.FieldCancelReason: + m.ResetCancelReason() + return nil case stocktakeorder.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/bj_power_wms/ent/runtime.go b/bj_power_wms/ent/runtime.go index ba9d207..4f46af2 100644 --- a/bj_power_wms/ent/runtime.go +++ b/bj_power_wms/ent/runtime.go @@ -291,12 +291,18 @@ func init() { 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) + // inventoryDescStocktakeNo is the schema descriptor for stocktake_no field. + inventoryDescStocktakeNo := inventoryFields[27].Descriptor() + // inventory.DefaultStocktakeNo holds the default value on creation for the stocktake_no field. + inventory.DefaultStocktakeNo = inventoryDescStocktakeNo.Default.(string) + // inventory.StocktakeNoValidator is a validator for the "stocktake_no" field. It is called by the builders before save. + inventory.StocktakeNoValidator = inventoryDescStocktakeNo.Validators[0].(func(string) error) // inventoryDescCreatedAt is the schema descriptor for created_at field. - inventoryDescCreatedAt := inventoryFields[28].Descriptor() + inventoryDescCreatedAt := inventoryFields[29].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[29].Descriptor() + inventoryDescUpdatedAt := inventoryFields[30].Descriptor() // inventory.DefaultUpdatedAt holds the default value on creation for the updated_at field. inventory.DefaultUpdatedAt = inventoryDescUpdatedAt.Default.(func() int64) inventorylockFields := schema.InventoryLock{}.Fields() @@ -604,7 +610,7 @@ func init() { // stocktakeitem.DefaultCounted holds the default value on creation for the counted field. stocktakeitem.DefaultCounted = stocktakeitemDescCounted.Default.(bool) // stocktakeitemDescUpdatedAt is the schema descriptor for updated_at field. - stocktakeitemDescUpdatedAt := stocktakeitemFields[12].Descriptor() + stocktakeitemDescUpdatedAt := stocktakeitemFields[14].Descriptor() // stocktakeitem.DefaultUpdatedAt holds the default value on creation for the updated_at field. stocktakeitem.DefaultUpdatedAt = stocktakeitemDescUpdatedAt.Default.(func() int64) stocktakeorderFields := schema.StocktakeOrder{}.Fields() @@ -617,8 +623,16 @@ func init() { stocktakeorderDescTotalTargets := stocktakeorderFields[3].Descriptor() // stocktakeorder.DefaultTotalTargets holds the default value on creation for the total_targets field. stocktakeorder.DefaultTotalTargets = stocktakeorderDescTotalTargets.Default.(int) + // stocktakeorderDescDiffCount is the schema descriptor for diff_count field. + stocktakeorderDescDiffCount := stocktakeorderFields[4].Descriptor() + // stocktakeorder.DefaultDiffCount holds the default value on creation for the diff_count field. + stocktakeorder.DefaultDiffCount = stocktakeorderDescDiffCount.Default.(int) + // stocktakeorderDescAdjusted is the schema descriptor for adjusted field. + stocktakeorderDescAdjusted := stocktakeorderFields[5].Descriptor() + // stocktakeorder.DefaultAdjusted holds the default value on creation for the adjusted field. + stocktakeorder.DefaultAdjusted = stocktakeorderDescAdjusted.Default.(bool) // stocktakeorderDescCreatedAt is the schema descriptor for created_at field. - stocktakeorderDescCreatedAt := stocktakeorderFields[4].Descriptor() + stocktakeorderDescCreatedAt := stocktakeorderFields[11].Descriptor() // stocktakeorder.DefaultCreatedAt holds the default value on creation for the created_at field. stocktakeorder.DefaultCreatedAt = stocktakeorderDescCreatedAt.Default.(func() int64) userFields := schema.User{}.Fields() diff --git a/bj_power_wms/ent/stocktakeitem.go b/bj_power_wms/ent/stocktakeitem.go index 6a533f7..63c4f97 100644 --- a/bj_power_wms/ent/stocktakeitem.go +++ b/bj_power_wms/ent/stocktakeitem.go @@ -40,6 +40,10 @@ type StocktakeItem struct { DiffQty int `json:"diffQty,omitempty"` // 是否已扫码 Counted bool `json:"counted,omitempty"` + // 实盘人(扫码录入者) + CountedBy string `json:"countedBy,omitempty"` + // 实盘时间(unix 秒) + CountedAt int64 `json:"countedAt,omitempty"` // UpdatedAt holds the value of the "updated_at" field. UpdatedAt int64 `json:"updatedAt,omitempty"` selectValues sql.SelectValues @@ -52,9 +56,9 @@ func (*StocktakeItem) scanValues(columns []string) ([]any, error) { switch columns[i] { case stocktakeitem.FieldCounted: values[i] = new(sql.NullBool) - case stocktakeitem.FieldID, stocktakeitem.FieldBookQty, stocktakeitem.FieldScannedQty, stocktakeitem.FieldDiffQty, stocktakeitem.FieldUpdatedAt: + case stocktakeitem.FieldID, stocktakeitem.FieldBookQty, stocktakeitem.FieldScannedQty, stocktakeitem.FieldDiffQty, stocktakeitem.FieldCountedAt, stocktakeitem.FieldUpdatedAt: values[i] = new(sql.NullInt64) - case stocktakeitem.FieldStocktakeNo, stocktakeitem.FieldTargetType, stocktakeitem.FieldTargetID, stocktakeitem.FieldMaterialCode, stocktakeitem.FieldZoneCode, stocktakeitem.FieldShelfNo, stocktakeitem.FieldLayerNo, stocktakeitem.FieldPositionNo: + case stocktakeitem.FieldStocktakeNo, stocktakeitem.FieldTargetType, stocktakeitem.FieldTargetID, stocktakeitem.FieldMaterialCode, stocktakeitem.FieldZoneCode, stocktakeitem.FieldShelfNo, stocktakeitem.FieldLayerNo, stocktakeitem.FieldPositionNo, stocktakeitem.FieldCountedBy: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -149,6 +153,18 @@ func (_m *StocktakeItem) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Counted = value.Bool } + case stocktakeitem.FieldCountedBy: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field counted_by", values[i]) + } else if value.Valid { + _m.CountedBy = value.String + } + case stocktakeitem.FieldCountedAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field counted_at", values[i]) + } else if value.Valid { + _m.CountedAt = value.Int64 + } case stocktakeitem.FieldUpdatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field updated_at", values[i]) @@ -227,6 +243,12 @@ func (_m *StocktakeItem) String() string { builder.WriteString("counted=") builder.WriteString(fmt.Sprintf("%v", _m.Counted)) builder.WriteString(", ") + builder.WriteString("counted_by=") + builder.WriteString(_m.CountedBy) + builder.WriteString(", ") + builder.WriteString("counted_at=") + builder.WriteString(fmt.Sprintf("%v", _m.CountedAt)) + builder.WriteString(", ") builder.WriteString("updated_at=") builder.WriteString(fmt.Sprintf("%v", _m.UpdatedAt)) builder.WriteByte(')') diff --git a/bj_power_wms/ent/stocktakeitem/stocktakeitem.go b/bj_power_wms/ent/stocktakeitem/stocktakeitem.go index fba8a05..a8c444d 100644 --- a/bj_power_wms/ent/stocktakeitem/stocktakeitem.go +++ b/bj_power_wms/ent/stocktakeitem/stocktakeitem.go @@ -35,6 +35,10 @@ const ( FieldDiffQty = "diff_qty" // FieldCounted holds the string denoting the counted field in the database. FieldCounted = "counted" + // FieldCountedBy holds the string denoting the counted_by field in the database. + FieldCountedBy = "counted_by" + // FieldCountedAt holds the string denoting the counted_at field in the database. + FieldCountedAt = "counted_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. FieldUpdatedAt = "updated_at" // Table holds the table name of the stocktakeitem in the database. @@ -56,6 +60,8 @@ var Columns = []string{ FieldScannedQty, FieldDiffQty, FieldCounted, + FieldCountedBy, + FieldCountedAt, FieldUpdatedAt, } @@ -150,6 +156,16 @@ func ByCounted(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCounted, opts...).ToFunc() } +// ByCountedBy orders the results by the counted_by field. +func ByCountedBy(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCountedBy, opts...).ToFunc() +} + +// ByCountedAt orders the results by the counted_at field. +func ByCountedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCountedAt, opts...).ToFunc() +} + // ByUpdatedAt orders the results by the updated_at field. func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() diff --git a/bj_power_wms/ent/stocktakeitem/where.go b/bj_power_wms/ent/stocktakeitem/where.go index 2d1945a..ff23a81 100644 --- a/bj_power_wms/ent/stocktakeitem/where.go +++ b/bj_power_wms/ent/stocktakeitem/where.go @@ -113,6 +113,16 @@ func Counted(v bool) predicate.StocktakeItem { return predicate.StocktakeItem(sql.FieldEQ(FieldCounted, v)) } +// CountedBy applies equality check predicate on the "counted_by" field. It's identical to CountedByEQ. +func CountedBy(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldEQ(FieldCountedBy, v)) +} + +// CountedAt applies equality check predicate on the "counted_at" field. It's identical to CountedAtEQ. +func CountedAt(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldEQ(FieldCountedAt, v)) +} + // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. func UpdatedAt(v int64) predicate.StocktakeItem { return predicate.StocktakeItem(sql.FieldEQ(FieldUpdatedAt, v)) @@ -818,6 +828,131 @@ func CountedNEQ(v bool) predicate.StocktakeItem { return predicate.StocktakeItem(sql.FieldNEQ(FieldCounted, v)) } +// CountedByEQ applies the EQ predicate on the "counted_by" field. +func CountedByEQ(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldEQ(FieldCountedBy, v)) +} + +// CountedByNEQ applies the NEQ predicate on the "counted_by" field. +func CountedByNEQ(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNEQ(FieldCountedBy, v)) +} + +// CountedByIn applies the In predicate on the "counted_by" field. +func CountedByIn(vs ...string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldIn(FieldCountedBy, vs...)) +} + +// CountedByNotIn applies the NotIn predicate on the "counted_by" field. +func CountedByNotIn(vs ...string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNotIn(FieldCountedBy, vs...)) +} + +// CountedByGT applies the GT predicate on the "counted_by" field. +func CountedByGT(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldGT(FieldCountedBy, v)) +} + +// CountedByGTE applies the GTE predicate on the "counted_by" field. +func CountedByGTE(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldGTE(FieldCountedBy, v)) +} + +// CountedByLT applies the LT predicate on the "counted_by" field. +func CountedByLT(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldLT(FieldCountedBy, v)) +} + +// CountedByLTE applies the LTE predicate on the "counted_by" field. +func CountedByLTE(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldLTE(FieldCountedBy, v)) +} + +// CountedByContains applies the Contains predicate on the "counted_by" field. +func CountedByContains(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldContains(FieldCountedBy, v)) +} + +// CountedByHasPrefix applies the HasPrefix predicate on the "counted_by" field. +func CountedByHasPrefix(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldHasPrefix(FieldCountedBy, v)) +} + +// CountedByHasSuffix applies the HasSuffix predicate on the "counted_by" field. +func CountedByHasSuffix(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldHasSuffix(FieldCountedBy, v)) +} + +// CountedByIsNil applies the IsNil predicate on the "counted_by" field. +func CountedByIsNil() predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldIsNull(FieldCountedBy)) +} + +// CountedByNotNil applies the NotNil predicate on the "counted_by" field. +func CountedByNotNil() predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNotNull(FieldCountedBy)) +} + +// CountedByEqualFold applies the EqualFold predicate on the "counted_by" field. +func CountedByEqualFold(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldEqualFold(FieldCountedBy, v)) +} + +// CountedByContainsFold applies the ContainsFold predicate on the "counted_by" field. +func CountedByContainsFold(v string) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldContainsFold(FieldCountedBy, v)) +} + +// CountedAtEQ applies the EQ predicate on the "counted_at" field. +func CountedAtEQ(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldEQ(FieldCountedAt, v)) +} + +// CountedAtNEQ applies the NEQ predicate on the "counted_at" field. +func CountedAtNEQ(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNEQ(FieldCountedAt, v)) +} + +// CountedAtIn applies the In predicate on the "counted_at" field. +func CountedAtIn(vs ...int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldIn(FieldCountedAt, vs...)) +} + +// CountedAtNotIn applies the NotIn predicate on the "counted_at" field. +func CountedAtNotIn(vs ...int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNotIn(FieldCountedAt, vs...)) +} + +// CountedAtGT applies the GT predicate on the "counted_at" field. +func CountedAtGT(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldGT(FieldCountedAt, v)) +} + +// CountedAtGTE applies the GTE predicate on the "counted_at" field. +func CountedAtGTE(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldGTE(FieldCountedAt, v)) +} + +// CountedAtLT applies the LT predicate on the "counted_at" field. +func CountedAtLT(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldLT(FieldCountedAt, v)) +} + +// CountedAtLTE applies the LTE predicate on the "counted_at" field. +func CountedAtLTE(v int64) predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldLTE(FieldCountedAt, v)) +} + +// CountedAtIsNil applies the IsNil predicate on the "counted_at" field. +func CountedAtIsNil() predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldIsNull(FieldCountedAt)) +} + +// CountedAtNotNil applies the NotNil predicate on the "counted_at" field. +func CountedAtNotNil() predicate.StocktakeItem { + return predicate.StocktakeItem(sql.FieldNotNull(FieldCountedAt)) +} + // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. func UpdatedAtEQ(v int64) predicate.StocktakeItem { return predicate.StocktakeItem(sql.FieldEQ(FieldUpdatedAt, v)) diff --git a/bj_power_wms/ent/stocktakeitem_create.go b/bj_power_wms/ent/stocktakeitem_create.go index 4d66010..5a97fe1 100644 --- a/bj_power_wms/ent/stocktakeitem_create.go +++ b/bj_power_wms/ent/stocktakeitem_create.go @@ -165,6 +165,34 @@ func (_c *StocktakeItemCreate) SetNillableCounted(v *bool) *StocktakeItemCreate return _c } +// SetCountedBy sets the "counted_by" field. +func (_c *StocktakeItemCreate) SetCountedBy(v string) *StocktakeItemCreate { + _c.mutation.SetCountedBy(v) + return _c +} + +// SetNillableCountedBy sets the "counted_by" field if the given value is not nil. +func (_c *StocktakeItemCreate) SetNillableCountedBy(v *string) *StocktakeItemCreate { + if v != nil { + _c.SetCountedBy(*v) + } + return _c +} + +// SetCountedAt sets the "counted_at" field. +func (_c *StocktakeItemCreate) SetCountedAt(v int64) *StocktakeItemCreate { + _c.mutation.SetCountedAt(v) + return _c +} + +// SetNillableCountedAt sets the "counted_at" field if the given value is not nil. +func (_c *StocktakeItemCreate) SetNillableCountedAt(v *int64) *StocktakeItemCreate { + if v != nil { + _c.SetCountedAt(*v) + } + return _c +} + // SetUpdatedAt sets the "updated_at" field. func (_c *StocktakeItemCreate) SetUpdatedAt(v int64) *StocktakeItemCreate { _c.mutation.SetUpdatedAt(v) @@ -337,6 +365,14 @@ func (_c *StocktakeItemCreate) createSpec() (*StocktakeItem, *sqlgraph.CreateSpe _spec.SetField(stocktakeitem.FieldCounted, field.TypeBool, value) _node.Counted = value } + if value, ok := _c.mutation.CountedBy(); ok { + _spec.SetField(stocktakeitem.FieldCountedBy, field.TypeString, value) + _node.CountedBy = value + } + if value, ok := _c.mutation.CountedAt(); ok { + _spec.SetField(stocktakeitem.FieldCountedAt, field.TypeInt64, value) + _node.CountedAt = value + } if value, ok := _c.mutation.UpdatedAt(); ok { _spec.SetField(stocktakeitem.FieldUpdatedAt, field.TypeInt64, value) _node.UpdatedAt = value @@ -585,6 +621,48 @@ func (u *StocktakeItemUpsert) UpdateCounted() *StocktakeItemUpsert { return u } +// SetCountedBy sets the "counted_by" field. +func (u *StocktakeItemUpsert) SetCountedBy(v string) *StocktakeItemUpsert { + u.Set(stocktakeitem.FieldCountedBy, v) + return u +} + +// UpdateCountedBy sets the "counted_by" field to the value that was provided on create. +func (u *StocktakeItemUpsert) UpdateCountedBy() *StocktakeItemUpsert { + u.SetExcluded(stocktakeitem.FieldCountedBy) + return u +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (u *StocktakeItemUpsert) ClearCountedBy() *StocktakeItemUpsert { + u.SetNull(stocktakeitem.FieldCountedBy) + return u +} + +// SetCountedAt sets the "counted_at" field. +func (u *StocktakeItemUpsert) SetCountedAt(v int64) *StocktakeItemUpsert { + u.Set(stocktakeitem.FieldCountedAt, v) + return u +} + +// UpdateCountedAt sets the "counted_at" field to the value that was provided on create. +func (u *StocktakeItemUpsert) UpdateCountedAt() *StocktakeItemUpsert { + u.SetExcluded(stocktakeitem.FieldCountedAt) + return u +} + +// AddCountedAt adds v to the "counted_at" field. +func (u *StocktakeItemUpsert) AddCountedAt(v int64) *StocktakeItemUpsert { + u.Add(stocktakeitem.FieldCountedAt, v) + return u +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (u *StocktakeItemUpsert) ClearCountedAt() *StocktakeItemUpsert { + u.SetNull(stocktakeitem.FieldCountedAt) + return u +} + // SetUpdatedAt sets the "updated_at" field. func (u *StocktakeItemUpsert) SetUpdatedAt(v int64) *StocktakeItemUpsert { u.Set(stocktakeitem.FieldUpdatedAt, v) @@ -867,6 +945,55 @@ func (u *StocktakeItemUpsertOne) UpdateCounted() *StocktakeItemUpsertOne { }) } +// SetCountedBy sets the "counted_by" field. +func (u *StocktakeItemUpsertOne) SetCountedBy(v string) *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.SetCountedBy(v) + }) +} + +// UpdateCountedBy sets the "counted_by" field to the value that was provided on create. +func (u *StocktakeItemUpsertOne) UpdateCountedBy() *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.UpdateCountedBy() + }) +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (u *StocktakeItemUpsertOne) ClearCountedBy() *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.ClearCountedBy() + }) +} + +// SetCountedAt sets the "counted_at" field. +func (u *StocktakeItemUpsertOne) SetCountedAt(v int64) *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.SetCountedAt(v) + }) +} + +// AddCountedAt adds v to the "counted_at" field. +func (u *StocktakeItemUpsertOne) AddCountedAt(v int64) *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.AddCountedAt(v) + }) +} + +// UpdateCountedAt sets the "counted_at" field to the value that was provided on create. +func (u *StocktakeItemUpsertOne) UpdateCountedAt() *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.UpdateCountedAt() + }) +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (u *StocktakeItemUpsertOne) ClearCountedAt() *StocktakeItemUpsertOne { + return u.Update(func(s *StocktakeItemUpsert) { + s.ClearCountedAt() + }) +} + // SetUpdatedAt sets the "updated_at" field. func (u *StocktakeItemUpsertOne) SetUpdatedAt(v int64) *StocktakeItemUpsertOne { return u.Update(func(s *StocktakeItemUpsert) { @@ -1316,6 +1443,55 @@ func (u *StocktakeItemUpsertBulk) UpdateCounted() *StocktakeItemUpsertBulk { }) } +// SetCountedBy sets the "counted_by" field. +func (u *StocktakeItemUpsertBulk) SetCountedBy(v string) *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.SetCountedBy(v) + }) +} + +// UpdateCountedBy sets the "counted_by" field to the value that was provided on create. +func (u *StocktakeItemUpsertBulk) UpdateCountedBy() *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.UpdateCountedBy() + }) +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (u *StocktakeItemUpsertBulk) ClearCountedBy() *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.ClearCountedBy() + }) +} + +// SetCountedAt sets the "counted_at" field. +func (u *StocktakeItemUpsertBulk) SetCountedAt(v int64) *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.SetCountedAt(v) + }) +} + +// AddCountedAt adds v to the "counted_at" field. +func (u *StocktakeItemUpsertBulk) AddCountedAt(v int64) *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.AddCountedAt(v) + }) +} + +// UpdateCountedAt sets the "counted_at" field to the value that was provided on create. +func (u *StocktakeItemUpsertBulk) UpdateCountedAt() *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.UpdateCountedAt() + }) +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (u *StocktakeItemUpsertBulk) ClearCountedAt() *StocktakeItemUpsertBulk { + return u.Update(func(s *StocktakeItemUpsert) { + s.ClearCountedAt() + }) +} + // SetUpdatedAt sets the "updated_at" field. func (u *StocktakeItemUpsertBulk) SetUpdatedAt(v int64) *StocktakeItemUpsertBulk { return u.Update(func(s *StocktakeItemUpsert) { diff --git a/bj_power_wms/ent/stocktakeitem_update.go b/bj_power_wms/ent/stocktakeitem_update.go index 7805542..d974f67 100644 --- a/bj_power_wms/ent/stocktakeitem_update.go +++ b/bj_power_wms/ent/stocktakeitem_update.go @@ -246,6 +246,53 @@ func (_u *StocktakeItemUpdate) SetNillableCounted(v *bool) *StocktakeItemUpdate return _u } +// SetCountedBy sets the "counted_by" field. +func (_u *StocktakeItemUpdate) SetCountedBy(v string) *StocktakeItemUpdate { + _u.mutation.SetCountedBy(v) + return _u +} + +// SetNillableCountedBy sets the "counted_by" field if the given value is not nil. +func (_u *StocktakeItemUpdate) SetNillableCountedBy(v *string) *StocktakeItemUpdate { + if v != nil { + _u.SetCountedBy(*v) + } + return _u +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (_u *StocktakeItemUpdate) ClearCountedBy() *StocktakeItemUpdate { + _u.mutation.ClearCountedBy() + return _u +} + +// SetCountedAt sets the "counted_at" field. +func (_u *StocktakeItemUpdate) SetCountedAt(v int64) *StocktakeItemUpdate { + _u.mutation.ResetCountedAt() + _u.mutation.SetCountedAt(v) + return _u +} + +// SetNillableCountedAt sets the "counted_at" field if the given value is not nil. +func (_u *StocktakeItemUpdate) SetNillableCountedAt(v *int64) *StocktakeItemUpdate { + if v != nil { + _u.SetCountedAt(*v) + } + return _u +} + +// AddCountedAt adds value to the "counted_at" field. +func (_u *StocktakeItemUpdate) AddCountedAt(v int64) *StocktakeItemUpdate { + _u.mutation.AddCountedAt(v) + return _u +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (_u *StocktakeItemUpdate) ClearCountedAt() *StocktakeItemUpdate { + _u.mutation.ClearCountedAt() + return _u +} + // SetUpdatedAt sets the "updated_at" field. func (_u *StocktakeItemUpdate) SetUpdatedAt(v int64) *StocktakeItemUpdate { _u.mutation.ResetUpdatedAt() @@ -368,6 +415,21 @@ func (_u *StocktakeItemUpdate) sqlSave(ctx context.Context) (_node int, err erro if value, ok := _u.mutation.Counted(); ok { _spec.SetField(stocktakeitem.FieldCounted, field.TypeBool, value) } + if value, ok := _u.mutation.CountedBy(); ok { + _spec.SetField(stocktakeitem.FieldCountedBy, field.TypeString, value) + } + if _u.mutation.CountedByCleared() { + _spec.ClearField(stocktakeitem.FieldCountedBy, field.TypeString) + } + if value, ok := _u.mutation.CountedAt(); ok { + _spec.SetField(stocktakeitem.FieldCountedAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedCountedAt(); ok { + _spec.AddField(stocktakeitem.FieldCountedAt, field.TypeInt64, value) + } + if _u.mutation.CountedAtCleared() { + _spec.ClearField(stocktakeitem.FieldCountedAt, field.TypeInt64) + } if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(stocktakeitem.FieldUpdatedAt, field.TypeInt64, value) } @@ -613,6 +675,53 @@ func (_u *StocktakeItemUpdateOne) SetNillableCounted(v *bool) *StocktakeItemUpda return _u } +// SetCountedBy sets the "counted_by" field. +func (_u *StocktakeItemUpdateOne) SetCountedBy(v string) *StocktakeItemUpdateOne { + _u.mutation.SetCountedBy(v) + return _u +} + +// SetNillableCountedBy sets the "counted_by" field if the given value is not nil. +func (_u *StocktakeItemUpdateOne) SetNillableCountedBy(v *string) *StocktakeItemUpdateOne { + if v != nil { + _u.SetCountedBy(*v) + } + return _u +} + +// ClearCountedBy clears the value of the "counted_by" field. +func (_u *StocktakeItemUpdateOne) ClearCountedBy() *StocktakeItemUpdateOne { + _u.mutation.ClearCountedBy() + return _u +} + +// SetCountedAt sets the "counted_at" field. +func (_u *StocktakeItemUpdateOne) SetCountedAt(v int64) *StocktakeItemUpdateOne { + _u.mutation.ResetCountedAt() + _u.mutation.SetCountedAt(v) + return _u +} + +// SetNillableCountedAt sets the "counted_at" field if the given value is not nil. +func (_u *StocktakeItemUpdateOne) SetNillableCountedAt(v *int64) *StocktakeItemUpdateOne { + if v != nil { + _u.SetCountedAt(*v) + } + return _u +} + +// AddCountedAt adds value to the "counted_at" field. +func (_u *StocktakeItemUpdateOne) AddCountedAt(v int64) *StocktakeItemUpdateOne { + _u.mutation.AddCountedAt(v) + return _u +} + +// ClearCountedAt clears the value of the "counted_at" field. +func (_u *StocktakeItemUpdateOne) ClearCountedAt() *StocktakeItemUpdateOne { + _u.mutation.ClearCountedAt() + return _u +} + // SetUpdatedAt sets the "updated_at" field. func (_u *StocktakeItemUpdateOne) SetUpdatedAt(v int64) *StocktakeItemUpdateOne { _u.mutation.ResetUpdatedAt() @@ -765,6 +874,21 @@ func (_u *StocktakeItemUpdateOne) sqlSave(ctx context.Context) (_node *Stocktake if value, ok := _u.mutation.Counted(); ok { _spec.SetField(stocktakeitem.FieldCounted, field.TypeBool, value) } + if value, ok := _u.mutation.CountedBy(); ok { + _spec.SetField(stocktakeitem.FieldCountedBy, field.TypeString, value) + } + if _u.mutation.CountedByCleared() { + _spec.ClearField(stocktakeitem.FieldCountedBy, field.TypeString) + } + if value, ok := _u.mutation.CountedAt(); ok { + _spec.SetField(stocktakeitem.FieldCountedAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedCountedAt(); ok { + _spec.AddField(stocktakeitem.FieldCountedAt, field.TypeInt64, value) + } + if _u.mutation.CountedAtCleared() { + _spec.ClearField(stocktakeitem.FieldCountedAt, field.TypeInt64) + } if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(stocktakeitem.FieldUpdatedAt, field.TypeInt64, value) } diff --git a/bj_power_wms/ent/stocktakeorder.go b/bj_power_wms/ent/stocktakeorder.go index e4eff6a..a61f9c6 100644 --- a/bj_power_wms/ent/stocktakeorder.go +++ b/bj_power_wms/ent/stocktakeorder.go @@ -18,12 +18,26 @@ type StocktakeOrder struct { ID int `json:"id,omitempty"` // 盘点单号 StocktakeNo string `json:"stocktakeNo,omitempty"` - // 进行中/已完成 + // 进行中/已完成/已写回/已取消 Status string `json:"status,omitempty"` // 发起人 Operator string `json:"operator,omitempty"` - // 快照目标数 + // 快照目标数(本次盘点覆盖的批次/SN总数) TotalTargets int `json:"totalTargets,omitempty"` + // 差异项数(diff!=0 的明细数) + DiffCount int `json:"diffCount,omitempty"` + // 差异是否已写回库存 + Adjusted bool `json:"adjusted,omitempty"` + // 盘点调整单号(写回时生成) + AdjustNo string `json:"adjustNo,omitempty"` + // 写回时间(unix 秒) + WrittenAt int64 `json:"writtenAt,omitempty"` + // 写回操作人 + WrittenBy string `json:"writtenBy,omitempty"` + // 未盘项处理 keep=按账面保留/zero=盘亏为0 + UncountedMode string `json:"uncountedMode,omitempty"` + // 取消原因 + CancelReason string `json:"cancelReason,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt int64 `json:"createdAt,omitempty"` // 完成时间 @@ -36,9 +50,11 @@ func (*StocktakeOrder) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case stocktakeorder.FieldID, stocktakeorder.FieldTotalTargets, stocktakeorder.FieldCreatedAt, stocktakeorder.FieldFinishedAt: + case stocktakeorder.FieldAdjusted: + values[i] = new(sql.NullBool) + case stocktakeorder.FieldID, stocktakeorder.FieldTotalTargets, stocktakeorder.FieldDiffCount, stocktakeorder.FieldWrittenAt, stocktakeorder.FieldCreatedAt, stocktakeorder.FieldFinishedAt: values[i] = new(sql.NullInt64) - case stocktakeorder.FieldStocktakeNo, stocktakeorder.FieldStatus, stocktakeorder.FieldOperator: + case stocktakeorder.FieldStocktakeNo, stocktakeorder.FieldStatus, stocktakeorder.FieldOperator, stocktakeorder.FieldAdjustNo, stocktakeorder.FieldWrittenBy, stocktakeorder.FieldUncountedMode, stocktakeorder.FieldCancelReason: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -85,6 +101,48 @@ func (_m *StocktakeOrder) assignValues(columns []string, values []any) error { } else if value.Valid { _m.TotalTargets = int(value.Int64) } + case stocktakeorder.FieldDiffCount: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field diff_count", values[i]) + } else if value.Valid { + _m.DiffCount = int(value.Int64) + } + case stocktakeorder.FieldAdjusted: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field adjusted", values[i]) + } else if value.Valid { + _m.Adjusted = value.Bool + } + case stocktakeorder.FieldAdjustNo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field adjust_no", values[i]) + } else if value.Valid { + _m.AdjustNo = value.String + } + case stocktakeorder.FieldWrittenAt: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field written_at", values[i]) + } else if value.Valid { + _m.WrittenAt = value.Int64 + } + case stocktakeorder.FieldWrittenBy: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field written_by", values[i]) + } else if value.Valid { + _m.WrittenBy = value.String + } + case stocktakeorder.FieldUncountedMode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field uncounted_mode", values[i]) + } else if value.Valid { + _m.UncountedMode = value.String + } + case stocktakeorder.FieldCancelReason: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field cancel_reason", values[i]) + } else if value.Valid { + _m.CancelReason = value.String + } case stocktakeorder.FieldCreatedAt: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) @@ -145,6 +203,27 @@ func (_m *StocktakeOrder) String() string { builder.WriteString("total_targets=") builder.WriteString(fmt.Sprintf("%v", _m.TotalTargets)) builder.WriteString(", ") + builder.WriteString("diff_count=") + builder.WriteString(fmt.Sprintf("%v", _m.DiffCount)) + builder.WriteString(", ") + builder.WriteString("adjusted=") + builder.WriteString(fmt.Sprintf("%v", _m.Adjusted)) + builder.WriteString(", ") + builder.WriteString("adjust_no=") + builder.WriteString(_m.AdjustNo) + builder.WriteString(", ") + builder.WriteString("written_at=") + builder.WriteString(fmt.Sprintf("%v", _m.WrittenAt)) + builder.WriteString(", ") + builder.WriteString("written_by=") + builder.WriteString(_m.WrittenBy) + builder.WriteString(", ") + builder.WriteString("uncounted_mode=") + builder.WriteString(_m.UncountedMode) + builder.WriteString(", ") + builder.WriteString("cancel_reason=") + builder.WriteString(_m.CancelReason) + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(fmt.Sprintf("%v", _m.CreatedAt)) builder.WriteString(", ") diff --git a/bj_power_wms/ent/stocktakeorder/stocktakeorder.go b/bj_power_wms/ent/stocktakeorder/stocktakeorder.go index 5c24b30..7e32fac 100644 --- a/bj_power_wms/ent/stocktakeorder/stocktakeorder.go +++ b/bj_power_wms/ent/stocktakeorder/stocktakeorder.go @@ -19,6 +19,20 @@ const ( FieldOperator = "operator" // FieldTotalTargets holds the string denoting the total_targets field in the database. FieldTotalTargets = "total_targets" + // FieldDiffCount holds the string denoting the diff_count field in the database. + FieldDiffCount = "diff_count" + // FieldAdjusted holds the string denoting the adjusted field in the database. + FieldAdjusted = "adjusted" + // FieldAdjustNo holds the string denoting the adjust_no field in the database. + FieldAdjustNo = "adjust_no" + // FieldWrittenAt holds the string denoting the written_at field in the database. + FieldWrittenAt = "written_at" + // FieldWrittenBy holds the string denoting the written_by field in the database. + FieldWrittenBy = "written_by" + // FieldUncountedMode holds the string denoting the uncounted_mode field in the database. + FieldUncountedMode = "uncounted_mode" + // FieldCancelReason holds the string denoting the cancel_reason field in the database. + FieldCancelReason = "cancel_reason" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldFinishedAt holds the string denoting the finished_at field in the database. @@ -34,6 +48,13 @@ var Columns = []string{ FieldStatus, FieldOperator, FieldTotalTargets, + FieldDiffCount, + FieldAdjusted, + FieldAdjustNo, + FieldWrittenAt, + FieldWrittenBy, + FieldUncountedMode, + FieldCancelReason, FieldCreatedAt, FieldFinishedAt, } @@ -53,6 +74,10 @@ var ( DefaultStatus string // DefaultTotalTargets holds the default value on creation for the "total_targets" field. DefaultTotalTargets int + // DefaultDiffCount holds the default value on creation for the "diff_count" field. + DefaultDiffCount int + // DefaultAdjusted holds the default value on creation for the "adjusted" field. + DefaultAdjusted bool // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() int64 ) @@ -85,6 +110,41 @@ func ByTotalTargets(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldTotalTargets, opts...).ToFunc() } +// ByDiffCount orders the results by the diff_count field. +func ByDiffCount(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDiffCount, opts...).ToFunc() +} + +// ByAdjusted orders the results by the adjusted field. +func ByAdjusted(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAdjusted, opts...).ToFunc() +} + +// ByAdjustNo orders the results by the adjust_no field. +func ByAdjustNo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAdjustNo, opts...).ToFunc() +} + +// ByWrittenAt orders the results by the written_at field. +func ByWrittenAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldWrittenAt, opts...).ToFunc() +} + +// ByWrittenBy orders the results by the written_by field. +func ByWrittenBy(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldWrittenBy, opts...).ToFunc() +} + +// ByUncountedMode orders the results by the uncounted_mode field. +func ByUncountedMode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUncountedMode, opts...).ToFunc() +} + +// ByCancelReason orders the results by the cancel_reason field. +func ByCancelReason(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCancelReason, 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/stocktakeorder/where.go b/bj_power_wms/ent/stocktakeorder/where.go index ea07b7c..ed6e2c6 100644 --- a/bj_power_wms/ent/stocktakeorder/where.go +++ b/bj_power_wms/ent/stocktakeorder/where.go @@ -73,6 +73,41 @@ func TotalTargets(v int) predicate.StocktakeOrder { return predicate.StocktakeOrder(sql.FieldEQ(FieldTotalTargets, v)) } +// DiffCount applies equality check predicate on the "diff_count" field. It's identical to DiffCountEQ. +func DiffCount(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldDiffCount, v)) +} + +// Adjusted applies equality check predicate on the "adjusted" field. It's identical to AdjustedEQ. +func Adjusted(v bool) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldAdjusted, v)) +} + +// AdjustNo applies equality check predicate on the "adjust_no" field. It's identical to AdjustNoEQ. +func AdjustNo(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldAdjustNo, v)) +} + +// WrittenAt applies equality check predicate on the "written_at" field. It's identical to WrittenAtEQ. +func WrittenAt(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldWrittenAt, v)) +} + +// WrittenBy applies equality check predicate on the "written_by" field. It's identical to WrittenByEQ. +func WrittenBy(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldWrittenBy, v)) +} + +// UncountedMode applies equality check predicate on the "uncounted_mode" field. It's identical to UncountedModeEQ. +func UncountedMode(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldUncountedMode, v)) +} + +// CancelReason applies equality check predicate on the "cancel_reason" field. It's identical to CancelReasonEQ. +func CancelReason(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldCancelReason, v)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v int64) predicate.StocktakeOrder { return predicate.StocktakeOrder(sql.FieldEQ(FieldCreatedAt, v)) @@ -328,6 +363,406 @@ func TotalTargetsLTE(v int) predicate.StocktakeOrder { return predicate.StocktakeOrder(sql.FieldLTE(FieldTotalTargets, v)) } +// DiffCountEQ applies the EQ predicate on the "diff_count" field. +func DiffCountEQ(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldDiffCount, v)) +} + +// DiffCountNEQ applies the NEQ predicate on the "diff_count" field. +func DiffCountNEQ(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldDiffCount, v)) +} + +// DiffCountIn applies the In predicate on the "diff_count" field. +func DiffCountIn(vs ...int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldDiffCount, vs...)) +} + +// DiffCountNotIn applies the NotIn predicate on the "diff_count" field. +func DiffCountNotIn(vs ...int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldDiffCount, vs...)) +} + +// DiffCountGT applies the GT predicate on the "diff_count" field. +func DiffCountGT(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldDiffCount, v)) +} + +// DiffCountGTE applies the GTE predicate on the "diff_count" field. +func DiffCountGTE(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldDiffCount, v)) +} + +// DiffCountLT applies the LT predicate on the "diff_count" field. +func DiffCountLT(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldDiffCount, v)) +} + +// DiffCountLTE applies the LTE predicate on the "diff_count" field. +func DiffCountLTE(v int) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldDiffCount, v)) +} + +// AdjustedEQ applies the EQ predicate on the "adjusted" field. +func AdjustedEQ(v bool) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldAdjusted, v)) +} + +// AdjustedNEQ applies the NEQ predicate on the "adjusted" field. +func AdjustedNEQ(v bool) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldAdjusted, v)) +} + +// AdjustNoEQ applies the EQ predicate on the "adjust_no" field. +func AdjustNoEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldAdjustNo, v)) +} + +// AdjustNoNEQ applies the NEQ predicate on the "adjust_no" field. +func AdjustNoNEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldAdjustNo, v)) +} + +// AdjustNoIn applies the In predicate on the "adjust_no" field. +func AdjustNoIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldAdjustNo, vs...)) +} + +// AdjustNoNotIn applies the NotIn predicate on the "adjust_no" field. +func AdjustNoNotIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldAdjustNo, vs...)) +} + +// AdjustNoGT applies the GT predicate on the "adjust_no" field. +func AdjustNoGT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldAdjustNo, v)) +} + +// AdjustNoGTE applies the GTE predicate on the "adjust_no" field. +func AdjustNoGTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldAdjustNo, v)) +} + +// AdjustNoLT applies the LT predicate on the "adjust_no" field. +func AdjustNoLT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldAdjustNo, v)) +} + +// AdjustNoLTE applies the LTE predicate on the "adjust_no" field. +func AdjustNoLTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldAdjustNo, v)) +} + +// AdjustNoContains applies the Contains predicate on the "adjust_no" field. +func AdjustNoContains(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContains(FieldAdjustNo, v)) +} + +// AdjustNoHasPrefix applies the HasPrefix predicate on the "adjust_no" field. +func AdjustNoHasPrefix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasPrefix(FieldAdjustNo, v)) +} + +// AdjustNoHasSuffix applies the HasSuffix predicate on the "adjust_no" field. +func AdjustNoHasSuffix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasSuffix(FieldAdjustNo, v)) +} + +// AdjustNoIsNil applies the IsNil predicate on the "adjust_no" field. +func AdjustNoIsNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIsNull(FieldAdjustNo)) +} + +// AdjustNoNotNil applies the NotNil predicate on the "adjust_no" field. +func AdjustNoNotNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotNull(FieldAdjustNo)) +} + +// AdjustNoEqualFold applies the EqualFold predicate on the "adjust_no" field. +func AdjustNoEqualFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEqualFold(FieldAdjustNo, v)) +} + +// AdjustNoContainsFold applies the ContainsFold predicate on the "adjust_no" field. +func AdjustNoContainsFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContainsFold(FieldAdjustNo, v)) +} + +// WrittenAtEQ applies the EQ predicate on the "written_at" field. +func WrittenAtEQ(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldWrittenAt, v)) +} + +// WrittenAtNEQ applies the NEQ predicate on the "written_at" field. +func WrittenAtNEQ(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldWrittenAt, v)) +} + +// WrittenAtIn applies the In predicate on the "written_at" field. +func WrittenAtIn(vs ...int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldWrittenAt, vs...)) +} + +// WrittenAtNotIn applies the NotIn predicate on the "written_at" field. +func WrittenAtNotIn(vs ...int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldWrittenAt, vs...)) +} + +// WrittenAtGT applies the GT predicate on the "written_at" field. +func WrittenAtGT(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldWrittenAt, v)) +} + +// WrittenAtGTE applies the GTE predicate on the "written_at" field. +func WrittenAtGTE(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldWrittenAt, v)) +} + +// WrittenAtLT applies the LT predicate on the "written_at" field. +func WrittenAtLT(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldWrittenAt, v)) +} + +// WrittenAtLTE applies the LTE predicate on the "written_at" field. +func WrittenAtLTE(v int64) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldWrittenAt, v)) +} + +// WrittenAtIsNil applies the IsNil predicate on the "written_at" field. +func WrittenAtIsNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIsNull(FieldWrittenAt)) +} + +// WrittenAtNotNil applies the NotNil predicate on the "written_at" field. +func WrittenAtNotNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotNull(FieldWrittenAt)) +} + +// WrittenByEQ applies the EQ predicate on the "written_by" field. +func WrittenByEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldWrittenBy, v)) +} + +// WrittenByNEQ applies the NEQ predicate on the "written_by" field. +func WrittenByNEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldWrittenBy, v)) +} + +// WrittenByIn applies the In predicate on the "written_by" field. +func WrittenByIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldWrittenBy, vs...)) +} + +// WrittenByNotIn applies the NotIn predicate on the "written_by" field. +func WrittenByNotIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldWrittenBy, vs...)) +} + +// WrittenByGT applies the GT predicate on the "written_by" field. +func WrittenByGT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldWrittenBy, v)) +} + +// WrittenByGTE applies the GTE predicate on the "written_by" field. +func WrittenByGTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldWrittenBy, v)) +} + +// WrittenByLT applies the LT predicate on the "written_by" field. +func WrittenByLT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldWrittenBy, v)) +} + +// WrittenByLTE applies the LTE predicate on the "written_by" field. +func WrittenByLTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldWrittenBy, v)) +} + +// WrittenByContains applies the Contains predicate on the "written_by" field. +func WrittenByContains(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContains(FieldWrittenBy, v)) +} + +// WrittenByHasPrefix applies the HasPrefix predicate on the "written_by" field. +func WrittenByHasPrefix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasPrefix(FieldWrittenBy, v)) +} + +// WrittenByHasSuffix applies the HasSuffix predicate on the "written_by" field. +func WrittenByHasSuffix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasSuffix(FieldWrittenBy, v)) +} + +// WrittenByIsNil applies the IsNil predicate on the "written_by" field. +func WrittenByIsNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIsNull(FieldWrittenBy)) +} + +// WrittenByNotNil applies the NotNil predicate on the "written_by" field. +func WrittenByNotNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotNull(FieldWrittenBy)) +} + +// WrittenByEqualFold applies the EqualFold predicate on the "written_by" field. +func WrittenByEqualFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEqualFold(FieldWrittenBy, v)) +} + +// WrittenByContainsFold applies the ContainsFold predicate on the "written_by" field. +func WrittenByContainsFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContainsFold(FieldWrittenBy, v)) +} + +// UncountedModeEQ applies the EQ predicate on the "uncounted_mode" field. +func UncountedModeEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldUncountedMode, v)) +} + +// UncountedModeNEQ applies the NEQ predicate on the "uncounted_mode" field. +func UncountedModeNEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldUncountedMode, v)) +} + +// UncountedModeIn applies the In predicate on the "uncounted_mode" field. +func UncountedModeIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldUncountedMode, vs...)) +} + +// UncountedModeNotIn applies the NotIn predicate on the "uncounted_mode" field. +func UncountedModeNotIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldUncountedMode, vs...)) +} + +// UncountedModeGT applies the GT predicate on the "uncounted_mode" field. +func UncountedModeGT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldUncountedMode, v)) +} + +// UncountedModeGTE applies the GTE predicate on the "uncounted_mode" field. +func UncountedModeGTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldUncountedMode, v)) +} + +// UncountedModeLT applies the LT predicate on the "uncounted_mode" field. +func UncountedModeLT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldUncountedMode, v)) +} + +// UncountedModeLTE applies the LTE predicate on the "uncounted_mode" field. +func UncountedModeLTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldUncountedMode, v)) +} + +// UncountedModeContains applies the Contains predicate on the "uncounted_mode" field. +func UncountedModeContains(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContains(FieldUncountedMode, v)) +} + +// UncountedModeHasPrefix applies the HasPrefix predicate on the "uncounted_mode" field. +func UncountedModeHasPrefix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasPrefix(FieldUncountedMode, v)) +} + +// UncountedModeHasSuffix applies the HasSuffix predicate on the "uncounted_mode" field. +func UncountedModeHasSuffix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasSuffix(FieldUncountedMode, v)) +} + +// UncountedModeIsNil applies the IsNil predicate on the "uncounted_mode" field. +func UncountedModeIsNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIsNull(FieldUncountedMode)) +} + +// UncountedModeNotNil applies the NotNil predicate on the "uncounted_mode" field. +func UncountedModeNotNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotNull(FieldUncountedMode)) +} + +// UncountedModeEqualFold applies the EqualFold predicate on the "uncounted_mode" field. +func UncountedModeEqualFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEqualFold(FieldUncountedMode, v)) +} + +// UncountedModeContainsFold applies the ContainsFold predicate on the "uncounted_mode" field. +func UncountedModeContainsFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContainsFold(FieldUncountedMode, v)) +} + +// CancelReasonEQ applies the EQ predicate on the "cancel_reason" field. +func CancelReasonEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEQ(FieldCancelReason, v)) +} + +// CancelReasonNEQ applies the NEQ predicate on the "cancel_reason" field. +func CancelReasonNEQ(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNEQ(FieldCancelReason, v)) +} + +// CancelReasonIn applies the In predicate on the "cancel_reason" field. +func CancelReasonIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIn(FieldCancelReason, vs...)) +} + +// CancelReasonNotIn applies the NotIn predicate on the "cancel_reason" field. +func CancelReasonNotIn(vs ...string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotIn(FieldCancelReason, vs...)) +} + +// CancelReasonGT applies the GT predicate on the "cancel_reason" field. +func CancelReasonGT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGT(FieldCancelReason, v)) +} + +// CancelReasonGTE applies the GTE predicate on the "cancel_reason" field. +func CancelReasonGTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldGTE(FieldCancelReason, v)) +} + +// CancelReasonLT applies the LT predicate on the "cancel_reason" field. +func CancelReasonLT(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLT(FieldCancelReason, v)) +} + +// CancelReasonLTE applies the LTE predicate on the "cancel_reason" field. +func CancelReasonLTE(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldLTE(FieldCancelReason, v)) +} + +// CancelReasonContains applies the Contains predicate on the "cancel_reason" field. +func CancelReasonContains(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContains(FieldCancelReason, v)) +} + +// CancelReasonHasPrefix applies the HasPrefix predicate on the "cancel_reason" field. +func CancelReasonHasPrefix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasPrefix(FieldCancelReason, v)) +} + +// CancelReasonHasSuffix applies the HasSuffix predicate on the "cancel_reason" field. +func CancelReasonHasSuffix(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldHasSuffix(FieldCancelReason, v)) +} + +// CancelReasonIsNil applies the IsNil predicate on the "cancel_reason" field. +func CancelReasonIsNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldIsNull(FieldCancelReason)) +} + +// CancelReasonNotNil applies the NotNil predicate on the "cancel_reason" field. +func CancelReasonNotNil() predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldNotNull(FieldCancelReason)) +} + +// CancelReasonEqualFold applies the EqualFold predicate on the "cancel_reason" field. +func CancelReasonEqualFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldEqualFold(FieldCancelReason, v)) +} + +// CancelReasonContainsFold applies the ContainsFold predicate on the "cancel_reason" field. +func CancelReasonContainsFold(v string) predicate.StocktakeOrder { + return predicate.StocktakeOrder(sql.FieldContainsFold(FieldCancelReason, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v int64) predicate.StocktakeOrder { return predicate.StocktakeOrder(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/bj_power_wms/ent/stocktakeorder_create.go b/bj_power_wms/ent/stocktakeorder_create.go index 7e13dc4..bbb622e 100644 --- a/bj_power_wms/ent/stocktakeorder_create.go +++ b/bj_power_wms/ent/stocktakeorder_create.go @@ -69,6 +69,104 @@ func (_c *StocktakeOrderCreate) SetNillableTotalTargets(v *int) *StocktakeOrderC return _c } +// SetDiffCount sets the "diff_count" field. +func (_c *StocktakeOrderCreate) SetDiffCount(v int) *StocktakeOrderCreate { + _c.mutation.SetDiffCount(v) + return _c +} + +// SetNillableDiffCount sets the "diff_count" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableDiffCount(v *int) *StocktakeOrderCreate { + if v != nil { + _c.SetDiffCount(*v) + } + return _c +} + +// SetAdjusted sets the "adjusted" field. +func (_c *StocktakeOrderCreate) SetAdjusted(v bool) *StocktakeOrderCreate { + _c.mutation.SetAdjusted(v) + return _c +} + +// SetNillableAdjusted sets the "adjusted" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableAdjusted(v *bool) *StocktakeOrderCreate { + if v != nil { + _c.SetAdjusted(*v) + } + return _c +} + +// SetAdjustNo sets the "adjust_no" field. +func (_c *StocktakeOrderCreate) SetAdjustNo(v string) *StocktakeOrderCreate { + _c.mutation.SetAdjustNo(v) + return _c +} + +// SetNillableAdjustNo sets the "adjust_no" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableAdjustNo(v *string) *StocktakeOrderCreate { + if v != nil { + _c.SetAdjustNo(*v) + } + return _c +} + +// SetWrittenAt sets the "written_at" field. +func (_c *StocktakeOrderCreate) SetWrittenAt(v int64) *StocktakeOrderCreate { + _c.mutation.SetWrittenAt(v) + return _c +} + +// SetNillableWrittenAt sets the "written_at" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableWrittenAt(v *int64) *StocktakeOrderCreate { + if v != nil { + _c.SetWrittenAt(*v) + } + return _c +} + +// SetWrittenBy sets the "written_by" field. +func (_c *StocktakeOrderCreate) SetWrittenBy(v string) *StocktakeOrderCreate { + _c.mutation.SetWrittenBy(v) + return _c +} + +// SetNillableWrittenBy sets the "written_by" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableWrittenBy(v *string) *StocktakeOrderCreate { + if v != nil { + _c.SetWrittenBy(*v) + } + return _c +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (_c *StocktakeOrderCreate) SetUncountedMode(v string) *StocktakeOrderCreate { + _c.mutation.SetUncountedMode(v) + return _c +} + +// SetNillableUncountedMode sets the "uncounted_mode" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableUncountedMode(v *string) *StocktakeOrderCreate { + if v != nil { + _c.SetUncountedMode(*v) + } + return _c +} + +// SetCancelReason sets the "cancel_reason" field. +func (_c *StocktakeOrderCreate) SetCancelReason(v string) *StocktakeOrderCreate { + _c.mutation.SetCancelReason(v) + return _c +} + +// SetNillableCancelReason sets the "cancel_reason" field if the given value is not nil. +func (_c *StocktakeOrderCreate) SetNillableCancelReason(v *string) *StocktakeOrderCreate { + if v != nil { + _c.SetCancelReason(*v) + } + return _c +} + // SetCreatedAt sets the "created_at" field. func (_c *StocktakeOrderCreate) SetCreatedAt(v int64) *StocktakeOrderCreate { _c.mutation.SetCreatedAt(v) @@ -140,6 +238,14 @@ func (_c *StocktakeOrderCreate) defaults() { v := stocktakeorder.DefaultTotalTargets _c.mutation.SetTotalTargets(v) } + if _, ok := _c.mutation.DiffCount(); !ok { + v := stocktakeorder.DefaultDiffCount + _c.mutation.SetDiffCount(v) + } + if _, ok := _c.mutation.Adjusted(); !ok { + v := stocktakeorder.DefaultAdjusted + _c.mutation.SetAdjusted(v) + } if _, ok := _c.mutation.CreatedAt(); !ok { v := stocktakeorder.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) @@ -157,6 +263,12 @@ func (_c *StocktakeOrderCreate) check() error { if _, ok := _c.mutation.TotalTargets(); !ok { return &ValidationError{Name: "total_targets", err: errors.New(`ent: missing required field "StocktakeOrder.total_targets"`)} } + if _, ok := _c.mutation.DiffCount(); !ok { + return &ValidationError{Name: "diff_count", err: errors.New(`ent: missing required field "StocktakeOrder.diff_count"`)} + } + if _, ok := _c.mutation.Adjusted(); !ok { + return &ValidationError{Name: "adjusted", err: errors.New(`ent: missing required field "StocktakeOrder.adjusted"`)} + } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "StocktakeOrder.created_at"`)} } @@ -203,6 +315,34 @@ func (_c *StocktakeOrderCreate) createSpec() (*StocktakeOrder, *sqlgraph.CreateS _spec.SetField(stocktakeorder.FieldTotalTargets, field.TypeInt, value) _node.TotalTargets = value } + if value, ok := _c.mutation.DiffCount(); ok { + _spec.SetField(stocktakeorder.FieldDiffCount, field.TypeInt, value) + _node.DiffCount = value + } + if value, ok := _c.mutation.Adjusted(); ok { + _spec.SetField(stocktakeorder.FieldAdjusted, field.TypeBool, value) + _node.Adjusted = value + } + if value, ok := _c.mutation.AdjustNo(); ok { + _spec.SetField(stocktakeorder.FieldAdjustNo, field.TypeString, value) + _node.AdjustNo = value + } + if value, ok := _c.mutation.WrittenAt(); ok { + _spec.SetField(stocktakeorder.FieldWrittenAt, field.TypeInt64, value) + _node.WrittenAt = value + } + if value, ok := _c.mutation.WrittenBy(); ok { + _spec.SetField(stocktakeorder.FieldWrittenBy, field.TypeString, value) + _node.WrittenBy = value + } + if value, ok := _c.mutation.UncountedMode(); ok { + _spec.SetField(stocktakeorder.FieldUncountedMode, field.TypeString, value) + _node.UncountedMode = value + } + if value, ok := _c.mutation.CancelReason(); ok { + _spec.SetField(stocktakeorder.FieldCancelReason, field.TypeString, value) + _node.CancelReason = value + } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(stocktakeorder.FieldCreatedAt, field.TypeInt64, value) _node.CreatedAt = value @@ -323,6 +463,132 @@ func (u *StocktakeOrderUpsert) AddTotalTargets(v int) *StocktakeOrderUpsert { return u } +// SetDiffCount sets the "diff_count" field. +func (u *StocktakeOrderUpsert) SetDiffCount(v int) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldDiffCount, v) + return u +} + +// UpdateDiffCount sets the "diff_count" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateDiffCount() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldDiffCount) + return u +} + +// AddDiffCount adds v to the "diff_count" field. +func (u *StocktakeOrderUpsert) AddDiffCount(v int) *StocktakeOrderUpsert { + u.Add(stocktakeorder.FieldDiffCount, v) + return u +} + +// SetAdjusted sets the "adjusted" field. +func (u *StocktakeOrderUpsert) SetAdjusted(v bool) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldAdjusted, v) + return u +} + +// UpdateAdjusted sets the "adjusted" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateAdjusted() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldAdjusted) + return u +} + +// SetAdjustNo sets the "adjust_no" field. +func (u *StocktakeOrderUpsert) SetAdjustNo(v string) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldAdjustNo, v) + return u +} + +// UpdateAdjustNo sets the "adjust_no" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateAdjustNo() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldAdjustNo) + return u +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (u *StocktakeOrderUpsert) ClearAdjustNo() *StocktakeOrderUpsert { + u.SetNull(stocktakeorder.FieldAdjustNo) + return u +} + +// SetWrittenAt sets the "written_at" field. +func (u *StocktakeOrderUpsert) SetWrittenAt(v int64) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldWrittenAt, v) + return u +} + +// UpdateWrittenAt sets the "written_at" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateWrittenAt() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldWrittenAt) + return u +} + +// AddWrittenAt adds v to the "written_at" field. +func (u *StocktakeOrderUpsert) AddWrittenAt(v int64) *StocktakeOrderUpsert { + u.Add(stocktakeorder.FieldWrittenAt, v) + return u +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (u *StocktakeOrderUpsert) ClearWrittenAt() *StocktakeOrderUpsert { + u.SetNull(stocktakeorder.FieldWrittenAt) + return u +} + +// SetWrittenBy sets the "written_by" field. +func (u *StocktakeOrderUpsert) SetWrittenBy(v string) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldWrittenBy, v) + return u +} + +// UpdateWrittenBy sets the "written_by" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateWrittenBy() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldWrittenBy) + return u +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (u *StocktakeOrderUpsert) ClearWrittenBy() *StocktakeOrderUpsert { + u.SetNull(stocktakeorder.FieldWrittenBy) + return u +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (u *StocktakeOrderUpsert) SetUncountedMode(v string) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldUncountedMode, v) + return u +} + +// UpdateUncountedMode sets the "uncounted_mode" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateUncountedMode() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldUncountedMode) + return u +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (u *StocktakeOrderUpsert) ClearUncountedMode() *StocktakeOrderUpsert { + u.SetNull(stocktakeorder.FieldUncountedMode) + return u +} + +// SetCancelReason sets the "cancel_reason" field. +func (u *StocktakeOrderUpsert) SetCancelReason(v string) *StocktakeOrderUpsert { + u.Set(stocktakeorder.FieldCancelReason, v) + return u +} + +// UpdateCancelReason sets the "cancel_reason" field to the value that was provided on create. +func (u *StocktakeOrderUpsert) UpdateCancelReason() *StocktakeOrderUpsert { + u.SetExcluded(stocktakeorder.FieldCancelReason) + return u +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (u *StocktakeOrderUpsert) ClearCancelReason() *StocktakeOrderUpsert { + u.SetNull(stocktakeorder.FieldCancelReason) + return u +} + // SetCreatedAt sets the "created_at" field. func (u *StocktakeOrderUpsert) SetCreatedAt(v int64) *StocktakeOrderUpsert { u.Set(stocktakeorder.FieldCreatedAt, v) @@ -475,6 +741,153 @@ func (u *StocktakeOrderUpsertOne) UpdateTotalTargets() *StocktakeOrderUpsertOne }) } +// SetDiffCount sets the "diff_count" field. +func (u *StocktakeOrderUpsertOne) SetDiffCount(v int) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetDiffCount(v) + }) +} + +// AddDiffCount adds v to the "diff_count" field. +func (u *StocktakeOrderUpsertOne) AddDiffCount(v int) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.AddDiffCount(v) + }) +} + +// UpdateDiffCount sets the "diff_count" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateDiffCount() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateDiffCount() + }) +} + +// SetAdjusted sets the "adjusted" field. +func (u *StocktakeOrderUpsertOne) SetAdjusted(v bool) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetAdjusted(v) + }) +} + +// UpdateAdjusted sets the "adjusted" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateAdjusted() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateAdjusted() + }) +} + +// SetAdjustNo sets the "adjust_no" field. +func (u *StocktakeOrderUpsertOne) SetAdjustNo(v string) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetAdjustNo(v) + }) +} + +// UpdateAdjustNo sets the "adjust_no" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateAdjustNo() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateAdjustNo() + }) +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (u *StocktakeOrderUpsertOne) ClearAdjustNo() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearAdjustNo() + }) +} + +// SetWrittenAt sets the "written_at" field. +func (u *StocktakeOrderUpsertOne) SetWrittenAt(v int64) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetWrittenAt(v) + }) +} + +// AddWrittenAt adds v to the "written_at" field. +func (u *StocktakeOrderUpsertOne) AddWrittenAt(v int64) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.AddWrittenAt(v) + }) +} + +// UpdateWrittenAt sets the "written_at" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateWrittenAt() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateWrittenAt() + }) +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (u *StocktakeOrderUpsertOne) ClearWrittenAt() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearWrittenAt() + }) +} + +// SetWrittenBy sets the "written_by" field. +func (u *StocktakeOrderUpsertOne) SetWrittenBy(v string) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetWrittenBy(v) + }) +} + +// UpdateWrittenBy sets the "written_by" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateWrittenBy() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateWrittenBy() + }) +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (u *StocktakeOrderUpsertOne) ClearWrittenBy() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearWrittenBy() + }) +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (u *StocktakeOrderUpsertOne) SetUncountedMode(v string) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetUncountedMode(v) + }) +} + +// UpdateUncountedMode sets the "uncounted_mode" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateUncountedMode() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateUncountedMode() + }) +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (u *StocktakeOrderUpsertOne) ClearUncountedMode() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearUncountedMode() + }) +} + +// SetCancelReason sets the "cancel_reason" field. +func (u *StocktakeOrderUpsertOne) SetCancelReason(v string) *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetCancelReason(v) + }) +} + +// UpdateCancelReason sets the "cancel_reason" field to the value that was provided on create. +func (u *StocktakeOrderUpsertOne) UpdateCancelReason() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateCancelReason() + }) +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (u *StocktakeOrderUpsertOne) ClearCancelReason() *StocktakeOrderUpsertOne { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearCancelReason() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *StocktakeOrderUpsertOne) SetCreatedAt(v int64) *StocktakeOrderUpsertOne { return u.Update(func(s *StocktakeOrderUpsert) { @@ -798,6 +1211,153 @@ func (u *StocktakeOrderUpsertBulk) UpdateTotalTargets() *StocktakeOrderUpsertBul }) } +// SetDiffCount sets the "diff_count" field. +func (u *StocktakeOrderUpsertBulk) SetDiffCount(v int) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetDiffCount(v) + }) +} + +// AddDiffCount adds v to the "diff_count" field. +func (u *StocktakeOrderUpsertBulk) AddDiffCount(v int) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.AddDiffCount(v) + }) +} + +// UpdateDiffCount sets the "diff_count" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateDiffCount() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateDiffCount() + }) +} + +// SetAdjusted sets the "adjusted" field. +func (u *StocktakeOrderUpsertBulk) SetAdjusted(v bool) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetAdjusted(v) + }) +} + +// UpdateAdjusted sets the "adjusted" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateAdjusted() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateAdjusted() + }) +} + +// SetAdjustNo sets the "adjust_no" field. +func (u *StocktakeOrderUpsertBulk) SetAdjustNo(v string) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetAdjustNo(v) + }) +} + +// UpdateAdjustNo sets the "adjust_no" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateAdjustNo() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateAdjustNo() + }) +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (u *StocktakeOrderUpsertBulk) ClearAdjustNo() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearAdjustNo() + }) +} + +// SetWrittenAt sets the "written_at" field. +func (u *StocktakeOrderUpsertBulk) SetWrittenAt(v int64) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetWrittenAt(v) + }) +} + +// AddWrittenAt adds v to the "written_at" field. +func (u *StocktakeOrderUpsertBulk) AddWrittenAt(v int64) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.AddWrittenAt(v) + }) +} + +// UpdateWrittenAt sets the "written_at" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateWrittenAt() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateWrittenAt() + }) +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (u *StocktakeOrderUpsertBulk) ClearWrittenAt() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearWrittenAt() + }) +} + +// SetWrittenBy sets the "written_by" field. +func (u *StocktakeOrderUpsertBulk) SetWrittenBy(v string) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetWrittenBy(v) + }) +} + +// UpdateWrittenBy sets the "written_by" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateWrittenBy() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateWrittenBy() + }) +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (u *StocktakeOrderUpsertBulk) ClearWrittenBy() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearWrittenBy() + }) +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (u *StocktakeOrderUpsertBulk) SetUncountedMode(v string) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetUncountedMode(v) + }) +} + +// UpdateUncountedMode sets the "uncounted_mode" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateUncountedMode() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateUncountedMode() + }) +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (u *StocktakeOrderUpsertBulk) ClearUncountedMode() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearUncountedMode() + }) +} + +// SetCancelReason sets the "cancel_reason" field. +func (u *StocktakeOrderUpsertBulk) SetCancelReason(v string) *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.SetCancelReason(v) + }) +} + +// UpdateCancelReason sets the "cancel_reason" field to the value that was provided on create. +func (u *StocktakeOrderUpsertBulk) UpdateCancelReason() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.UpdateCancelReason() + }) +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (u *StocktakeOrderUpsertBulk) ClearCancelReason() *StocktakeOrderUpsertBulk { + return u.Update(func(s *StocktakeOrderUpsert) { + s.ClearCancelReason() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *StocktakeOrderUpsertBulk) SetCreatedAt(v int64) *StocktakeOrderUpsertBulk { return u.Update(func(s *StocktakeOrderUpsert) { diff --git a/bj_power_wms/ent/stocktakeorder_update.go b/bj_power_wms/ent/stocktakeorder_update.go index f2c954b..13b9d5b 100644 --- a/bj_power_wms/ent/stocktakeorder_update.go +++ b/bj_power_wms/ent/stocktakeorder_update.go @@ -96,6 +96,148 @@ func (_u *StocktakeOrderUpdate) AddTotalTargets(v int) *StocktakeOrderUpdate { return _u } +// SetDiffCount sets the "diff_count" field. +func (_u *StocktakeOrderUpdate) SetDiffCount(v int) *StocktakeOrderUpdate { + _u.mutation.ResetDiffCount() + _u.mutation.SetDiffCount(v) + return _u +} + +// SetNillableDiffCount sets the "diff_count" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableDiffCount(v *int) *StocktakeOrderUpdate { + if v != nil { + _u.SetDiffCount(*v) + } + return _u +} + +// AddDiffCount adds value to the "diff_count" field. +func (_u *StocktakeOrderUpdate) AddDiffCount(v int) *StocktakeOrderUpdate { + _u.mutation.AddDiffCount(v) + return _u +} + +// SetAdjusted sets the "adjusted" field. +func (_u *StocktakeOrderUpdate) SetAdjusted(v bool) *StocktakeOrderUpdate { + _u.mutation.SetAdjusted(v) + return _u +} + +// SetNillableAdjusted sets the "adjusted" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableAdjusted(v *bool) *StocktakeOrderUpdate { + if v != nil { + _u.SetAdjusted(*v) + } + return _u +} + +// SetAdjustNo sets the "adjust_no" field. +func (_u *StocktakeOrderUpdate) SetAdjustNo(v string) *StocktakeOrderUpdate { + _u.mutation.SetAdjustNo(v) + return _u +} + +// SetNillableAdjustNo sets the "adjust_no" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableAdjustNo(v *string) *StocktakeOrderUpdate { + if v != nil { + _u.SetAdjustNo(*v) + } + return _u +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (_u *StocktakeOrderUpdate) ClearAdjustNo() *StocktakeOrderUpdate { + _u.mutation.ClearAdjustNo() + return _u +} + +// SetWrittenAt sets the "written_at" field. +func (_u *StocktakeOrderUpdate) SetWrittenAt(v int64) *StocktakeOrderUpdate { + _u.mutation.ResetWrittenAt() + _u.mutation.SetWrittenAt(v) + return _u +} + +// SetNillableWrittenAt sets the "written_at" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableWrittenAt(v *int64) *StocktakeOrderUpdate { + if v != nil { + _u.SetWrittenAt(*v) + } + return _u +} + +// AddWrittenAt adds value to the "written_at" field. +func (_u *StocktakeOrderUpdate) AddWrittenAt(v int64) *StocktakeOrderUpdate { + _u.mutation.AddWrittenAt(v) + return _u +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (_u *StocktakeOrderUpdate) ClearWrittenAt() *StocktakeOrderUpdate { + _u.mutation.ClearWrittenAt() + return _u +} + +// SetWrittenBy sets the "written_by" field. +func (_u *StocktakeOrderUpdate) SetWrittenBy(v string) *StocktakeOrderUpdate { + _u.mutation.SetWrittenBy(v) + return _u +} + +// SetNillableWrittenBy sets the "written_by" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableWrittenBy(v *string) *StocktakeOrderUpdate { + if v != nil { + _u.SetWrittenBy(*v) + } + return _u +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (_u *StocktakeOrderUpdate) ClearWrittenBy() *StocktakeOrderUpdate { + _u.mutation.ClearWrittenBy() + return _u +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (_u *StocktakeOrderUpdate) SetUncountedMode(v string) *StocktakeOrderUpdate { + _u.mutation.SetUncountedMode(v) + return _u +} + +// SetNillableUncountedMode sets the "uncounted_mode" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableUncountedMode(v *string) *StocktakeOrderUpdate { + if v != nil { + _u.SetUncountedMode(*v) + } + return _u +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (_u *StocktakeOrderUpdate) ClearUncountedMode() *StocktakeOrderUpdate { + _u.mutation.ClearUncountedMode() + return _u +} + +// SetCancelReason sets the "cancel_reason" field. +func (_u *StocktakeOrderUpdate) SetCancelReason(v string) *StocktakeOrderUpdate { + _u.mutation.SetCancelReason(v) + return _u +} + +// SetNillableCancelReason sets the "cancel_reason" field if the given value is not nil. +func (_u *StocktakeOrderUpdate) SetNillableCancelReason(v *string) *StocktakeOrderUpdate { + if v != nil { + _u.SetCancelReason(*v) + } + return _u +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (_u *StocktakeOrderUpdate) ClearCancelReason() *StocktakeOrderUpdate { + _u.mutation.ClearCancelReason() + return _u +} + // SetCreatedAt sets the "created_at" field. func (_u *StocktakeOrderUpdate) SetCreatedAt(v int64) *StocktakeOrderUpdate { _u.mutation.ResetCreatedAt() @@ -203,6 +345,48 @@ func (_u *StocktakeOrderUpdate) sqlSave(ctx context.Context) (_node int, err err if value, ok := _u.mutation.AddedTotalTargets(); ok { _spec.AddField(stocktakeorder.FieldTotalTargets, field.TypeInt, value) } + if value, ok := _u.mutation.DiffCount(); ok { + _spec.SetField(stocktakeorder.FieldDiffCount, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedDiffCount(); ok { + _spec.AddField(stocktakeorder.FieldDiffCount, field.TypeInt, value) + } + if value, ok := _u.mutation.Adjusted(); ok { + _spec.SetField(stocktakeorder.FieldAdjusted, field.TypeBool, value) + } + if value, ok := _u.mutation.AdjustNo(); ok { + _spec.SetField(stocktakeorder.FieldAdjustNo, field.TypeString, value) + } + if _u.mutation.AdjustNoCleared() { + _spec.ClearField(stocktakeorder.FieldAdjustNo, field.TypeString) + } + if value, ok := _u.mutation.WrittenAt(); ok { + _spec.SetField(stocktakeorder.FieldWrittenAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedWrittenAt(); ok { + _spec.AddField(stocktakeorder.FieldWrittenAt, field.TypeInt64, value) + } + if _u.mutation.WrittenAtCleared() { + _spec.ClearField(stocktakeorder.FieldWrittenAt, field.TypeInt64) + } + if value, ok := _u.mutation.WrittenBy(); ok { + _spec.SetField(stocktakeorder.FieldWrittenBy, field.TypeString, value) + } + if _u.mutation.WrittenByCleared() { + _spec.ClearField(stocktakeorder.FieldWrittenBy, field.TypeString) + } + if value, ok := _u.mutation.UncountedMode(); ok { + _spec.SetField(stocktakeorder.FieldUncountedMode, field.TypeString, value) + } + if _u.mutation.UncountedModeCleared() { + _spec.ClearField(stocktakeorder.FieldUncountedMode, field.TypeString) + } + if value, ok := _u.mutation.CancelReason(); ok { + _spec.SetField(stocktakeorder.FieldCancelReason, field.TypeString, value) + } + if _u.mutation.CancelReasonCleared() { + _spec.ClearField(stocktakeorder.FieldCancelReason, field.TypeString) + } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(stocktakeorder.FieldCreatedAt, field.TypeInt64, value) } @@ -307,6 +491,148 @@ func (_u *StocktakeOrderUpdateOne) AddTotalTargets(v int) *StocktakeOrderUpdateO return _u } +// SetDiffCount sets the "diff_count" field. +func (_u *StocktakeOrderUpdateOne) SetDiffCount(v int) *StocktakeOrderUpdateOne { + _u.mutation.ResetDiffCount() + _u.mutation.SetDiffCount(v) + return _u +} + +// SetNillableDiffCount sets the "diff_count" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableDiffCount(v *int) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetDiffCount(*v) + } + return _u +} + +// AddDiffCount adds value to the "diff_count" field. +func (_u *StocktakeOrderUpdateOne) AddDiffCount(v int) *StocktakeOrderUpdateOne { + _u.mutation.AddDiffCount(v) + return _u +} + +// SetAdjusted sets the "adjusted" field. +func (_u *StocktakeOrderUpdateOne) SetAdjusted(v bool) *StocktakeOrderUpdateOne { + _u.mutation.SetAdjusted(v) + return _u +} + +// SetNillableAdjusted sets the "adjusted" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableAdjusted(v *bool) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetAdjusted(*v) + } + return _u +} + +// SetAdjustNo sets the "adjust_no" field. +func (_u *StocktakeOrderUpdateOne) SetAdjustNo(v string) *StocktakeOrderUpdateOne { + _u.mutation.SetAdjustNo(v) + return _u +} + +// SetNillableAdjustNo sets the "adjust_no" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableAdjustNo(v *string) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetAdjustNo(*v) + } + return _u +} + +// ClearAdjustNo clears the value of the "adjust_no" field. +func (_u *StocktakeOrderUpdateOne) ClearAdjustNo() *StocktakeOrderUpdateOne { + _u.mutation.ClearAdjustNo() + return _u +} + +// SetWrittenAt sets the "written_at" field. +func (_u *StocktakeOrderUpdateOne) SetWrittenAt(v int64) *StocktakeOrderUpdateOne { + _u.mutation.ResetWrittenAt() + _u.mutation.SetWrittenAt(v) + return _u +} + +// SetNillableWrittenAt sets the "written_at" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableWrittenAt(v *int64) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetWrittenAt(*v) + } + return _u +} + +// AddWrittenAt adds value to the "written_at" field. +func (_u *StocktakeOrderUpdateOne) AddWrittenAt(v int64) *StocktakeOrderUpdateOne { + _u.mutation.AddWrittenAt(v) + return _u +} + +// ClearWrittenAt clears the value of the "written_at" field. +func (_u *StocktakeOrderUpdateOne) ClearWrittenAt() *StocktakeOrderUpdateOne { + _u.mutation.ClearWrittenAt() + return _u +} + +// SetWrittenBy sets the "written_by" field. +func (_u *StocktakeOrderUpdateOne) SetWrittenBy(v string) *StocktakeOrderUpdateOne { + _u.mutation.SetWrittenBy(v) + return _u +} + +// SetNillableWrittenBy sets the "written_by" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableWrittenBy(v *string) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetWrittenBy(*v) + } + return _u +} + +// ClearWrittenBy clears the value of the "written_by" field. +func (_u *StocktakeOrderUpdateOne) ClearWrittenBy() *StocktakeOrderUpdateOne { + _u.mutation.ClearWrittenBy() + return _u +} + +// SetUncountedMode sets the "uncounted_mode" field. +func (_u *StocktakeOrderUpdateOne) SetUncountedMode(v string) *StocktakeOrderUpdateOne { + _u.mutation.SetUncountedMode(v) + return _u +} + +// SetNillableUncountedMode sets the "uncounted_mode" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableUncountedMode(v *string) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetUncountedMode(*v) + } + return _u +} + +// ClearUncountedMode clears the value of the "uncounted_mode" field. +func (_u *StocktakeOrderUpdateOne) ClearUncountedMode() *StocktakeOrderUpdateOne { + _u.mutation.ClearUncountedMode() + return _u +} + +// SetCancelReason sets the "cancel_reason" field. +func (_u *StocktakeOrderUpdateOne) SetCancelReason(v string) *StocktakeOrderUpdateOne { + _u.mutation.SetCancelReason(v) + return _u +} + +// SetNillableCancelReason sets the "cancel_reason" field if the given value is not nil. +func (_u *StocktakeOrderUpdateOne) SetNillableCancelReason(v *string) *StocktakeOrderUpdateOne { + if v != nil { + _u.SetCancelReason(*v) + } + return _u +} + +// ClearCancelReason clears the value of the "cancel_reason" field. +func (_u *StocktakeOrderUpdateOne) ClearCancelReason() *StocktakeOrderUpdateOne { + _u.mutation.ClearCancelReason() + return _u +} + // SetCreatedAt sets the "created_at" field. func (_u *StocktakeOrderUpdateOne) SetCreatedAt(v int64) *StocktakeOrderUpdateOne { _u.mutation.ResetCreatedAt() @@ -444,6 +770,48 @@ func (_u *StocktakeOrderUpdateOne) sqlSave(ctx context.Context) (_node *Stocktak if value, ok := _u.mutation.AddedTotalTargets(); ok { _spec.AddField(stocktakeorder.FieldTotalTargets, field.TypeInt, value) } + if value, ok := _u.mutation.DiffCount(); ok { + _spec.SetField(stocktakeorder.FieldDiffCount, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedDiffCount(); ok { + _spec.AddField(stocktakeorder.FieldDiffCount, field.TypeInt, value) + } + if value, ok := _u.mutation.Adjusted(); ok { + _spec.SetField(stocktakeorder.FieldAdjusted, field.TypeBool, value) + } + if value, ok := _u.mutation.AdjustNo(); ok { + _spec.SetField(stocktakeorder.FieldAdjustNo, field.TypeString, value) + } + if _u.mutation.AdjustNoCleared() { + _spec.ClearField(stocktakeorder.FieldAdjustNo, field.TypeString) + } + if value, ok := _u.mutation.WrittenAt(); ok { + _spec.SetField(stocktakeorder.FieldWrittenAt, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedWrittenAt(); ok { + _spec.AddField(stocktakeorder.FieldWrittenAt, field.TypeInt64, value) + } + if _u.mutation.WrittenAtCleared() { + _spec.ClearField(stocktakeorder.FieldWrittenAt, field.TypeInt64) + } + if value, ok := _u.mutation.WrittenBy(); ok { + _spec.SetField(stocktakeorder.FieldWrittenBy, field.TypeString, value) + } + if _u.mutation.WrittenByCleared() { + _spec.ClearField(stocktakeorder.FieldWrittenBy, field.TypeString) + } + if value, ok := _u.mutation.UncountedMode(); ok { + _spec.SetField(stocktakeorder.FieldUncountedMode, field.TypeString, value) + } + if _u.mutation.UncountedModeCleared() { + _spec.ClearField(stocktakeorder.FieldUncountedMode, field.TypeString) + } + if value, ok := _u.mutation.CancelReason(); ok { + _spec.SetField(stocktakeorder.FieldCancelReason, field.TypeString, value) + } + if _u.mutation.CancelReasonCleared() { + _spec.ClearField(stocktakeorder.FieldCancelReason, field.TypeString) + } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(stocktakeorder.FieldCreatedAt, field.TypeInt64, value) } diff --git a/bj_power_wms/frontend/src/components/MaterialSelect.vue b/bj_power_wms/frontend/src/components/MaterialSelect.vue index 63ae2c1..c4fec7c 100644 --- a/bj_power_wms/frontend/src/components/MaterialSelect.vue +++ b/bj_power_wms/frontend/src/components/MaterialSelect.vue @@ -1,7 +1,8 @@ diff --git a/bj_power_wms/frontend/src/help.js b/bj_power_wms/frontend/src/help.js index 94d9fe2..d2b1f5b 100644 --- a/bj_power_wms/frontend/src/help.js +++ b/bj_power_wms/frontend/src/help.js @@ -62,12 +62,17 @@ export const helpInbound = { export const helpOutbound = { title: '出库管理(统一主表)', overview: - '所有出库都落在同一张「出库主表」(OutboundOrder),用"类型"区分,装箱信息(箱号/SN清单/合同号)也直接记在主表上,不再单独建装箱表。\n两个出库页签 + 一个记录页签:\n· 工单备料出库(MES):对接 MES 工单台账领料,强约束"单物料累计出库 ≤ 工单需求总量";未启用 MES 或工单未同步时,工单下拉可能为空,可直接手输工单号(需台账已同步)。\n· 通用出库:不依赖工单的手动出库(退料/样品/报废/发货),支持结构件批次与电气件 SN,填箱号即一并记录装箱;MES 不可用或无需挂靠工单时请用本页。\n· 出库记录:统一主表的全量查询与导出(一键导出 CSV,含装箱信息)。\n选用原则:有 MES 工单要按 物料清单 领料 → 工单备料出库;无工单的自由出库/装箱发货 → 通用出库。\n\n说明:出库列表统一以"图号"展示物料(客户口径统一称"图号");通用出库涵盖退料/样品/报废/发货等场景,无固定分类下拉,具体去向记在"备注"。质量门禁:质量状态为「不合格/待退」的批次禁止领料上线,须先完成不合格处置。', + '所有出库都落在同一张「出库主表」(OutboundOrder),用"类型"区分,装箱信息(箱号/SN清单/合同号)也直接记在主表上,不再单独建装箱表。\n三个页签:\n· 工单备料出库(MES):对接 MES 工单台账领料,强约束"单物料累计出库 ≤ 工单需求总量";未启用 MES 或工单未同步时,工单下拉可能为空,可直接手输工单号(需台账已同步)。\n· 通用出库:不依赖工单的手动出库(退料/退货/样品/报废/发货/其他),支持结构件批次与电气件 SN,填箱号即一并记录装箱;MES 不可用或无需挂靠工单时请用本页。\n· 出库记录:统一主表的全量查询与导出(一键导出 CSV,含装箱信息),含工单号与目标工位。\n选用原则:有 MES 工单要按 物料清单 领料 → 工单备料出库;无工单的自由出库/装箱发货 → 通用出库。\n\n通用出库操作顺序(客户诉求 问题17/18/20):① 先选物料(下拉统一显示 编码/图号/类型/品类)→ ② 系统自动判定出库类型(电气件/结构件,无需手选)→ ③ 选出库类别(纯标签,不联动任何地方;选「其他」时备注必填原因)→ ④ 填归属(工单编号/科技项目号/其他,非其他时编号必填)→ ⑤ 结构件从可用批次下拉选批次(默认 FIFO 首个)并填数量 / 电气件逐个扫 SN → ⑥ 选目标工位(下拉,来自 MES 工位列表,不可手输)。\n\n说明:出库单号由系统自动生成、不可修改,规则与入库一致(入库 IB+ / 出库 OB+ 同一编号规则);出库无法与入库批次/合同对应,故出库类别仅是标签记录,列表与出库单都显示。质量门禁:发货/样品/其他类出库仅允许「合格」品;退货/报废/退料类豁免(本身即处置不合格品)。', fields: [ { name: '备料出库-工单号', source: '手动输入。数据来自 MES 已建的工单及导入的物料台账。', purpose: '出库挂靠的工单,决定可领料总量与需求缺口', fill: '输入工单号回车,查询该工单的物料台账' }, { name: '备料出库-目标工位', source: '下拉选择 DOCK01~DOCK20(选填,可过滤搜索)。', purpose: 'AGV 配送送达的接驳台(toDock 参数)', fill: '选择如 DOCK05;不选默认运到产线入口 DOCK01' }, { name: '备料出库-一键分配出库', source: '系统自动按 FIFO 分配。数据来自合格区Z02的批次库存。', purpose: '从合格区按先进先出凑够缺口并出库', fill: '点按钮,系统自动算批次并二次确认' }, - { name: '通用出库-物料/类型', source: '下拉选择物料;类型选"结构件(批次)"或"电气件(SN)"。', purpose: '决定走批次扣减还是 SN 扣减', fill: '选物料后选类型;批次填批次号+数量,SN 扫入 SN 列表' }, + { name: '通用出库-物料', source: '下拉选择(全项目统一组件)。', purpose: '选定要出库的物料', fill: '下拉显示 编码/图号/类型(电气件/结构件/其他)/品类(原材料/半成品/成品/其他),支持远程搜索' }, + { name: '通用出库-出库类型', source: '系统自动判定(来自物料档案的管理粒度)。', purpose: '决定走批次扣减还是 SN 扣减', fill: '只读:选物料后自动显示结构件(批次)或电气件(SN),无需也不允许手选' }, + { name: '通用出库-出库类别', source: '下拉点选(纯标签记录,不联动任何地方)。', purpose: '区分退料/退货/样品/报废/发货/其他,列表与出库单均显示', fill: '必选;选「其他」时需在备注填写具体原因' }, + { name: '通用出库-归属', source: '下拉选归属类型 + 手填归属编号。', purpose: '记录出库归属(工单编号/科技项目号/其他),仅追溯不联动 MES', fill: '默认「其他」;选工单编号/科技项目号时归属编号必填,长度≤64' }, + { name: '通用出库-目标工位', source: '下拉选择(来自 MES 工位列表,不允许手输)。', purpose: '出库送到哪个装配工位,出库记录留痕工位号', fill: '下拉选工位(可搜索);选填' }, + { name: '通用出库-批次号', source: '结构件:下拉选择该物料在库可用批次(不手输)。电气件:逐个扫 SN。', purpose: '定位扣减哪个批次/哪些 SN', fill: '结构件默认带出 FIFO 首个可用批次(显示可用量/质量状态/区域)可改;电气件扫入 SN 列表(必填)' }, { name: '通用出库-区域 / 货架 / 层 / 位置(四级库位)', source: '与入库同口径的逐级联动下拉(选填)。选物料或批次后自动带入:① 该批次在库的实际库位 ② 该物料最近一次入库库位 ③ 都无则按所选区域推荐同区空位。', purpose: '指明从哪个货位取料,免手工挑选,出库留痕到具体货位', fill: '选物料/批次即自动填入四级,可逐级手动改;切换上级会自动清空下级' }, { name: '通用出库-箱号', source: '手动填写(选填)。', purpose: '整箱发货时填写,装箱信息随出库单记录(无需再单独录入装箱)', fill: '填箱号后,本出库单即带装箱属性,可在"出库记录"按箱号追溯' }, { name: '通用出库-合同号', source: '手动填写(选填)。', purpose: '关联合同,便于按合同追溯', fill: '可空' }, @@ -81,11 +86,14 @@ export const helpOutbound = { export const helpInventory = { title: '库存查询', overview: - '查看当前库存:全品类统一一张库存主表——原材料(结构件按批次/电气件按SN)、半成品(MES 暂存退库)、成品(MES 完工自动回流,系统生成 SN)。数据由入库、出库、检验、盘点及 MES 回流等业务实时维护,本页只读。\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: '结构件=批次数量管理;电气件=SN 单件管理', fill: '全部 / 结构件(批次) / 电气件(SN)' }, + { name: '品类', source: '单选(全部/原材料/半成品/成品)。', purpose: '按品类过滤:原材料=采购到货入库;半成品=MES 工位暂存退库;成品=MES 完工自动回流(成品 SN 系统生成,入库单号形如 MES:工单号)。另有「其他」品类(辅料/工装/试验设备,itemType=4)在区域汇总「类型」列单列', fill: '全部 / 原材料 / 半成品 / 成品;三个视图与导出均生效' }, + { name: '区域汇总-类型', source: '系统按管理粒度自动判定。', purpose: '看每个大区各类型货物量', fill: '只读:结构件(批次)/电气件(SN)/其他(itemType=4 辅料工装);不再混入半成品/成品品类' }, + { name: '区域汇总-质量状态', source: '系统按细分组统计。', purpose: '看每个大区各质量状态的货量', fill: '只读:每行是「区域×质量×类型」细分,质量指该组内库存质量,并非整个区域是否合格' }, + { name: '数量等式(七大域审计)', source: '系统实时计算,本页只读。', purpose: '保证库存数量自洽', fill: '库存量(总数) = 可用量 + 锁定量;总数 = 合格 + 未检 + 不合格(质量分布守恒);可用量 = 总数 − 锁定量' }, { name: '物料汇总-质量分布', source: '系统按当前在库行统计。', purpose: '一个物料合格/未检/不合格各多少', fill: '合格/未检/不合格三列,自动统计' }, { name: '物料汇总-分布区域', source: '系统统计该物料当前占用了哪些区域。', purpose: '一个物料可跨多个区域存放(批次可拆分),物料行不写死单一区域', fill: '如 Z02、Z05;点行下钻可看每个批次/SN 具体在哪个区' }, { name: '库存明细-区域/质量/入库时间', source: '仅「库存明细」视图生效的筛选。', purpose: '在物料基础上再按存放区/质量状态/入库时间收窄', fill: '可组合筛选' }, @@ -99,32 +107,48 @@ export const helpInventory = { export const helpInspection = { title: '检验(来料/过程/完工)', overview: - '对入库的未检物料/SN 做检验:把"未检"翻转为【合格】或【不合格】。\n检验只修改"质量状态"字段,与物料/SN 的存放区域无关。\n合格品才能参与出库与锁库(库存校验只取质量状态=合格的批次)。\n\n本页分两个页签(录入在前默认打开,记录在后点击才见),参照出库管理:\n· 质量检验录入(常操作,默认打开):单条与批量已合并为同一处——扫码枪连续扫、或从"库存"下拉直接选,都先进入下方"已选清单",点一次"提交检验"统一翻转。\n· 检验记录(点击才加载):分页查询历史检验记录,支持按 批次/SN、物料、检验类型、结论、检验员、时间 筛选与导出。\n菜单按业务分为「来料检验 / 过程检验 / 完工检验」三个入口,同一张表、同一接口,仅按检验类别默认过滤(页面内不再切换类别)。记录支持「报检单号」字段,用于与生产报检流程关联;按需求已移除“不合格原因”字段。\n编号是否真实存在会即时校验:找不到对应库存记录的编号会直接报错,不会"提示成功但库存仍是未检"。\n不合格处置:判定为「不合格」时必须选择处置方式——退货(须填货运单号) / 报废 / 部分入库(一次只处理 1 个批次);不合格品不入库、不上产线。已判定不合格的记录也可在列表中再次执行处置。\n附件:每条检验记录可上传/查看附件(如检测报告、照片)。', + '【三检分工与边界】\n· 来料检(incoming):对采购/外协到货、入库前的物料验收,按入库批次/SN 判定。合格才能入库上架,不合格隔离退货。\n· 过程检(process):对产线在制物料/半成品巡检,判定质量状态。\n· 完工检(finished):对 MES 回流的成品 SN 做最终检验(对象固定为成品 SN,无“批次”选项)。成品入库时质量状态为“未检”,完工检合格后置“合格”,成品方可发货;不合格可返修/让步接收。闭环:MES 报工→成品入库(未检)→WMS 完工检→合格→可发货。\n\n【检验做什么】把库存行的“质量状态”从未检翻转为合格/不合格,与存放区域无关。库存校验只取质量状态=合格的行参与出库/锁库。\n\n【报检数守恒】报检数 = 合格数 + 不合格数;合格数不得大于报检数;判定合格时合格数应等于报检数(否则请改判不合格并选处置方式)。\n\n【不合格处置(需求23 换修闭环)】判不合格必选处置:退货(须填货运单号、库存置待退) / 换修(隔离为不合格,等返修后重新报检) / 让步接收(审批后放行,质量状态置合格) / 报废 / 部分入库(一次只处理 1 个批次,按合格数拆行)。已判不合格的记录也可在列表里再次处置。\n\n【归属口径(需求22)】归属类型 = 工单编号 / 科技项目号 / 其他,默认“其他”。这里的“工单编号”是独立维度标签,与 MES 工单号不是同一东西;手动输入、不联动 MES、不校验格式(长度≤64)。\n\n【按物料统计】统计页需先选择物料再点“统计”,不做全表实时聚合。\n\n本页分页签:质量检验录入(默认打开)/ 检验记录(点击才加载)/ 按物料统计。菜单按业务分为来料/过程/完工三个入口,同一张表、同一接口,仅按检验类别默认过滤。每条检验记录可上传/查看附件(检测报告/照片)。', fields: [ - { name: '目标类型', source: '单选,决定录入框标题与可选库存范围。', purpose: '检验对象是批次(结构件)还是 SN(电气件)', fill: '批次 BATCH / 序列号 SN;切换时会自动刷新"从库存选"下拉' }, + { name: '三检边界', source: '菜单入口锁定检验类别。', purpose: '来料检=入库验收;过程检=在制巡检;完工检=成品 SN 终检', fill: '按左侧菜单进入对应页面,页内不再切换类别' }, + { name: '目标类型', source: '单选,决定录入框标题与可选库存范围。完工检固定为 SN,不展示切换。', purpose: '检验对象是批次(结构件)还是 SN(电气件/成品)', fill: '批次 BATCH / 序列号 SN;切换时自动刷新“从库存选”下拉' }, { name: '检验结论', source: '单选。', purpose: '判定结果', fill: '合格 / 不合格' }, - { name: '检验员', source: '手动填写(默认当前用户)。', purpose: '谁检验的,追溯用', fill: '检验员姓名' }, - { name: '从库存选批次号/SN', source: '下拉选择,数据来自当前库存(按目标类型过滤)。', purpose: '避免手输编号、防止输错', fill: '在库存里挑待检的批次号/SN,自动加入清单' }, + { name: '记录人 / 审核人', source: '手动填写(默认当前用户)。完工检显示为“审核人”。', purpose: '谁检验/审核的,追溯用', fill: '姓名' }, + { name: '从库存选批次号/SN', source: '下拉选择,数据来自当前库存(按目标类型过滤;完工检只列成品 category=3)。', purpose: '避免手输编号、防止输错', fill: '在库存里挑待检的批次号/SN,自动加入清单' }, { name: '录入批次号/SN', source: '扫码枪/粘贴。来自批次号/SN条码。', purpose: '单条或多条待检编号', fill: '扫入或粘贴后点【录入到列表】,多编号用逗号/分号/空格/回车分隔' }, - { name: '已选清单', source: '本页待提交列表。', purpose: '统一查看、可逐个删除', fill: '列在此处的编号,点【提交检验】一次性翻转' }, - { name: '检测单号 / 报检单号', source: '手工填写(可空)。检测单号用于与入库单据关联传递,报检单号用于关联生产报检流程。', purpose: '把检验记录与来料入库/生产报检挂钩,便于追溯', fill: '如检测单号随入库带入;报检单号可空' }, - { name: '不合格处置', source: '判定"不合格"时必选:退货 / 报废 / 部分入库。', purpose: '不合格品不入库、不上产线,须明确去向;退货须填货运单号,部分入库一次只处理 1 个批次', fill: '退货(填货运单号) / 报废 / 部分入库(单批次);判定不合格却不选处置会被拦截' }, + { name: '报检数 / 合格数', source: '手动填写。守恒:报检数 = 合格数 + 不合格数。', purpose: '量化抽检结果', fill: '合格数≤报检数;判合格时两者相等' }, + { name: '归属', source: '下拉选择类型 + 手动输入编号(默认其他)。', purpose: '标记检验对象归属,便于按归属查询', fill: '工单编号 / 科技项目号 / 其他;编号手动输入、不校验格式、长度≤64' }, + { name: '不合格处置', source: '判定“不合格”时必选:退货 / 换修 / 让步接收 / 报废 / 部分入库。', purpose: '不合格品不入库、不上产线,须明确去向(需求23 换修闭环)', fill: '退货(填货运单号→待退) / 换修(隔离不合格→待返修) / 让步接收(放行→合格) / 报废 / 部分入库(单批次)' }, { name: '附件', source: '行内附件抽屉(bizType=检验记录)。', purpose: '为该检验记录上传/查看附件(如检测报告、照片)', fill: '点行内附件按钮,在抽屉中上传或查看' }, - { name: '检验记录-筛选/导出', source: '切换到"检验记录"页签后可见。', purpose: '按条件分页查询历史记录并导出', fill: '支持按 批次/SN、图号/物料名称/型号(三框模糊:框内模糊、框间取交集)、检验类型、结论、检验员、时间范围(默认近3个月) 组合筛选;点【导出】下载 xlsx' } + { name: '检验记录-筛选/导出', source: '切换到“检验记录”页签后可见。', purpose: '按条件分页查询历史记录并导出', fill: '支持按 批次/SN、图号/名称/型号(三框模糊)、归属单号、检验类型、结论、记录人、时间范围(默认近3个月) 组合筛选;点【导出】下载 xlsx' }, + { name: '按物料统计', source: '切换到“按物料统计”页签,先选物料再点统计。', purpose: '看某物料的检验次数/合格率', fill: '必选物料;按时间区间与检验类别聚合,每次检验独立计数' } ] } export const helpStocktake = { title: '盘点(库存核对)', overview: - '核对账面与实际库存,支持扫码快速盘点,完成时按差异修正库存。\n页面顶部有步骤条引导流程:发起盘点 → 扫码实盘 → 确认差异并写回。\n流程:① 发起盘点(全库/按区域/按物料,生成盘点单号) → ② 扫批次号/SN 录实盘数(回车提交) → ③ 完成盘点(可选:差异写回库存),并展示差异明细。\n\n「物料分布」相关视图已并入「库存查询」页的物料汇总/库存明细,本页专注盘点流程。', + '盘的是「库存量」(实物在库总数),不是可用量、不是锁定量。账面数=发起时快照的库存量,实盘数=实际数到的库存量,差异=实盘-账面。\n\n' + + '完整闭环(顶部步骤条引导):\n' + + '① 发起盘点:选范围(全库/按区域/按物料) → 冻结库存快照 + 锁定该范围库存 → 生成盘点单号,状态「进行中」,目标数量=本次覆盖的批次/SN 总数(自动算)。\n' + + '② 扫码实盘:扫批次号(结构件)弹数量框填实盘数;扫 SN(电气件)自动记 1、直接提交。发起后盘点单号自动带入扫码区,无需手动粘贴。\n' + + '③ 完成盘点:先选未盘项处理方式(按账面保留/盘亏为0) → 二次确认是否写回 → 写回则生成盘点调整单修正库存 → 解锁库存。\n\n' + + '三条铁律:\n' + + '• 盘点期间库存冻结:进行中盘点单锁定的批次/SN,禁止出库、入库追加、锁定,避免差异混入正常业务变动(完成或取消后自动解锁)。\n' + + '• 写回只改库存量:锁定量不动,可用量自动重算;写回后强制满足「库存量 = 可用量 + 锁定量」,若盘亏后库存量将小于锁定量则拒绝写回,须先解锁。\n' + + '• 写回走调整单留痕:生成调整单号,调整前/调整量/调整后全部入操作日志,可追溯谁在何时改了什么;写回不可逆,需主管以上权限。\n\n' + + '状态机:进行中(已锁定) → 已完成(仅出结论不改库存) / 已写回(差异已修正库存) / 已取消(解锁并记录原因)。', fields: [ - { name: '盘点范围', source: '发起时选择。', purpose: '冻结快照的范围', fill: '全库 / 按区域(多选) / 按物料(多选+类型)' }, + { name: '盘点范围', source: '发起时选择。', purpose: '冻结快照 + 锁定库存的范围', fill: '全库 / 按区域(多选) / 按物料(多选+类型)' }, { name: '操作人', source: '登录账号自动带出。', purpose: '发起盘点人', fill: '自动' }, - { name: '发起盘点', source: '按钮操作。', purpose: '按所选范围生成盘点单号并冻结库存快照', fill: '点按钮选择范围后发起' }, - { name: '差异写回库存', source: '开关。', purpose: '完成后是否用实盘差异修正账面库存', fill: '开=写回;关=仅记录差异' }, - { name: '批次号 / SN 序列号', source: '扫码枪。来自批次号/SN条码。', purpose: '要盘点的批次号(结构件)或 SN(电气件)', fill: '扫批次号/SN,回车提交;同一目标重复扫=覆盖实盘数' }, - { name: '实盘数量', source: '扫码/手输。', purpose: '实际数到数量', fill: '非负整数;0=盘亏为零' } + { name: '目标数量', source: '发起时自动统计。', purpose: '本次盘点覆盖的批次/SN 总数', fill: '自动,无需填写' }, + { name: '发起盘点', source: '按钮操作(权限 stocktake:start)。', purpose: '按范围冻结快照并锁定库存,生成盘点单号', fill: '点按钮选范围后发起;范围内库存若已被别的进行中盘点单锁定则拒绝' }, + { name: '批次号 / SN 序列号', source: '扫码枪。来自批次号/SN条码。', purpose: '要盘点的批次号(结构件)或 SN(电气件)', fill: '扫码回车提交;同一目标重复扫=覆盖实盘数;SN 自动记 1' }, + { name: '实盘数量', source: '扫码/手输。', purpose: '实际数到的库存量', fill: '仅批次件需填非负整数;SN 由系统自动置 1,无需填' }, + { name: '未盘项处理', source: '完成盘点弹窗选择。', purpose: '盘点单覆盖但未扫到的项如何归置', fill: '按账面保留(不计差异) / 盘亏为0(实盘记0,差异=-账面)' }, + { name: '差异写回库存', source: '完成盘点弹窗勾选(默认勾选,可取消;权限 stocktake:writeback)。', purpose: '是否用实盘差异修正账面库存', fill: '勾选=生成盘点调整单写回;取消=仅出结论不改库存。已用二次确认弹窗取代旧开关,防误触' }, + { name: '取消盘点', source: '按钮操作(权限 stocktake:start)。', purpose: '发起错误或扫码错误过多时退出', fill: '点「取消盘点」填写原因→确认;解锁库存,状态置「已取消」,已扫记录作废' }, + { name: '历史列表', source: '查询结果。', purpose: '追溯历次盘点', fill: '含状态/差异项数/是否写回/写回时间/取消原因,可按单号或状态筛选' }, + { name: '导出明细', source: '历史行末按钮(权限 stocktake:view)。', purpose: '导出盘点明细为 xlsx', fill: '列:盘点单号/类型/批次或SN/物料编码/货位/账面数/实盘数/差异/是否已盘/实盘人/实盘时间/发起人/发起时间' } ] } @@ -163,31 +187,38 @@ export const helpSemi = { export const helpLedger = { title: '工单物料台账', overview: - '实时展示每个工单下每个物料:需求总量、累计已出库、剩余缺口。\n系统强约束"累计出库 ≤ 需求总量",达到即标记"领料完结",禁止再出库。\n数据链路:MES 工单 + 物料清单(物料清单) → 同步导入本台账 → 出库时自动扣减累计,形成缺口 / 完结。\n\n说明:本页只读查询,数据来自 MES 同步的工单台账。若列表为空,表示当前没有已同步的台账数据(请先在 MES 建工单并同步物料清单),不会展示任何模拟数据。', + '实时展示每个工单下每个物料:需求总量、已出库、已退回、净领用、已消耗、在途/线边、剩余缺口。\n数据链路:MES 工单 + 物料清单 → 生成备料单时同步「工单级需求总量」到本台账(total_qty)→ 出库累加 out_qty → 退库累加 returned_qty → 报工反冲累加 consumed_qty。\n\n数量等式(必须成立,页顶「闭环核对」实时校验):\n· 净领用 = 已出库 − 已退回\n· 需求量 = 净领用 + 剩余缺口(强约束:净领用 ≤ 需求量,等于时「领料完结」)\n· 在途/线边 = 净领用 − 已消耗(已发出未消耗,含在途+工位余量两段,细分在 MES 侧)\n\n说明:本页只读查询,数据来自 MES 同步的工单台账。若列表为空,表示当前没有已同步的台账数据(请先在 MES 建工单、排产并生成备料单触发同步),不会展示任何模拟数据。', fields: [ { name: '工单号', source: '筛选条件。数据来自 MES 工单。', purpose: '只看某个工单的领料台账', fill: '输入工单号(可空=查全部工单)' }, { name: '图号', source: '筛选条件。', purpose: '按图号精确过滤', fill: '输入图号(可空)' }, { name: '物料名称', source: '筛选条件。', purpose: '按物料名称模糊过滤', fill: '输入物料名称(可空)' }, - { name: '状态', source: '筛选条件。可选项:待领料 / 部分领料 / 领料完结。', purpose: '按领料进度过滤', fill: '待领料=还没出过库;部分领料=已出库但没领完;领料完结=缺口为 0(前两种均由"进行中"按已出库数实时派生)' }, - { name: 'ID', source: '台账记录主键(系统生成,只读)。', purpose: '唯一标识一行台账,便于定位与对接', fill: '系统自动生成,无需填写' }, - { name: '需求总量 / 已出库 / 缺口', source: '系统计算。需求总量来自 物料清单;已出库随出库自动累加;缺口 = 需求总量 − 已出库。', purpose: '一眼看清某物料还差多少未领', fill: '自动;缺口为 0 即领料完结' }, - { name: '状态标签', source: '系统实时派生。', purpose: '待领料(未出过库) / 部分领料(出过但仍有缺口) / 领料完结(已足额出库)', fill: '自动' } + { name: '备料状态', source: '筛选条件。齐套/预警/未齐套/未到货(服务端按实时可用库存+未来5天需求派生)。', purpose: '按备料充足度过滤', fill: '齐套=可用≥需求;预警=可用<未来5天需求;未齐套=0<可用<需求;未到货=可用为0且未出过库' }, + { name: '需求总量', source: 'MES 同步(工单数量 × 单台用量 × (1+损耗率),向上取整)。', purpose: '该工单该物料共需多少', fill: '系统自动,只读' }, + { name: '已出库 / 已退回 / 已消耗', source: '出库 / 退库收货 / 报工反冲各自写入(一个字段一个写者)。', purpose: '分段追踪物料去向', fill: '自动,只读' }, + { name: '净领用', source: '系统计算 = 已出库 − 已退回。', purpose: '用于超量拦截与「领料完结」判定', fill: '自动;等于需求总量即领料完结' }, + { name: '在途/线边', source: '系统计算 = 净领用 − 已消耗。', purpose: '已发出但产线尚未消耗的量(含在途+工位余量)', fill: '自动,只读' }, + { name: '缺口', source: '系统计算 = 需求总量 − 净领用(下限 0)。', purpose: '还差多少未领', fill: '自动;为 0 即领料完结' }, + { name: '闭环核对栏', source: '页顶实时汇总本页所有行。', purpose: '校验数量等式是否成立,账实不符时红色告警', fill: '自动;净领用>需求 或 已消耗>净领用 即判定账实不符' } ] } export const helpReturnOrder = { title: '退库确认', overview: - '工位退库的接收确认环节:工位终端上操作工把「没用完 / 用错的物料」做退库发起,系统预建一张「待确认收货」的退库单;仓库保管员在本页审核并实物点收,点「确认收货」后系统把该数量加回库存,退库单置为「已收货」,整条退库链路闭环。\n\n数据链路:工位终端退库 → MES 落事件日志并调用 WMS 内部接口 → WMS 预建退库单(PENDING) → 本页确认收货 → 库存加回(批次物料按「图号 + 在库」合并到已有库存行;无在库行则新建一行,入库单号记为 RET:退库单号)。\n\n说明:退货单状态只有两种——待确认收货 / 已收货。同一工单 + 同一物料 + 同一工位只会预建一条待收货退库单(防止重复提交)。本页不含「删除」,退库一经确认即形成库存流水,不可撤销。', + '工位退库的接收确认环节:工位终端上操作工把「没用完 / 有问题的物料」做退库发起,系统预建一张「待确认收货」的退库单;仓库保管员在本页审核并实物点收,点「确认收货」后系统按规则把物料加回库存,退库单置为「已收货」,整条退库链路闭环。\n\n数据链路:工位终端退库 → MES 落事件日志并调用 WMS 内部接口 → WMS 预建退库单(PENDING) → 本页确认收货(选退库类型、退回库位、电气件逐个扫码 / 结构件按数量)→ 库存加回 → 回写工单备料台账「退回量」,缺口恢复可再叫料。\n\n三个关键规则(客户诉求 问题14):\n1) 退库类型——「剩余物料」= 正常用剩,加回后置「未检」,由检验员判定合格/不合格;「问题物料」= 有质量疑虑,加回后直接置「不合格」隔离,绝不混入可用/合格库存。\n2) 退回库位——默认带入该物料最近一次的原库位(区域/货架/层/位置),可手动修改;目标货位即使已被其他物料占用也允许混放,系统只记数量、不校验位置冲突。\n3) 数量录入方式按物料管理粒度自动区分——电气件(序列号管理)必须逐个扫码录入 SN,扫码数须与退库数完全一致(一物一码守恒,SN 复用原出库行回到在库);结构件(批次管理)按退库数量整体加回,可指定批次号,留空则沿用/自动生成。\n\n说明:退库单状态只有两种——待确认收货 / 已收货。同一工单 + 同一物料 + 同一工位只会预建一条待收货退库单(防止重复提交)。本页不含「删除」,退库一经确认即形成库存流水,不可撤销。', fields: [ { name: '状态', source: '筛选条件,系统维护。', purpose: '只看待确认收货或已收货的退库单', fill: '选「待确认收货」优先处理' }, { name: '退库单号', source: '系统自动生成(TH + 时间戳)。', purpose: '唯一标识一张退库单,便于对账追溯', fill: '自动生成,无需填写' }, { name: '工单号 / 退库工位 / 产品序列号', source: '工位终端退库时自动带入。', purpose: '定位退的是什么工单、哪件产品的物料', fill: '只读' }, { name: '图号 / 名称 / 规格', source: '工位终端退库时带入(来自物料档案)。', purpose: '是哪一种物料退库', fill: '只读' }, - { name: '数量 / 单位', source: '工位终端填写。', purpose: '退多少(确认收货后按此数加回库存)', fill: '只读' }, + { name: '数量 / 单位', source: '工位终端填写。', purpose: '退多少(电气件须逐个扫码等于此数;结构件按此数整体加回)', fill: '只读' }, { name: '退库原因', source: '工位终端填写。', purpose: '说明为什么退库(如剩余料 / 错料 / 不良更换)', fill: '只读' }, + { name: '退库类型', source: '库管确认收货时选择。', purpose: '区分「剩余物料」与「问题物料」,决定加回后的质量状态', fill: '剩余物料→置未检走检验;问题物料→置不合格隔离' }, + { name: '退回库位(区域/货架/层/位置)', source: '库管确认收货时选择,默认带入原库位。', purpose: '物料退回到哪个货位,贯穿库存明细/盘点/导出全链路', fill: '逐级下拉选择,可手动改;允许混放,不校验位置冲突' }, + { name: '扫码录入 SN', source: '库管确认收货时逐个扫码(仅电气件)。', purpose: '一物一码,保证退回的每个序列号可追溯', fill: '扫描/输入 SN 回车添加,扫码数须等于退库数量' }, + { name: '批次号', source: '库管确认收货时填写(仅结构件)。', purpose: '指定加回到哪个批次', fill: '留空则沿用同料同货位同状态在库批次,无则自动生成' }, { name: '退库人 / 退库时间', source: '系统记录。', purpose: '追溯谁在什么时候发起的退库', fill: '只读' }, - { name: '确认收货', source: '库管在本页操作(权限码 return:confirm)。', purpose: '实物点收无误后确认,库存加回', fill: '核对实物后点击「确认收货」→ 二次确认' }, + { name: '确认收货', source: '库管在本页操作(权限码 return:confirm)。', purpose: '实物点收无误后确认,库存加回', fill: '核对实物 → 选退库类型/库位 →(电气件扫码)→ 点「确认收货」' }, { name: '收货人 / 收货时间', source: '系统在确认时记录当前登录人。', purpose: '追溯谁确认的收货', fill: '只读,确认后自动写入' } ] } @@ -195,7 +226,7 @@ export const helpReturnOrder = { export const helpAgv = { title: 'AGV 配送', overview: - 'AGV(海康 RCS)配送管理:库房出库后由 AGV 将物料托盘送到产线接驳台。\n\n触发链路:MES 排产时确认送达接驳台(DOCK01~20 产线 / DOCK21 库房)→ 备料单自动带上 target_dock → 本页「下发AGV」由仓库人员勾选待发料行、确认目标接驳台后点击下发 → 系统按目标口分组生成 AGV 任务并调海康 RCS。\n\n接驳台状态:有传感器,PLC 可反馈「是否有托盘」;AGV 到达目标口前系统可查询该口是否空闲,托盘未清走前不会再次下料到该口。\n\n说明:RCS 未配置 appKey/appSecret 时走模拟模式(mock),用于联调完整链路;现场配置后自动切换真实调度。', + 'AGV(海康 RCS)配送管理:库房出库后由 AGV 将物料托盘送到产线接驳台。\n\n触发链路:MES 排产时确认送达接驳台(DOCK01~20 产线 / DOCK21 库房)→ 备料单自动带上 target_dock → 本页「下发AGV」由仓库人员勾选待发料行、确认目标接驳台后点击下发 → 系统按目标口分组生成 AGV 任务并调海康 RCS。\n\n接驳台状态:有传感器,PLC 可反馈「是否有托盘」;AGV 到达目标口前系统可查询该口是否空闲,托盘未清走前不会再次下料到该口。\n\n说明:RCS 未配置 appKey/appSecret 时走模拟模式(mock),用于联调完整链路;现场配置后自动切换真实调度。\n\n【当前状态:待现场对接】AGV 默认运行于模拟模式(EnableMock=true),下发/到位/取消均为本地模拟、不驱动真实小车;顶部黄色横幅即此提示。对接真实海康 RCS 时,在服务配置中关闭 EnableMock 并填写 BaseURL / AppKey / AppSecret 即可,业务代码无需改动。', fields: [ { name: '接驳台状态', source: '系统维护。DOCK01~20 产线(对应工位 1~20)+ DOCK21 库房。', purpose: '查看每个接驳台当前是否有托盘', fill: '绿色=空闲(无托盘);橙色=有托盘(占用中,AGV 到达前需确认空闲)' }, { name: 'AGV任务', source: '系统生成。每次下发一个目标口生成一条任务。', purpose: '追踪每批料的搬运进度', fill: '状态:待下发 → 待搬运 → 搬运中 → 已到位;到位后自动回写备料单状态' }, @@ -207,16 +238,17 @@ export const helpAgv = { export const helpZone = { title: '区域维护(库位四级)', overview: - '维护库区/库位字典,供入库/半成品/出库等所有页面的"区域"下拉框选择。\n库位按四级结构组织:区域 → 货架 → 层 → 位置号,四个页签各自独立维护、独立分页、按上级筛选。上级字段用下拉选择(不重复录入)。\n\n· 区域:物理库区(如待检、合格 Z02、不合格…),入库时必须指明放到哪个区,质量检验合格后再转移到合格区;\n· 货架:挂在某个区域下;新建货架时会自动创建一个「默认位置」,物料可不细分到层时直接挂这里;\n· 层:挂在某个货架下;\n· 位置号:挂在某个层(或货架默认位置)下,可填「位置名称」(如"A架3层12位",可空)。\n\n位置号支持「批量生成」:选区域 → 货架 → 层范围 → 位置范围,先预览(标出已存在)再确认写入,已存在的位置自动跳过。\n\n区域字典是库房的第一道地基:先建好区域,入库、检验、出库才能选到正确的存放位置。', + '维护库区/库位字典,供入库/半成品/出库等所有页面的"区域"下拉框选择。\n库位按四级结构组织:区域 → 货架 → 层 → 位置号,四个页签各自独立维护、独立分页、按上级筛选。上级字段用下拉选择(不重复录入)。\n\n· 区域:物理库区(如待检、合格 Z02、不合格…),入库时必须指明放到哪个区,质量检验合格后再转移到合格区;\n· 货架:挂在某个区域下;新建货架时会自动创建一个「默认位置」,物料可不细分到层时直接挂这里;\n· 层:挂在某个货架下;\n· 位置号:挂在某个层(或货架默认位置)下。\n\n四级均可维护「名称」与「备注」(需求E);新建默认「启用」。库位不删除、只停用(避免库存/单据追溯断裂);停用后不再出现在入库/出库/盘点的可选库位下拉中,历史数据保留。\n\n列表实时库存(需求E/需求7):每次打开/查询页面实时聚合当前在库量(quantity>0 且非出库/报废),不缓存;区域=全区域合计、货架/层/位置逐级细化。列表页禁用关键字混搜,编码与名称各自独立筛选。\n\n位置号支持「批量生成」:选区域 → 货架 → 层范围 → 位置范围,先预览(标出已存在)再确认写入,已存在的位置自动跳过。', fields: [ { name: '区域编码', source: '手工填写,仓库自定义(新增后不可改)。', purpose: '库区唯一编号', fill: '如 Z02、Z05,建议字母+数字,唯一;新增后不可改' }, - { name: '区域名称', source: '手工填写。', purpose: '库区中文名,便于识别', fill: '如 合格区、待检区、不合格区' }, + { name: '名称(四级)', source: '手工填写(可空)。区域/货架/层/位置四级均可命名。', purpose: '本级中文名称,便于识别', fill: '区域如 合格区/待检区;位置如 A架3层12位' }, { name: '货架号', source: '新增货架时从「所属区域」下拉选,再填货架号。', purpose: '库位第二级,细化存放位置', fill: '如 A、B' }, { name: '层号', source: '新增层时从「所属区域+货架」下拉选,再填层号。', purpose: '库位第三级', fill: '如 3' }, { name: '位置号', source: '新增位置号时从「区域+货架+(层)」下拉选,再填位置号;层留空=该货架默认位置。', purpose: '库位第四级,最小存放单位', fill: '如 12' }, - { name: '位置名称', source: '手工填写(可空)。', purpose: '位置的中文说明', fill: '如 A架3层12位;可空' }, + { name: '实时库存', source: '系统实时聚合(不缓存)。', purpose: '直观看到该区域/货架/层/位置当前在库总量', fill: '只读;区域为全区域合计,逐级向下细化' }, + { name: '状态', source: '启用/停用(新建默认启用)。', purpose: '不删除、只停用;停用后不再进入可选库位下拉', fill: '编辑弹窗切换启用/停用' }, { name: '批量生成', source: '位置号页签「批量生成」按钮。', purpose: '一次生成一批位置号(货架 × 层范围 × 位置范围)', fill: '选区域+货架+层范围+位置范围 → 预览 → 确认生成;已存在的自动跳过' }, - { name: '说明', source: '手工填写(可空)。', purpose: '补充描述', fill: '可空' } + { name: '备注', source: '手工填写(可空),四级均可。', purpose: '补充描述', fill: '可空' } ] } @@ -237,15 +269,16 @@ export const helpDock = { export const helpAttachment = { title: '附件中心', overview: - '统一查看全系统上传的附件(图纸/报告/照片/Excel 等)。附件按「业务类型」挂靠具体单据,磁盘按「年/月/日/文件类型/uuid.ext」分层存储,库内只存相对路径。\n\n本页只读查询 + 预览/下载/删除;删除为逻辑删除(索引标记,文件由「归档清理」统一处理)。顶部会显示附件磁盘剩余空间告警:低于 20% 提醒、低于 10% 提示立即归档清理。\n\n归档策略:本地保留最近 1 年(可在配置 Attachment.LocalRetentionYears 调整),早于此的附件可「归档清理」——备份到外部目录(Attachment.ArchiveDir)并标记已归档,索引保留可追溯。', + '统一查看全系统上传的附件(图纸/报告/照片/Excel 等)。附件按「业务类型」挂靠具体单据,磁盘按「年/月/日/文件类型/uuid.ext」分层存储,库内只存相对路径。\n\n本页只读查询 + 下载/删除(不提供在线预览“查看”,统一走下载)。删除为逻辑删除(索引标记),真正的磁盘文件由「空间清理」统一处理。顶部会显示附件磁盘剩余空间告警:低于 20% 提醒、低于 10% 提示立即清理。\n\n删除保护:工艺流程(PROCESS_PDF)、流程卡(PROCESS_CARD) 为关键工艺文件,全程禁删;检验照片/报告(INSPECTION_*) 在质量追溯保留期(本地保留年数)内禁删。\n\n空间清理(页面右上「空间清理」按钮,需 attachment:manage 权限)归拢三项,每项先「预检」看影响范围、确认后再执行:① 归档清理(早于本地保留年数的附件备份到外部目录并标记);② 物理清理已删附件(删除已标记删除的磁盘文件,索引保留);③ 日志清理(仅系统管理员,清理日志目录下早于 N 天的 *.log,默认保留 30 天,系统亦每日自动滚动清理)。', fields: [ { name: '文件类型', source: '筛选条件。文件类型目录(大写),决定磁盘存放目录。', purpose: '按附件类别过滤', fill: '工艺流程/检验照片/检验报告/图纸/流程卡/包装照片/Excel导入/其他' }, { name: '业务类型', source: '筛选条件。附件挂靠的业务对象。', purpose: '只看某类业务的附件', fill: '入库/物料档案/质量检验/半成品/出库' }, { name: '业务标识', source: '筛选条件。', purpose: '按单号/编码/ID 定位某条业务记录的附件', fill: '如入库单号、物料编码、检验记录 ID' }, - { name: '查看 / 下载', source: '行末按钮。', purpose: '在线预览(PDF/图片)或下载原件', fill: '查看=新窗口内联预览;下载=带原文件名下载' }, - { name: '删除', source: '行末按钮(权限 attachment:manage)。', purpose: '逻辑删除附件索引(文件待归档任务清理)', fill: '确认后标记已删除;可勾选「含已删除」查看' }, - { name: '归档清理', source: '按钮(权限 attachment:manage)。', purpose: '把早于本地保留年数的附件备份到外部目录并标记归档,释放本地空间', fill: '点「归档清理」确认执行;结果给出移动/标记/失败条数' }, - { name: '磁盘告警', source: '系统每天早上 7:00 自动检测附件目录剩余空间。', purpose: '提前发现空间不足,避免写入失败', fill: '剩余 <20% 告警、<10% 提示立即归档清理;本页顶部同步展示' } + { name: '下载', source: '行末按钮。', purpose: '下载原件(带原文件名)', fill: '本页不提供在线“查看”预览,统一下载后本地打开' }, + { name: '删除', source: '行末按钮(权限 attachment:manage)。', purpose: '逻辑删除附件索引(文件待「空间清理-物理清理」统一清除)', fill: '确认后标记已删除;工艺流程/流程卡及保留期内检验记录受保护不可删' }, + { name: '空间清理', source: '页面右上按钮(权限 attachment:manage),抽屉式归拢归档/物理清理/日志清理。', purpose: '释放磁盘空间:归档旧附件、物理删除已删文件、清理旧日志', fill: '每项先「预检」(dryRun)看影响数量与释放空间,再「执行」前二次确认' }, + { name: '日志清理', source: '空间清理抽屉第③项(仅系统管理员 admin)。', purpose: '清理日志目录下早于 N 天的 *.log(默认 30 天)', fill: '不开放普通仓管;系统每日自动滚动清理,此为手动“立即清理”补充入口' }, + { name: '磁盘告警', source: '系统每天早上 7:00 自动检测附件目录剩余空间。', purpose: '提前发现空间不足,避免写入失败', fill: '剩余 <20% 告警、<10% 提示立即清理;本页顶部同步展示' } ] } diff --git a/bj_power_wms/frontend/src/pages/Agv.vue b/bj_power_wms/frontend/src/pages/Agv.vue index e9eeb12..bff6204 100644 --- a/bj_power_wms/frontend/src/pages/Agv.vue +++ b/bj_power_wms/frontend/src/pages/Agv.vue @@ -161,6 +161,11 @@ onMounted(() => { loadDocks(); loadTasks(); loadMode() })
+ + + @@ -181,8 +186,6 @@ onMounted(() => { loadDocks(); loadTasks(); loadMode() }) -
diff --git a/bj_power_wms/frontend/src/pages/AttachmentCenter.vue b/bj_power_wms/frontend/src/pages/AttachmentCenter.vue index 9e61e32..6788a42 100644 --- a/bj_power_wms/frontend/src/pages/AttachmentCenter.vue +++ b/bj_power_wms/frontend/src/pages/AttachmentCenter.vue @@ -1,10 +1,11 @@ diff --git a/bj_power_wms/frontend/src/pages/Inbound.vue b/bj_power_wms/frontend/src/pages/Inbound.vue index a648654..ebddb00 100644 --- a/bj_power_wms/frontend/src/pages/Inbound.vue +++ b/bj_power_wms/frontend/src/pages/Inbound.vue @@ -306,7 +306,7 @@ async function submitOther() { return } if ((form3.ownershipType === 'workorder' || form3.ownershipType === 'project') && !form3.ownershipNo.trim()) { - ElMessage.warning('请填写归属编号(工单号 / 科技项目号)') + ElMessage.warning('请填写归属编号(工单编号 / 科技项目号)') return } if (otherMaterial.value && !String(otherMaterial.value.spec || '').trim()) { @@ -316,7 +316,7 @@ async function submitOther() { try { await ElMessageBox.confirm( '确认其他入库:物料 ' + form3.materialCode + '|数量 ' + form3.quantity + - '|区域 ' + form3.zoneCode + '|归属 ' + (form3.ownershipType ? (form3.ownershipType === 'other' ? '其他' : form3.ownershipNo) : '无') + '?', + '|区域 ' + form3.zoneCode + '|归属 ' + (form3.ownershipType ? (form3.ownershipType === 'other' ? '其他' : form3.ownershipNo) : '其他') + '?', '其他入库提交确认', { type: 'warning' }) } catch { return } submitting3.value = true @@ -487,10 +487,10 @@ function typeTagType(row) { } function ownershipLabel(row) { const t = row?.ownershipType - if (t === 'workorder') return '工单号 ' + (row.ownershipNo || '') + if (t === 'workorder') return '工单编号 ' + (row.ownershipNo || '') if (t === 'project') return '科技项目号 ' + (row.ownershipNo || '') if (t === 'other') return '其他' + (row.ownershipNo ? ' ' + row.ownershipNo : '') - return '无' + return '其他' } // 库位串(记录列表展示):货架/层/位置,跳过空段 @@ -648,7 +648,7 @@ onActivated(() => { - + @@ -738,7 +738,7 @@ onActivated(() => { - + @@ -843,7 +843,7 @@ onActivated(() => { - + diff --git a/bj_power_wms/frontend/src/pages/Inspection.vue b/bj_power_wms/frontend/src/pages/Inspection.vue index 35bd024..a4691a1 100644 --- a/bj_power_wms/frontend/src/pages/Inspection.vue +++ b/bj_power_wms/frontend/src/pages/Inspection.vue @@ -10,6 +10,7 @@ import { exportXlsxFetch } from '../utils/export' import { can } from '../utils/perm' import PageHelp from '../components/PageHelp.vue' import AttachmentPanel from '../components/AttachmentPanel.vue' +import MaterialSelect from '../components/MaterialSelect.vue' import { helpInspection } from '../help' // ===== 多 Tab:录入在前默认打开,记录在后点击才见(参照出库管理) ===== @@ -19,9 +20,13 @@ const activeTab = ref('entry') // entry=质量检验(录入) / records=检验记 const route = useRoute() const router = useRouter() const pageType = String(route.meta.inspectionType || '') +// 三检边界(需求23/30):来料检=入库物料验收;过程检=产线在制巡检;完工检=成品 SN 终检。 +const isFinal = pageType === 'finished' +const isIncoming = pageType === 'incoming' /* ===================== Tab1 质量检验(录入,默认打开) ===================== */ -const targetType = ref('BATCH') +// 完工检对象一定是成品 SN,无「批次」选项(临时.md L101) +const targetType = ref(isFinal ? 'SN' : 'BATCH') const status = ref('合格') const inspector = ref(getRealName()) const submitting = ref(false) @@ -32,8 +37,8 @@ const passQty = ref(0) const checkDesc = ref('') const inspectionNo = ref('') const reportNo = ref('') -// 新增字段(客户诉求 L107/L150/L494) -const ownershipType = ref('none') +// 新增字段(客户诉求 L107/L150/L494;需求22:归属默认「其他」) +const ownershipType = ref('other') const ownershipNo = ref('') const relatedStandard = ref('') const manufacturer = ref('') @@ -63,9 +68,10 @@ async function loadPicker(kw = '') { pickerLoading.value = true try { const mm = targetType.value === 'BATCH' ? 1 : 2 - const data = await request.get('/stock/query', { - params: { manageMode: mm, keyword: kw || '', page: 1, pageSize: 100 } - }) + const params = { manageMode: mm, keyword: kw || '', page: 1, pageSize: 100 } + // 完工检只从成品库存(category=3)拾取 SN + if (isFinal) params.category = 3 + const data = await request.get('/stock/query', { params }) pickerOptions.value = data?.list || [] } catch (e) { pickerOptions.value = [] @@ -112,7 +118,7 @@ async function submitInspection() { } // 判定不合格必须选择处置方式(不合格不入库、不上产线) if (status.value === '不合格' && disposalType.value === 'NONE') { - ElMessage.warning('判定不合格时必须选择处置方式(退货 / 报废 / 部分入库)') + ElMessage.warning('判定不合格时必须选择处置方式(退货 / 换修 / 让步接收 / 报废 / 部分入库)') return } if (status.value === '不合格' && disposalType.value === 'RETURN' && !returnTrackingNo.value.trim()) { @@ -123,6 +129,17 @@ async function submitInspection() { ElMessage.warning('「部分入库」一次只能处理 1 个批次,请分开提交') return } + // 报检数守恒校验(临时.md §六:报检数 = 合格数 + 不合格数) + const iq = Number(inspectQty.value) || 0 + const pq = Number(passQty.value) || 0 + if (iq > 0 && pq > iq) { + ElMessage.warning('合格数不能大于报检数(报检数 = 合格数 + 不合格数)') + return + } + if (status.value === '合格' && iq > 0 && pq !== iq) { + ElMessage.warning('判定合格时,合格数应等于报检数(否则请改判不合格并选处置方式)') + return + } submitting.value = true try { const data = await request.post('/inspection/batch-flip', { @@ -154,7 +171,7 @@ async function submitInspection() { reportNo.value = '' disposalType.value = 'NONE' returnTrackingNo.value = '' - ownershipType.value = 'none' + ownershipType.value = 'other' ownershipNo.value = '' relatedStandard.value = '' manufacturer.value = '' @@ -225,13 +242,26 @@ const filters = reactive({ const loading = ref(false) const statsList = ref([]) const statsLoading = ref(false) +// 需求13/58:统计必须先选物料再点「统计」,不做全表实时聚合 +const statsMaterialCode = ref('') +const statsQueried = ref(false) async function loadStats() { + if (!statsMaterialCode.value) { + ElMessage.warning('请先选择物料再统计') + return + } statsLoading.value = true try { const data = await request.get('/inspection/stats', { - params: { inspectionType: filters.inspectionType, startDate: filters.startDate, endDate: filters.endDate } + params: { + inspectionType: filters.inspectionType, + materialCode: statsMaterialCode.value, + startDate: filters.startDate, + endDate: filters.endDate + } }) statsList.value = data?.list || [] + statsQueried.value = true } finally { statsLoading.value = false } @@ -249,9 +279,14 @@ function targetTypeLabel(t) { } // 不合格处置方式显示(NONE=未处置) function disposalLabel(t) { - const m = { RETURN: '退货', SCRAP: '报废', PARTIAL: '部分入库' } + const m = { RETURN: '退货', SCRAP: '报废', PARTIAL: '部分入库', REWORK: '换修', CONCESSION: '让步接收' } return m[t] || '未处置' } +// 归属类型显示(需求22:工单编号/科技项目号/其他;旧 none/空 → 其他) +function ownershipTypeLabel(t) { + const m = { workorder: '工单编号', project: '科技项目号' } + return m[t] || '其他' +} async function loadRecords() { loading.value = true @@ -321,9 +356,16 @@ onMounted(() => { + + + - - + + 批次 (结构件) 序列号 SN (电气件) @@ -336,11 +378,13 @@ onMounted(() => { - + - - + +
- - + + - +
- + - + - + @@ -538,8 +582,11 @@ onMounted(() => { - - + + @@ -574,11 +621,12 @@ onMounted(() => { -
- 统计 - 按当前时间区间与检验类别统计;每次检验独立计数,不合并记录 +
+ + 统计 + 需先选物料再统计(按当前时间区间与检验类别,每次检验独立计数,不合并记录)
- + @@ -609,6 +657,8 @@ onMounted(() => { 退货 + 换修 + 让步接收 报废 部分入库 diff --git a/bj_power_wms/frontend/src/pages/Inventory.vue b/bj_power_wms/frontend/src/pages/Inventory.vue index 4a282fb..49f528c 100644 --- a/bj_power_wms/frontend/src/pages/Inventory.vue +++ b/bj_power_wms/frontend/src/pages/Inventory.vue @@ -173,6 +173,10 @@ const detailCond = reactive({ materialCode: '', zoneCode: '', qualityStatus: '', function typeLabel(m) { return m === 1 ? '结构件' : m === 2 ? '电气件' : '-' } +// 品类标签(需求24):1原材料/2半成品/3成品/4其他(辅料/工装/试验设备) +function categoryLabel(c) { + return c === 1 ? '原材料' : c === 2 ? '半成品' : c === 3 ? '成品' : c === 4 ? '其他' : '-' +} // 兼容两类行:物料汇总行(无区域/质量) 与 库存明细行(带区域/质量) function openDetails(row) { @@ -478,11 +482,20 @@ onMounted(() => { + + + diff --git a/bj_power_wms/frontend/src/pages/Ledger.vue b/bj_power_wms/frontend/src/pages/Ledger.vue index 9b04143..912cec1 100644 --- a/bj_power_wms/frontend/src/pages/Ledger.vue +++ b/bj_power_wms/frontend/src/pages/Ledger.vue @@ -1,6 +1,6 @@ @@ -208,16 +297,29 @@ const currentStep = computed(() => { {{ operator || '-' }} - {{ active.no ? '(已完成发起)' : (scope.type === 'all' ? '全库' : scope.type === 'zone' ? `按区域【${scope.zones.join('、')}】` : `按物料【${scope.materialCodes.join('、')}】`) }} + {{ active.no ? '(已发起)' : (scope.type === 'all' ? '全库' : scope.type === 'zone' ? `按区域【${scope.zones.join('、')}】` : `按物料【${scope.materialCodes.join('、')}】`) }} + + + + 未发起 - {{ active.no || '未发起' }} {{ active.no ? `${active.totalTargets} 项` : '-' }} + {{ active.no ? `已盘 ${active.counted} / ${active.totalTargets}` : '-' }} +
- 发起盘点(可选范围) + + 取消盘点 +
@@ -256,25 +358,17 @@ const currentStep = computed(() => { - - - - 开启后按实盘差异修正库存 - - - 完成并生成差异 - - +

+ 盘点单号:{{ active.no || '未发起' }}(自动关联当前进行中单) +

+
+ 完成并处理差异 +
- { - 提交 + 提交 +

+ 扫 SN(电气件) 自动记实盘数 = 1,无需填写数量;扫 批次号(结构件) 才需填「实盘数量」。 +

- - + + + + + - - - + + + @@ -304,9 +404,35 @@ const currentStep = computed(() => {
+ + + + + + + 按账面保留(不计差异) + 盘亏为 0(实盘记 0) + + + + + 写回(生成盘点调整单,修正库存量) + + 无写回权限(需主管以上),本次仅出结论 + + + + + + @@ -330,30 +456,55 @@ const currentStep = computed(() => { - + + + + + + + + 查询 - - - + + - - + + + + + + + + + + + + + + - - + + @@ -366,4 +517,4 @@ const currentStep = computed(() => { .mt12 { margin-top: 12px; } .tip { margin-left: 10px; color: #909399; font-size: 12px; } .bad { color: #f56c6c; font-weight: 700; } - \ No newline at end of file + diff --git a/bj_power_wms/frontend/src/pages/UserManage.vue b/bj_power_wms/frontend/src/pages/UserManage.vue index a9d554a..a677851 100644 --- a/bj_power_wms/frontend/src/pages/UserManage.vue +++ b/bj_power_wms/frontend/src/pages/UserManage.vue @@ -5,6 +5,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import request from '../utils/request' import { fmtTime } from '../utils/format' import { can } from '../utils/perm' +import { confirmAction } from '../utils/confirm' import DetailDrawer from '../components/DetailDrawer.vue' // 角色数据源:实时取自角色表(含自定义角色),code/name 与 RoleManage 同源 @@ -90,6 +91,11 @@ async function save() { if (!editingId.value && !form.password) return ElMessage.warning('请输入初始密码') if (form.password && form.password.length < 6) return ElMessage.warning('密码长度至少 6 位') if (!form.roleId) return ElMessage.warning('请选择角色') + const okSave = await confirmAction( + editingId.value ? `确认保存账号「${form.username}」的修改?` : `确认新建账号「${form.username.trim()}」?`, + editingId.value ? '编辑确认' : '新建确认' + ) + if (!okSave) return saving.value = true try { if (editingId.value) { @@ -121,6 +127,9 @@ async function save() { } async function toggleActive(row) { + const action = row.isActive ? '停用' : '启用' + const go = await confirmAction(`确认${action}账号「${row.username}」?${row.isActive ? '停用后该账号将无法登录。' : ''}`, `${action}确认`) + if (!go) return try { await request.post('/user/update', { id: row.id, isActive: !row.isActive }) ElMessage.success(row.isActive ? '已停用' : '已启用') @@ -143,6 +152,24 @@ async function removeUser(row) { } catch { /* 拦截器已提示 */ } } +/* ---------- 详情抽屉(只读查看账号信息,修复模板引用 openDetail/detail 未定义导致整页空白) ---------- */ +const detail = reactive({ show: false, title: '账号详情', row: null, fields: [] }) +function openDetail(row) { + detail.row = row + detail.title = `账号详情 · ${row.username}` + detail.fields = [ + { key: 'id', label: 'ID' }, + { key: 'username', label: '用户名' }, + { key: 'realName', label: '姓名' }, + { key: 'roleId', label: '角色', format: (v, d) => roleName(d) }, + { key: 'dept', label: '部门' }, + { key: 'isActive', label: '状态', format: (v) => (v ? '启用' : '停用') }, + { key: 'lastLoginAt', label: '最近登录', type: 'time' }, + { key: 'createdAt', label: '创建时间', type: 'time' } + ] + detail.show = true +} + onMounted(() => { loadRoles() loadUsers() diff --git a/bj_power_wms/frontend/src/utils/confirm.js b/bj_power_wms/frontend/src/utils/confirm.js new file mode 100644 index 0000000..3ec6c28 --- /dev/null +++ b/bj_power_wms/frontend/src/utils/confirm.js @@ -0,0 +1,25 @@ +import { ElMessageBox } from 'element-plus' + +// 统一二次确认工具(需求6): +// 所有「单次、不可逆、影响面大」的操作——新建 / 编辑 / 保存 / 删除 / 停用启用 / 差异写回 / 批量导入—— +// 都必须先弹此确认框,且 message 需回显影响范围(对象、数量、区域等摘要)。 +// 高频连续操作(连续扫码入库、工位报工、叫料)不使用本工具,避免打断作业。 +// +// 用法: +// if (!(await confirmAction('确认删除物料「X」?将影响 N 条库存记录。', '删除确认'))) return +// await doDelete() +export async function confirmAction(message, title = '操作确认', options = {}) { + try { + await ElMessageBox.confirm(message, title, { + type: 'warning', + confirmButtonText: '确认', + cancelButtonText: '取消', + ...options + }) + return true + } catch { + return false + } +} + +export default confirmAction diff --git a/bj_power_wms/frontend/src/utils/perm.js b/bj_power_wms/frontend/src/utils/perm.js index 3aa409b..59839b4 100644 --- a/bj_power_wms/frontend/src/utils/perm.js +++ b/bj_power_wms/frontend/src/utils/perm.js @@ -12,4 +12,15 @@ export function can(code) { } catch { return false } +} + +// isAdmin 判断当前登录用户是否系统管理员(role==='admin')。 +// 用于仅管理员可见的操作入口(如日志清理)。后端 requireAdmin 为准,前端仅控展示。 +export function isAdmin() { + try { + const u = JSON.parse(localStorage.getItem('user') || 'null') + return !!u && u.role === 'admin' + } catch { + return false + } } \ No newline at end of file diff --git a/bj_power_wms/internal/db/db.go b/bj_power_wms/internal/db/db.go index f88aef6..4d9fdce 100644 --- a/bj_power_wms/internal/db/db.go +++ b/bj_power_wms/internal/db/db.go @@ -249,6 +249,18 @@ func applyColumnPatches(ctx context.Context) { // 出库单状态 + 是否叫AGV(配送进度只读展示用;「已接料」不落在本表) `ALTER TABLE outbound_orders ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT '已出库'`, `ALTER TABLE outbound_orders ADD COLUMN IF NOT EXISTS need_agv boolean NOT NULL DEFAULT false`, + // 盘点15问(2026-09-20 最优方案):库存冻结标记 + 盘点单状态机/写回调整单/未盘项/取消原因 + // inventories.stocktake_no 非空=该行正被盘点锁定,出库/入库/锁定入口据此拦截,保证盘点期间账实冻结。 + `ALTER TABLE inventories ADD COLUMN IF NOT EXISTS stocktake_no text NOT NULL DEFAULT ''`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS diff_count bigint NOT NULL DEFAULT 0`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS adjusted boolean NOT NULL DEFAULT false`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS adjust_no text`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS written_at bigint`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS written_by text`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS uncounted_mode text`, + `ALTER TABLE stocktake_orders ADD COLUMN IF NOT EXISTS cancel_reason text`, + `ALTER TABLE stocktake_items ADD COLUMN IF NOT EXISTS counted_by text`, + `ALTER TABLE stocktake_items ADD COLUMN IF NOT EXISTS counted_at bigint`, } for _, s := range stmts { if _, err := rawDB.ExecContext(ctx, s); err != nil { diff --git a/bj_power_wms/internal/handler/attachment.go b/bj_power_wms/internal/handler/attachment.go index d4a4d62..1b4899a 100644 --- a/bj_power_wms/internal/handler/attachment.go +++ b/bj_power_wms/internal/handler/attachment.go @@ -353,6 +353,24 @@ func deleteAttachmentHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusNotFound, "附件不存在") return } + // 删除保护(需求1):关键业务附件禁止删除,避免质量/工艺记录丢失。 + // 1) 工艺流程(PROCESS_PDF)、流程卡(PROCESS_CARD):全程禁删(工艺依据,需永久留存)。 + // 2) 检验照片/报告(INSPECTION_*):未超本地保留年数禁删(质量追溯期内不可删)。 + switch rec.FileType { + case FileTypeProcessPDF, FileTypeProcessCard: + fail(w, http.StatusForbidden, "工艺流程 / 流程卡为关键工艺文件,禁止删除") + return + case FileTypeInspectionPhoto, FileTypeInspectionPDF: + years := ctx.Config.Attachment.LocalRetentionYears + if years <= 0 { + years = 1 + } + cutYear := timeNow().Year() - years + if time.Unix(rec.CreatedAt, 0).Year() >= cutYear { + fail(w, http.StatusForbidden, fmt.Sprintf("检验记录在质量追溯保留期(%d 年)内,禁止删除", years)) + return + } + } if _, err := ctx.EntClient.Attachment.UpdateOneID(int(req.ID)). SetDeleted(true).SetDeletedAt(timeNow().Unix()). SetNillableDeletedBy(strPtr(r.Header.Get("X-Username"))). @@ -593,3 +611,115 @@ func checkDiskOnce(ctx *svc.ServiceContext) { logx.Infof("[磁盘监控] 正常:剩余 %d%%(%s)", 100-st.UsedPercent, st.RootDir) } } + +// purgeDeletedHandler 物理清理已逻辑删除的附件(需求2): +// 扫描 deleted=true 的索引,删除对应磁盘文件(仅当文件未被归档到 ArchiveDir 时), +// 释放本地空间;索引保留(deleted 标记不变)以便追溯。默认 dryRun 预检,返回将清理数量与释放字节。 +// POST /api/attachments/purge-deleted body: { dryRun } +func purgeDeletedHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + DryRun bool `json:"dryRun"` + } + _ = parseJSON(r, &req) + root := attRoot(ctx) + recs, err := ctx.EntClient.Attachment.Query(). + Where(attachment.DeletedEQ(true)).All(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + removed, freed, missing, failed := 0, int64(0), 0, 0 + for _, rec := range recs { + // 已归档的文件已移至 ArchiveDir,不在本地,跳过 + if rec.Archived { + missing++ + continue + } + abs, okp := safeAbsPath(root, rec.FilePath) + if !okp { + failed++ + continue + } + info, statErr := os.Stat(abs) + if statErr != nil { + missing++ // 文件已不存在(已清理或丢失),仅计数 + continue + } + size := info.Size() + if req.DryRun { + removed++ + freed += size + continue + } + if err := os.Remove(abs); err != nil { + failed++ + continue + } + removed++ + freed += size + } + if !req.DryRun { + ctx.EventLog.Write(ctx0(), "attachment.purge", r.Header.Get("X-Username"), "attachment", "purge", + fmt.Sprintf("物理清理已删附件:删除%d 释放%.2fMB 缺失%d 失败%d", removed, float64(freed)/1024/1024, missing, failed), + map[string]any{"removed": removed, "freed": freed, "missing": missing, "failed": failed}) + } + ok(w, map[string]any{"removed": removed, "freed": freed, "missing": missing, "failed": failed, "dryRun": req.DryRun}) + } +} + +// cleanLogsHandler 日志清理(需求2,仅管理员):清理日志目录(Config.Log.Path)下 +// 早于 N 天(默认 30)的 *.log 文件。与 logdaily 自动滚动清理互补,提供手动「立即清理」入口。 +// 默认 dryRun 预检。POST /api/attachments/clean-logs body: { days, dryRun } +func cleanLogsHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + Days int `json:"days"` + DryRun bool `json:"dryRun"` + } + _ = parseJSON(r, &req) + days := req.Days + if days <= 0 { + days = 30 // 默认保留 30 天 + } + dir := strings.TrimSpace(ctx.Config.Log.Path) + if dir == "" { + dir = "logs" + } + cutoff := timeNow().AddDate(0, 0, -days) + entries, err := os.ReadDir(dir) + if err != nil { + fail(w, http.StatusInternalServerError, "读取日志目录失败: "+err.Error()) + return + } + removed, failed := 0, 0 + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".log") { + continue + } + info, ierr := e.Info() + if ierr != nil { + continue + } + // 按文件名日期(base-2006-01-02.log)或修改时间判定,早于 cutoff 才清 + if !info.ModTime().Before(cutoff) { + continue + } + if req.DryRun { + removed++ + continue + } + if err := os.Remove(filepath.Join(dir, e.Name())); err != nil { + failed++ + continue + } + removed++ + } + if !req.DryRun { + ctx.EventLog.Write(ctx0(), "system.cleanlog", r.Header.Get("X-Username"), "log", dir, + fmt.Sprintf("清理日志:删除%d 个早于%d天的日志文件,失败%d", removed, days, failed), + map[string]any{"removed": removed, "failed": failed, "days": days}) + } + ok(w, map[string]any{"removed": removed, "failed": failed, "days": days, "dir": dir, "dryRun": req.DryRun}) + } +} diff --git a/bj_power_wms/internal/handler/dock.go b/bj_power_wms/internal/handler/dock.go index 6a92fbf..3e8cc4f 100644 --- a/bj_power_wms/internal/handler/dock.go +++ b/bj_power_wms/internal/handler/dock.go @@ -1,7 +1,6 @@ package handler import ( - "errors" "net/http" "strings" @@ -76,8 +75,8 @@ func dockMaintRowOf(d *ent.Dock) dockMaintRow { } } -// listDocksMaintHandler GET /api/docks?dockType=&keyword=&page=&pageSize= -// 接驳台主数据分页查询(真分页在 SQL 层),默认按创建时间倒序。 +// listDocksMaintHandler GET /api/docks?dockType=&dockCode=&name=&status=&page=&pageSize= +// 接驳台主数据只读分页查询(真分页在 SQL 层),编码精确/名称模糊/状态精确各自独立筛选。 func listDocksMaintHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() @@ -85,11 +84,15 @@ func listDocksMaintHandler(ctx *svc.ServiceContext) http.HandlerFunc { if typ := q.Get("dockType"); typ != "" { query = query.Where(dock.DockType(typ)) } - if kw := strings.TrimSpace(q.Get("keyword")); kw != "" { - query = query.Where(dock.Or( - dock.DockCodeContainsFold(kw), - dock.NameContainsFold(kw), - )) + // 列表页禁止关键字混搜:编码精确、名称模糊、状态精确,各字段独立筛选(需求5、N) + if code := strings.TrimSpace(q.Get("dockCode")); code != "" { + query = query.Where(dock.DockCodeEqualFold(code)) + } + if name := strings.TrimSpace(q.Get("name")); name != "" { + query = query.Where(dock.NameContainsFold(name)) + } + if status := strings.TrimSpace(q.Get("status")); status != "" { + query = query.Where(dock.StatusEQ(status)) } total, err := query.Count(ctx0()) if err != nil { @@ -119,159 +122,36 @@ func listDocksMaintHandler(ctx *svc.ServiceContext) http.HandlerFunc { } } -// dockReq 接驳台保存请求 -type dockReq struct { - ID int64 `json:"id"` - DockCode string `json:"dockCode"` - Name string `json:"name"` - DockType string `json:"dockType"` +// 接驳台为系统预置只读主数据(需求5):不提供新增/修改/删除,仅保留查询。 +// 原 createDockHandler / updateDockHandler / deleteDockHandler / validateDock / dockReq 已彻底移除, +// 接驳台数据由 db/seed.go 种子初始化,调整由实施维护种子或数据库完成。 + +// stationOption 出库目标工位下拉项(需求20:目标工位可下拉选择、不允许手输)。 +// 客户口径「出库工位号来自 MES 工位列表」——WMS 侧以接驳台主数据中 line 类型 +// (产线接驳台与工位 1:1 绑定)为真源派生工位选项,避免前端写死 1~20。 +type stationOption struct { StationNo int `json:"stationNo"` - Status string `json:"status"` + Name string `json:"name"` + DockCode string `json:"dockCode"` } -// validateDock 校验接驳台字段: -// - 编码/名称必填,编码全库唯一; -// - 类型仅 line/store/other; -// - 产线接驳台必须绑定工位号(>0)且与工位 1:1;库房/其他接驳台不绑定工位(固定 0)。 -func validateDock(ctx *svc.ServiceContext, req *dockReq, selfID int64) error { - req.DockCode = strings.TrimSpace(req.DockCode) - req.Name = strings.TrimSpace(req.Name) - if req.DockCode == "" { - return errors.New("接驳台编码必填") - } - if req.Name == "" { - return errors.New("接驳台名称必填") - } - switch req.DockType { - case "line", "store", "other": - default: - return errors.New("接驳台类型仅支持 产线/库房/其他") - } - if req.Status != "ENABLED" && req.Status != "DISABLED" { - return errors.New("接驳台状态仅支持 启用/停用") - } - if req.DockType != "line" { - req.StationNo = 0 - } else if req.StationNo <= 0 { - return errors.New("产线接驳台必须绑定工位号") - } - dupCode := ctx.EntClient.Dock.Query().Where(dock.DockCode(req.DockCode)) - if selfID > 0 { - dupCode = dupCode.Where(dock.IDNEQ(int(selfID))) - } - if exists, err := dupCode.Exist(ctx0()); err == nil && exists { - return errors.New("接驳台编码已存在:" + req.DockCode) - } - if req.StationNo > 0 { - q := ctx.EntClient.Dock.Query().Where(dock.StationNo(req.StationNo)) - if selfID > 0 { - q = q.Where(dock.IDNEQ(int(selfID))) - } - other, err := q.First(ctx0()) - if err == nil && other != nil { - return errors.New("该工位已绑定接驳台 " + other.DockCode + ",一个工位只能有一个接驳台") - } - } - return nil -} - -// createDockHandler POST /api/docks 新增接驳台 -func createDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { +// listStationOptionsHandler GET /api/outbound/stations 返回产线工位下拉选项(无权限包裹,供出库页调用)。 +func listStationOptionsHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req dockReq - if err := parseJSON(r, &req); err != nil { - fail(w, http.StatusBadRequest, "参数错误") - return - } - if req.DockType == "" { - req.DockType = "line" - } - if req.Status == "" { - req.Status = "ENABLED" - } - if err := validateDock(ctx, &req, 0); err != nil { - fail(w, http.StatusBadRequest, err.Error()) - return - } - _, err := ctx.EntClient.Dock.Create(). - SetDockCode(req.DockCode).SetName(req.Name).SetDockType(req.DockType). - SetStationNo(req.StationNo).SetHasPallet(false).SetStatus(req.Status). - Save(ctx0()) + list, err := ctx.EntClient.Dock.Query(). + Where(dock.DockType("line")). + Order(ent.Asc("station_no")).All(ctx0()) if err != nil { - fail(w, http.StatusInternalServerError, "新增接驳台失败: "+err.Error()) + fail(w, http.StatusInternalServerError, "查询工位失败: "+err.Error()) return } - ctx.EventLog.Write(ctx0(), "dock.create", r.Header.Get("X-Username"), "dock", req.DockCode, - "新增接驳台", map[string]any{"dockCode": req.DockCode, "dockType": req.DockType, "stationNo": req.StationNo}) - ok(w, map[string]any{"message": "接驳台已新增"}) - } -} - -// updateDockHandler POST /api/docks/update 修改接驳台(编码不可改,是产线/AGV 任务的引用锚点) -func updateDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req dockReq - if err := parseJSON(r, &req); err != nil { - fail(w, http.StatusBadRequest, "参数错误") - return - } - if req.ID <= 0 { - fail(w, http.StatusBadRequest, "缺少 id") - return - } - old, err := ctx.EntClient.Dock.Get(ctx0(), int(req.ID)) - if err != nil { - fail(w, http.StatusBadRequest, "接驳台不存在") - return - } - req.DockCode = old.DockCode - if err := validateDock(ctx, &req, req.ID); err != nil { - fail(w, http.StatusBadRequest, err.Error()) - return - } - _, err = old.Update(). - SetName(req.Name).SetDockType(req.DockType). - SetStationNo(req.StationNo).SetStatus(req.Status).Save(ctx0()) - if err != nil { - fail(w, http.StatusInternalServerError, "修改接驳台失败: "+err.Error()) - return - } - ctx.EventLog.Write(ctx0(), "dock.update", r.Header.Get("X-Username"), "dock", old.DockCode, - "修改接驳台", map[string]any{"dockCode": old.DockCode, "dockType": req.DockType, "stationNo": req.StationNo}) - ok(w, map[string]any{"message": "接驳台已保存"}) - } -} - -// deleteDockHandler POST /api/docks/delete 删除接驳台 -// 正在占用(有托盘)的接驳台不允许删除,避免 AGV/产线任务指向已删除的接驳台。 -func deleteDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req struct { - ID int64 `json:"id"` - } - if err := parseJSON(r, &req); err != nil { - fail(w, http.StatusBadRequest, "参数错误") - return - } - if req.ID <= 0 { - fail(w, http.StatusBadRequest, "缺少 id") - return - } - d, err := ctx.EntClient.Dock.Get(ctx0(), int(req.ID)) - if err != nil { - fail(w, http.StatusBadRequest, "接驳台不存在") - return - } - if d.HasPallet { - fail(w, http.StatusBadRequest, "接驳台 "+d.DockCode+" 当前有托盘占用,请先处理托盘后再删除") - return - } - if err := ctx.EntClient.Dock.DeleteOneID(d.ID).Exec(ctx0()); err != nil { - fail(w, http.StatusInternalServerError, "删除接驳台失败: "+err.Error()) - return - } - ctx.EventLog.Write(ctx0(), "dock.delete", r.Header.Get("X-Username"), "dock", d.DockCode, - "删除接驳台", map[string]any{"dockCode": d.DockCode}) - ok(w, map[string]any{"message": "接驳台已删除"}) + rows := make([]stationOption, 0, len(list)) + for _, d := range list { + if d.StationNo <= 0 { + continue + } + rows = append(rows, stationOption{StationNo: d.StationNo, Name: d.Name, DockCode: d.DockCode}) + } + ok(w, map[string]any{"list": rows}) } } diff --git a/bj_power_wms/internal/handler/inbound.go b/bj_power_wms/internal/handler/inbound.go index 201eaa2..7c289c8 100644 --- a/bj_power_wms/internal/handler/inbound.go +++ b/bj_power_wms/internal/handler/inbound.go @@ -198,6 +198,11 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { existing, e := tx.Inventory.Query(). Where(inventory.ManageModeEQ(1), inventory.BatchNoEQ(batchNo)).Only(ctx0()) if e == nil { + // 盘点冻结守卫(问题2):盘点期间库存量冻结,禁止向已锁定批次追加入库。 + if existing.StocktakeNo != "" { + fail(w, http.StatusConflict, "批次 "+batchNo+" 正在盘点单 "+existing.StocktakeNo+" 锁定中,盘点期间禁止入库追加") + return + } if _, err = tx.Inventory.UpdateOneID(existing.ID). AddQuantity(qty). SetNillableSupplier(strPtr(req.Supplier)). @@ -487,13 +492,16 @@ func exportInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { } } -// ownershipTypeOrNone 归属类型归一化(工单号/科技项目号/其他/无) +// ownershipTypeOrNone 归属类型归一化(需求22)。 +// 归属三选一:工单编号(workorder) / 科技项目号(project) / 其他(other),默认「其他」。 +// 这里的「工单编号」是独立维度标签,与 MES 工单号不是同一东西,手动输入、不联动 MES、不校验格式(长度≤64 由前端约束)。 +// 历史遗留的 "none"/空 一律归为 "other"(需求22:显示「无」不对,应为「其他」)。 func ownershipTypeOrNone(v string) string { switch v { - case "workorder", "project", "other": + case "workorder", "project": return v default: - return "none" + return "other" } } diff --git a/bj_power_wms/internal/handler/inspection.go b/bj_power_wms/internal/handler/inspection.go index d2f2fef..dde038f 100644 --- a/bj_power_wms/internal/handler/inspection.go +++ b/bj_power_wms/internal/handler/inspection.go @@ -109,7 +109,7 @@ func createInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { SetTargetID(req.TargetId). SetMaterialCode(req.MaterialCode). SetNillableMaterialName(strPtr(materialName)). - SetNillableOwnershipType(strPtr(req.OwnershipType)). + SetNillableOwnershipType(strPtr(ownershipTypeOrNone(req.OwnershipType))). SetNillableOwnershipNo(strPtr(req.OwnershipNo)). SetNillableRelatedStandard(strPtr(req.RelatedStandard)). SetNillableManufacturer(strPtr(req.Manufacturer)). @@ -231,7 +231,7 @@ func batchFlipInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { SetTargetID(t.id). SetMaterialCode(t.material). SetNillableMaterialName(strPtr(matName)). - SetNillableOwnershipType(strPtr(req.OwnershipType)). + SetNillableOwnershipType(strPtr(ownershipTypeOrNone(req.OwnershipType))). SetNillableOwnershipNo(strPtr(req.OwnershipNo)). SetNillableRelatedStandard(strPtr(req.RelatedStandard)). SetNillableManufacturer(strPtr(req.Manufacturer)). @@ -453,10 +453,10 @@ func materialCodesBySpec(ctx *svc.ServiceContext, spec string) []string { return codes } -// disposalTypeOrNone 处置方式归一化(客户诉求 L137-145:退货/报废/部分入库) +// disposalTypeOrNone 处置方式归一化(需求23:不合格可转退货 / 换修(返修)/ 让步接收 / 报废 / 部分入库) func disposalTypeOrNone(v string) string { switch v { - case "RETURN", "SCRAP", "PARTIAL": + case "RETURN", "SCRAP", "PARTIAL", "REWORK", "CONCESSION": return v default: return "NONE" @@ -464,12 +464,16 @@ func disposalTypeOrNone(v string) string { } // qualityAfterInspection 检验后的库存质量状态: -// 合格 → 合格(可出库);不合格且选退货 → 待退(隔离);其余 → 不合格(隔离)。 +// 合格 → 合格(可出库);让步接收(CONCESSION) → 合格(审批后放行使用); +// 退货(RETURN) → 待退(隔离);返修(REWORK)/报废/其余不合格 → 不合格(隔离,禁止上线)。 func qualityAfterInspection(status, disposalType string) string { if status == "合格" { return "合格" } - if disposalTypeOrNone(disposalType) == "RETURN" { + switch disposalTypeOrNone(disposalType) { + case "CONCESSION": + return "合格" + case "RETURN": return "待退" } return "不合格" @@ -545,6 +549,18 @@ func disposeInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error()) return } + case "REWORK": + // 换修/返修:隔离为不合格,备注标返修,等待返修后重新报检(不上线、不发货) + if _, err = tx.Inventory.UpdateOneID(inv.ID).SetQualityStatus("不合格").Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error()) + return + } + case "CONCESSION": + // 让步接收:不合格但审批后放行使用,质量状态置合格(可出库/上线) + if _, err = tx.Inventory.UpdateOneID(inv.ID).SetQualityStatus("合格").Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error()) + return + } case "SCRAP": if _, err = tx.Inventory.UpdateOneID(inv.ID).SetQualityStatus("不合格").SetStatus("报废").Save(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "更新库存状态失败: "+err.Error()) @@ -601,6 +617,7 @@ func disposeInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { func statsInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { inspectionType := r.URL.Query().Get("inspectionType") + materialCode := r.URL.Query().Get("materialCode") // 需求13:统计先选物料,按物料聚焦 startDate := r.URL.Query().Get("startDate") endDate := r.URL.Query().Get("endDate") @@ -608,6 +625,9 @@ func statsInspectionHandler(ctx *svc.ServiceContext) http.HandlerFunc { if inspectionType != "" { q = q.Where(inspectionrecord.InspectionTypeEQ(inspectionType)) } + if materialCode != "" { + q = q.Where(inspectionrecord.MaterialCodeContainsFold(materialCode)) + } if startDate != "" { if t, err := time.ParseInLocation("2006-01-02", startDate, time.Local); err == nil { q = q.Where(inspectionrecord.CreatedAtGTE(t.Unix())) diff --git a/bj_power_wms/internal/handler/misc.go b/bj_power_wms/internal/handler/misc.go index cbb47ea..13545a1 100644 --- a/bj_power_wms/internal/handler/misc.go +++ b/bj_power_wms/internal/handler/misc.go @@ -175,6 +175,11 @@ func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusConflict, catLabel+"不在库: "+rec.Status) return } + // 盘点冻结守卫(问题2):盘点期间库存量冻结,禁止半成品/成品出库。 + if rec.StocktakeNo != "" { + fail(w, http.StatusConflict, catLabel+" "+req.Sn+" 正在盘点单 "+rec.StocktakeNo+" 锁定中,盘点期间禁止出库") + return + } ctx.EntClient.Inventory.UpdateOneID(rec.ID). SetStatus("出库"). diff --git a/bj_power_wms/internal/handler/outbound.go b/bj_power_wms/internal/handler/outbound.go index 56817e3..cec83e0 100644 --- a/bj_power_wms/internal/handler/outbound.go +++ b/bj_power_wms/internal/handler/outbound.go @@ -87,6 +87,10 @@ func deductStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "batchNo 或 snList 至少提供一个") return } + // 盘点冻结守卫(问题2):盘点期间该批库存禁止出库,避免差异混入业务变动。 + if !assertNotFrozenTargets(ctx, w, req.BatchNo, req.SnList) { + return + } outboundNo := "OB" + uuid.NewString()[:12] @@ -456,6 +460,10 @@ func generalOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "数量必须大于 0") return } + // 盘点冻结守卫(问题2):盘点期间该批库存禁止出库。 + if !assertNotFrozenTargets(ctx, w, req.BatchNo, req.SnList) { + return + } // 库位兜底(与工单出库同口径):未填的层级按「批次在库 → 首个 SN 在库」补全 req.ZoneCode, req.ShelfNo, req.LayerNo, req.PositionNo = diff --git a/bj_power_wms/internal/handler/return_order.go b/bj_power_wms/internal/handler/return_order.go index 7dab644..584fbc3 100644 --- a/bj_power_wms/internal/handler/return_order.go +++ b/bj_power_wms/internal/handler/return_order.go @@ -4,6 +4,7 @@ import ( "net/http" "sort" "strconv" + "strings" "time" "bj_power_wms/ent" @@ -28,9 +29,21 @@ type returnOrderCreateRequest struct { StationNo int `json:"stationNo"` } -// returnOrderConfirmRequest WMS 库管确认收货 +// returnOrderConfirmRequest WMS 库管确认收货(客户诉求 问题14 退库闭环)。 +// +// returnType:剩余物料 surplus(正常用剩,退回置「未检」走检验)/ 问题物料 problem(有质量疑虑,退回直接置「不合格」隔离)。 +// 四级库位:退回到哪个货位,可手动选择;被占用允许混放,只记数量不校验位置冲突(客户拍板)。 +// snList:电气件(manage_mode=2)逐个扫码录入的 SN 清单,扫码数须与退库数一致;结构件(manage_mode=1)按数量加回、可指定 batchNo。 type returnOrderConfirmRequest struct { - ReturnNo string `json:"returnNo"` + ReturnNo string `json:"returnNo"` + ReturnType string `json:"returnType"` // surplus/problem + ZoneCode string `json:"zoneCode"` + ShelfNo string `json:"shelfNo"` + LayerNo string `json:"layerNo"` + PositionNo string `json:"positionNo"` + BatchNo string `json:"batchNo"` + SNList []string `json:"snList"` + Remark string `json:"remark"` } // createReturnOrderInternalHandler 预建「待确认收货」退库单(X-API-TOKEN 内部接口) @@ -148,32 +161,162 @@ func confirmReturnOrderHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } - // 库存加回:同物料同管理粒度的在库行追加,否则新建。 - // 退回料默认「未检」(= 客户口径的“待检”,系统既有枚举),由检验员判定合格/不合格, - // 不直接置合格,避免“用剩/错领/有问题”的料未经检验混入可用库存(方案 U2)。 - existing, e := tx.Inventory.Query(). - Where(inventory.ManageModeEQ(mMode), inventory.MaterialCodeEQ(ro.MaterialCode), inventory.StatusEQ("在库")). - Order(ent.Asc("id")).First(ctx0()) - if e == nil { - if _, err = tx.Inventory.UpdateOneID(existing.ID). - AddQuantity(ro.Qty).Save(ctx0()); err != nil { - fail(w, http.StatusInternalServerError, "库存加回失败: "+err.Error()) + // 退库类型决定质量状态(客户诉求 问题14): + // 剩余物料(surplus)=正常用剩,置「未检」由检验员判定合格/不合格,不直接置合格避免未经检验混入可用库存; + // 问题物料(problem)=有质量疑虑,直接置「不合格」隔离,绝不混入可用/合格库存。 + quality := "未检" + if req.ReturnType == "problem" { + quality = "不合格" + } + + if mMode == 2 { + // 电气件:SN 逐个扫码录入(客户诉求 问题14)。扫码数必须与退库数一致,保证一物一码守恒。 + seen := map[string]bool{} + sns := make([]string, 0, len(req.SNList)) + for _, sn := range req.SNList { + sn = strings.TrimSpace(sn) + if sn == "" || seen[sn] { + continue + } + seen[sn] = true + sns = append(sns, sn) + } + sort.Strings(sns) + if len(sns) == 0 { + fail(w, http.StatusBadRequest, "电气件退库需逐个扫码录入 SN") return } - } else { - if _, err = tx.Inventory.Create(). - SetManageMode(mMode). - SetMaterialCode(ro.MaterialCode). - SetNillableMaterialName(strPtr(mName)). - SetBatchNo(genBatchNo(ctx, ro.MaterialCode)). - SetQuantity(ro.Qty).SetLockedQty(0). - SetQualityStatus("未检"). - SetStatus("在库"). - SetInboundNo("RET:" + ro.ReturnNo). - Save(ctx0()); err != nil { - fail(w, http.StatusInternalServerError, "库存加回失败: "+err.Error()) + if len(sns) != ro.Qty { + fail(w, http.StatusBadRequest, "扫码 SN 数("+strconv.Itoa(len(sns))+")与退库数量("+strconv.Itoa(ro.Qty)+")不符") return } + for _, sn := range sns { + // 出库时 SN 库存行保留(status=出库),退库优先复用原行回到「在库」,避免撞 sn_code 全局唯一索引。 + row, e := tx.Inventory.Query().Where(inventory.SnCodeEQ(sn)).Only(ctx0()) + if e == nil && row != nil { + // 盘点冻结守卫(问题2):目标 SN 行若正被盘点锁定,禁止退库加回,避免污染快照。 + if row.StocktakeNo != "" { + fail(w, http.StatusConflict, "SN "+sn+" 正在盘点单 "+row.StocktakeNo+" 锁定中,盘点期间禁止退库加回,请先完成或取消该盘点") + return + } + upd := tx.Inventory.UpdateOneID(row.ID). + SetStatus("在库").SetLockedQty(0). + SetQualityStatus(quality). + SetInboundNo("RET:" + ro.ReturnNo). + SetUpdatedAt(time.Now().Unix()) + if req.ZoneCode != "" { + upd.SetZoneCode(req.ZoneCode) + } + if req.ShelfNo != "" { + upd.SetShelfNo(req.ShelfNo) + } + if req.LayerNo != "" { + upd.SetLayerNo(req.LayerNo) + } + if req.PositionNo != "" { + upd.SetPositionNo(req.PositionNo) + } + if _, err = upd.Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "SN "+sn+" 库存加回失败: "+err.Error()) + return + } + continue + } + // 无历史行(异常/手工补录):新建 SN 行。 + cr := tx.Inventory.Create(). + SetManageMode(2). + SetMaterialCode(ro.MaterialCode). + SetNillableMaterialName(strPtr(mName)). + SetSnCode(sn). + SetQuantity(1).SetLockedQty(0). + SetQualityStatus(quality). + SetStatus("在库"). + SetInboundNo("RET:" + ro.ReturnNo) + if req.ZoneCode != "" { + cr.SetZoneCode(req.ZoneCode) + } + if req.ShelfNo != "" { + cr.SetShelfNo(req.ShelfNo) + } + if req.LayerNo != "" { + cr.SetLayerNo(req.LayerNo) + } + if req.PositionNo != "" { + cr.SetPositionNo(req.PositionNo) + } + if _, err = cr.Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "SN "+sn+" 库存加回失败: "+err.Error()) + return + } + } + } else { + // 结构件(批次):按数量加回。允许混放(客户拍板)——目标库位即使已被其他物料占用也只记数量、不校验位置冲突。 + // 按 质量状态 + 四级库位 + 可选批次 精确匹配同料同状态同货位的在库行追加;否则新建行。 + // 匹配带上 quality_status,确保「不合格」问题料独立成行,绝不并入合格批次。 + q := tx.Inventory.Query().Where( + inventory.ManageModeEQ(1), inventory.MaterialCodeEQ(ro.MaterialCode), + inventory.StatusEQ("在库"), inventory.QualityStatusEQ(quality)) + if req.ZoneCode != "" { + q = q.Where(inventory.ZoneCodeEQ(req.ZoneCode)) + } + if req.ShelfNo != "" { + q = q.Where(inventory.ShelfNoEQ(req.ShelfNo)) + } + if req.LayerNo != "" { + q = q.Where(inventory.LayerNoEQ(req.LayerNo)) + } + if req.PositionNo != "" { + q = q.Where(inventory.PositionNoEQ(req.PositionNo)) + } + if req.BatchNo != "" { + q = q.Where(inventory.BatchNoEQ(req.BatchNo)) + } + existing, e := q.Order(ent.Asc("id")).First(ctx0()) + if e == nil && existing != nil { + // 盘点冻结守卫(问题2):目标批次行若正被盘点锁定,禁止追加退库数量,避免污染快照。 + if existing.StocktakeNo != "" { + fail(w, http.StatusConflict, "目标批次 "+existing.BatchNo+" 正在盘点单 "+existing.StocktakeNo+" 锁定中,盘点期间禁止退库加回,请先完成或取消该盘点") + return + } + if _, err = tx.Inventory.UpdateOneID(existing.ID). + AddQuantity(ro.Qty).SetUpdatedAt(time.Now().Unix()).Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "库存加回失败: "+err.Error()) + return + } + } else { + bn := strings.TrimSpace(req.BatchNo) + if bn == "" { + bn = genBatchNo(ctx, ro.MaterialCode) + } + cr := tx.Inventory.Create(). + SetManageMode(1). + SetMaterialCode(ro.MaterialCode). + SetNillableMaterialName(strPtr(mName)). + SetBatchNo(bn). + SetQuantity(ro.Qty).SetLockedQty(0). + SetQualityStatus(quality). + SetStatus("在库"). + SetInboundNo("RET:" + ro.ReturnNo) + if req.ZoneCode != "" { + cr.SetZoneCode(req.ZoneCode) + } + if req.ShelfNo != "" { + cr.SetShelfNo(req.ShelfNo) + } + if req.LayerNo != "" { + cr.SetLayerNo(req.LayerNo) + } + if req.PositionNo != "" { + cr.SetPositionNo(req.PositionNo) + } + if req.Remark != "" { + cr.SetRemark(req.Remark) + } + if _, err = cr.Save(ctx0()); err != nil { + fail(w, http.StatusInternalServerError, "库存加回失败: "+err.Error()) + return + } + } } // 退料回收台账缺口:累加 returned_qty,并把「领料完结」拉回「进行中」。 @@ -203,10 +346,49 @@ func confirmReturnOrderHandler(ctx *svc.ServiceContext) http.HandlerFunc { } committed = true + rtLabel := "剩余物料" + if req.ReturnType == "problem" { + rtLabel = "问题物料" + } + locStr := strings.Join([]string{req.ZoneCode, req.ShelfNo, req.LayerNo, req.PositionNo}, "/") ctx.EventLog.Write(ctx0(), "return.order.confirm", receiveBy, "return_order", ro.ReturnNo, - "确认收货 "+ro.MaterialCode+" x"+strconv.Itoa(ro.Qty)+" 库存已加回", - map[string]any{"orderNo": ro.OrderNo, "stationNo": ro.StationNo}) - ok(w, map[string]any{"returnNo": ro.ReturnNo, "status": "RECEIVED", "addedQty": ro.Qty}) + "确认收货 "+ro.MaterialCode+" x"+strconv.Itoa(ro.Qty)+" 库存已加回("+rtLabel+"/"+quality+")", + map[string]any{"orderNo": ro.OrderNo, "stationNo": ro.StationNo, "returnType": req.ReturnType, + "qualityStatus": quality, "location": locStr, "snList": req.SNList, "batchNo": req.BatchNo}) + ok(w, map[string]any{"returnNo": ro.ReturnNo, "status": "RECEIVED", "addedQty": ro.Qty, "qualityStatus": quality}) + } +} + +// suggestReturnLocationHandler 退库确认弹窗预填(客户诉求 问题14「回到原库位、可手动选择」): +// 返回该物料的管理粒度(manageMode:1=结构件按数量 / 2=电气件逐个扫 SN)与最近一次货位作为默认退回库位,库管可手动改。 +func suggestReturnLocationHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("materialCode") + if code == "" { + ok(w, map[string]any{"manageMode": 1}) + return + } + manageMode := 1 + if m, e := ctx.EntClient.Material.Query().Where(material.CodeEQ(code)).First(ctx0()); e == nil && m != nil { + manageMode = m.ManageMode + } + res := map[string]any{"manageMode": manageMode} + // 取该物料最近一条有货位的库存行作为「原库位」建议(在库优先)。 + inv, e := ctx.EntClient.Inventory.Query(). + Where(inventory.MaterialCodeEQ(code), inventory.ManageModeEQ(manageMode), inventory.StatusEQ("在库")). + Order(ent.Desc("updated_at"), ent.Desc("id")).First(ctx0()) + if e != nil || inv == nil { + inv, _ = ctx.EntClient.Inventory.Query(). + Where(inventory.MaterialCodeEQ(code), inventory.ManageModeEQ(manageMode)). + Order(ent.Desc("updated_at"), ent.Desc("id")).First(ctx0()) + } + if inv != nil { + res["zoneCode"] = inv.ZoneCode + res["shelfNo"] = inv.ShelfNo + res["layerNo"] = inv.LayerNo + res["positionNo"] = inv.PositionNo + } + ok(w, res) } } diff --git a/bj_power_wms/internal/handler/routes.go b/bj_power_wms/internal/handler/routes.go index 47f7b81..36c4856 100644 --- a/bj_power_wms/internal/handler/routes.go +++ b/bj_power_wms/internal/handler/routes.go @@ -52,16 +52,16 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { }, ) - // 物料档案 + // 物料档案(写操作权限守卫;query/list/detail 供各页下拉拾取器使用,保持登录可访问) server.AddRoutes( []rest.Route{ - {Method: http.MethodPost, Path: "/api/material/create", Handler: createMaterialHandler(ctx)}, - {Method: http.MethodPost, Path: "/api/material/update", Handler: updateMaterialHandler(ctx)}, + {Method: http.MethodPost, Path: "/api/material/create", Handler: requirePerm(ctx, "material:create", createMaterialHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/material/update", Handler: requirePerm(ctx, "material:edit", updateMaterialHandler(ctx))}, {Method: http.MethodGet, Path: "/api/material/query", Handler: queryMaterialsHandler(ctx)}, {Method: http.MethodGet, Path: "/api/material/list", Handler: listMaterialsHandler(ctx)}, {Method: http.MethodGet, Path: "/api/material/detail", Handler: getMaterialHandler(ctx)}, - {Method: http.MethodGet, Path: "/api/material/export", Handler: exportMaterialsHandler(ctx)}, - {Method: http.MethodPost, Path: "/api/material/import", Handler: excelMaterialImportHandler(ctx)}, + {Method: http.MethodGet, Path: "/api/material/export", Handler: requirePerm(ctx, "material:export", exportMaterialsHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/material/import", Handler: requirePerm(ctx, "material:import", excelMaterialImportHandler(ctx))}, }, ) @@ -89,6 +89,8 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { {Method: http.MethodGet, Path: "/api/outbound/export", Handler: exportOutboundHandler(ctx)}, // 出库库位联动:优先批次在库库位,其次该物料最近一次入库库位(客户诉求 一.9「出库相同」) {Method: http.MethodGet, Path: "/api/outbound/location-hint", Handler: outboundLocationHintHandler(ctx)}, + // 目标工位下拉选项(需求20:来自 MES 工位列表,不允许手输) + {Method: http.MethodGet, Path: "/api/outbound/stations", Handler: listStationOptionsHandler(ctx)}, }, ) @@ -137,10 +139,8 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { // 接驳台主数据(独立主数据:产线/库房/其他,产线类型与工位 1:1 绑定) server.AddRoutes( []rest.Route{ + // 接驳台为系统预置只读主数据(需求5):仅保留查询,不提供新增/修改/删除 {Method: http.MethodGet, Path: "/api/docks", Handler: requirePerm(ctx, "dock:manage", listDocksMaintHandler(ctx))}, - {Method: http.MethodPost, Path: "/api/docks", Handler: requirePerm(ctx, "dock:add", createDockHandler(ctx))}, - {Method: http.MethodPost, Path: "/api/docks/update", Handler: requirePerm(ctx, "dock:edit", updateDockHandler(ctx))}, - {Method: http.MethodPost, Path: "/api/docks/delete", Handler: requirePerm(ctx, "dock:delete", deleteDockHandler(ctx))}, }, ) @@ -158,13 +158,16 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { }, ) - // 盘点 + // 盘点(问题12 权限:发起/扫码/取消需 stocktake:start;查询/导出需 stocktake:view;写回分支在 handler 内再校 stocktake:writeback) server.AddRoutes( []rest.Route{ - {Method: http.MethodPost, Path: "/api/stocktake/start", Handler: startStocktakeHandler(ctx)}, - {Method: http.MethodPost, Path: "/api/stocktake/record", Handler: recordStocktakeHandler(ctx)}, - {Method: http.MethodPost, Path: "/api/stocktake/finish", Handler: finishStocktakeHandler(ctx)}, - {Method: http.MethodGet, Path: "/api/stocktake/query", Handler: queryStocktakeHandler(ctx)}, + {Method: http.MethodPost, Path: "/api/stocktake/start", Handler: requirePerm(ctx, "stocktake:start", startStocktakeHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/stocktake/record", Handler: requirePerm(ctx, "stocktake:start", recordStocktakeHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/stocktake/finish", Handler: requirePerm(ctx, "stocktake:start", finishStocktakeHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/stocktake/cancel", Handler: requirePerm(ctx, "stocktake:start", cancelStocktakeHandler(ctx))}, + {Method: http.MethodGet, Path: "/api/stocktake/query", Handler: requirePerm(ctx, "stocktake:view", queryStocktakeHandler(ctx))}, + {Method: http.MethodGet, Path: "/api/stocktake/active", Handler: requirePerm(ctx, "stocktake:view", activeStocktakeHandler(ctx))}, + {Method: http.MethodGet, Path: "/api/stocktake/export", Handler: requirePerm(ctx, "stocktake:view", exportStocktakeHandler(ctx))}, }, ) @@ -182,6 +185,7 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { []rest.Route{ {Method: http.MethodPost, Path: "/api/return-order/confirm", Handler: confirmReturnOrderHandler(ctx)}, {Method: http.MethodGet, Path: "/api/return-order/query", Handler: queryReturnOrdersHandler(ctx)}, + {Method: http.MethodGet, Path: "/api/return-order/suggest-location", Handler: suggestReturnLocationHandler(ctx)}, {Method: http.MethodGet, Path: "/api/return-order/ledger", Handler: queryReturnLedgerHandler(ctx)}, }, ) @@ -204,6 +208,9 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) { {Method: http.MethodGet, Path: "/api/attachments/all", Handler: requirePerm(ctx, "attachment:view", listAllAttachmentsHandler(ctx))}, {Method: http.MethodGet, Path: "/api/attachments/disk-status", Handler: requirePerm(ctx, "attachment:view", diskStatusHandler(ctx))}, {Method: http.MethodPost, Path: "/api/attachments/archive", Handler: requirePerm(ctx, "attachment:manage", archiveAttachmentsHandler(ctx))}, + // 空间清理(需求2):物理清理已删附件归 attachment:manage;日志清理仅管理员 + {Method: http.MethodPost, Path: "/api/attachments/purge-deleted", Handler: requirePerm(ctx, "attachment:manage", purgeDeletedHandler(ctx))}, + {Method: http.MethodPost, Path: "/api/attachments/clean-logs", Handler: requireAdmin(cleanLogsHandler(ctx))}, }, ) diff --git a/bj_power_wms/internal/handler/stock.go b/bj_power_wms/internal/handler/stock.go index 184d888..38714e0 100644 --- a/bj_power_wms/internal/handler/stock.go +++ b/bj_power_wms/internal/handler/stock.go @@ -69,6 +69,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { zoneCode := r.URL.Query().Get("zoneCode") qualityStatus := r.URL.Query().Get("qualityStatus") manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部 + category := r.URL.Query().Get("category") // 1原材料/2半成品/3成品/空=全部(完工检 SN 拾取器按成品过滤) inboundNo := r.URL.Query().Get("inboundNo") startDate := r.URL.Query().Get("startDate") // 默认近3个月(由接口保证) endDate := r.URL.Query().Get("endDate") @@ -82,6 +83,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { ZoneCode: zoneCode, QualityStatus: qualityStatus, ManageMode: manageMode, + Category: category, InboundNo: inboundNo, StartDate: startDate, EndDate: endDate, @@ -703,23 +705,27 @@ func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) { qty int lastCreated int64 } + // 物料品类(itemType)映射:区域汇总「类型」列需按 itemType=4 补「其他」(需求24)。 + mats, _ := ctx.EntClient.Material.Query().All(ctx0()) + itemTypeMap := make(map[string]int, len(mats)) + for _, m := range mats { + itemTypeMap[m.Code] = m.ItemType + } agg := map[string]*aggInfo{} // key: zc|quality|type for _, inv := range rows { zc := inv.ZoneCode if zc == "" { zc = "未分区" } - // 类型标签:批次 / 电气件 / 半成品 / 成品(统一主表收编后按品类细分) - t := "批次" - if inv.ManageMode == 2 { - switch inv.Category { - case invCatSemi: - t = "半成品" - case invCatFinished: - t = "成品" - default: - t = "电气件" - } + // 类型标签口径统一按管理粒度(manageMode),并对 itemType=4「其他」品类单列(需求24): + // itemType=4 → 其他(辅料/工装/试验设备);manageMode=2 → 电气件(SN);manageMode=1 → 结构件(批次)。 + // 不再混入半成品/成品品类(半成品已在上方排除;成品按 manageMode 归电气件/结构件), + // 品类维度在「物料汇总/库存明细」的品类列查看,避免「类型」列口径混乱。 + t := "结构件" + if itemTypeMap[inv.MaterialCode] == invItemOther { + t = "其他" + } else if inv.ManageMode == 2 { + t = "电气件" } key := zc + "|" + inv.QualityStatus + "|" + t g, ok := agg[key] @@ -827,6 +833,18 @@ func lockStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { if item.MaterialCode == "" { continue } + // 盘点冻结守卫(问题2):盘点期间锁定量亦冻结,禁止新锁定。 + if item.Sn != "" { + if fno := frozenByStocktake(ctx, "", item.Sn, ""); fno != "" { + fail(w, http.StatusConflict, "SN "+item.Sn+" 正在盘点单 "+fno+" 锁定中,盘点期间禁止锁定") + return + } + } else if item.BatchNo != "" { + if fno := frozenByStocktake(ctx, item.BatchNo, "", ""); fno != "" { + fail(w, http.StatusConflict, "批次 "+item.BatchNo+" 正在盘点单 "+fno+" 锁定中,盘点期间禁止锁定") + return + } + } // 电气件:锁 SN(原子:仅 在库 且 合格 才能锁) if item.Sn != "" { aff, err := ctx.EntClient.Inventory.Update(). @@ -924,6 +942,21 @@ func unlockStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } + // 盘点冻结守卫(问题2):盘点期间锁定量亦冻结,禁止解锁。 + // unlock 无事务且用 SaveX,故先全量校验再释放,避免部分解锁后中断造成不一致。 + for _, l := range locks { + var fno string + if l.TargetType == "SN" { + fno = frozenByStocktake(ctx, "", l.TargetID, "") + } else { + fno = frozenByStocktake(ctx, l.TargetID, "", "") + } + if fno != "" { + fail(w, http.StatusConflict, "目标 "+l.TargetID+" 正在盘点单 "+fno+" 锁定中,盘点期间禁止解锁") + return + } + } + released := 0 for _, l := range locks { // 还原批次锁定数 / SN 状态 diff --git a/bj_power_wms/internal/handler/stocktake.go b/bj_power_wms/internal/handler/stocktake.go index 618c5ac..082f341 100644 --- a/bj_power_wms/internal/handler/stocktake.go +++ b/bj_power_wms/internal/handler/stocktake.go @@ -2,27 +2,90 @@ package handler import ( "net/http" + "strings" "time" "bj_power_wms/ent" "bj_power_wms/ent/inventory" "bj_power_wms/ent/stocktakeitem" "bj_power_wms/ent/stocktakeorder" + "bj_power_wms/ent/user" "bj_power_wms/internal/svc" "github.com/google/uuid" ) -// startStocktakeHandler 发起盘点:冻结当前库存快照 +// ============================ 盘点15问 最优方案(2026-09-20) ============================ +// 盘点盘的是「库存量」:账面数=快照库存量,实盘数=实际数到,差异=实盘-账面。 +// 核心闭环:①发起(冻结快照 + 锁库存) → ②扫码实盘(SN自动=1/批次填数) → ③完成(处理未盘项 + +// 二次确认写回 + 生成盘点调整单 + 校验 库存量=可用量+锁定量 + 解锁)。 +// 状态机:进行中(已锁定) → 已完成(仅出结论) / 已写回(差异已修正库存) / 已取消。 +// ======================================================================================== + +// hasPermCode 在当前请求上下文里判定登录用户是否拥有指定权限码(支持通配 *)。 +// 用于同一 handler 内按分支做细粒度鉴权(如「完成盘点」写回分支需 stocktake:writeback)。 +func hasPermCode(ctx *svc.ServiceContext, r *http.Request, code string) bool { + username := strings.TrimSpace(r.Header.Get("X-Username")) + if username == "" { + return false + } + u, err := ctx.EntClient.User.Query().Where(user.UsernameEQ(username)).Only(ctx0()) + if err != nil { + return false + } + return hasCode(permissionsForUser(ctx, u), code) +} + +// frozenByStocktake 返回锁定该批次/SN/物料的进行中盘点单号,空串=未冻结。 +// 出库/入库/锁定入口据此拦截,保证盘点期间「库存量/锁定量/可用量」冻结(问题2 最严重)。 +func frozenByStocktake(ctx *svc.ServiceContext, batchNo, snCode, materialCode string) string { + q := ctx.EntClient.Inventory.Query().Where(inventory.StocktakeNoNEQ("")) + switch { + case strings.TrimSpace(snCode) != "": + q = q.Where(inventory.SnCodeEQ(snCode)) + case strings.TrimSpace(batchNo) != "": + q = q.Where(inventory.BatchNoEQ(batchNo)) + case strings.TrimSpace(materialCode) != "": + q = q.Where(inventory.MaterialCodeEQ(materialCode)) + default: + return "" + } + row, err := q.First(ctx0()) + if err != nil || row == nil { + return "" + } + return row.StocktakeNo +} + +// assertNotFrozenTargets 出库/锁定入口前置守卫:批次/SN 若被进行中盘点锁定则拦截(问题2)。 +// 返回 true=未被冻结可继续;返回 false=已写回 409 冲突响应,调用方应直接 return。 +func assertNotFrozenTargets(ctx *svc.ServiceContext, w http.ResponseWriter, batchNo string, snList []string) bool { + if strings.TrimSpace(batchNo) != "" { + if no := frozenByStocktake(ctx, batchNo, "", ""); no != "" { + fail(w, http.StatusConflict, "批次 "+batchNo+" 正在盘点单 "+no+" 锁定中,盘点期间禁止出库/锁定") + return false + } + } + for _, sn := range snList { + if no := frozenByStocktake(ctx, "", sn, ""); no != "" { + fail(w, http.StatusConflict, "SN "+sn+" 正在盘点单 "+no+" 锁定中,盘点期间禁止出库/锁定") + return false + } + } + return true +} + +// startStocktakeHandler 发起盘点:冻结当前库存快照 + 锁定该范围库存(问题2/7)。 // 快照统一来自 inventory 一张表;结构件以批次为单位(bookQty=批次余量),电气件以 SN 为单位(bookQty=1)。 +// 目标数量=本次覆盖的批次/SN 总数(问题15),发起时自动算出。 func startStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { - Operator string `json:"operator"` - Zones []string `json:"zones"` // 可选:限定盘点区域,空=全部 - MaterialCodes []string `json:"materialCodes"` // 可选:限定盘点物料,空=全部 - ManageMode int `json:"manageMode"` // 可选:1结构件/2电气件,0=全部 - QualityStatus string `json:"qualityStatus"` // 可选:限定质量状态,空=全部 + Operator string `json:"operator"` + Zones []string `json:"zones"` // 可选:限定盘点区域,空=全部 + MaterialCodes []string `json:"materialCodes"` // 可选:限定盘点物料,空=全部 + ManageMode int `json:"manageMode"` // 可选:1结构件/2电气件,0=全部 + QualityStatus string `json:"qualityStatus"` // 可选:限定质量状态,空=全部 } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误: "+err.Error()) @@ -49,9 +112,19 @@ func startStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } + if len(rows) == 0 { + fail(w, http.StatusBadRequest, "盘点范围内没有在库库存,无需发起") + return + } + // 冲突守卫:范围内若有库存正被别的进行中盘点单锁定,禁止重复发起(避免两单争抢同一批库存)。 + for _, inv := range rows { + if inv.StocktakeNo != "" { + fail(w, http.StatusConflict, "范围内库存正被盘点单 "+inv.StocktakeNo+" 锁定,请先完成或取消该盘点单") + return + } + } - // 盘点快照是多行写入(每条库存一行明细 + 一张盘点单),整体事务化: - // 要么快照完整落库、要么全回滚,杜绝部分明细写入后失败导致盘点基准不完整。 + // 快照 + 锁库存整体事务化:要么完整落库并冻结、要么全回滚。 tx, err := ctx.EntClient.Tx(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error()) @@ -65,6 +138,7 @@ func startStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { }() count := 0 + ids := make([]int, 0, len(rows)) for _, inv := range rows { tt := "BATCH" tid := inv.BatchNo @@ -92,11 +166,24 @@ func startStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, "创建盘点明细失败: "+e.Error()) return } + ids = append(ids, inv.ID) count++ } + // 锁库存(问题2):给快照覆盖的库存行打盘点单号标记,出库/入库/锁定入口据此拦截。 + if len(ids) > 0 { + if _, e := tx.Inventory.Update(). + Where(inventory.IDIn(ids...)). + SetStocktakeNo(no). + Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "锁定库存失败: "+e.Error()) + return + } + } + rec, err := tx.StocktakeOrder.Create(). SetStocktakeNo(no). + SetStatus("进行中"). SetNillableOperator(strPtr(req.Operator)). SetTotalTargets(count). Save(ctx0()) @@ -111,18 +198,19 @@ func startStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { committed = true ctx.EventLog.Write(ctx0(), "stocktake.start", r.Header.Get("X-Username"), "stocktake", rec.StocktakeNo, - "发起盘点 "+rec.StocktakeNo, map[string]any{"totalTargets": count, "zones": req.Zones, + "发起盘点 "+rec.StocktakeNo+"(已锁定库存)", map[string]any{"totalTargets": count, "zones": req.Zones, "materialCodes": req.MaterialCodes, "manageMode": req.ManageMode}) - ok(w, map[string]any{"stocktakeNo": rec.StocktakeNo, "totalTargets": count}) + ok(w, map[string]any{"stocktakeNo": rec.StocktakeNo, "totalTargets": count, "status": rec.Status}) } } -// recordStocktakeHandler 扫码盘点记录:查快照并记录实盘数与差异 +// recordStocktakeHandler 扫码盘点记录:查快照并记录实盘数与差异(问题6)。 +// 电气件(SN)逐件扫码,实盘数强制=1(不需用户填);结构件(BATCH)按用户填入的数量。 func recordStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { StocktakeNo string `json:"stocktakeNo"` - TargetType string `json:"targetType"` // BATCH/SN,可空则自动判断 + TargetType string `json:"targetType"` // BATCH/SN,可空则按快照判断 TargetId string `json:"targetId"` ScanQty int `json:"scanQty"` } @@ -142,7 +230,7 @@ func recordStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } if order.Status != "进行中" { - fail(w, http.StatusConflict, "盘点单已完成,禁止录入") + fail(w, http.StatusConflict, "盘点单状态为「"+order.Status+"」,禁止录入") return } @@ -157,20 +245,27 @@ func recordStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { } qty := req.ScanQty - if qty < 0 { + // SN 逐件扫码,实盘数恒为 1(问题6);批次由用户填入非负整数。 + if item.TargetType == "SN" { + qty = 1 + } else if qty < 0 { fail(w, http.StatusBadRequest, "scanQty 不能为负数") return } diff := qty - item.BookQty + nowTs := time.Now().Unix() ctx.EntClient.StocktakeItem.UpdateOneID(item.ID). SetScannedQty(qty). SetDiffQty(diff). SetCounted(true). - SetUpdatedAt(time.Now().Unix()). + SetNillableCountedBy(strPtr(r.Header.Get("X-Username"))). + SetCountedAt(nowTs). + SetUpdatedAt(nowTs). ExecX(ctx0()) ok(w, map[string]any{ "targetId": req.TargetId, + "targetType": item.TargetType, "bookQty": item.BookQty, "scannedQty": qty, "diffQty": diff, @@ -183,16 +278,21 @@ func recordStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { } } -// finishStocktakeHandler 完成盘点:统计差异、按需按"增量"修正库存 -// 调整原则(问题2):绝不覆盖最终值,只按差异增减——quantity += diffQty。 -// - 结构件:批次余量 +diff(盘盈)/-diff(盘亏) -// - 电气件:差异为 -1(账面1实盘0)按报废处理;其余差异仅记录 -// 整个调整包在事务内,保证盘点修正原子性。 +// finishStocktakeHandler 完成盘点:处理未盘项 → 统计差异 → 按需写回(生成盘点调整单) → 解锁库存。 +// +// 未盘项处理(问题5):uncountedMode=keep 按账面保留(差异0) / zero 盘亏为0(实盘0,差异=-账面)。 +// 差异写回(问题4/11):只按差异增减「库存量」,锁定量不动,可用量自动重算; +// +// 写回后强制校验 库存量 >= 锁定量(即 可用量>=0,等式 库存量=可用量+锁定量 成立),否则整体回滚。 +// 写回走盘点调整单号 adjust_no,调整前/调整量/调整后全部入操作日志,可追溯谁在何时改了什么。 +// +// 权限(问题12):写回需 stocktake:writeback(主管以上),仅出结论无需写回权限。 func finishStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { - StocktakeNo string `json:"stocktakeNo"` - Adjust bool `json:"adjust"` // true=把差异写回库存 + StocktakeNo string `json:"stocktakeNo"` + Adjust bool `json:"adjust"` // true=把差异写回库存 + UncountedMode string `json:"uncountedMode"` // keep=按账面保留 / zero=盘亏为0 } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") @@ -202,12 +302,26 @@ func finishStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "stocktakeNo 必填") return } + uncountedMode := req.UncountedMode + if uncountedMode != "zero" { + uncountedMode = "keep" + } + username := r.Header.Get("X-Username") + if req.Adjust && !hasPermCode(ctx, r, "stocktake:writeback") { + fail(w, http.StatusForbidden, "差异写回需主管以上权限(stocktake:writeback)") + return + } + order, err := ctx.EntClient.StocktakeOrder.Query(). Where(stocktakeorder.StocktakeNoEQ(req.StocktakeNo)).Only(ctx0()) if err != nil { fail(w, http.StatusNotFound, "盘点单不存在") return } + if order.Status != "进行中" { + fail(w, http.StatusConflict, "盘点单状态为「"+order.Status+"」,不能重复完成") + return + } items, err := ctx.EntClient.StocktakeItem.Query(). Where(stocktakeitem.StocktakeNoEQ(req.StocktakeNo)).All(ctx0()) if err != nil { @@ -227,86 +341,233 @@ func finishStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { LayerNo string `json:"layerNo"` PositionNo string `json:"positionNo"` } - diffs := []diffRow{} - counted := 0 - uncounted := 0 + // 调整单审计行:调整前/调整量/调整后(问题4) + type adjustRow struct { + TargetId string `json:"targetId"` + Type string `json:"type"` + Before int `json:"before"` + Delta int `json:"delta"` + After int `json:"after"` + Note string `json:"note"` + } - // 需要调整时开启事务 - var tx *ent.Tx - if req.Adjust { - tx, err = ctx.EntClient.Tx(ctx0()) - if err != nil { - fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error()) - return - } + tx, err := ctx.EntClient.Tx(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error()) + return } committed := false defer func() { - if tx != nil && !committed { + if !committed { _ = tx.Rollback() } }() + now := time.Now().Unix() + adjustNo := "ADJ" + time.Now().Format("20060102150405") + uuid.NewString()[:4] + diffs := []diffRow{} + adjusts := []adjustRow{} + counted, uncounted, diffCount := 0, 0, 0 + for _, it := range items { - if !it.Counted { - uncounted++ - continue + book := it.BookQty + scanned := it.ScannedQty + diff := it.DiffQty + isCounted := it.Counted + + // 未盘项处理(问题5) + if !isCounted { + if uncountedMode == "zero" { + // 盘亏为0:实盘0,差异=-账面,落库以便追溯 + scanned = 0 + diff = -book + isCounted = true + if _, e := tx.StocktakeItem.UpdateOneID(it.ID). + SetScannedQty(0).SetDiffQty(diff).SetCounted(true). + SetNillableCountedBy(strPtr(username)).SetCountedAt(now).SetUpdatedAt(now). + Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "处理未盘项失败: "+e.Error()) + return + } + } else { + // 按账面保留:不计差异,仍标记未盘 + uncounted++ + continue + } } counted++ - if it.DiffQty != 0 { - diffs = append(diffs, diffRow{it.TargetID, it.TargetType, it.BookQty, it.ScannedQty, it.DiffQty, - it.ZoneCode, it.ShelfNo, it.LayerNo, it.PositionNo}) - if req.Adjust { - if it.TargetType == "BATCH" { - // 增量调整:quantity += diffQty(不覆盖终值) - if _, e := tx.Inventory.Update(). - Where(inventory.ManageModeEQ(1), inventory.BatchNoEQ(it.TargetID)). - AddQuantity(it.DiffQty). - Save(ctx0()); e != nil { - fail(w, http.StatusInternalServerError, "调整批次失败: "+e.Error()) - return - } - } else if it.TargetType == "SN" && it.DiffQty == -1 { - // 账面1实盘0 → 报废 - if _, e := tx.Inventory.Update(). - Where(inventory.ManageModeEQ(2), inventory.SnCodeEQ(it.TargetID), inventory.StatusIn("在库", "锁定")). - SetStatus("报废").SetLockedQty(0). - Save(ctx0()); e != nil { - fail(w, http.StatusInternalServerError, "报废 SN 失败: "+e.Error()) - return - } - } + if diff == 0 { + continue + } + diffCount++ + diffs = append(diffs, diffRow{it.TargetID, it.TargetType, book, scanned, diff, + it.ZoneCode, it.ShelfNo, it.LayerNo, it.PositionNo}) + + if !req.Adjust { + continue + } + // 写回:只改库存量,锁定量不动,可用量随之重算(问题11) + if it.TargetType == "BATCH" { + row, e := tx.Inventory.Query(). + Where(inventory.ManageModeEQ(1), inventory.BatchNoEQ(it.TargetID)).Only(ctx0()) + if e != nil || row == nil { + continue + } + after := row.Quantity + diff + if after < 0 { + after = 0 + } + if after < row.LockedQty { + fail(w, http.StatusConflict, + "批次 "+it.TargetID+" 盘亏后库存量("+itoa(after)+")将小于锁定量("+itoa(row.LockedQty)+"),违反 库存量=可用量+锁定量,请先解除锁定再写回") + return + } + if _, e = tx.Inventory.UpdateOneID(row.ID). + SetQuantity(after).SetUpdatedAt(now).Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "调整批次失败: "+e.Error()) + return + } + adjusts = append(adjusts, adjustRow{it.TargetID, "BATCH", row.Quantity, diff, after, "批次余量调整"}) + } else if it.TargetType == "SN" && diff < 0 { + // 账面1实盘0 → 报废(移出在库,库存量随之减少) + aff, e := tx.Inventory.Update(). + Where(inventory.ManageModeEQ(2), inventory.SnCodeEQ(it.TargetID), inventory.StatusIn("在库", "锁定")). + SetStatus("报废").SetLockedQty(0).SetUpdatedAt(now). + Save(ctx0()) + if e != nil { + fail(w, http.StatusInternalServerError, "报废 SN 失败: "+e.Error()) + return + } + if aff > 0 { + adjusts = append(adjusts, adjustRow{it.TargetID, "SN", 1, -1, 0, "盘亏报废"}) } } } - if req.Adjust { - if err = tx.Commit(); err != nil { - fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error()) - return - } - committed = true + // 解锁库存(问题2/7):完成即释放冻结,业务恢复可出入库/锁定。 + if _, e := tx.Inventory.Update(). + Where(inventory.StocktakeNoEQ(req.StocktakeNo)). + SetStocktakeNo(""). + Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "解锁库存失败: "+e.Error()) + return } - ctx.EntClient.StocktakeOrder.UpdateOneID(order.ID). - SetStatus("已完成"). - SetFinishedAt(time.Now().Unix()). - ExecX(ctx0()) + upd := tx.StocktakeOrder.UpdateOneID(order.ID). + SetFinishedAt(now). + SetDiffCount(diffCount). + SetUncountedMode(uncountedMode) + if req.Adjust { + upd = upd.SetStatus("已写回").SetAdjusted(true).SetAdjustNo(adjustNo). + SetWrittenAt(now).SetNillableWrittenBy(strPtr(username)) + } else { + upd = upd.SetStatus("已完成") + } + if _, e := upd.Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "更新盘点单失败: "+e.Error()) + return + } - ctx.EventLog.Write(ctx0(), "stocktake.finish", r.Header.Get("X-Username"), "stocktake", req.StocktakeNo, - "完成盘点 "+req.StocktakeNo, map[string]any{ - "adjusted": req.Adjust, "counted": counted, "uncounted": uncounted, "diffs": diffs}) + if err = tx.Commit(); err != nil { + fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error()) + return + } + committed = true + + newStatus := "已完成" + if req.Adjust { + newStatus = "已写回" + } + ctx.EventLog.Write(ctx0(), "stocktake.finish", username, "stocktake", req.StocktakeNo, + "完成盘点 "+req.StocktakeNo+"("+newStatus+")", map[string]any{ + "adjusted": req.Adjust, "adjustNo": adjustNo, "counted": counted, + "uncounted": uncounted, "diffCount": diffCount, "uncountedMode": uncountedMode, + "diffs": diffs, "adjustments": adjusts}) ok(w, map[string]any{ - "stocktakeNo": req.StocktakeNo, - "adjusted": req.Adjust, - "counted": counted, - "uncounted": uncounted, - "diffs": diffs, + "stocktakeNo": req.StocktakeNo, + "status": newStatus, + "adjusted": req.Adjust, + "adjustNo": adjustNo, + "counted": counted, + "uncounted": uncounted, + "diffCount": diffCount, + "uncountedMode": uncountedMode, + "diffs": diffs, + "adjustments": adjusts, }) } } -// queryStocktakeHandler 盘点结果查询 +// cancelStocktakeHandler 取消盘点(问题9):解锁库存、状态=已取消、记录原因。 +// 仅「进行中」可取消;已写回/已完成为终态不可撤。权限:发起人以上(stocktake:start)。 +func cancelStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req struct { + StocktakeNo string `json:"stocktakeNo"` + Reason string `json:"reason"` + } + if err := parseJSON(r, &req); err != nil { + fail(w, http.StatusBadRequest, "参数错误") + return + } + if req.StocktakeNo == "" { + fail(w, http.StatusBadRequest, "stocktakeNo 必填") + return + } + order, err := ctx.EntClient.StocktakeOrder.Query(). + Where(stocktakeorder.StocktakeNoEQ(req.StocktakeNo)).Only(ctx0()) + if err != nil { + fail(w, http.StatusNotFound, "盘点单不存在") + return + } + if order.Status != "进行中" { + fail(w, http.StatusConflict, "盘点单状态为「"+order.Status+"」,仅进行中可取消") + return + } + + tx, err := ctx.EntClient.Tx(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, "开启事务失败: "+err.Error()) + return + } + committed := false + defer func() { + if !committed { + _ = tx.Rollback() + } + }() + + // 解锁库存 + if _, e := tx.Inventory.Update(). + Where(inventory.StocktakeNoEQ(req.StocktakeNo)). + SetStocktakeNo(""). + Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "解锁库存失败: "+e.Error()) + return + } + if _, e := tx.StocktakeOrder.UpdateOneID(order.ID). + SetStatus("已取消"). + SetNillableCancelReason(strPtr(req.Reason)). + SetFinishedAt(time.Now().Unix()). + Save(ctx0()); e != nil { + fail(w, http.StatusInternalServerError, "更新盘点单失败: "+e.Error()) + return + } + if err = tx.Commit(); err != nil { + fail(w, http.StatusInternalServerError, "提交事务失败: "+err.Error()) + return + } + committed = true + + ctx.EventLog.Write(ctx0(), "stocktake.cancel", r.Header.Get("X-Username"), "stocktake", req.StocktakeNo, + "取消盘点 "+req.StocktakeNo+"(已解锁库存)", map[string]any{"reason": req.Reason}) + ok(w, map[string]any{"stocktakeNo": req.StocktakeNo, "status": "已取消"}) + } +} + +// queryStocktakeHandler 盘点结果查询: +// - 无 stocktakeNo:返回历史盘点单列表(含状态/差异项数/是否写回/写回时间/取消原因,问题13),支持按状态筛选。 +// - 有 stocktakeNo:返回该单明细(分页)。 func queryStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { no := r.URL.Query().Get("stocktakeNo") @@ -314,8 +575,11 @@ func queryStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { pageSize := atoi(r.URL.Query().Get("pageSize"), 50) if no == "" { - orders, _ := ctx.EntClient.StocktakeOrder.Query(). - Order(ent.Desc("created_at"), ent.Desc("id")).Limit(100).All(ctx0()) + q := ctx.EntClient.StocktakeOrder.Query() + if st := strings.TrimSpace(r.URL.Query().Get("status")); st != "" { + q = q.Where(stocktakeorder.StatusEQ(st)) + } + orders, _ := q.Order(ent.Desc("created_at"), ent.Desc("id")).Limit(100).All(ctx0()) ok(w, map[string]any{"list": orders}) return } @@ -346,3 +610,75 @@ func queryStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { }) } } + +// activeStocktakeHandler GET /api/stocktake/active:返回当前进行中的盘点单(问题7/8)。 +// 前端进入页面/发起后据此自动带入盘点单号,扫码区与完成按钮自动关联当前进行中单,无需手动粘贴。 +func activeStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + order, err := ctx.EntClient.StocktakeOrder.Query(). + Where(stocktakeorder.StatusEQ("进行中")). + Order(ent.Desc("created_at"), ent.Desc("id")).First(ctx0()) + if err != nil || order == nil { + ok(w, map[string]any{"active": nil}) + return + } + counted, _ := ctx.EntClient.StocktakeItem.Query(). + Where(stocktakeitem.StocktakeNoEQ(order.StocktakeNo), stocktakeitem.CountedEQ(true)).Count(ctx0()) + ok(w, map[string]any{ + "active": map[string]any{ + "stocktakeNo": order.StocktakeNo, + "totalTargets": order.TotalTargets, + "counted": counted, + "operator": order.Operator, + "createdAt": order.CreatedAt, + }, + }) + } +} + +// exportStocktakeHandler GET /api/stocktake/export?stocktakeNo= 导出盘点明细(问题14)。 +// 列:盘点单号/类型/批次或SN/物料编码/货位/账面数/实盘数/差异/是否已盘/发起人/发起时间/完成时间。 +func exportStocktakeHandler(ctx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + no := strings.TrimSpace(r.URL.Query().Get("stocktakeNo")) + if no == "" { + fail(w, http.StatusBadRequest, "stocktakeNo 必填") + return + } + order, err := ctx.EntClient.StocktakeOrder.Query(). + Where(stocktakeorder.StocktakeNoEQ(no)).First(ctx0()) + if err != nil { + fail(w, http.StatusNotFound, "盘点单不存在: "+no) + return + } + items, err := ctx.EntClient.StocktakeItem.Query(). + Where(stocktakeitem.StocktakeNoEQ(no)). + Order(ent.Asc("id")).All(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + headers := []string{"盘点单号", "类型", "批次/SN", "物料编码", "货位", "账面数", "实盘数", "差异", "是否已盘", "实盘人", "实盘时间", "发起人", "发起时间"} + matrix := make([][]any, 0, len(items)) + for _, it := range items { + typ := "批次" + if it.TargetType == "SN" { + typ = "SN" + } + scanned := "-" + if it.Counted { + scanned = itoa(it.ScannedQty) + } + counted := "否" + if it.Counted { + counted = "是" + } + matrix = append(matrix, []any{ + no, typ, it.TargetID, it.MaterialCode, locLabel(it.ZoneCode, it.ShelfNo, it.LayerNo, it.PositionNo), + itoa(it.BookQty), scanned, itoa(it.DiffQty), counted, + it.CountedBy, unixFmt(it.CountedAt), order.Operator, unixFmt(order.CreatedAt), + }) + } + sendExcel(w, xlsxFilename("盘点明细_"+no), headers, matrix) + } +} diff --git a/bj_power_wms/internal/handler/zone.go b/bj_power_wms/internal/handler/zone.go index ee417b1..f2a6666 100644 --- a/bj_power_wms/internal/handler/zone.go +++ b/bj_power_wms/internal/handler/zone.go @@ -48,6 +48,9 @@ type zoneNodeVO struct { Status string `json:"status"` Description string `json:"description"` CreatedAt int64 `json:"createdAt"` + // StockQty 本级实时库存总量(需求E/需求7):每次查询实时聚合 inventories(quantity>0 且非出库/报废),不缓存。 + // level1=区域合计、level2=货架合计、level3=层合计、level4=该位置合计。 + StockQty int `json:"stockQty"` } func toZoneNodeVO(z *ent.Zone) zoneNodeVO { @@ -59,7 +62,7 @@ func toZoneNodeVO(z *ent.Zone) zoneNodeVO { } // listZonesHandler 按层级列出节点(4 个 Tab 各自独立分页 + 按上级筛选) -// 参数:level(必填1~4)、parentCode、zoneCode、shelfNo、layerNo、keyword、status、page、pageSize +// 参数:level(必填1~4)、parentCode、zoneCode、shelfNo、layerNo、code、name、status、page、pageSize func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { level := atoi(r.URL.Query().Get("level"), 0) @@ -92,8 +95,12 @@ func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { if ly := strings.TrimSpace(r.URL.Query().Get("layerNo")); ly != "" { q = q.Where(zone.LayerNoEQ(ly)) } - if kw := strings.TrimSpace(r.URL.Query().Get("keyword")); kw != "" { - q = q.Where(zone.Or(zone.CodeContainsFold(kw), zone.NameContainsFold(kw))) + // 列表页禁止关键字混搜:本节点编码、名称各自独立模糊筛选(需求N) + if code := strings.TrimSpace(r.URL.Query().Get("code")); code != "" { + q = q.Where(zone.CodeContainsFold(code)) + } + if name := strings.TrimSpace(r.URL.Query().Get("name")); name != "" { + q = q.Where(zone.NameContainsFold(name)) } if st := strings.TrimSpace(r.URL.Query().Get("status")); st != "" { q = q.Where(zone.StatusEQ(st)) @@ -111,13 +118,50 @@ func listZonesHandler(ctx *svc.ServiceContext) http.HandlerFunc { return } list := make([]zoneNodeVO, 0, len(rows)) + byZone, byShelf, byLayer, byPos := zoneStockMaps(ctx) for _, z := range rows { - list = append(list, toZoneNodeVO(z)) + vo := toZoneNodeVO(z) + zc := strings.ToLower(strings.TrimSpace(z.ZoneCode)) + sh := strings.ToLower(strings.TrimSpace(z.ShelfNo)) + ly := strings.TrimSpace(z.LayerNo) + switch z.Level { + case zoneLevelRegion: + vo.StockQty = byZone[zc] + case zoneLevelShelf: + vo.StockQty = byShelf[zc+"|"+sh] + case zoneLevelLayer: + vo.StockQty = byLayer[zc+"|"+sh+"|"+ly] + case zoneLevelPos: + vo.StockQty = byPos[locKey(z.ZoneCode, z.ShelfNo, z.LayerNo, z.PositionNo)] + } + list = append(list, vo) } ok(w, map[string]any{"total": total, "list": list, "page": page, "pageSize": pageSize}) } } +// zoneStockMaps 实时聚合各级库存总量(需求E):一次扫描在库 inventories(quantity>0 且 status 非出库/报废), +// 分别按 区域 / 区域+货架 / 区域+货架+层 / 完整四级货位 累加,供四级列表展示实时库存。 +// 键归一与 locKey 一致:区域/货架小写,层/位置原样 trim。 +func zoneStockMaps(ctx *svc.ServiceContext) (byZone, byShelf, byLayer, byPos map[string]int) { + byZone, byShelf, byLayer, byPos = map[string]int{}, map[string]int{}, map[string]int{}, map[string]int{} + invs, err := ctx.EntClient.Inventory.Query(). + Where(inventory.QuantityGT(0), inventory.StatusNotIn("出库", "报废")).All(ctx0()) + if err != nil { + return + } + for _, iv := range invs { + zc := strings.ToLower(strings.TrimSpace(iv.ZoneCode)) + sh := strings.ToLower(strings.TrimSpace(iv.ShelfNo)) + ly := strings.TrimSpace(iv.LayerNo) + byZone[zc] += iv.Quantity + byShelf[zc+"|"+sh] += iv.Quantity + byLayer[zc+"|"+sh+"|"+ly] += iv.Quantity + byPos[locKey(iv.ZoneCode, iv.ShelfNo, iv.LayerNo, iv.PositionNo)] += iv.Quantity + } + return +} + // zoneNodeReq 新建层级节点请求 type zoneNodeReq struct { Level int `json:"level"` @@ -220,11 +264,10 @@ func createZoneNodeHandler(ctx *svc.ServiceContext) http.HandlerFunc { SetLevel(req.Level).SetCode(req.Code).SetParentCode(parentCode). SetZoneCode(zoneCode).SetNillableShelfNo(strPtr(shelfNo)). SetNillableLayerNo(strPtr(layerNo)).SetNillablePositionNo(strPtr(req.Code)). - SetStatus("启用").SetNillableDescription(strPtr(req.Remark)) + SetStatus("启用").SetNillableDescription(strPtr(req.Remark)). + SetName(req.Name) // 四级均可有名称(需求E) if req.Level == 1 { - create.SetZoneName(req.Name) // 区域名称 - } else if req.Level == 4 { - create.SetName(req.Name) // 位置名称 + create.SetZoneName(req.Name) // 区域名称冗余,兼容 stock.go 区域名映射 } z, err := create.Save(ctx0()) if err != nil { @@ -254,7 +297,8 @@ type zoneNodeUpdateReq struct { Status string `json:"status"` } -// updateZoneNodeHandler 编辑节点(区域改名称/说明/停用;位置改位置名称/停用;其余层级仅改停用) +// updateZoneNodeHandler 编辑节点(四级均可改名称/备注/启用停用;区域额外同步 zone_name 冗余)。 +// 需求E:不删只停用,无删除入口;名称与备注全级可维护。 func updateZoneNodeHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req zoneNodeUpdateReq @@ -279,13 +323,11 @@ func updateZoneNodeHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusBadRequest, "状态取值非法") return } - upd := ctx.EntClient.Zone.UpdateOneID(req.ID).SetStatus(status) + upd := ctx.EntClient.Zone.UpdateOneID(req.ID).SetStatus(status). + SetName(strings.TrimSpace(req.Name)). + SetNillableDescription(strPtr(strings.TrimSpace(req.Remark))) if z.Level == 1 { - upd.SetZoneName(strings.TrimSpace(req.Name)) - upd.SetNillableDescription(strPtr(strings.TrimSpace(req.Remark))) - } - if z.Level == 4 { - upd.SetName(strings.TrimSpace(req.Name)) + upd.SetZoneName(strings.TrimSpace(req.Name)) // 区域名称冗余同步 } if _, err := upd.Save(ctx0()); err != nil { fail(w, http.StatusInternalServerError, err.Error()) diff --git a/bj_power_wms/schema/inventory.go b/bj_power_wms/schema/inventory.go index 3ad7b18..611eb21 100644 --- a/bj_power_wms/schema/inventory.go +++ b/bj_power_wms/schema/inventory.go @@ -95,6 +95,9 @@ func (Inventory) Fields() []ent.Field { field.Int64("recorded_at").Optional().Comment("记录日期(unix 秒)"), // 半成品/成品出库原因(客户诉求 L224:包装/继续生产/检验试验/其他) field.String("outbound_reason").Optional().Comment("出库原因(半成品/成品出库用)"), + // 盘点冻结标记(盘点15问 问题2 最严重):非空=本行正被该盘点单锁定,盘点期间禁止出库/入库/锁定, + // 保证「库存量/锁定量/可用量」在盘点期间冻结,差异只反映盘盈盘亏而非业务变动。发起时置盘点单号,完成/取消时清空。 + field.String("stocktake_no").Default("").MaxLen(64).Comment("盘点冻结标记(进行中盘点单号;空=未冻结)"), // 备注。 field.String("remark").Optional().Comment("备注"), diff --git a/bj_power_wms/schema/stocktake.go b/bj_power_wms/schema/stocktake.go index 76d90c8..cf9eec2 100644 --- a/bj_power_wms/schema/stocktake.go +++ b/bj_power_wms/schema/stocktake.go @@ -14,9 +14,21 @@ type StocktakeOrder struct { func (StocktakeOrder) Fields() []ent.Field { return []ent.Field{ field.String("stocktake_no").Unique().Comment("盘点单号"), - field.String("status").Default("进行中").Comment("进行中/已完成"), + // 状态机(盘点15问 问题10):进行中(已锁定库存) → 已完成(仅出结论) / 已写回(差异已修正库存) / 已取消 + field.String("status").Default("进行中").Comment("进行中/已完成/已写回/已取消"), field.String("operator").Optional().Comment("发起人"), - field.Int("total_targets").Default(0).Comment("快照目标数"), + field.Int("total_targets").Default(0).Comment("快照目标数(本次盘点覆盖的批次/SN总数)"), + // 差异项数(问题13):完成时统计 diff!=0 的明细行数,便于历史列表一眼看出盘盈盘亏规模。 + field.Int("diff_count").Default(0).Comment("差异项数(diff!=0 的明细数)"), + // 是否写回 + 调整单号 + 写回时间/操作人(问题4/13):写回走盘点调整单,留痕可追溯。 + field.Bool("adjusted").Default(false).Comment("差异是否已写回库存"), + field.String("adjust_no").Optional().Comment("盘点调整单号(写回时生成)"), + field.Int64("written_at").Optional().Comment("写回时间(unix 秒)"), + field.String("written_by").Optional().Comment("写回操作人"), + // 未盘项处理方式(问题5):keep=按账面保留 / zero=盘亏为0 + field.String("uncounted_mode").Optional().Comment("未盘项处理 keep=按账面保留/zero=盘亏为0"), + // 取消原因(问题9):取消盘点时记录,便于追溯。 + field.String("cancel_reason").Optional().Comment("取消原因"), field.Int64("created_at").DefaultFunc(nowUnix), field.Int64("finished_at").Optional().Comment("完成时间"), } @@ -48,6 +60,9 @@ func (StocktakeItem) Fields() []ent.Field { field.Int("scanned_qty").Default(-1).Comment("实盘数量,-1=未盘"), field.Int("diff_qty").Default(0).Comment("差异 = 实盘 - 账面"), field.Bool("counted").Default(false).Comment("是否已扫码"), + // 实盘人/实盘时间(问题14):导出盘点明细时「操作人」取实盘扫码人,而非发起人。 + field.String("counted_by").Optional().Comment("实盘人(扫码录入者)"), + field.Int64("counted_at").Optional().Comment("实盘时间(unix 秒)"), field.Int64("updated_at").DefaultFunc(nowUnix), } } diff --git a/bj_power_wms/web/static/index.html b/bj_power_wms/web/static/index.html index fa97878..539077d 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 07a5917..881e1b6 100644 --- a/临时.md +++ b/临时.md @@ -1,335 +1,952 @@ -# 出库与配送闭环 · 最终方案(2026-09-19 定稿) +参考实际项目代码、根目录 的 md 文档、客户提问"C:\Users\ThinkPad\Desktop\软件系统问题-hd.doc" 、 网上搜索行业实际案例和标准 ,其中客户提问里面的“答” 是ai生成的,仅供参考。 -> 本文为**唯一版本**,覆盖此前所有草稿;外部建议(DeepSeek 两轮)**仅作参考**,逐条给了采纳/否决理由。 -> 全部结论**基于真实代码逐行核实**,位置标注 `文件:行`。 -> **本文是方案,未改任何代码。全部改动需授权后执行。** +全文要求:不要改代码,出计划,要有详细的 更改方案,不确定问题,一个一个让我确认,再出 计划。 + + +1、 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1、附件中心----操作----查看,去除查看。 +2、附件中心--- 帮助文档,添加清楚建议和说明,包括如何删除附件,有什么影响。个人电脑如何清楚,比如 回收站 下载中心 ,等。 本项目日志文件路径 和占用大小,等等,清除按钮,允许清除1个月前的所有日志文件和文件夹。 把空间清除,归拢到一起,方便查看和清除。 本清除页面,不可清除在使用的 工艺流程 、不可清除 1年内 的 检测单据。 清除时,一项一项清除 (比如: 导出、导入、上传的附件、检查单 不能一起清除一项一项确认,提示影响范围,点击确认,再删除)。 罗列相关内容,给我方案,做出指示以后再 开干 +3、wms账号管理,页面空白了。查原因并修复 +4、wms来料检验,缺少,其他入库的一个检查,要和入库一一对应。 +5、接驳台维护,是给agv使用的。 和 实际接驳台一一对应,所以,不能新增、修改,只有要给查看功能。 种子数据相关sql ,默认初始化接驳台。 更新你的记忆,永远不允许出现 “关键字” 混合搜索,为什么总是这样设计呢?说了很多遍了。 +6、所有的新建、编辑、保存,都要有 二次确认弹框。当前项目,很多地方还没有这个弹窗 +7、区域维护--- 货架、层、位置号,都要有名称、备注说明。并显示库存实时库存总数量。 这4级,一旦新建,不允许删除,只能停用。 搜索条件,状态 默认只搜索 启用状态的。 +8、区域维护--- 位置号,列表 显示全区域 编码,区域编码、名称,货架编码、货架名称,层编码、层名称 +9、wms物料档案, 新增应该参考编辑页面,允许上传附件。并且,上面有要求:所有 新增、新建、编辑、上传、保存、导入,等涉及到更改数据 的操作,都要有 二次 确认框,等用户确认后再继续。弹出框明确描述 影响范围。 +10、MES 不告诉 WMS “这个工单总共要多少料”,导致 WMS 没法出库。请出方案,并修复。 +11、wms完工检验,等,缺少帮助文档。 并且 归属 字段 请和其他 菜单功能保持一致,参考客户是怎么要求的 。这个文档"C:\Users\ThinkPad\Desktop\软件系统问题-hd.doc" 里面的 答,是ai说的,不具备参考价值,上面的问题,是 用户提问的,是真实需求 +12、当前项目无法闭环,请联网搜索 实操项目经验 , 产品级项目 方案,吸取有点,整合到我的项目,让我的项目能闭环。 给我详细的方案,不要改代码,等我决策再动手。 +13、来料检查,也是个半成品,请重新设计。 并且,如果tab按物料统计是实时 聚合出来的数据,那也应该先 选择物料,再点击 统计按钮。你设计设计吧,太烂了。 +14、退库确认,能闭环吗?库存怎么操作、回到原 仓库位置号?能不能手动选择。退库退的是 剩余物料or有问题的物料。退库sn编号,应该一个一个扫码录入,非 电子气件呢 ? 认真考虑这些问题,一个也不可以遗漏、忽略 +15、AGV配送 ? 海康agv相关文档,能提供这些数据吗? 这个页面暂放,等真实对接agv时再改。 +16、wms工单备料台账,搜索网页行业经验,看看是否符合标准,能否闭环。 +17、用户提问中“增加科技项目出库类别” 。 实际 出库管理---通用出库,没看到这个。我理解:出库是无法和入库的批次号、合同号对应的, 所以出库的 出库 类别不需要和任何 地方联动,仅仅是个 标签记录。你直接实现它就可以了,列表、出库单 都显示清楚即可。 类似入库,让他必填编号。你动动脑子吧。 +18、客户提问:通用出库:先区分一下类别(摁钮点选,退料、样品、报废、发货、其它“手动输入”),然后分别填写归属(工单号or科技项目号)、类型(电气件、结构件、其它)、名称(图号)、批次(非强制)/SN(强制)(能否根据类型选择自动更换?) 。 我的理解: 其他需要手动输入,说明 这些类型来源于数据库。 类型里面,还有一个 其他,你他妈也没给客户实现。你是瞎子吗? 批次号,根据 是 物料类型觉得。物料类型,物料选择里面就有,你还让客户手动输入,你有病吗? 物料 选择框,全项目统一组件,并新增下拉框显示字段,当前是 编码、图号 ,改为 编码、图号、类型、品类 +问题18,不局限于某个 菜单,全项目 审查 +19、 出库管理 ,同理,见 问题18 +20、wms出库管理,目标工位,可以选择,不应该让输入。 并且客户明确表示 出库记录 要有 工单号 、 工位号 +21、来料检验,是根据 入库管理 检查吧。当前无法闭环,并且 又要客户输入这么多参数,你是个傻逼吗。网上搜索 互联网 标准吧。 +22、来料检验----归属--- 科技项目号、工单号、其他 ,当前显示的是 无,不对应该是其他吧,看客户要求。 这里的工单号,和 科技项目号 并列,不是跟mes的工单号 一个维度,也不是一个东西,改名叫工单编号。 +23、客户问题:实际使用过程中不合格,转为退货或换修,系统如何修改和调整 。 文档中的回复,全是乱编,根本没有闭环,没有这些按钮功能。完全胡扯!!! 定义好,来料检、过程检、完工检——分工与边界,修复完成以后,再把客户问题闭环。 +24、wms库存查询, 质量状态? 你是有病吗。整个区域为什么有个 是否合格。类型 也不对。 区域现在是4级,所以 都不对了。 如果没有好的方案or没必要显示这个tab,就去除此tab +25、数量定义一定要清楚,不能有任何差异,请一个一个审核,涉及到 数量核算的地方,参考: +26、客户问:物料汇总和库存明细有什么差别? 自我审核清楚,在帮助文档介绍清楚。 +27、库存盘点。 全功能规划 + + + + + + + + + + + + + + + + +下面都是ai生成的,仅供参考: + +过程检验、完工检查,等等 + + +我理解是完工检查,因为,左边菜单,有来料检查。如果是完工检查,就没有 目标类型 区分。你再捋捋,当前系统完全无法闭环。你能分析我本地项目代码吗 + +并且 ,按物料统计,有个统计按钮,完全不合时宜。整个 完工检查,是个半成品,请重新设计,必须闭环。 + +允许联网搜索 标准案例 实现情况 + + + + +## 先回答你能不能分析代码 + +**不能。我无法访问你本地的 `D:\hardman\bj_power` 代码。** 我只能基于你发的截图和之前对话里给的信息来分析。如果要我基于真实代码分析,你得把相关文件贴出来,或者告诉我文件路径,我只能靠你贴的片段来判断。 --- -## 0. 结论先行 +## 你的理解是对的 -1. **现状不是"缺功能",而是"同一份料的库存被扣两次、同一个字段被两个语义写"。** 这是数据正确性缺陷,必须先于补功能修 —— 否则功能越全,账越乱。 -2. 外部方案描述的「四条链路」**约 70% 已在代码里跑着**(工单物料台账、AGV 任务、接料确认、退库、手动出库、接驳台主数据、叫料、分批出库、工位级拆单、工序-物料绑定**全部已实现**)。照它重建会推翻现有可用结构。 -3. **唯一真空白**:数量不符的**处理侧**(上报有、处理无)。已确认 `material_qty_report.resolved_at` 是**从未被写入的死字段**。 -4. **1 个真 bug**:叫料单不写工位号 → 工位终端「待接料」查不到自己叫的料(本轮逐行复核再次确认)。 -5. 改进方向:**修 1 处重复扣减、拆 2 个被污染的字段、补 1 处空白、修 1 个 bug、语义化 1 个枚举**。 -6. **已定调**:库存扣减走 **A(出库扣、反冲只核销)**。但 A 有三个前提必须先落,否则会从"双扣"变成"漏扣"(§3.2)。 -7. **已否决**:在 MES 建「线边库存」实体表(客户从未提出、可完全派生、会引出第三本账)。 +**这个页面应该是完工检验,不是来料检验。** + +理由: +1. 左侧菜单已经明确分开:来料检验、过程检验、完工检验。 +2. 当前高亮的就是“完工检验”。 +3. 如果这是来料检验,左侧就不会再有一个独立的“来料检验”菜单。 + +**所以:这是完工检验的壳,套了来料检验的字段。** --- -## 1. 根因:三个"唯一性"缺失 +## 完工检验的准确定义 -### 1.1 `inventories.quantity` 的全部写入点(事实核查) - -| # | 位置 | 触发动作 | 上限校验 | -|---|---|---|---| -| 1 | `bj_power_wms/internal/handler/outbound.go:107` 工单出库 | 仓管发料 | ✅ 409 | -| 2 | `bj_power_wms/internal/handler/outbound.go:436` 通用出库 | 仓管发料 | ✅ 409 | -| 3 | `bj_power_wms/internal/handler/outbound.go:deductStockHandler`(`/api/internal/stock/deduct`) | **MES 备料自动出库** | ✅ 409 | -| 4 | `bj_power_wms/internal/handler/backflush.go:192` `fifoConsume` | **报工反冲** | ❌ **无任何上限** | - -**第 3 条是关键**:MES `logic/material.go:231` 的备料自动出库走的正是 `/api/internal/stock/deduct`,**已经扣过一次库存**;随后 MES 报工 `logic/workpiece.go:379` 调 `/api/internal/material/backflush`,**同一份料又被 `fifoConsume` 扣一次**。 - -> **料只出一次门,库存扣两次。** 且反冲按 FIFO 从"当前在库任意合格批次"扣,不认工位实领的那批 —— 领料后若有新批次入库,还会扣错批。 - -### 1.2 `out_qty` / `sent_qty` 的多写者 - -**WMS `order_material_ledger.out_qty`(字段注释"累计已出库数量")有 3 个写者,其中 1 个语义完全不同:** - -| 写者 | 语义 | 校验 | -|---|---|---| -| `outbound.go` 工单出库 | 已发出 | ✅ | -| `outbound.go:deductStockHandler` | 已发出 | ✅ | -| `backflush.go:100` `newOut := ledger.OutQty + consumed` | **已消耗** | ❌ 无上限 → 可 > `total_qty` | - -**MES `material_request.sent_qty` 有 2 个写者,语义互斥:** - -| 写者 | 语义 | 后果 | -|---|---|---| -| `logic/material.go:241` `AddSentQty(sent)`(自动出库) | 已发出 | 同时是 `:192 need = ReqQty − SentQty` 的幂等基准 | -| `logic/material.go:383` `SetSentQty(newSent)`(接料) | **已接料** | 把 sent_qty 顶到 ReqQty → **`need` 恒为 0 → 后续补发永久失效**;且 `add<=0` 时算出的实数不落库 → **实收量根本没记** | - -### 1.3 三处结构性缺陷 - -| # | 缺陷 | 证据 | -|---|---|---| -| A | **反冲被实现成了"第二次出库"** | `backflush.go:63` 创建 `outbound_type=backflush` 出库单 + `:192` 扣库存 + `:100` 累加 `out_qty`。已核实**该出库单无任何消费方**(列表/导出按 type 过滤,导出 `outbound.go:641` 只认 `workorder`,反冲单被归入"通用出库")→ 纯账目污染 | -| B | **退库不回收缺口** | `return_order.go:59/72` 建单 PENDING → `:137` 收货 RECEIVED → `:158` `AddQuantity(ro.Qty)` 回库存,**但不回写台账**。来源确认是**工位退料**(MES `logic/station_ops.go:128` 调 `wmsclient.CreateReturnOrder`,注释「工位退料回库房」) | -| C | **台账缺字段** | `schema/order_material_ledger.go` 仅有 `total_qty / out_qty / status / target_station / completed_at / remark`,**无 `consumed_qty`、无 `returned_qty`** → 消耗与退回无处安放,只能挤进 `out_qty` | - -### 1.4 "退库 → 补发"死锁(本轮新发现,外部建议与上一版方案均未点到) - -`outbound.go` 与 `deductStockHandler` 的超量判定都是 `newOutQty > TotalQty → 409`,而退库不回写台账: - -| 步骤 | total_qty | out_qty | 结果 | -|---|---|---|---| -| 出库 100 | 100 | 100 | 领料完结 | -| 工位退料 20(多发) | 100 | 100 | 库存 +20,**台账缺口仍为 0** | -| 工位再叫料 20 | 100 | **120 > 100** | **409 拒绝 → 补料永远发不出去** | - -→ 必须引入 `returned_qty`,缺口与完结判定改**净额**。 - -### 1.5 其他 - -- 补料与备料**同表同状态无来源标记**(无 `source`),AGV 待发料列表无法按"工位急件优先"排序。 -- 接料**实收量不落库**,与"数量不符"无法自动对账。 -- 出库单**无 `need_agv`**,分不清"这批走没走车"。 -- 数据现状(硬证据,查库):`order_material_ledgers` **0 行**、`outbound_orders` **0 行** → 隐患**尚未触发**,库里无坏数据。属"上线即坏",非"已经坏了"。 +| 维度 | 完工检验是什么 | +|------|--------------| +| **检什么** | 一件成品工件(SN),走完全部工序后的最终检验 | +| **对象** | 成品SN(唯一),不是库存批次 | +| **选什么** | 选工单 → 选SN → 看该SN的所有工序记录 | +| **看什么** | 该SN经过的每道工序的检验结论、操作人、时间、拧紧数据 | +| **判什么** | 整件合格/不合格;不合格的处理方式(返修/让步接收) | +| **完成后** | 触发成品入库、生成流程卡/追溯报告 | --- -## 2. 目标态:三条铁律 +## 当前页面的问题 -### 铁律 1 —— 一次物理位移 = 一次库存账变 -`inventories.quantity` **只在两处变**:出库(−)、退库收货(+)。**消耗不动库存。** +| 字段 | 问题 | +|------|------| +| **目标类型:批次/SN** | 完工检验的对象一定是成品SN,不存在“批次”选项。这是来料检验的逻辑。 | +| **生产厂家** | 来料检验的字段,完工检验不需要(成品是自己生产的) | +| **检测单号(与入库单关联)** | 来料检验的字段,完工检验不需要关联入库单 | +| **报检单号(与报检流程关联)** | 来料检验的字段 | +| **从库存选批次** | 来料检验的字段,完工检验是从已完工工件选SN | +| **检验数量/合格数量** | 完工检验是单件,不应该有“数量”概念,要么合格要么不合格 | +| **缺少工单号** | 完工检验必须关联工单,当前没有 | +| **缺少产品编号** | 完工检验必须知道检的是哪个产品 | +| **缺少工序汇总** | 完工检验要展示该SN经过的所有工序检验结论 | +| **缺少总合格率** | 完工检验要算整件的合格率 | +| **缺少审核人** | 完工检验需要最终审核人签字 | -### 铁律 2 —— 一个字段只有一个写者 +--- -| 字段 | 唯一写者 | 语义 | -|---|---|---| -| `inventories.quantity` | 出库单 / 退库收货 | 仓库实物 | -| `order_material_ledger.out_qty` | 出库(含自动出库) | 已发出 | -| `order_material_ledger.returned_qty` 🆕 | 退库收货(仅挂工单的) | 已退回 | -| `order_material_ledger.consumed_qty` 🆕 | 报工反冲 | 已消耗 | -| `material_request.sent_qty` | 自动出库 | 已发出 | -| `material_request.received_qty` 🆕 | 工位接料 | 已接料 | - -### 铁律 3 —— 不新增账本,在途/线边全部派生 +## 完工检验应该长什么样 ``` -缺口 gap = total_qty − (out_qty − returned_qty) ← 净额,修 1.4 死锁 -在途 in_transit = out_qty − received_qty -线边 line_side = received_qty − consumed_qty − 工位退回量 -恒等式:入库总量 = 在库 + 在途 + 线边 + 已消耗 + 退库回仓 + 盘点差异 -``` - -**派生依据全部现成**:在途 → `agv_task`(已有 `DISPATCHED/DELIVERING/DONE` 与 `arrived_at`);线边 → `material_request`;已消耗 → `ledger.consumed_qty`。**不需要新表。** - -### 四时点口径表(对外沟通用) - -| 时点 | 触发 | 写什么 | 动库存 | -|---|---|---|---| -| T1 出库提交 | 仓管 / MES 自动出库 | `ledger.out_qty += n`、`mr.sent_qty += n` | ✅ **唯一减点** | -| T2 AGV 送达 | RCS 回调 | `agv_task.status / arrived_at` | ❌ | -| T3 工位接料 | 工位终端扫码 | `mr.received_qty += n` | ❌ | -| T4 报工消耗 | MES 报工 | `ledger.consumed_qty += n` | ❌ | -| T5 退库收货 | WMS 退库确认 | `ledger.returned_qty += n` | ✅ 加点 | - ---- - -## 3. 方案 - -### 3.1 P0(必须先修)—— 数据正确性:不改,库存与台账本身就是错的 - -#### 0-1 反冲停扣库存、停写 `out_qty`、停产反冲出库单 -**文件**:`bj_power_wms/internal/handler/backflush.go` - -- 删除 `:63-77` 反冲出库单创建(**已核实无任何消费方,删除零影响**);`outboundNo` 随之去掉。 -- 删除 `:86` 对 `fifoConsume` 的调用;`:145` `fifoConsume` 整体删除;其中写 `inventories`(`:174` `SetStatus("出库")`、`:192` `AddQuantity(-take)`)与 `OutboundDetail.Create`(`:180/:200`)的分支全部删除。 -- 台账改写新字段:`SetConsumedQty(ledger.ConsumedQty + n)`;完结判定改净额(见 0-2)。 -- **新增核销上限**:`可核销量 = (out_qty − returned_qty) − consumed_qty`,实际核销 `min(应核销, 可核销量)`;超出部分**不写台账**,落差异记录 + 预警(`material_over_consume`),**不阻断报工**(生产线不能被系统卡死)。响应体保留 `consumed`(实际核销)与 `shortfall`(未核销额)。 - -#### 0-2 台账加两列 + 退库回收缺口 -**文件**:`bj_power_wms/schema/order_material_ledger.go`、`internal/handler/return_order.go`、`internal/db/db.go` - -- schema 加 `consumed_qty`(默认 0,「累计已消耗数量(报工反冲)」)、`returned_qty`(默认 0,「累计已退回数量」)。 -- ent 重生成:`cd bj_power_wms && go run -tags entgenerate ./tools`。 -- `internal/db/db.go` 的 `applyColumnPatches` 登记两列(**带默认值 → 无需进 `applyPreMigratePatches`**,不阻断启动)。 -- `return_order.go` 收货(`:137` RECEIVED)事务内:若退库单**挂工单** → `ledger.returned_qty += qty`;不挂工单(仓库自身退料)不动台账。 - -#### 0-3 所有超量判定改净额 -**文件**:`bj_power_wms/internal/handler/outbound.go` - -- `deductStockHandler` 与工单出库的 409 判定:`newOutQty > TotalQty` → `newOutQty > TotalQty + ReturnedQty`。 -- 「领料完结」判定同步改净额。 -- 错误文案中文化:`出库数量超过工单需求(需求 X,已退 Y,累计将达 Z)`。 - -#### 0-4 MES 接料改写 `received_qty` -**文件**:`bj_power_mes/schema/material_request.go`、`internal/logic/material.go:366-395` - -- schema 加 `received_qty`(默认 0)、`received_at`(可选)、`receive_by`(可选)。 -- `ReceiveMaterialRequest`:`:383-387` 改为 `SetReceivedQty + SetReceivedAt + SetReceiveBy(operator)`,**删掉 `SetSentQty`**;`add<=0` 的兜底改用 `ReqQty − ReceivedQty` 计算。 -- `sent_qty` 语义**锁定为「已发出」**(唯一写者 = 自动出库 `:241`),`need = ReqQty − SentQty` 保持成立。 -- 「DONE」推进改由 `received_qty >= req_qty` 触发。 -- MES 侧 `internal/db/db.go` 的 `schemaPatchSQL` 登记三个新列。 - -#### 0-5 出库类型语义化 -- `outbound_type` 最终三值:`workorder`(备料/工单领料)/ `refill`(补料)/ `general`(手动出库),移除 `backflush`。 -- 导出与列表类型文案同步(`outbound.go:641` 的 `if ob.OutboundType == "workorder"` 分支补 `refill`)。 - -**P0 验收**:连续跑「备料自动出库 → 报工反冲」,`inventories.quantity` 只减一次;`out_qty` 不因反冲变化;`out_qty − returned_qty` 与 `consumed_qty` 均可解释。 - -### 3.2 A 方案的三个前提(已采纳 A,但前提不落则变"漏扣") - -| # | 前提 | 不做的后果 | -|---|---|---| -| 1 | 反冲核销**有上限** = `net_out_qty − consumed_qty` | 工位自取的料(辅料/胶水/现场替换)被全额核销,但库存从没扣过 → **库存永不下降** | -| 2 | **退库必须回收缺口**(`returned_qty`) | §1.4 死锁:退料后补料被 409 永久拦死 | -| 3 | 出库扣到**实领批次/SN** | 只解决"扣几次",不解决"扣哪批"(现有出库本就按批次/SN 指定,已满足) | - -### 3.3 P1(该做,但不动现有数据)—— 功能空白与真 bug - -#### 1-1 修 bug:叫料单补工位号(零成本,必做) -**文件**:`bj_power_mes/internal/logic/line.go` `CallMaterial` - -- 已逐行复核:建单只有 `SetRequestNo/OrderNo/PlanDate/MaterialCode/MaterialName/Unit/ManageMode/ReqQty/Status/TargetDock/Operator`,**无 `SetStationNo`、无 `SetProcessCode`** → `station_no=0`。 -- 而工位终端「待接料」按 `stationNo=本工位` 查询 → **工位看不见自己叫的料**(扫码接料直查备料单号,不受影响,所以隐蔽)。 -- 修法:`SetStationNo(...)` + `SetProcessCode(...)`(按该物料在本工位的装配工序取,取不到则 0)+ `SetSource("REFILL")`。 -- 一次性数据修复:历史 `stationNo=0` 的叫料单按 `target_dock → dock.station_no` 映射回填(幂等)。 - -#### 1-2 补空白:数量不符处理侧 —— **真源留在 MES,处理页也放 MES** - -**理由(关键,与上一版方案的结论相反)**: -- 上报链路、表、预警**全在 MES**:工位终端 `InspectPanel ⑥` → `/api/qty-report` → 代理 MES `/api/internal/station/qty-report` → `logic.CreateQtyReport`(`logic/station_ops.go:64`)落 `material_qty_report` + 预警 `type=qty_diff`。 -- 处理动作在 MES 侧可完全闭环:**补发** = 生成 `source=REFILL` 备料单 → 走已有自动出库链路;**退库** = 走已有 `wmsclient.CreateReturnOrder`(MES→WMS 方向已存在);**调整** = 本地记账。 -- 仓管本来就在 MES 操作备料(`MaterialRequest.vue` 已含「按日排产生成备料」「一键自动出库」)。 -- 放 WMS 虽技术可行(`bj_power_wms/internal/mesclient/client.go` 已具备反向调用能力),但需新增 3 个接口,且补发要绕 `WMS→MES→WMS` 一圈,**没有理由多绕**。 - -**实施**: -- 表:`material_qty_report` 已有 `status`(PENDING/RESOLVED)、`resolved_at`(**当前死字段**)→ 只需补 `handle_type`(REFILL/RETURN/ADJUST)、`handle_note`、`handled_by`、`handle_ref_no`。 -- 接口:`GET /api/v1/qty-diffs`(状态/工位/物料/时间筛选 + **后端 SQL 真分页**)、`GET /api/v1/qty-diffs/:id`(明细,配 `el-drawer` 懒加载)、`POST /api/v1/qty-diffs/:id/handle`。 -- 处理分支: - - `REFILL` → 生成 `source=REFILL` 备料单(可走自动出库)→ 回写 `handle_ref_no` - - `RETURN` → `wmsclient.CreateReturnOrder` → 回写退库单号 - - `ADJUST` → 仅记账关单 -- 照片:走**统一附件体系** `biz_type=QTY_DIFF` + `biz_id=差异记录ID`,**不加列**。 -- 权限:新增 `produce.qtydiff:view` / `produce.qtydiff:handle`,**同时登记进 `logic/seed.go` 的 menus 与 buttons** 及 `handler/perm.go` 的 `pathPermMap`(本项目栽过一次"菜单码未登记 → 任何角色永远授不了权")。 -- 前端:MES 新增「数量不符处理」页 + `MainLayout` 菜单 + `help.js`。 -- **客户 DOC 同步**:`seed.go:165 migrateLegacyQtyReport` 留的「明细由 WMS 处理」改为「在 MES 数量不符处理页处理」。 - -#### 1-3 补料来源标记 -- `material_request` 加 `source`(默认 `PLAN`);叫料与差异补发写 `REFILL`。 -- `AutoOutboundRequests` 与 WMS 待发料列表按 `source=REFILL` 优先排序(**不新建单据类型、不新建优先级字段**)。 -- 补料出库默认叫 AGV(沿用现有 AGV 下发条件,不新增 `need_agv` 字段)。 - -#### 1-4 只读「配送进度」聚合 -- **不给出库单加五态**(状态分散在 `agv_task` / `material_request`,硬塞会互斗)。改一条只读接口按出库单号聚合:出库单(已出库)+ AGV 任务(配送中/已送达)+ 备料单(已接料/完结)。 - -### 3.4 P2(可有可无)—— 可选增强 - -| 项 | 说明 | -|---|---| -| 「物料去向」只读对账页 | 按物料展示 在库/在途/线边/已消耗,直接验 §2 恒等式 | -| 在途对账预警 | `out_qty − received_qty` 长期 > 0(滞留在途)或 < 0(未发先接)→ 预警 | -| 消耗超接料预警 | `consumed_qty > received_qty` → 提示"未接料先消耗",不阻断 | - ---- - -## 4. 明确不做(含理由) - -| 不做 | 理由 | -|---|---| -| **MES「线边库存」实体表** | ① 客户《软件系统问题-hd.doc》全文检索「线边/工位库存/工位上的料/余料」**命中 0**,只提过仓库盘点(第 102 段)→ 为未提出的需求建表即过度设计;② 会在 WMS 库存 + WMS 台账 + MES 备料单 + MES 线边**四处同步**,本项目已栽过一次跨系统对不上;③ 线边量可由 `received_qty − consumed_qty` **完全派生**。**唯一例外**:客户明确要求"盘工位上的料"时再建,并接受同步成本(列入待确认) | -| **出库单五态**(待出库→已出库→已送达→已接料→已完成) | 状态分散在 `agv_task` / `material_request`,硬塞进出库单会产生三份互相打架的状态副本。改只读聚合(1-4) | -| **新建 `difference_report` 表** | `material_qty_report` 已存在且字段齐全(含 `resolved_at` 死字段),补 4 列即可,不造同义表 | -| **双写两份账本** | 违反单一真源。`sent_qty` 的教训已经很清楚 | -| **接料"可选"、AGV"可选"** | 接料确认是消耗核销的唯一依据,保持强校验 | -| **补料另建单据类型** | 用 `source` 字段标记即可,避免两套单据各自维护缺口 | -| **OA 对接** | 客户已定:永久不做,不再提 | - ---- - -## 5. 影响面与风险 - -| 改动 | 影响 | 处置 | -|---|---|---| -| 反冲单不再产生 | 出库列表/导出少掉 `backflush` 类单据 | **已核实无消费方**;导出文案补 `refill` | -| 反冲不再扣库存 | 若存在"从未出库的料被消耗"(工位自取/辅料) | 由 0-1 的**核销上限**兜住:超出部分落差异+预警,不写台账、不阻断报工 | -| `sent_qty` 语义锁定 | MES 页面/看板若把 `sent_qty` 当"已接料"展示 | 一并改为读 `received_qty` | -| 台账新增两列 | 存量 **0 行**,无回填 | 直接加列 | -| `material_request` 新增列 | 存量可能有数据 | 均带默认值,安全 | - ---- - -## 6. 验收标准 - -1. **库存账**:`备料自动出库 → 报工反冲` 连续两轮,`inventories.quantity` 只在出库时减一次;反冲前后库存不变。 -2. **台账账**:`out_qty` 只由出库写;反冲只增 `consumed_qty`;超量报工不使 `out_qty > total_qty`。 -3. **死锁解除**:出库满额 → 工位退料 → 再补料,**不再被 409 拦住**。 -4. **接料账**:`received_qty` 落库;接料后 `need = ReqQty − SentQty` 不被污染,补发仍可执行。 -5. **叫料 bug**:工位终端叫料后,该工位「待接料」列表**能看到自己叫的料**。 -6. **数量不符闭环**:工位上报 → 处理页可见 → 补发/退库/调整 → 差异关闭,生成单据可追溯。 -7. **负向验证**:非 admin 角色在缺权限时看不到/点不动处理按钮(防"菜单码未登记"老坑)。 - ---- - -## 7. 待确认 - -1. **`refill` 补料是否允许超出工单 BOM 需求**?现规则是硬 409(含 0-3 净额修正)。补料语义上可能确实需要超额(损耗/报废),那就必须走"数量不符/额外需求"通道而非工单领料通道 —— **需业务确认**。本方案默认仍受净额上限约束。 -2. **是否要求「工位线边盘点」**?要 → 建 `line_stock` 实体表并接受四处同步成本;不要 → 线边量走派生视图(**默认**)。 -3. **数量不符处理页放 MES** 与客户 DOC 原写「明细由 WMS 处理」不一致 → 建议改 DOC(理由见 1-2)。 -4. **`outbound_type` 由三值变四值**(新增 `refill`)是否影响历史统计口径(现存量 `workorder` 中是否混有旧叫料出库)。 -5. **环境依赖**:AGV 真机联调需海康 RCS 地址与 appKey/appSecret(现 `Agv.EnableMock: true`);附件归档需外部备份路径(现 `ArchiveDir` 为空)。 - ---- - -## 附:实施顺序(一次性做完,不分阶段交付) - -``` -【编号说明】P0=必须先修(P0-1..P0-6);P1=该做(P1-1..P1-4);P2=可选。 - -P0-1 WMS schema: ledger 加 consumed_qty / returned_qty → ent 重生成 → applyColumnPatches 登记 -P0-2 MES schema: material_request 加 received_qty/received_at/receive_by → ent 重生成 → schemaPatchSQL 登记 -P0-3 WMS backflush.go: 删出库单 + 删 fifoConsume + 改 consumed_qty + 加核销上限 -P0-4 WMS return_order.go: 收货回写 returned_qty -P0-5 WMS outbound.go: 两处 409 与完结判定改净额 -P0-6 MES material.go: ReceiveMaterialRequest 改写 received_qty -P1-1 MES line.go: CallMaterial 补 station_no/process_code/source + 历史回填 -P1-2 MES: material_qty_report 加 4 列 + 处理接口(真分页) + 权限(菜单+按钮) + 页面 + help.js -P1-3 MES: material_request 加 source + 排序优先 -P1-4 MES: 只读配送进度聚合接口 -P2 只读「物料去向」对账页 + 两条对账预警(可选) -DOC 同步客户问题 DOC;改「明细由 WMS 处理」措辞 +┌─────────────────────────────────────────────────────────────┐ +│ 完工检验 │ +│ │ +│ 工单号 [WO001________] [查询] │ +│ 产品编号 [HLX-01A] 产品名称 [汇流箱] │ +│ │ +│ 选择待检SN: [扫码或输入SN________] [添加] │ +│ │ +│ 待检清单: │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ SN │ 产品 │ 工序完成情况 │ │ +│ │ SN-HLX-001 │ HLX-01A │ 工序1✅ 工序2✅ 工序3✅ │ │ +│ │ SN-HLX-002 │ HLX-01A │ 工序1✅ 工序2✅ 工序3❌ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 选中 SN-HLX-001,显示详情: │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 工序1 操作人:张三 检验:李四 结论:合格 09:30 │ │ +│ │ 工序2 操作人:张三 检验:李四 结论:合格 10:15 │ │ +│ │ 工序3 操作人:王五 检验:赵六 结论:合格 11:00 │ │ +│ │ 拧紧数据:螺丝A 2.48Nm ✅ 螺丝B 2.52Nm ✅ │ │ +│ │ 总合格率:100% │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 检验结论: ● 合格 ○ 不合格 │ +│ 不合格处理:[返修 / 让步接收] │ +│ 审核人:[李四________] │ +│ 备注:[________] │ +│ │ +│ [提交完工检验] │ +└─────────────────────────────────────────────────────────────┘ ``` --- -## 增强⑧「物料去向对账」最优方案(2026-09-19 定稿,待授权实施) - -### 0. 结论 - -1. **对账后端已经 100% 存在**:上一轮实施时顺手落了 `GET /api/v1/material-requests/progress`(MES 聚合 WMS `ledger/progress` + 本地已接料),返回字段正是对账表全部列——需求/已发出/已退回/已接料/已消耗/在途/线边/待发缺口/台账状态。备料单页已有「配送进度」弹窗在读它。 -2. DeepSeek 方案**定位对(只读/不建表/不做自动修正),但四个硬伤**:①"差异=需求−已出、非0标红"是错的(在产工单缺口天然非0,照做满屏假红);②示例第3行自己算错符号;③需求总量重算 BOM 是重复建设(total_qty 已随台账同步,重算必与备料时点不一致);④工位余公式漏了退库。 -3. 剩余工作量 = **纯前端**:把已有进度弹窗升级为对账视图(异常标记 + 明细展开 + 导出)。后端 0 行新代码(明细过滤 `/event-logs` 已支持 orderNo+eventType)。 - -### 1. 公式(纠正版,与后端派生完全一致) +## 为什么当前系统无法闭环 ``` -待发缺口 gap = totalQty − (outQty − returnedQty) ← 生产中非0是常态,不标红 -在途 inTransit = outQty − receivedQty -工位余 lineSide = receivedQty − consumedQty − returnedQty ← DeepSeek 漏了退库 +MES 生产 → 工件走完所有工序 → 工位报工 + ↓ +完工检验(当前页面)→ 用户不知道选什么(没有工单、没有SN入口) + ↓ +检验完成后 → 没有触发成品入库 + ↓ +WMS 没有收到成品入库 → 成品库存为0 + ↓ +成品无法出库 → 销售发货断链 ``` -恒等式:入库总量 = 在库 + 在途 + 工位余 + 已消耗 + 退库回仓 + 盘点差异 +**根本原因:当前页面既没接上“工件已完工”的数据,也没触发“成品入库”的动作,中间是断的。** -### 2. 异常判定(三条硬异常,全部可从现有字段判定,前端现算) +--- -| 异常 | 判定 | 含义 | 标记 | -|---|---|---|---| -| 超领 | netOut > totalQty | 实领超过 BOM(超领原因在 WMS 留痕 material.over) | 红 | -| 送达未接 | inTransit > 0 且该工单物料存在 status=已送达 的出库单 | 料到了工位没人扫码确认 | 红 | -| 完结剩料 | 台账状态=领料完结 且 lineSide > 0 | 工单做完了工位还压着料,没消耗也没退库 | 红 | -| 在途等待 | inTransit > 0 且无已送达出库单 | AGV 正在送,正常 | 黄(提醒) | +## 给 AI 的修改描述 -### 3. 页面形态(升级既有弹窗,不新建菜单) +``` +### 完工检验页面重做 -- 位置:MES 备料单页「配送进度」弹窗 → 改名「物料去向对账」。 -- 列:现有 9 列 + 「异常」列(红/黄/无,hover 显示判定原因)。 -- 行展开明细(全部现成数据,无后端改动): - - 出库明细 → 行内备料单已含出库单号;超领原因在 WMS 出库记录页(按工单筛)查看,不跨系统重复建设; - - 接料明细 → material_request 行(receivedQty/receiveBy/receivedAt); - - 消耗明细 → `/event-logs?orderNo=xxx&eventType=material.backflush`(含工位/数量/缺口)。 -- 导出:xlsx,复用 exportXlsxFetch。 -- 权限:沿用 produce.material(只读,不新增权限码)。 +当前问题: +- 页面标称“完工检验”,但字段是来料检验的(生产厂家、检测单号、报检单号、从库存选批次)。 +- 缺少完工检验必须的字段(工单号、产品编号、SN、工序汇总、总合格率、审核人)。 +- 检验完成后没有触发成品入库,闭环断裂。 -### 4. 明确不做 +修改方案: -- 不重算 BOM 需求量(读 total_qty); -- 不做"差异=需求−已出"列与标红(就是 gap 列,完结后必为 0,出现非 0 即数据错,属可发现的脏数据而非业务异常); -- 不做跨系统明细钻取(WMS 出库/核销明细由 WMS 出库记录页承担,已可按工单筛选); -- 不做自动对账修正、不建新表、不加权限码。 +1. 去掉字段:目标类型(批次/SN)、生产厂家、检测单号、报检单号、从库存选批次。 -### 5. 工作量与顺序 +2. 增加字段: + - 工单号(下拉选择或输入查询) + - 产品编号、产品名称(选工单后自动带出) + - 待检SN(扫码或输入,可多选) + - 工序汇总(显示该SN经过的所有工序的检验结论、操作人、时间) + - 拧紧数据摘要 + - 总合格率 + - 审核人 + - 不合格处理方式(返修/让步接收) -前端单文件(MaterialRequest.vue)+ help.js 一条说明,约半天。排在"①~⑦ 编译验证"之后做。 +3. 交互流程: + - 选工单 → 带出产品信息 + - 扫SN → 校验该SN是否走完全部工序 + - 全部工序完成 → 展示工序汇总 + - 未全部完成 → 提示“该SN未完工,无法进行完工检验” + - 填检验结论 → 提交 + +4. 提交后触发: + - 成品入库(调用WMS内部接口,写入成品库存) + - 生成流程卡/追溯报告 + +5. 页面 Tab 名称与左侧菜单一致:完工检验。 +``` + + + + +本系统,关于量的定义 必须准确、明确、正确,不能出任何差异。 请审核下面ai生成的内容。独立审计,如何正确,出方案,我确认后,记录到 库存查询的帮助文档里面(简要记录,帮助文档级别的记录不是照搬) + + + +# 数量字段完整定义与落地方案 + +## 一、总原则 + +1. **每个量只表达一个含义,不混用。** +2. **所有量都有明确的计算公式和约束条件。** +3. **跨系统(MES/WMS)的量,必须注明存在哪一侧。** +4. **任何时刻,关系式必须成立,差一个就是账实不符。** + + +## 二、库存域(WMS 侧,权威数据) + +| 字段 | 定义 | 计算公式 | 存在表 | 约束 | +|------|------|----------|--------|------| +| **库存量** | 仓库里实际有多少 | 入库累加 − 出库累减 | inventory_batch.quantity | ≥ 0 | +| **锁定量** | 已被备料单占用、未出库的数量 | 备料锁定累加 − 出库释放 − 解锁 | inventory_batch.locked_qty | ≥ 0,≤ 库存量 | +| **可用量** | 还能被新备料单占用的数量 | 库存量 − 锁定量 | 计算得出 | ≥ 0 | +| **已出库量** | 累计从仓库发出的数量 | 出库累加 | order_material_ledger.out_qty | ≥ 0,≤ 需求量 | + +**关键约束:** +``` +库存量 = 可用量 + 锁定量 +可用量 ≥ 0 +锁定量 ≤ 库存量 +``` + +**关键点:** +- 锁定量在**备料单生成时**产生,不是排产时。 +- 备料单生成时,调 WMS 锁定,锁定量 += 备料量。 +- 出库时,库存量 −= 出库量,锁定量 −= 出库量。 +- 锁定失败(可用量不足)→ 备料单生成失败,提示缺料。 + + +## 三、MES 侧:线边库存域 + +| 字段 | 定义 | 计算公式 | 约束 | +|------|------|----------|------| +| **已出库量** | 累计从仓库发出的数量(从WMS同步) | 出库累加 | ≥ 0 | +| **已接料量** | 工位确认收货的数量 | 接料累加 | ≥ 0,≤ 已出库量 | +| **已消耗量** | 报工反冲扣除的数量 | 报工反冲累加 | ≥ 0,≤ 已接料量 | +| **在途量** | 已发出、工位还没接的数量 | 已出库量 − 已接料量 | ≥ 0 | +| **工位余量** | 工位上还没用完的料 | 已接料量 − 已消耗量 − 退库量 | ≥ 0 | + +**关键约束:** +``` +已出库量 = 已接料量 + 在途量 +已接料量 = 已消耗量 + 工位余量 + 退库量 +``` + +**关键点:** +- 出库即扣WMS库存,物料进入MES侧管理。 +- 工位余量是MES侧的概念,不纳入WMS盘点。 +- 报工反冲只动MES侧的已消耗量和工位余量,不动WMS库存。 + + +## 四、工单域(MES 侧) + +| 字段 | 定义 | 计算公式 | 约束 | +|------|------|----------|------| +| **工单数量** | 计划生产的产品总数 | 建单时填 | > 0 | +| **单台用量** | 生产1台产品需要该物料的数量 | 物料清单维护 | > 0 | +| **损耗率** | 考虑报废/损耗的加放比例 | 物料清单维护 | ≥ 0 | +| **需求量** | 该工单该物料的总需求 | 工单数量 × 单台用量 × (1+损耗率) | > 0 | +| **已完工数量** | 已完工的产品数 | 完工累加 | ≥ 0,≤ 工单数量 | +| **剩余缺口** | 该物料还差多少没出库 | 需求量 − 已出库量 | ≥ 0 | + +**关键点:** +- **需求量是工单级的,不是日级的。** +- 出库累计约束:**累计出库 ≤ 需求量**。 +- 用日级需求会误卡跨天生产。 + + +## 五、排产域(MES 侧) + +| 字段 | 定义 | 计算公式 | 约束 | +|------|------|----------|------| +| **排产计划量** | 某工单某天的计划数量 | 计划员填 | ≥ 0,可大于库存 | +| **工位分配量** | 同一工序多工位时,每个工位分到的数量 | 计划员分配或系统平均分 | 合计 = 排产计划量 | +| **已执行量** | 该排产实际完成的数量 | 报工累加 | ≥ 0,≤ 排产计划量 | + +**关键约束:** +``` +同一工单多天排产合计 ≤ 工单数量 +工位分配量合计 = 排产计划量 +``` + +**关键点:** +- **排产计划量可以大于库存**(提醒采购补料)。 +- **排产不锁定库存**,锁定在备料单生成时才发生。 + + +## 六、检验域(WMS/MES 通用) + +| 字段 | 定义 | 计算公式 | 约束 | +|------|------|----------|------| +| **报检数** | 本次送检的总数量 | 人工填或扫码计数 | ≥ 0 | +| **合格数** | 判定为合格的数量 | 检验后统计 | ≥ 0,≤ 报检数 | +| **不合格数** | 判定为不合格的数量 | 报检数 − 合格数 | ≥ 0 | +| **让步接收数** | 不合格但允许使用的数量 | 审批后填 | ≥ 0,≤ 不合格数 | + +**关键约束:** +``` +报检数 = 合格数 + 不合格数 +不合格数 = 退货数 + 报废数 + 让步接收数 + 待处理数 +``` + +**关键点:** +- 报检数是送检总数,不是“检验合格数”。 +- 合格入库,不合格入隔离区。 +- 不合格禁止出库到产线。 + + +## 七、备料/配送域 + +| 字段 | 定义 | 计算公式 | 约束 | +|------|------|----------|------| +| **应发量** | 备料单要求发出的数量 | 系统按物料清单算 | ≥ 0 | +| **已发量** | 累计已出库的数量 | 出库累加 | ≥ 0,≤ 应发量 | +| **实收量** | 工位确认收到的数量 | 接料时填 | ≥ 0 | +| **差异量** | 实收与应发的差值 | 实收量 − 应发量 | 可正可负 | +| **退库量** | 工位退回仓库的数量 | 退库单累加 | ≥ 0 | +| **待发量** | 还没发的数量 | 应发量 − 已发量 | ≥ 0 | + +**关键约束:** +``` +已发量 ≤ 应发量 +差异量 = 实收量 − 应发量(正=多发,负=少发) +待发量 = 应发量 − 已发量 +``` + +**关键点:** +- 分批出库时,已发量累加,待发量减少。 +- 差异量非0触发数量不符处理流程。 + + +## 八、绩效域(MES 侧) + +| 字段 | 定义 | 计算公式 | +|------|------|----------| +| **完成数** | 操作工完成的工件数 | 报工累加 | +| **合格数** | 完成的工件里合格的 | 检验结论统计 | +| **不合格数** | 完成的工件里不合格的 | 检验结论统计 | +| **合格率** | 合格数 ÷ 完成数 | 百分比 | + + +## 九、所有关系式(必须成立) + +``` +库存量 = 可用量 + 锁定量 +已出库量 = 已接料量 + 在途量 +已接料量 = 已消耗量 + 工位余量 + 退库量 +报检数 = 合格数 + 不合格数 +需求量 = 已出库量 + 剩余缺口 +应发量 = 已发量 + 待发量 +工位分配量合计 = 排产计划量 +同一工单多天排产合计 ≤ 工单数量 +``` + +**任何时刻,这些等式必须成立。差一个,就是账实不符。** + + +## 十、每个操作的量变化 + +| 操作 | 库存量 | 锁定量 | 已出库量 | 已接料量 | 已消耗量 | 工位余量 | +|------|--------|--------|----------|----------|----------|----------| +| 备料单生成 | — | +备料量 | — | — | — | — | +| 出库 | −出库量 | −出库量 | +出库量 | — | — | — | +| 工位接料 | — | — | — | +实收量 | — | — | +| 报工反冲 | — | — | — | — | +消耗量 | — | +| 退库 | +退库量 | — | — | — | — | −退库量 | +| 盘点调整 | ±调整量 | — | — | — | — | — | + + +## 十一、给 AI 的落地描述 + +``` +### 数量字段完整定义 + +1. 库存域(WMS侧): + - 库存量:实际在库数量,入库累加、出库累减 + - 锁定量:备料单占用、未出库,备料时+,出库时− + - 可用量 = 库存量 − 锁定量 + - 已出库量:累计发出,出库时+ + +2. 线边库存域(MES侧): + - 已接料量:工位确认收货,接料时+ + - 已消耗量:报工反冲,报工时+ + - 在途量 = 已出库量 − 已接料量 + - 工位余量 = 已接料量 − 已消耗量 − 退库量 + +3. 工单域(MES侧): + - 需求量 = 工单数量 × 单台用量 × (1+损耗率) + - 已完工数量:完工时+ + - 剩余缺口 = 需求量 − 已出库量 + +4. 排产域(MES侧): + - 排产计划量:可大于库存 + - 工位分配量合计 = 排产计划量 + - 同一工单多天排产合计 ≤ 工单数量 + +5. 检验域: + - 报检数 = 合格数 + 不合格数 + - 让步接收数从不合格数里划出 + +6. 备料域: + - 应发量 = 已发量 + 待发量 + - 差异量 = 实收量 − 应发量 + +7. 关键约束(必须成立): + - 库存量 = 可用量 + 锁定量 + - 已出库量 = 已接料量 + 在途量 + - 已接料量 = 已消耗量 + 工位余量 + 退库量 + - 报检数 = 合格数 + 不合格数 + - 需求量 = 已出库量 + 剩余缺口 + +8. 严禁混用: + - 锁定量 ≠ 排产计划量 + - 排产计划量可以大于库存 + - 锁定量必须 ≤ 可用库存 + - 需求量是工单级,不是日级 + - 报检数是送检总数,不是合格数 + - 工位余量不纳入WMS盘点 + +9. 每个操作的量变化: + 备料:锁定量+ + 出库:库存量−、锁定量−、已出库量+ + 接料:已接料量+ + 报工:已消耗量+、工位余量− + 退库:库存量+、工位余量− +``` + + + +下面,又是一段ai聊天内容: + +## 结论:主流程能跑,但细节有多处断点,严格说**不能闭环**。 + +--- + +## 一、能闭环的部分 + +| 环节 | 状态 | +|------|------| +| 发起盘点 → 生成盘点单号 | ✅ 有 | +| 扫码实盘 → 记录实盘数 | ✅ 有 | +| 完成盘点 → 生成差异 | ✅ 有 | +| 差异写回 → 修正库存 | ✅ 有 | +| 历史盘点单查询 | ✅ 有 | + +**主链路是通的。** + +--- + +## 二、断点(不能闭环的地方) + +### 断点1:发起后状态不更新 +截图中「盘点单号」显示“未发起”,「目标数量」显示“-”。发起盘点后,这两个字段会不会自动更新为实际的盘点单号和数量?**如果不会,用户不知道当前有没有进行中的盘点。** + +### 断点2:扫码区和盘点单没自动关联 +扫码区有个「盘点单号」输入框,写着“也可粘贴已有进行中的盘点单号”。**如果用户已经发起了盘点,为什么还要手动粘贴?** 应该发起后自动带入,不需要用户再操作。 + +### 断点3:完成盘点时不知道完成哪个单 +「完成并生成差异」按钮独立存在,和上面的扫码区没有明显关联。**用户扫完码点完成,系统怎么知道是完成哪个盘点单?** 应该自动关联当前进行中的盘点单。 + +### 断点4:没有取消盘点的入口 +如果发起错了,或者扫错了很多,怎么取消?**截图中没有取消按钮。** 用户只能重新发起,但旧的盘点单会一直挂在“进行中”。 + +### 断点5:未盘项没处理 +盘点单覆盖 50 个批次,只扫了 45 个,剩下 5 个怎么办?**系统没说要怎么处理,直接完成会导致这 5 个的差异算不出来。** + +### 断点6:按 SN 和按批次交互没区分 +截图里「实盘数量」对所有类型都一样。但电气件(SN)逐件扫码,实盘数应该是 1,不需要用户填。**当前交互不区分,用户扫了 SN 还要手动填 1,多余且易错。** + +### 断点7:盘点期间库存能变动 +发起盘点时冻结了快照,但盘点期间如果有出库/入库,快照和实际就不一致。**没有锁定机制,盘点结果不可靠。** + +### 断点8:差异写回用开关,不安全 +开关容易被误触,写回库存是不可逆操作。**用户扫完码随手一划,库存就被改了。** + +--- + +## 三、要闭环,必须补什么 + +| # | 补什么 | 作用 | +|---|--------|------| +| 1 | 发起后自动更新盘点单号、目标数量 | 用户知道当前状态 | +| 2 | 扫码区自动关联当前盘点单 | 不用手动粘贴 | +| 3 | 完成盘点自动关联当前单 | 知道完成哪个 | +| 4 | 增加「取消盘点」按钮 | 错了能退 | +| 5 | 未盘项处理弹窗 | 剩余项有归宿 | +| 6 | 按 SN/按批次区分交互 | SN 自动=1,批次填数 | +| 7 | 盘点期间锁定库存 | 快照和实际一致 | +| 8 | 差异写回改二次确认弹窗 | 防误触 | +| 9 | 盘点单状态机(进行中/已锁定/已完成/已写回) | 状态可追踪 | +| 10 | 写回用调整单,不直接覆盖 | 可追溯 | + +**补完这 10 项,盘点才真正闭环。** + +--- + +## 四、一句话总结 + +**主流程是通的,但“发起后状态不更新、扫码区和盘点单没关联、完成时不知道完成哪个单、没有取消、未盘项没处理、SN和批次没区分、盘点期间能改库存、写回用开关”——这 8 个断点任何一条都会导致盘点结果不可靠。补完这 8 条加状态机和调整单,才算闭环。** + + +结合刚才,我讲的 ,量 ,的定义 +## 结合“量”的定义,重新评估盘点页面 + +### 一、盘点盘的是哪个量? + +根据刚才的定义: +``` +库存量 = 可用量 + 锁定量 +``` + +**盘点盘的是「库存量」,即实物在仓库里的总数。** + +| 量 | 是否参与盘点 | 原因 | +|----|-------------|------| +| **库存量** | ✅ 盘这个 | 实物在仓库里,能数 | +| **锁定量** | ❌ 不盘 | 只是账面占用,实物还在仓库里,已经包含在库存量里了 | +| **可用量** | ❌ 不盘 | 计算值,不独立存在 | +| **在途量** | ❌ 不盘 | 已出库,不在仓库里 | +| **工位余量** | ❌ 不盘 | 在工位上,不在仓库里 | + +**盘点结果:实盘数 = 实际数到的库存量。** + +差异量 = 实盘数 − 账面库存量(快照时的库存量)。 + +--- + +### 二、关键问题:盘点期间库存变了怎么办 + +**这是当前页面最大的断点,和量的定义直接相关。** + +场景: +``` +发起盘点 → 快照库存量 = 100(其中锁定量 30,可用量 70) + ↓ +盘点期间有工单出库 → 库存量变成 80,锁定量变成 10,可用量 70 + ↓ +用户扫完码,实盘数 = 80 +差异 = 80 − 100 = −20 ❌ 错!这是出库导致的,不是盘亏 +``` + +**如果不锁库存,差异会包含正常业务变动,盘点结果不可信。** + +**两种解法:** + +| 解法 | 做法 | 优点 | 缺点 | +|------|------|------|------| +| **A. 锁库存** | 发起盘点时,锁定该范围库存,不允许出库/入库 | 差异干净,全是盘盈盘亏 | 盘点期间产线领不了料 | +| **B. 动态对账** | 不锁,但记录盘点期间所有出入库,差异里扣除这些变动 | 不影响生产 | 实现复杂,容易算错 | + +**你们规模小,建议选 A(锁库存)。** 盘点本身就是短时间操作(几十分钟),锁一下影响不大。 + +**锁的是「库存量」还是「锁定量」?** + +锁的是**该范围库存不允许任何变动**——即: +- 不允许出库(库存量不能减) +- 不允许入库(库存量不能加) +- 不允许锁定(锁定量不能变) + +**盘点期间,库存量、锁定量、可用量全部冻结。** + +--- + +### 三、盘点后的写回 + +盘点完成,差异写回时: + +``` +调整前:库存量 = 100,锁定量 = 30,可用量 = 70 +实盘数 = 95,差异 = −5 +调整后:库存量 = 95,锁定量 = 30,可用量 = 65 +``` + +**关键约束:写回后,`库存量 = 可用量 + 锁定量` 仍要成立。** + +如果锁定量是 30,库存量从 100 调到 95,可用量自动变成 65。**锁定量不动。** + +--- + +### 四、结合量定义的完整盘点闭环 + +``` +① 发起盘点 + - 冻结快照:记录当前库存量、锁定量、可用量 + - 锁定库存:不允许出库/入库/锁定 + - 盘点范围:全库 / 按区域 / 按物料 + - 生成盘点单号,状态=进行中 + +② 扫码实盘 + - 扫批次号(结构件)→ 弹实盘数量框 → 用户填 + - 扫 SN(电气件)→ 实盘数=1,自动提交 + - 记录:批次/SN、货位、账面数(快照库存量)、实盘数、差异 + +③ 完成盘点 + - 检查未盘项 → 弹窗选处理方式(按账面保留 / 盘亏为0) + - 展示差异汇总 + - 二次确认弹窗:是否写回库存 + - 写回:生成盘盈/盘亏调整单,修正库存量 + - 解锁库存 + - 状态=已写回 +``` + +**每一步都要保证:** +``` +库存量 = 可用量 + 锁定量 (写回后仍成立) +``` + +--- + +### 五、当前页面缺什么(结合量定义) + +| # | 缺什么 | 结合量定义的影响 | +|---|--------|----------------| +| 1 | 发起后状态不更新 | 不知道当前盘点单、目标数量 | +| 2 | 扫码区和盘点单没关联 | 不知道在盘哪个单 | +| 3 | 没有取消盘点入口 | 发起了退不出 | +| 4 | **盘点期间没锁库存** | **库存量会变,差异算错** | +| 5 | 未盘项没处理 | 部分批次没盘,差异不完整 | +| 6 | SN 和批次交互没区分 | SN 实盘数应该自动=1 | +| 7 | 写回用开关 | 误触导致库存被改 | +| 8 | 写回没走调整单 | 库存量改了,但查不到谁改的 | +| 9 | 没有状态机 | 盘点单状态不可追踪 | +| 10 | 写回后没校验等式 | 写完库存量,可用量、锁定量可能对不上 | + +**第 4 条最严重——不锁库存,差异直接算错,盘点结果不可信。** +**第 10 条次严重——写回后如果 `库存量 ≠ 可用量 + 锁定量`,整个库存账就崩了。** + +--- + +### 六、给 AI 的修改描述(结合量定义) + +``` +### 库存盘点页面修改(结合数量定义) + +1. 盘点对象:盘的是「库存量」,不是可用量,不是锁定量。 + - 账面数 = 快照时的库存量 + - 实盘数 = 实际数到的库存量 + - 差异 = 实盘数 − 账面数 + +2. 发起盘点时锁定库存: + - 冻结该范围的库存量、锁定量、可用量 + - 盘点期间不允许出库/入库/锁定 + - 盘点完成或取消后解锁 + +3. 差异写回: + - 生成盘盈/盘亏调整单,记录调整前后数量 + - 修正库存量,锁定量不动 + - 写回后校验:库存量 = 可用量 + 锁定量(必须成立) + - 走二次确认弹窗,不用开关 + +4. 按物料类型区分扫码: + - 扫批次号(结构件)→ 弹实盘数量框 + - 扫 SN(电气件)→ 实盘数=1,自动提交 + +5. 未盘项处理: + - 完成盘点时,如有未盘项,弹窗选:按账面保留 / 盘亏为0 + +6. 取消盘点: + - 增加「取消盘点」按钮,解锁库存,状态=已取消 + +7. 状态机:进行中 → 已锁定 → 已完成 → 已写回 / 已取消 + +8. 发起后自动带入盘点单号到扫码区,扫码区自动关联当前盘点单 + +9. 历史盘点单增加字段:差异项数、是否写回、写回时间 + +10. 盘点明细导出:盘点单号、批次/SN、货位、账面数、实盘数、差异、操作人、时间 + +11. 写回权限:主管以上 + +12. 目标数量定义:本次盘点覆盖的批次/SN 总数,发起时自动算出 +``` + +--- + +### 七、一句话总结 + +**盘点盘的是「库存量」,不是可用量;盘点期间必须锁库存,否则差异会混入业务变动;写回后必须校验 `库存量 = 可用量 + 锁定量`;不锁库存、不校验等式,盘点结果就是错的,不能闭环。** + + +库存盘点,你给我总结成 严厉的问题提问,并给我相应最优方案,我问问其他ai, + + +## 库存盘点模块——严厉问题清单与最优方案 + +--- + +### 问题1:盘点对象没定义清楚 + +**问题:** 盘点页面没有明确说盘的是「库存量」、「可用量」还是「锁定量」。用户不知道账面数从哪来,实盘数对应哪个量。 + +**最优方案:** 盘点盘的是**库存量**(实物在仓库里的总数)。 +- 账面数 = 快照时的库存量 +- 实盘数 = 实际数到的库存量 +- 差异 = 实盘数 − 账面数 +- 锁定量、可用量不参与盘点(它们是计算值,不是实物) + +**约束:** 写回后必须满足 `库存量 = 可用量 + 锁定量`。 + + +### 问题2:盘点期间库存会变,差异算错 + +**问题:** 发起盘点时冻结了快照,但盘点期间如果发生出库/入库/锁定,快照和实际就不一致。差异里会混入正常业务变动,不是纯盘盈盘亏。 + +**最优方案:** 发起盘点时**锁定该范围库存**,盘点期间不允许出库/入库/锁定。盘点完成或取消后解锁。 +- 库存量、锁定量、可用量全部冻结 +- 差异干净,全是盘盈盘亏 +- 盘点本身就是短时间操作(几十分钟),锁一下影响不大 + + +### 问题3:差异写回用开关,容易被误触 + +**问题:** 截图里「差异写回库存」是一个开关,用户随手一划就可能改了库存。而且写回库存是**不可逆操作**,改了很难恢复。 + +**最优方案:** 去掉开关,改为「完成盘点」时弹**二次确认弹窗**: +- 显示盘点单号、盘点范围、已盘项/总数、差异项数 +- 默认勾选「将差异写回库存」,用户可取消 +- 确认后执行写回或仅记录差异 +- 写回是主动确认的动作,不是误触的开关 + + +### 问题4:写回直接改库存,没有审计痕迹 + +**问题:** 差异写回时如果直接覆盖库存数量,查不到是谁在什么时候改的、改了多少。出问题无法追溯。 + +**最优方案:** 写回时生成一张「**盘点调整单**」(盘盈/盘亏),记录: +- 调整前数量、调整量、调整后数量 +- 操作人、时间、盘点单号 +- 原库存量 + 调整量 = 新库存量 +- 调整单入操作日志,可追溯、可反查 + + +### 问题5:未盘项没处理 + +**问题:** 盘点单覆盖50个批次,只扫了45个,剩下5个怎么办?系统没说要怎么处理,直接完成会导致这5个的差异算不出来。 + +**最优方案:** 完成盘点时,如果有未盘项,弹窗让用户选择: +- **按账面保留**(视为无误,推荐)——未盘项不改库存 +- **盘亏为0**(视为丢失)——未盘项库存量全部调为0 +- 用户必须选一个才能完成盘点 + + +### 问题6:SN和批次交互没区分 + +**问题:** 截图里「实盘数量」对所有类型都一样,用户扫了SN还要手动填1,多余且易错。 + +**最优方案:** 按物料类型区分交互: +- 扫**批次号**(结构件)→ 弹出实盘数量输入框,用户填 +- 扫 **SN**(电气件)→ 实盘数量自动=1,不可编辑,直接提交 +- 扫码枪扫进去的内容先判断类型,再决定是否弹数量框 + + +### 问题7:发起盘点后状态不更新 + +**问题:** 截图中「盘点单号」显示“未发起”,「目标数量」显示“-”。发起盘点后,这两个字段会不会自动更新?如果不会,用户不知道当前有没有进行中的盘点。 + +**最优方案:** +- 发起盘点后,自动更新盘点单号、目标数量、状态=进行中 +- 步骤条自动高亮步骤② +- 扫码区自动带入盘点单号,不用用户手动粘贴 + + +### 问题8:完成盘点时不知道完成哪个单 + +**问题:** 「完成并生成差异」按钮独立存在,和上面的扫码区没有明显关联。用户扫完码点完成,系统怎么知道是完成哪个盘点单? + +**最优方案:** 完成盘点时自动关联**当前进行中的盘点单**。如果同时有多个进行中的盘点单,弹窗让用户选择。 + + +### 问题9:没有取消盘点的入口 + +**问题:** 如果发起错了,或者扫错了很多,怎么取消?截图中没有取消按钮。用户只能重新发起,但旧的盘点单会一直挂在“进行中”。 + +**最优方案:** 增加「取消盘点」按钮: +- 解锁库存 +- 状态=已取消 +- 记录取消原因、操作人、时间 +- 已扫描的记录保留,但不写回库存 + + +### 问题10:没有盘点单状态机 + +**问题:** 盘点单没有明确的状态流转,用户不知道当前盘点处于哪个阶段。 + +**最优方案:** 盘点单状态机: +``` +进行中 → 已锁定 → 已完成 → 已写回 / 已取消 +``` +- 进行中:刚发起,未开始扫 +- 已锁定:库存已锁,正在扫 +- 已完成:扫完提交,未写回 +- 已写回:差异已修正库存 +- 已取消:中途取消 + + +### 问题11:写回后没校验等式 + +**问题:** 写回后库存量改了,但可用量、锁定量可能对不上。如果 `库存量 ≠ 可用量 + 锁定量`,整个库存账就崩了。 + +**最优方案:** 写回后强制校验: +``` +库存量 = 可用量 + 锁定量 (必须成立) +``` +- 写回时只改库存量,锁定量不变,可用量自动重算 +- 如果校验不通过,拒绝写回,报错并记录 + + +### 问题12:权限没定义 + +**问题:** 谁能发起盘点?谁能写回差异?没有定义。 + +**最优方案:** +- 发起盘点:仓管/主管 +- 扫码实盘:仓管 +- 写回差异:主管以上(写回是高权限操作) +- 取消盘点:发起人以上 + + +### 问题13:历史盘点单字段不全 + +**问题:** 截图里历史列表只有 ID、盘点单号、操作人、目标数、状态、发起时间、完成时间。缺差异项数、是否写回、写回时间。 + +**最优方案:** 历史盘点单列表增加: +- 差异项数 +- 是否写回 +- 写回时间 +- 取消原因(已取消的) +- 支持按状态、时间、操作人筛选 + + +### 问题14:盘点明细无法导出 + +**问题:** 盘点完成后无法导出明细,没法给财务或审计。 + +**最优方案:** 支持导出: +- 盘点单号、批次/SN、货位、账面数、实盘数、差异、操作人、时间 +- 格式:Excel + + +### 问题15:目标数量定义不明 + +**问题:** 截图里有「目标数量」,但功能定义里没解释。用户不知道这个数字代表什么。 + +**最优方案:** 明确定义: +- 目标数量 = 本次盘点覆盖的批次/SN 总数 +- 发起时系统自动算出 +- 例:按区域Z01盘点,Z01有30个批次,目标数量=30 + + +## 给其他 AI 的提问模板 + +``` +请独立审计以下库存盘点模块,指出问题并给出最优方案: + +【当前实现】 +- 三步流程:发起盘点 → 扫码实盘 → 完成盘点(可写回差异) +- 盘点范围:全库/按区域/按物料 +- 差异写回用开关控制 +- 扫码区有盘点单号输入框,需手动粘贴 + +【数量定义】 +- 库存量 = 可用量 + 锁定量 +- 盘点盘的是库存量 + +【我认为的问题】 +1. 没说明盘的是哪个量(库存量/可用量/锁定量) +2. 盘点期间库存会变,差异算错 +3. 差异写回用开关,容易被误触 +4. 写回直接改库存,没有审计痕迹 +5. 未盘项没处理 +6. SN和批次交互没区分 +7. 发起后状态不更新 +8. 完成时不知道完成哪个单 +9. 没有取消盘点的入口 +10. 没有盘点单状态机 +11. 写回后没校验 库存量=可用量+锁定量 +12. 权限没定义 +13. 历史盘点单字段不全 +14. 盘点明细无法导出 +15. 目标数量定义不明 + +【要求】 +- 逐条判断我的问题是否成立 +- 如果成立,给出最优方案 +- 如果有遗漏,补充问题 +- 结合数量定义,确保写回后库存账不崩 +``` \ No newline at end of file diff --git a/技术约束.md b/技术约束.md index 0a40e87..f6d96b2 100644 --- a/技术约束.md +++ b/技术约束.md @@ -45,7 +45,8 @@ ## 业务架构约束 -- **产线仅为装配线**:设备只有「扫码枪 + 拧紧枪」,其余为人工操作。不存在 CNC、清洗机、检验设备、内窥镜、AGV/RGV、机器人、接驳台等设备(前端看板/页面不得包含这些设备)。 +- **产线仅为装配线**:工位设备只有「扫码枪 + 拧紧枪」,其余为人工操作;不存在 CNC、清洗机、检验设备、内窥镜、机器人等加工设备。 +- **厂内物流(AGV / 接驳台)**:接驳台是 AGV 点位(DOCK01~20 产线 / DOCK21 库房,共 21 个),属 **WMS 侧只读主数据**(不可新增/修改,仅查看,与实际接驳台一一对应);PLC 仅反馈「接驳台上是否有托盘」的到位信号,不控制其动作。AGV 配送由 WMS「AGV 配送」页管理,**当前为模拟模式(mock)、待现场对接真实海康 RCS**(协议见《AGV海康.md》;对接时在 WMS 配置关闭 `Agv.EnableMock` 并填写 `BaseURL/AppKey/AppSecret` 即切换真实调度)。MES 工位页与看板不含 AGV / 接驳台控制。 - **数据库隔离**:MES 与 WMS 使用各自独立数据库,跨系统通过内部 API 通信。 - **工位终端**:拧紧数据用 SQLite 本地缓存,完成后上报 MES(两段式:点「完成」才上报,失败本地保留重试/断点续传)。 - **半成品流转**:工位「暂存/退回库房」时记录已完成工序,MES 调 WMS 半成品入库/出库,重上线从下一工序继续。 diff --git a/部署手册.md b/部署手册.md index ecc79c8..7377da8 100644 --- a/部署手册.md +++ b/部署手册.md @@ -74,7 +74,7 @@ go build -o bj_power_mes.exe . - 服务 :8888;访问 http://127.0.0.1:8888 即内嵌登录页(前端已构建进 `public/` 并 go:embed)。 - 若需改 MES 前端源码:`cd frontend && npm install && npm run build`,产物输出到 `../public/` 后重新 `go build`。 - PLC 默认 `Host: 127.0.0.1:102` 且 `Plc.Enable: false`(模拟模式,直接写工序码+读完成信号逻辑);真实设备改 yaml 中 Plc.Host/Port 并 Enable: true。 -- 本产线仅有「扫码枪+拧紧枪」,无 AGV/机器人/接驳台/CNC/清洗机等设备,前端无相关页。 +- 本产线工位仅有「扫码枪+拧紧枪」,无 CNC/清洗机/机器人等加工设备。厂内物流侧另有接驳台(AGV 点位)与 AGV 配送:接驳台为 WMS 只读主数据(不可增改),AGV 配送页在 WMS(8890),**当前默认模拟模式(`Agv.EnableMock=true`)、待现场对接海康 RCS**(协议见《AGV海康.md》);对接时在 WMS 配置关闭 `EnableMock` 并填 `BaseURL/AppKey/AppSecret` 即可,业务代码无需改动。 ### C — WMS(后端+前端一体) @@ -87,6 +87,7 @@ go build -o bj_power_wms.exe . 服务 :8890,首次启动自动建表+种子数据。内嵌前端页面,浏览器打开 http://127.0.0.1:8890。 若需改前端:`cd frontend && npm install && npm run build` 后重新编译 Go。 出入库两台台式机无需单独部署客户端(原 8891 库房客户端已并入),浏览器直接访问 8890 即可;扫码枪为 USB 键盘口(HID)设备,页面输入框直接读取。 +- AGV 配送默认为模拟模式(`etc/bj_power_wms-api.yaml` 中 `Agv.EnableMock: true`),用于离线联调完整链路(下发→到位→回写备料单),不驱动真实小车;现场对接海康 RCS-2000 时置 `EnableMock: false` 并填 `Agv.BaseURL/AppKey/AppSecret`(协议见《AGV海康.md》),重启服务即切换真实调度。接驳台(DOCK01~21)为只读主数据,首次启动自动种子。 ### D — 工位终端(12 台触控一体机)