diff --git a/docs/verification/context-reset/entry-capability-requirement-matrix.json b/docs/verification/context-reset/entry-capability-requirement-matrix.json index d12691a..3ea0b62 100644 --- a/docs/verification/context-reset/entry-capability-requirement-matrix.json +++ b/docs/verification/context-reset/entry-capability-requirement-matrix.json @@ -1781,7 +1781,8 @@ "requirements": [ "agent-funds-commission::充值边界", "identity-access::数据范围拒绝", - "order-payment-wallet::金额单位" + "order-payment-wallet::金额单位", + "employee-collection-bill::员工代收款账单来源、金额与建账判据" ], "classification": "behavior" }, @@ -2187,7 +2188,8 @@ "requirements": [ "identity-access::数据范围拒绝", "order-refund-exchange::订单、退款与换货状态门禁", - "package-lifecycle::套餐使用记录价格快照" + "package-lifecycle::套餐使用记录价格快照", + "employee-collection-bill::员工代收款账单来源、金额与建账判据" ], "classification": "behavior" }, @@ -2783,7 +2785,8 @@ "entry": "POST /api/callback/wecom/approval/{application_id}", "capability": "external-integration", "requirements": [ - "external-integration::企微回调与补偿" + "external-integration::企微回调与补偿", + "employee-collection-bill::核销申请审批、重提与幂等" ], "classification": "behavior" }, @@ -3237,14 +3240,20 @@ "entry_type": "async", "entry": "constants.OutboxEventTypeApprovalSubmissionRequested", "capability": "infrastructure", - "requirements": [], + "requirements": [ + "employee-collection-bill::核销申请审批、重提与幂等" + ], "classification": "route_index_or_infrastructure" }, { "entry_type": "async", "entry": "constants.OutboxEventTypeApprovalTerminalDecision", "capability": "infrastructure", - "requirements": [], + "requirements": [ + "employee-collection-bill::员工代收款账单来源、金额与建账判据", + "employee-collection-bill::核销申请审批、重提与幂等", + "employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], "classification": "route_index_or_infrastructure" }, { @@ -3557,7 +3566,8 @@ "capability": "external-integration", "requirements": [ "external-integration::企微回调与补偿", - "external-integration::外部调用重试边界" + "external-integration::外部调用重试边界", + "employee-collection-bill::核销申请审批、重提与幂等" ], "classification": "behavior" }, @@ -3566,7 +3576,8 @@ "entry": "constants.TaskTypeWeComApprovalSync", "capability": "external-integration", "requirements": [ - "external-integration::企微审批状态" + "external-integration::企微审批状态", + "employee-collection-bill::核销申请审批、重提与幂等" ], "classification": "behavior" }, @@ -3762,5 +3773,116 @@ "merchant-payment-routing::商户池唯一性与轮询配置" ], "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-payment-methods", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "POST /api/admin/employee-collection-payment-methods", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "PUT /api/admin/employee-collection-payment-methods/{id}", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "DELETE /api/admin/employee-collection-payment-methods/{id}", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-bills", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-bills/statistics", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-bills/{id}", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "POST /api/admin/employee-collection-bills/{id}/close", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "POST /api/admin/employee-collection-applications", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::外部付款核销申请与分摊", + "employee-collection-bill::核销申请审批、重提与幂等" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-applications", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::外部付款核销申请与分摊", + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "GET /api/admin/employee-collection-applications/{id}", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::外部付款核销申请与分摊", + "employee-collection-bill::账单余额、状态与关闭" + ], + "classification": "behavior" + }, + { + "entry_type": "http", + "entry": "PUT /api/admin/employee-collection-applications/{id}", + "capability": "employee-collection-bill", + "requirements": [ + "employee-collection-bill::核销申请审批、重提与幂等" + ], + "classification": "behavior" } ] diff --git a/docs/verification/context-reset/requirement-evidence.json b/docs/verification/context-reset/requirement-evidence.json index a414c14..3ae36c1 100644 --- a/docs/verification/context-reset/requirement-evidence.json +++ b/docs/verification/context-reset/requirement-evidence.json @@ -2669,5 +2669,198 @@ ], "exit_status": 0 } -} +}, + { + "capability": "employee-collection-bill", + "requirement": "员工代收款账单来源、金额与建账判据", + "spec": "openspec/specs/employee-collection-bill/spec.md", + "entries": [ + "POST /api/admin/agent-recharges/{id}/offline-pay", + "POST /api/admin/orders", + "constants.OutboxEventTypeApprovalTerminalDecision" + ], + "handler_consumer_job": [ + "internal/service/order/service.go:Service.CreateAdminOrder", + "internal/service/agent_recharge/service.go:Service.OfflinePay", + "internal/application/agentrecharge/approval_decision.go:ApprovalDecisionHandler.Handle" + ], + "application_service_query": [ + "internal/application/employeecollection/bill_creation.go:BillCreationService.CreateFromOrderInTx", + "internal/application/employeecollection/bill_creation.go:BillCreationService.CreateFromRechargeInTx" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/order_bill.go:ShouldCreateBillForOrder", + "internal/domain/employeecollection/order_bill.go:ShouldCreateBillForRecharge", + "internal/domain/employeecollection/order_bill.go:OrderBillSubjectFromOrder" + ], + "store_migration_config": [ + "internal/model/employee_collection.go:EmployeeCollectionBill", + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/order_bill.go\", \"func ShouldCreateBillForOrder(subject OrderBillSubject) bool\"), (\"internal/domain/employeecollection/order_bill.go\", \"subject.PaymentMethod == model.PaymentMethodOffline\"), (\"internal/service/order/service.go\", \"s.billCreation.CreateFromOrderInTx(ctx, tx, order, hasGiftPackage)\"), (\"internal/service/agent_recharge/service.go\", \"s.billCreation.CreateFromRechargeInTx(ctx, tx, record)\"), (\"internal/application/agentrecharge/approval_decision.go\", \"h.billCreation.CreateFromRechargeInTx(ctx, tx, record)\"), (\"internal/application/employeecollection/bill_creation.go\", \"Columns: []clause.Column{{Name: \\\"source_key\\\"}}\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::员工代收款账单来源、金额与建账判据\")'", + "literal_output": [ + "OK: employee-collection-bill::员工代收款账单来源、金额与建账判据" + ], + "exit_status": 0 + } + }, + { + "capability": "employee-collection-bill", + "requirement": "账单余额、状态与关闭", + "spec": "openspec/specs/employee-collection-bill/spec.md", + "entries": [ + "GET /api/admin/employee-collection-bills", + "GET /api/admin/employee-collection-bills/statistics", + "GET /api/admin/employee-collection-bills/{id}", + "POST /api/admin/employee-collection-bills/{id}/close", + "GET /api/admin/employee-collection-applications", + "GET /api/admin/employee-collection-applications/{id}" + ], + "handler_consumer_job": [ + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListBills", + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.StatisticsBills", + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.GetBill", + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.CloseBill" + ], + "application_service_query": [ + "internal/query/employeecollection/bill.go:BillQuery.List/Statistics/Detail", + "internal/query/employeecollection/bill.go:applyBillVisibility", + "internal/query/employeecollection/application.go:applyApplicationVisibility", + "internal/application/employeecollection/bill_close.go:BillCloseService.Close" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/bill.go:BillAmounts.Reserve/Release/Approve", + "internal/domain/employeecollection/bill.go:BillAmounts.DerivedStatus", + "internal/domain/employeecollection/bill.go:ValidateBillClose" + ], + "store_migration_config": [ + "internal/model/employee_collection.go:EmployeeCollectionBill", + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) Reserve(amount int64) (BillAmounts, error)\"), (\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) DerivedStatus() int\"), (\"internal/application/employeecollection/bill_close.go\", \"func (s *BillCloseService) Close(ctx context.Context, id uint, reason string)\"), (\"internal/query/employeecollection/bill.go\", \"func applyBillVisibility(ctx context.Context, query *gorm.DB) (*gorm.DB, error)\"), (\"internal/query/employeecollection/bill.go\", \"func (q *BillQuery) Statistics(\"), (\"internal/query/employeecollection/application.go\", \"func applyApplicationVisibility(ctx context.Context, query *gorm.DB) (*gorm.DB, error)\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::账单余额、状态与关闭\")'", + "literal_output": [ + "OK: employee-collection-bill::账单余额、状态与关闭" + ], + "exit_status": 0 + } + }, + { + "capability": "employee-collection-bill", + "requirement": "外部付款核销申请与分摊", + "spec": "openspec/specs/employee-collection-bill/spec.md", + "entries": [ + "POST /api/admin/employee-collection-applications", + "GET /api/admin/employee-collection-applications", + "GET /api/admin/employee-collection-applications/{id}" + ], + "handler_consumer_job": [ + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.CreateApplication", + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListApplications", + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.GetApplication", + "internal/handler/admin/employee_collection.go:bindApplicationRequest" + ], + "application_service_query": [ + "internal/application/employeecollection/application.go:ApplicationService.Create/submit", + "internal/application/employeecollection/application.go:lockBillsInAscendingOrder", + "internal/application/employeecollection/application.go:createAllocations", + "internal/query/employeecollection/application.go:ApplicationQuery.List/Detail" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/allocation.go:ValidateAllocations", + "internal/domain/employeecollection/bill.go:BillAmounts.Reserve" + ], + "store_migration_config": [ + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application", + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application_allocation", + "internal/model/employee_collection.go:EmployeeCollectionApplicationAllocation" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/allocation.go\", \"func ValidateAllocations(paidAmount int64, candidates []AllocationCandidate) error\"), (\"internal/application/employeecollection/application.go\", \"func (s *ApplicationService) Create(ctx context.Context, command SubmitApplicationCommand) (*ApplicationSubmitResult, error)\"), (\"internal/handler/admin/employee_collection.go\", \"func (h *EmployeeCollectionHandler) CreateApplication(c *fiber.Ctx) error\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::外部付款核销申请与分摊\")'", + "literal_output": [ + "OK: employee-collection-bill::外部付款核销申请与分摊" + ], + "exit_status": 0 + } + }, + { + "capability": "employee-collection-bill", + "requirement": "核销申请审批、重提与幂等", + "spec": "openspec/specs/employee-collection-bill/spec.md", + "entries": [ + "POST /api/callback/wecom/approval/{application_id}", + "constants.OutboxEventTypeApprovalSubmissionRequested", + "constants.OutboxEventTypeApprovalTerminalDecision", + "constants.TaskTypeWeComApprovalRecovery", + "constants.TaskTypeWeComApprovalSync", + "POST /api/admin/employee-collection-applications", + "PUT /api/admin/employee-collection-applications/{id}" + ], + "handler_consumer_job": [ + "internal/application/employeecollection/approval_decision.go:ApprovalDecisionHandler.Handle", + "internal/handler/callback/wecom_approval.go:WeComApprovalHandler.Receive", + "cmd/worker/main.go:ApprovalBusinessTypeEmployeeCollection -> employeecollectionApp.NewApprovalDecisionHandler", + "cmd/worker/main.go:TaskTypeWeComApprovalSync/TaskTypeWeComApprovalRecovery -> 既有查询与恢复机制" + ], + "application_service_query": [ + "internal/application/employeecollection/application.go:ApplicationService.Resubmit", + "internal/application/employeecollection/approval_decision.go:applyApproved/applyClosed/applyRevoked/revokeApproved/revokePending", + "internal/application/approval/create.go:CreationService.CreateInTx", + "internal/application/employeecollection/application.go:buildAttempt/nextAttemptNo" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/application.go:ValidateApplicationResubmit", + "pkg/constants/approval.go:ApprovalBusinessTypeEmployeeCollection" + ], + "store_migration_config": [ + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application_attempt", + "internal/model/employee_collection.go:EmployeeCollectionApplicationAttempt" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/application.go\", \"func (s *ApplicationService) Resubmit(ctx context.Context, applicationID uint, command SubmitApplicationCommand) (*ApplicationSubmitResult, error)\"), (\"internal/application/employeecollection/approval_decision.go\", \"func (h *ApprovalDecisionHandler) Handle(ctx context.Context, event approvalapp.TerminalDecisionEvent) error\"), (\"internal/application/employeecollection/approval_decision.go\", \"func (h *ApprovalDecisionHandler) revokePending(\"), (\"pkg/constants/approval.go\", \"ApprovalBusinessTypeEmployeeCollection = \\\"employee_collection_approval\\\"\"), (\"cmd/worker/main.go\", \"constants.ApprovalBusinessTypeEmployeeCollection: employeecollectionApp.NewApprovalDecisionHandler(\"), (\"internal/handler/callback/wecom_approval.go\", \"func (h *WeComApprovalHandler) Receive(c *fiber.Ctx) error\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::核销申请审批、重提与幂等\")'", + "literal_output": [ + "OK: employee-collection-bill::核销申请审批、重提与幂等" + ], + "exit_status": 0 + } + }, + { + "capability": "employee-collection-bill", + "requirement": "收款方式字典、退款联动与可追溯性", + "spec": "openspec/specs/employee-collection-bill/spec.md", + "entries": [ + "constants.OutboxEventTypeApprovalTerminalDecision", + "GET /api/admin/employee-collection-payment-methods", + "POST /api/admin/employee-collection-payment-methods", + "PUT /api/admin/employee-collection-payment-methods/{id}", + "DELETE /api/admin/employee-collection-payment-methods/{id}" + ], + "handler_consumer_job": [ + "internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListPaymentMethods/CreatePaymentMethod/UpdatePaymentMethod/DeletePaymentMethod", + "internal/routes/employee_collection.go:requireEmployeeCollectionConfigAccess", + "internal/service/refund/approval_decision.go:Service.applyApprovedDecision" + ], + "application_service_query": [ + "internal/application/employeecollection/payment_method.go:PaymentMethodService.Create/Update/Delete", + "internal/application/employeecollection/refund_offset.go:RefundOffsetService.ApplyInTx" + ], + "domain_state_amount": [ + "internal/domain/employeecollection/bill.go:BillAmounts.ReduceReceivable", + "internal/domain/employeecollection/refund_offset.go:DecideRefundOffset" + ], + "store_migration_config": [ + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_payment_method", + "migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill_refund", + "internal/model/employee_collection.go:EmployeeCollectionPaymentMethod", + "internal/model/employee_collection.go:EmployeeCollectionBillRefund" + ], + "verification": { + "command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/payment_method.go\", \"func (s *PaymentMethodService) Delete(ctx context.Context, id uint) error\"), (\"internal/application/employeecollection/payment_method.go\", \"func countPaymentMethodReferences(\"), (\"internal/routes/employee_collection.go\", \"func requireEmployeeCollectionConfigAccess(handler fiber.Handler) fiber.Handler\"), (\"internal/application/employeecollection/refund_offset.go\", \"func (s *RefundOffsetService) ApplyInTx(ctx context.Context, tx *gorm.DB, source RefundOffsetSource) error\"), (\"internal/domain/employeecollection/refund_offset.go\", \"func DecideRefundOffset(bill BillAmounts, refundAmount int64) (RefundOffsetDecision, error)\"), (\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) ReduceReceivable(amount int64) (BillAmounts, error)\"), (\"internal/service/refund/approval_decision.go\", \"s.refundOffset.ApplyInTx(ctx, tx, employeecollectionapp.RefundOffsetSource{\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::收款方式字典、退款联动与可追溯性\")'", + "literal_output": [ + "OK: employee-collection-bill::收款方式字典、退款联动与可追溯性" + ], + "exit_status": 0 + } + } ] diff --git a/openspec/specs/employee-collection-bill/spec.md b/openspec/specs/employee-collection-bill/spec.md index 9c85b0e..acb85d6 100644 --- a/openspec/specs/employee-collection-bill/spec.md +++ b/openspec/specs/employee-collection-bill/spec.md @@ -125,3 +125,19 @@ #### Scenario: 被引用字典项停用 - **WHEN** 超级管理员停用已被核销申请引用的线下收款方式 - **THEN** 新申请不可选择该方式,历史申请仍展示其冻结名称和稳定编码 + +## 可达操作索引 + +本节只用于入口导航,不是行为 Requirement;业务义务以上述 Requirements 为准。 + +### 线下收款方式字典 + +`GET /api/admin/employee-collection-payment-methods`(查询线下收款方式);`POST /api/admin/employee-collection-payment-methods`(创建线下收款方式);`PUT /api/admin/employee-collection-payment-methods/{id}`(更新线下收款方式);`DELETE /api/admin/employee-collection-payment-methods/{id}`(删除线下收款方式)。 + +### 员工代收款账单 + +`GET /api/admin/employee-collection-bills`(查询本人或全部账单);`GET /api/admin/employee-collection-bills/statistics`(账单金额统计);`GET /api/admin/employee-collection-bills/{id}`(查询账单详情);`POST /api/admin/employee-collection-bills/{id}/close`(关闭账单)。 + +### 核销申请 + +`POST /api/admin/employee-collection-applications`(创建核销申请);`GET /api/admin/employee-collection-applications`(查询核销申请);`GET /api/admin/employee-collection-applications/{id}`(查询核销申请详情);`PUT /api/admin/employee-collection-applications/{id}`(修改并重提核销申请)。