@yss123/cage-cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/approval-tree.d.ts +88 -0
- package/dist/approval-tree.js +18 -0
- package/dist/approval-tree.js.map +1 -0
- package/dist/capabilities.js +46 -31
- package/dist/capabilities.js.map +1 -1
- package/dist/graph-approvals.d.ts +11 -1
- package/dist/graph-approvals.js +18 -5
- package/dist/graph-approvals.js.map +1 -1
- package/dist/graph-relations.js +10 -8
- package/dist/graph-relations.js.map +1 -1
- package/dist/graph.js +2 -0
- package/dist/graph.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/ontology.js +8 -5
- package/dist/ontology.js.map +1 -1
- package/dist/project-structure.js +2 -2
- package/dist/project-structure.js.map +1 -1
- package/dist/sync.d.ts +1 -1
- package/dist/sync.js +25 -108
- package/dist/sync.js.map +1 -1
- package/graph-ui/approval-flow.js +28 -8
- package/graph-ui/approval-parallel.js +110 -0
- package/graph-ui/approval-tree.js +17 -0
- package/graph-ui/business.css +26 -0
- package/graph-ui/business.js +30 -13
- package/graph-ui/graph-approvals.js +18 -5
- package/graph-ui/graph-relations.js +10 -8
- package/package.json +6 -6
- package/templates/app/.agents/skills/cage-development/SKILL.md +10 -0
- package/templates/app/AGENTS.md +39 -122
- package/templates/app/CLAUDE.md +6 -0
- package/templates/app/README.md +9 -8
- package/templates/app/apps/api/package.json +2 -2
- package/templates/app/apps/web/package.json +1 -1
- package/templates/app/apps/web/src/modules/approval/components/task-operation-dialog.tsx +61 -0
- package/templates/app/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +6 -9
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +81 -49
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks.css +18 -0
- package/templates/app/apps/web/src/modules/purchase/components/approval-history.tsx +39 -4
- package/templates/app/apps/web/src/modules/purchase/components/order-reviewers.tsx +38 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +9 -2
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-intelligence.css +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +2 -0
- package/templates/app/apps/web/src/shared/lib/error-message.ts +6 -1
- package/templates/app/apps/web/src/shared/ui/approval-member-picker.css +10 -0
- package/templates/app/apps/web/src/shared/ui/approval-member-picker.tsx +51 -0
- package/templates/app/cage.config.json +14 -0
- package/templates/app/db/migrations/0004_draft.sql +27 -0
- package/templates/app/docs/README.md +11 -0
- package/templates/app/docs/development/README.md +64 -0
- package/templates/app/docs/development/examples/note/note-detail-page.tsx +106 -0
- package/templates/app/docs/development/examples/note/note.ts +67 -0
- package/templates/app/docs/development/examples/note/notes-page.tsx +87 -0
- package/templates/{blank/docs/ai → app/docs/development/examples}/purchase-example.md +24 -10
- package/templates/{blank/docs/ai → app/docs/development/getting-started}/architecture.md +4 -4
- package/templates/app/docs/development/getting-started/first-feature.md +66 -0
- package/templates/{blank/docs/ai → app/docs/development/getting-started}/workflow.md +13 -12
- package/templates/app/docs/development/guides/approvals.md +270 -0
- package/templates/app/docs/development/guides/business-collaboration.md +63 -0
- package/templates/{blank/docs/ai → app/docs/development/guides}/files.md +5 -1
- package/templates/app/docs/{ai → development/guides}/intelligence.md +4 -4
- package/templates/{blank/docs/ai → app/docs/development/guides}/recipes.md +31 -11
- package/templates/app/docs/{ai → development/guides}/relations.md +2 -2
- package/templates/app/docs/{ai → development/guides}/routing.md +1 -1
- package/templates/{blank/docs/ai → app/docs/development/guides}/transfers.md +1 -1
- package/templates/{blank/docs/ai → app/docs/development/reference}/commenting.md +1 -1
- package/templates/app/docs/{ai → development/reference}/primitives.md +8 -62
- package/templates/app/docs/{ai → development/reference}/project-structure.md +2 -2
- package/templates/app/docs/development/skills/cage-development/SKILL.md +134 -0
- package/templates/{blank/docs/ai → app/docs/development/tools}/diagnostics.md +3 -3
- package/templates/{blank/docs/ai → app/docs/development/tools}/ontology-context.md +3 -3
- package/templates/app/docs/{ai → development/tools}/troubleshooting.md +7 -1
- package/templates/app/docs/errors/CAGE-AI-001.md +3 -3
- package/templates/app/docs/errors/CAGE-APPR-001.md +13 -10
- package/templates/app/docs/errors/CAGE-APPR-002.md +1 -1
- package/templates/app/docs/errors/CAGE-APPR-003.md +1 -1
- package/templates/app/docs/errors/CAGE-APPR-004.md +1 -1
- package/templates/app/docs/errors/CAGE-APPR-005.md +5 -5
- package/templates/app/docs/errors/CAGE-APPR-006.md +25 -10
- package/templates/app/docs/errors/CAGE-APPR-007.md +9 -7
- package/templates/app/docs/errors/CAGE-APPR-010.md +1 -1
- package/templates/app/docs/errors/CAGE-AUDIT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-AUTH-001.md +1 -1
- package/templates/app/docs/errors/CAGE-BIZ-001.md +1 -1
- package/templates/app/docs/errors/CAGE-BIZ-002.md +1 -1
- package/templates/app/docs/errors/CAGE-BUDGET-001.md +1 -1
- package/templates/app/docs/errors/CAGE-BUDGET-002.md +1 -1
- package/templates/app/docs/errors/CAGE-BUDGET-003.md +1 -1
- package/templates/app/docs/errors/CAGE-CMD-002.md +1 -1
- package/templates/app/docs/errors/CAGE-CMD-003.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-001.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-002.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-003.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-004.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-005.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-006.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-007.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-008.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-009.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-010.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-011.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-012.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-013.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-014.md +1 -1
- package/templates/app/docs/errors/CAGE-CONN-015.md +1 -1
- package/templates/app/docs/errors/CAGE-CREATE-001.md +2 -2
- package/templates/app/docs/errors/CAGE-DECL-001.md +1 -1
- package/templates/app/docs/errors/CAGE-DECL-002.md +1 -1
- package/templates/app/docs/errors/CAGE-DECL-003.md +1 -1
- package/templates/app/docs/errors/CAGE-DECL-004.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-001.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-002.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-003.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-004.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-005.md +1 -1
- package/templates/app/docs/errors/CAGE-DIAG-001.md +1 -1
- package/templates/app/docs/errors/CAGE-DLQ-001.md +1 -1
- package/templates/app/docs/errors/CAGE-ENV-001.md +1 -1
- package/templates/app/docs/errors/CAGE-ENV-002.md +1 -1
- package/templates/app/docs/errors/CAGE-ENV-003.md +1 -1
- package/templates/app/docs/errors/CAGE-EVENT-001.md +2 -2
- package/templates/app/docs/errors/CAGE-EVENT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-FIELD-001.md +1 -1
- package/templates/app/docs/errors/CAGE-FIELD-002.md +1 -1
- package/templates/app/docs/errors/CAGE-FIELD-003.md +1 -1
- package/templates/app/docs/errors/CAGE-FIELD-004.md +1 -1
- package/templates/app/docs/errors/CAGE-FILE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-GEN-001.md +1 -1
- package/templates/app/docs/errors/CAGE-GEN-002.md +1 -1
- package/templates/app/docs/errors/CAGE-GEN-003.md +1 -1
- package/templates/app/docs/errors/CAGE-GRANT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-GRANT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-GRANT-003.md +1 -1
- package/templates/app/docs/errors/CAGE-IMPACT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-IMPACT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-003.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-004.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-005.md +1 -1
- package/templates/app/docs/errors/CAGE-JOB-001.md +1 -1
- package/templates/app/docs/errors/CAGE-JOB-002.md +1 -1
- package/templates/app/docs/errors/CAGE-JOB-003.md +1 -1
- package/templates/app/docs/errors/CAGE-LINT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-MIG-001.md +1 -1
- package/templates/app/docs/errors/CAGE-MIG-002.md +1 -1
- package/templates/app/docs/errors/CAGE-MIG-005.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-001.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-002.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-003.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-004.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-005.md +1 -1
- package/templates/app/docs/errors/CAGE-NAME-006.md +1 -1
- package/templates/app/docs/errors/CAGE-ORG-001.md +1 -1
- package/templates/app/docs/errors/CAGE-PAIR-002.md +1 -1
- package/templates/app/docs/errors/CAGE-QUERY-001.md +1 -1
- package/templates/app/docs/errors/CAGE-QUERY-002.md +1 -1
- package/templates/app/docs/errors/CAGE-QUERY-003.md +1 -1
- package/templates/app/docs/errors/CAGE-REACT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-REACT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-REACT-003.md +1 -1
- package/templates/app/docs/errors/CAGE-READS-001.md +1 -1
- package/templates/app/docs/errors/CAGE-REG-001.md +2 -2
- package/templates/app/docs/errors/CAGE-REG-002.md +1 -1
- package/templates/app/docs/errors/CAGE-REG-003.md +1 -1
- package/templates/app/docs/errors/CAGE-REL-001.md +1 -1
- package/templates/app/docs/errors/CAGE-ROUTE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SCOPE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SCOPE-002.md +1 -1
- package/templates/app/docs/errors/CAGE-SECRET-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SECRET-002.md +1 -1
- package/templates/app/docs/errors/CAGE-SENS-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SQL-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SQL-002.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-002.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-003.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-004.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-005.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-010.md +1 -1
- package/templates/app/docs/errors/CAGE-STATE-011.md +1 -1
- package/templates/app/docs/errors/CAGE-STRUCT-001.md +4 -4
- package/templates/app/docs/errors/CAGE-STRUCT-002.md +3 -3
- package/templates/app/docs/errors/CAGE-SYNC-001.md +4 -4
- package/templates/app/docs/errors/CAGE-TEST-001.md +1 -1
- package/templates/app/docs/errors/CAGE-TEST-002.md +1 -1
- package/templates/app/docs/errors/CAGE-TEST-003.md +1 -1
- package/templates/app/docs/errors/CAGE-UNSAFE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-UPGRADE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-WORKER-001.md +1 -1
- package/templates/app/docs/errors/CAGE-WORKER-002.md +1 -1
- package/templates/app/docs/errors/CAGE-WORKER-003.md +1 -1
- package/templates/app/docs/errors/README.md +3 -3
- package/templates/app/docs/errors/runtime.md +1 -1
- package/templates/app/package.json +3 -3
- package/templates/app/packages/contracts/package.json +1 -1
- package/templates/app/packages/contracts/src/aggregates/approval-member/index.ts +70 -0
- package/templates/app/packages/contracts/src/aggregates/purchase-order/index.ts +10 -2
- package/templates/app/packages/contracts/src/index.ts +9 -0
- package/templates/app/packages/domain/package.json +5 -5
- package/templates/app/packages/domain/src/modules/purchase/approval-members.ts +54 -0
- package/templates/app/packages/domain/src/modules/purchase/approvals.ts +19 -8
- package/templates/app/packages/domain/src/modules/purchase/index.ts +2 -1
- package/templates/app/packages/domain/src/modules/purchase/intelligence.test.ts +169 -7
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.ts +56 -5
- package/templates/blank/.agents/skills/cage-development/SKILL.md +10 -0
- package/templates/blank/AGENTS.md +39 -122
- package/templates/blank/CLAUDE.md +6 -0
- package/templates/blank/README.md +3 -3
- package/templates/blank/apps/api/package.json +1 -1
- package/templates/blank/apps/web/package.json +1 -1
- package/templates/blank/docs/README.md +11 -0
- package/templates/blank/docs/development/README.md +64 -0
- package/templates/blank/docs/development/examples/note/note-detail-page.tsx +106 -0
- package/templates/blank/docs/development/examples/note/note.ts +67 -0
- package/templates/blank/docs/development/examples/note/notes-page.tsx +87 -0
- package/templates/{app/docs/ai → blank/docs/development/examples}/purchase-example.md +24 -10
- package/templates/{app/docs/ai → blank/docs/development/getting-started}/architecture.md +4 -4
- package/templates/blank/docs/development/getting-started/first-feature.md +66 -0
- package/templates/{app/docs/ai → blank/docs/development/getting-started}/workflow.md +13 -12
- package/templates/blank/docs/development/guides/approvals.md +270 -0
- package/templates/blank/docs/development/guides/business-collaboration.md +63 -0
- package/templates/{app/docs/ai → blank/docs/development/guides}/files.md +5 -1
- package/templates/blank/docs/{ai → development/guides}/intelligence.md +4 -4
- package/templates/{app/docs/ai → blank/docs/development/guides}/recipes.md +31 -11
- package/templates/blank/docs/{ai → development/guides}/relations.md +2 -2
- package/templates/blank/docs/{ai → development/guides}/routing.md +1 -1
- package/templates/{app/docs/ai → blank/docs/development/guides}/transfers.md +1 -1
- package/templates/{app/docs/ai → blank/docs/development/reference}/commenting.md +1 -1
- package/templates/blank/docs/{ai → development/reference}/primitives.md +8 -62
- package/templates/blank/docs/{ai → development/reference}/project-structure.md +2 -2
- package/templates/blank/docs/development/skills/cage-development/SKILL.md +134 -0
- package/templates/{app/docs/ai → blank/docs/development/tools}/diagnostics.md +3 -3
- package/templates/{app/docs/ai → blank/docs/development/tools}/ontology-context.md +3 -3
- package/templates/blank/docs/{ai → development/tools}/troubleshooting.md +7 -1
- package/templates/blank/docs/errors/CAGE-AI-001.md +3 -3
- package/templates/blank/docs/errors/CAGE-APPR-001.md +13 -10
- package/templates/blank/docs/errors/CAGE-APPR-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-APPR-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-APPR-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-APPR-005.md +5 -5
- package/templates/blank/docs/errors/CAGE-APPR-006.md +25 -10
- package/templates/blank/docs/errors/CAGE-APPR-007.md +9 -7
- package/templates/blank/docs/errors/CAGE-APPR-010.md +1 -1
- package/templates/blank/docs/errors/CAGE-AUDIT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-AUTH-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-BIZ-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-BIZ-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-BUDGET-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-BUDGET-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-BUDGET-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-CMD-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-CMD-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-006.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-007.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-008.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-009.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-010.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-011.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-012.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-013.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-014.md +1 -1
- package/templates/blank/docs/errors/CAGE-CONN-015.md +1 -1
- package/templates/blank/docs/errors/CAGE-CREATE-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-DECL-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-DECL-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-DECL-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-DECL-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-DIAG-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-DLQ-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-ENV-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-ENV-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-ENV-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-EVENT-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-EVENT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-FIELD-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-FIELD-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-FIELD-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-FIELD-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-FILE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-GEN-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-GEN-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-GEN-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-GRANT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-GRANT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-GRANT-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-IMPACT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-IMPACT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-JOB-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-JOB-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-JOB-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-LINT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-MIG-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-MIG-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-MIG-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-NAME-006.md +1 -1
- package/templates/blank/docs/errors/CAGE-ORG-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-PAIR-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-QUERY-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-QUERY-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-QUERY-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-REACT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-REACT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-REACT-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-READS-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-REG-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-REG-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-REG-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-REL-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-ROUTE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SCOPE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SCOPE-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-SECRET-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SECRET-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-SENS-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SQL-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SQL-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-005.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-010.md +1 -1
- package/templates/blank/docs/errors/CAGE-STATE-011.md +1 -1
- package/templates/blank/docs/errors/CAGE-STRUCT-001.md +4 -4
- package/templates/blank/docs/errors/CAGE-STRUCT-002.md +3 -3
- package/templates/blank/docs/errors/CAGE-SYNC-001.md +4 -4
- package/templates/blank/docs/errors/CAGE-TEST-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-TEST-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-TEST-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-UNSAFE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-UPGRADE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-WORKER-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-WORKER-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-WORKER-003.md +1 -1
- package/templates/blank/docs/errors/README.md +3 -3
- package/templates/blank/docs/errors/runtime.md +1 -1
- package/templates/blank/docs/examples/purchase-admin/README.md +1 -1
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/components/task-operation-dialog.tsx +61 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +6 -9
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +81 -49
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks.css +18 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/approval-history.tsx +39 -4
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/order-reviewers.tsx +38 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +9 -2
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-intelligence.css +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +6 -1
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/approval-member-picker.css +10 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/approval-member-picker.tsx +51 -0
- package/templates/blank/docs/examples/purchase-admin/cage.config.json +14 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/approval-member/index.ts +70 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +10 -2
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +9 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approval-members.ts +54 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts +19 -8
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +2 -1
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/intelligence.test.ts +169 -7
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +56 -5
- package/templates/blank/package.json +3 -3
- package/templates/blank/packages/contracts/package.json +1 -1
- package/templates/blank/packages/domain/package.json +5 -5
- package/templates/app/docs/ai/README.md +0 -98
- package/templates/blank/docs/ai/README.md +0 -98
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 以实际采购声明、应用迁移、真实 PostgreSQL 和 worker 回归 AI 预审到人工 OA 的完整业务链。
|
|
3
|
+
* 只把模型生成器换为固定工具裁决,采购路由、协作名录、结论命令及库存反应仍使用应用实现。
|
|
4
|
+
* 断言任务等待、会签完成和最终库存占用的区别;不连接应用数据库,也不把测试成功当作浏览器验收。
|
|
5
|
+
*/
|
|
2
6
|
import { beforeAll, afterAll, expect, it } from 'vitest';
|
|
3
7
|
import { randomUUID } from 'node:crypto';
|
|
4
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -12,27 +16,64 @@ let decision: 'pass' | 'return' | 'manual' = 'return';
|
|
|
12
16
|
/** 保留真实工具调用协议,只替换模型生成器;领域声明、审批引擎、数据库和 worker 仍使用实际实现。 */
|
|
13
17
|
const intelligence = ai({ models: { main: { spec: { kind: 'test-only' }, generate: async (): Promise<ModelResult> => ({ text: '', calls: [{ id: randomUUID(), name: 'review_decide',
|
|
14
18
|
arguments: { decision, reason: '按当前用途审查', suggestions: decision === 'return' ? ['补充具体使用场景'] : [], evidence: [{ source: 'subject', detail: 'reason 字段' }] } }] }) } } });
|
|
15
|
-
let env: TestEnv, buyer: TestUser, reviewer: TestUser;
|
|
19
|
+
let env: TestEnv, buyer: TestUser, reviewer: TestUser, specialistA: TestUser, specialistB: TestUser, financeA: TestUser, financeB: TestUser;
|
|
16
20
|
/**
|
|
17
21
|
* 显式建立人工负责人和自动审查角色,证明声明本身不授予 AI 执行权限。
|
|
18
22
|
* 自动角色只获 read/returnForChanges,不授 approve;通过 AI 节点后必须继续走人工经理结论。
|
|
19
23
|
*/
|
|
20
24
|
beforeAll(async () => {
|
|
21
25
|
env = await createTestEnv({ domain: { ...purchase, intelligence }, migrations: readMigrations(fileURLToPath(new URL('../../../../../db/migrations/', import.meta.url))) });
|
|
22
|
-
buyer = await env.user({ caps: ['PurchaseOrder.read', 'PurchaseOrder.create', 'PurchaseOrder.explainPurpose', 'PurchaseOrder.submit']
|
|
26
|
+
buyer = await env.user({ caps: ['PurchaseOrder.read', 'PurchaseOrder.create', 'PurchaseOrder.explainPurpose', 'PurchaseOrder.submit', 'PurchaseOrder.setReviewers', 'ApprovalMember.read'],
|
|
27
|
+
scopes: { ApprovalMember: 'all' } });
|
|
23
28
|
reviewer = await env.user({ caps: ['PurchaseOrder.read', 'PurchaseOrder.approve', 'PurchaseOrder.returnForChanges'], scopes: { PurchaseOrder: 'dept' }, params: { 'PurchaseOrder.approve': { ceiling: 50000 } } });
|
|
24
29
|
await env.kernel.execute('Dept.update', { id: env.rootDeptId, leaderId: reviewer.id }, env.system());
|
|
25
30
|
await env.kernel.execute('Inventory.create', { sku: 'AI-01', available: 100 }, env.system());
|
|
26
31
|
const role = await env.kernel.execute('Role.create', { code: 'purchase_ai_reviewer', name: '材料预审角色' }, env.system());
|
|
27
32
|
for (const capability of ['PurchaseOrder.read', 'PurchaseOrder.returnForChanges']) await env.kernel.execute('Role.grantCapability', { id: role.id, capability }, env.system());
|
|
28
33
|
await env.kernel.execute('Role.setScope', { id: role.id, aggregate: 'PurchaseOrder', combinator: 'all' }, env.system());
|
|
34
|
+
specialistA = await collaborator('设备技术复核'); specialistB = await collaborator('采购合规复核');
|
|
35
|
+
financeA = await collaborator('财务共享受理甲'); financeB = await collaborator('财务共享受理乙');
|
|
36
|
+
const financeRole = await env.kernel.execute('Role.create', { code: 'finance', name: '采购财务候选人员' }, env.system());
|
|
37
|
+
for (const user of [financeA, financeB]) await env.kernel.execute('User.assignRoles', { id: user.id, roleIds: [user.roleId, financeRole.id] }, env.system());
|
|
29
38
|
});
|
|
30
39
|
afterAll(() => env?.close());
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 通过组织和业务命令登记同部门协作人;10 万元额度只供本隔离库验证大额流程,名录本身不授予能力。
|
|
43
|
+
* 专业和财务共享这些真实审批能力,财务候选身份由额外的 finance 角色单独赋予。
|
|
44
|
+
*/
|
|
45
|
+
async function collaborator(specialty: string) {
|
|
46
|
+
const user = await env.user({ caps: ['PurchaseOrder.read', 'PurchaseOrder.approve', 'PurchaseOrder.reject', 'PurchaseOrder.returnForChanges'],
|
|
47
|
+
scopes: { PurchaseOrder: 'dept' }, params: { 'PurchaseOrder.approve': { ceiling: 100000 } } });
|
|
48
|
+
await env.kernel.execute('ApprovalMember.create', { userId: user.id, displayName: specialty, specialty }, env.system());
|
|
49
|
+
return user;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 创建固定物料草稿;金额按元传输,专业名单仍交真实 create/submit 命令校验。 */
|
|
53
|
+
async function draft(options: { amount?: string; needsSpecialistReview?: boolean; reviewerIds?: string[] } = {}) {
|
|
54
|
+
return env.kernel.execute('PurchaseOrder.create', { amount: '1200.00', reason: '用途待补充', items: [{ sku: 'AI-01', qty: 2 }], ...options }, await buyer.ctx());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 提交真实采购单并消费当前事件;固定模型输出使每例从明确 AI 结果开始,不替换人工审批路由。 */
|
|
58
|
+
async function submit(options: { amount?: string; needsSpecialistReview?: boolean; reviewerIds?: string[] } = {}) {
|
|
59
|
+
const order = await draft(options);
|
|
34
60
|
await env.kernel.execute('PurchaseOrder.submit', { id: order.id }, await buyer.ctx()); await env.drain(); return order;
|
|
35
61
|
}
|
|
62
|
+
|
|
63
|
+
/** 从操作者本人待办重新取得当前 revision,避免复用动作发生前的旧页面版本。 */
|
|
64
|
+
async function taskOf(user: TestUser, orderId: string, status = 'pending') {
|
|
65
|
+
const task = (await purchase.poRoute.engine(env.kernel).tasksOf(user.id, env.tenantId, status)).find(item => item.businessKey === orderId);
|
|
66
|
+
expect(task).toBeDefined(); return task!;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 通过真实待办批准当前节点,最终节点才会执行采购领域命令和同事务库存占用。 */
|
|
70
|
+
async function approveBy(user: TestUser, orderId: string) {
|
|
71
|
+
const task = await taskOf(user, orderId);
|
|
72
|
+
await purchase.poRoute.engine(env.kernel).decide(task.id, user.id, env.tenantId, 'approved', undefined, task.revision);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 以采购员安全读侧核对业务状态,不从任务通过的局部结果推断整张单据已经批准。 */
|
|
76
|
+
async function orderStatus(orderId: string) { return (await env.kernel.read.get(purchase.PurchaseOrder, orderId, await buyer.ctx())).status; }
|
|
36
77
|
/** 退回不覆盖旧审批证据;重新提交产生新轮次,历史保留上次建议且最新轮次继续经理待办。 */
|
|
37
78
|
it('AI 退回修改后保存用途并重新提交,形成新轮次且先重新经过 AI 节点', async () => {
|
|
38
79
|
decision = 'return'; const order = await submit();
|
|
@@ -49,7 +90,7 @@ it('AI 通过只推进到经理节点,经理通过后才执行批准命令及
|
|
|
49
90
|
decision = 'pass'; const order = await submit(), engine = purchase.poRoute.engine(env.kernel);
|
|
50
91
|
expect((await env.kernel.read.get(purchase.PurchaseOrder, order.id, await buyer.ctx())).status).toBe('pending_approval');
|
|
51
92
|
const task = (await engine.tasksOf(reviewer.id, env.tenantId)).find(t => t.businessKey === order.id)!;
|
|
52
|
-
await engine.decide(task.id, reviewer.id, env.tenantId, 'approved');
|
|
93
|
+
await engine.decide(task.id, reviewer.id, env.tenantId, 'approved', undefined, task.revision);
|
|
53
94
|
expect((await env.kernel.read.get(purchase.PurchaseOrder, order.id, await buyer.ctx())).status).toBe('approved');
|
|
54
95
|
expect((await env.query('select status from core.inventory_lock where id = $1', [order.id]))[0].status).toBe('held');
|
|
55
96
|
});
|
|
@@ -60,3 +101,124 @@ it('AI 转人工保留在本节点,人工可退回修改', async () => {
|
|
|
60
101
|
await engine.decide(task.id, reviewer.id, env.tenantId, 'returned', '请补充审批材料');
|
|
61
102
|
expect((await env.kernel.read.get(purchase.PurchaseOrder, order.id, await buyer.ctx())).status).toBe('draft');
|
|
62
103
|
});
|
|
104
|
+
|
|
105
|
+
/** 草稿允许暂存,但编辑和提交都拒绝不完整、自审、重复或未登记名单,失败不产生审批实例。 */
|
|
106
|
+
it.each(['无人', '重复', '本人', '未登记', '关闭仍有名单'] as const)('专业复核名单%s时,设置和提交均拒绝且保留草稿', async boundary => {
|
|
107
|
+
const reviewerIds = boundary === '无人' ? [] : boundary === '重复' ? [specialistA.id, specialistA.id]
|
|
108
|
+
: boundary === '本人' ? [buyer.id] : boundary === '未登记' ? [randomUUID()] : [specialistA.id];
|
|
109
|
+
const needsSpecialistReview = boundary !== '关闭仍有名单';
|
|
110
|
+
const domainCode = { 无人: 'REVIEWERS_REQUIRED', 重复: 'DUPLICATE_REVIEWERS', 本人: 'REVIEWER_IS_APPLICANT', 未登记: 'REVIEWER_UNAVAILABLE', 关闭仍有名单: 'REVIEWERS_NOT_REQUIRED' }[boundary];
|
|
111
|
+
const order = await draft({ needsSpecialistReview, reviewerIds });
|
|
112
|
+
expect(await env.callAs(await buyer.ctx(), 'PurchaseOrder.setReviewers', { id: order.id, needsSpecialistReview, reviewerIds })).toMatchObject({ ok: false, domainCode });
|
|
113
|
+
expect(await env.callAs(await buyer.ctx(), 'PurchaseOrder.submit', { id: order.id })).toMatchObject({ ok: false, domainCode });
|
|
114
|
+
expect(await orderStatus(order.id)).toBe('draft');
|
|
115
|
+
expect(await env.query('select id from flow.approval_instance where business_key=$1', [order.id])).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/** 名录通过业务 read 开放,用户无需 User.read;提交会重查设置后退役的成员,防止旧草稿带入新流程。 */
|
|
119
|
+
it('采购员无需系统用户读权限即可配置协作人,但提交拒绝已退役名单', async () => {
|
|
120
|
+
const person = await env.user({ caps: [] });
|
|
121
|
+
const member = await env.kernel.execute('ApprovalMember.create', { userId: person.id, displayName: '临时合同复核', specialty: '合同条款' }, env.system());
|
|
122
|
+
expect((await env.kernel.read.get(purchase.ApprovalMember, member.id, await buyer.ctx())).displayName).toBe('临时合同复核');
|
|
123
|
+
const order = await draft();
|
|
124
|
+
await env.kernel.execute('PurchaseOrder.setReviewers', { id: order.id, needsSpecialistReview: true, reviewerIds: [person.id] }, await buyer.ctx());
|
|
125
|
+
await env.kernel.execute('ApprovalMember.retire', { id: member.id }, env.system());
|
|
126
|
+
expect(await env.callAs(await buyer.ctx(), 'PurchaseOrder.submit', { id: order.id })).toMatchObject({ ok: false, domainCode: 'REVIEWER_UNAVAILABLE' });
|
|
127
|
+
expect(await orderStatus(order.id)).toBe('draft');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/** 两位字段选人均须完成;第一票不能提前触发批准命令和库存锁,最终签署者仍走真实领域约束。 */
|
|
131
|
+
it('经理后开启所选专业人员 all 会签,全部通过才批准并锁库存', async () => {
|
|
132
|
+
decision = 'pass'; const order = await submit({ needsSpecialistReview: true, reviewerIds: [specialistA.id, specialistB.id] });
|
|
133
|
+
await approveBy(reviewer, order.id);
|
|
134
|
+
const a = await taskOf(specialistA, order.id), b = await taskOf(specialistB, order.id);
|
|
135
|
+
expect(a.step).toBe(2); expect(b.step).toBe(2); expect(a.id).not.toBe(b.id);
|
|
136
|
+
expect(a.actions).toEqual({ transfer: true, delegate: true, addSign: true });
|
|
137
|
+
// 历史标题来自当前实例的步骤快照,帮助详情区分部门审核与专业会签;不读取或展示 AI 策略全文。
|
|
138
|
+
const history = await purchase.poRoute.engine(env.kernel).history('PurchaseOrder', order.id, await buyer.ctx());
|
|
139
|
+
expect(history[0].steps.map(step => step.title)).toEqual(['AI 材料预审', '部门经理审批', '专业人员会签']);
|
|
140
|
+
await approveBy(specialistA, order.id);
|
|
141
|
+
expect(await orderStatus(order.id)).toBe('pending_approval');
|
|
142
|
+
expect(await env.query('select id from core.inventory_lock where id=$1', [order.id])).toHaveLength(0);
|
|
143
|
+
await approveBy(specialistB, order.id);
|
|
144
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
145
|
+
expect((await env.query('select status from core.inventory_lock where id=$1', [order.id]))[0].status).toBe('held');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/** gt 阈值必须保持严格大于;恰好 5 万元时经理的同额 ceiling 足够,不创建财务候选任务。 */
|
|
149
|
+
it('恰好 50000 元经理通过后直接批准,不产生财务候选待办', async () => {
|
|
150
|
+
decision = 'pass'; const order = await submit({ amount: '50000.00' });
|
|
151
|
+
await approveBy(reviewer, order.id);
|
|
152
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
153
|
+
expect((await purchase.poRoute.engine(env.kernel).candidateTasks(financeA.id, env.tenantId)).some(task => task.businessKey === order.id)).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/** 同一大额采购仅建一个候选任务,领取/释放保留 revision,另一财务领取并批准后才产生库存锁。 */
|
|
157
|
+
it('超过 50000 元进入财务共享候选池,释放后另一人领取并完成真实采购审批', async () => {
|
|
158
|
+
decision = 'pass'; const order = await submit({ amount: '50000.01' }), engine = purchase.poRoute.engine(env.kernel);
|
|
159
|
+
await approveBy(reviewer, order.id);
|
|
160
|
+
const a = (await engine.candidateTasks(financeA.id, env.tenantId)).find(task => task.businessKey === order.id)!;
|
|
161
|
+
const b = (await engine.candidateTasks(financeB.id, env.tenantId)).find(task => task.businessKey === order.id)!;
|
|
162
|
+
expect(a.id).toBe(b.id); expect(a.assignment).toBe('candidate'); expect(a.assigneeId).toBeNull();
|
|
163
|
+
await expect(engine.decide(a.id, financeA.id, env.tenantId, 'approved', undefined, a.revision)).rejects.toMatchObject({ code: 'NOT_FOUND' });
|
|
164
|
+
await engine.act(a.id, financeA.id, env.tenantId, { action: 'claim', expectedRevision: a.revision, reason: '财务甲领取大额采购' });
|
|
165
|
+
const claimed = await taskOf(financeA, order.id);
|
|
166
|
+
await engine.act(a.id, financeA.id, env.tenantId, { action: 'unclaim', expectedRevision: claimed.revision, reason: '转回共享池由在岗财务受理' });
|
|
167
|
+
await expect(engine.act(b.id, financeB.id, env.tenantId, { action: 'claim', expectedRevision: b.revision, reason: '旧列表尝试领取' })).rejects.toMatchObject({ code: 'CONFLICT' });
|
|
168
|
+
const available = (await engine.candidateTasks(financeB.id, env.tenantId)).find(task => task.businessKey === order.id)!;
|
|
169
|
+
await engine.act(available.id, financeB.id, env.tenantId, { action: 'claim', expectedRevision: available.revision, reason: '财务乙刷新后领取' });
|
|
170
|
+
expect(await orderStatus(order.id)).toBe('pending_approval');
|
|
171
|
+
await approveBy(financeB, order.id);
|
|
172
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
173
|
+
expect((await env.query('select status from core.inventory_lock where id=$1', [order.id]))[0].status).toBe('held');
|
|
174
|
+
const history = await engine.taskHistory('PurchaseOrder', order.id, await buyer.ctx(), { limit: 100 });
|
|
175
|
+
expect(history.items.map(entry => entry.action)).toEqual(['approved', 'claim', 'unclaim', 'claim', 'approved']);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/** 转办在本例经理节点真实开放,原人不能用旧待办决定;最终采购命令使用接手人的身份。 */
|
|
179
|
+
it('经理将采购审核转办给协作人,由接手人完成批准并留下转办和决定记录', async () => {
|
|
180
|
+
decision = 'pass'; const order = await submit(), engine = purchase.poRoute.engine(env.kernel), task = await taskOf(reviewer, order.id);
|
|
181
|
+
await engine.act(task.id, reviewer.id, env.tenantId, { action: 'transfer', targetUserId: specialistA.id, expectedRevision: task.revision, reason: '设备细节交专业人员审核' });
|
|
182
|
+
await expect(engine.decide(task.id, reviewer.id, env.tenantId, 'approved', undefined, task.revision)).rejects.toMatchObject({ code: 'NOT_FOUND' });
|
|
183
|
+
await approveBy(specialistA, order.id);
|
|
184
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
185
|
+
const history = await engine.taskHistory('PurchaseOrder', order.id, await buyer.ctx());
|
|
186
|
+
expect(history.items.map(entry => entry.action)).toEqual(['transfer', 'approved']);
|
|
187
|
+
expect(await env.audit({ aggregateId: order.id, command: 'PurchaseOrder.approve', kind: 'write' })).toMatchObject([{ on_behalf_of: specialistA.id }]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/** 委派用于协助核验,接手人只能完成归还;原经理作最终决定,避免把委派误作转办。 */
|
|
191
|
+
it('经理委派采购材料核查,协作人归还后仍由经理正式批准', async () => {
|
|
192
|
+
decision = 'pass'; const order = await submit(), engine = purchase.poRoute.engine(env.kernel), task = await taskOf(reviewer, order.id);
|
|
193
|
+
await engine.act(task.id, reviewer.id, env.tenantId, { action: 'delegate', targetUserId: specialistA.id, expectedRevision: task.revision, reason: '请核对设备技术材料' });
|
|
194
|
+
const delegated = await taskOf(specialistA, order.id);
|
|
195
|
+
expect(delegated).toMatchObject({ ownerId: reviewer.id, delegation: 'pending' });
|
|
196
|
+
await expect(engine.decide(delegated.id, specialistA.id, env.tenantId, 'approved', undefined, delegated.revision)).rejects.toMatchObject({ code: 'INVALID_TRANSITION' });
|
|
197
|
+
await engine.act(delegated.id, specialistA.id, env.tenantId, { action: 'resolve', expectedRevision: delegated.revision, reason: '已核对技术材料并归还' });
|
|
198
|
+
expect(await orderStatus(order.id)).toBe('pending_approval');
|
|
199
|
+
await approveBy(reviewer, order.id);
|
|
200
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
201
|
+
const history = await engine.taskHistory('PurchaseOrder', order.id, await buyer.ctx());
|
|
202
|
+
expect(history.items.map(entry => entry.action)).toEqual(['delegate', 'resolve', 'approved']);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/** 三种加签共用真实采购单:首票后都不能建锁,前/后置的等待状态必须按指定签序开放。 */
|
|
206
|
+
it.each(['before', 'parallel', 'after'] as const)('经理节点 %s 加签要求原审批与附加意见都完成才批准采购', async placement => {
|
|
207
|
+
decision = 'pass'; const order = await submit(), engine = purchase.poRoute.engine(env.kernel), task = await taskOf(reviewer, order.id);
|
|
208
|
+
await engine.act(task.id, reviewer.id, env.tenantId, { action: 'addSign', userIds: [specialistA.id], placement, expectedRevision: task.revision, reason: '采购设备补充专业意见' });
|
|
209
|
+
if (placement === 'before') {
|
|
210
|
+
expect((await taskOf(reviewer, order.id, 'waiting')).id).toBe(task.id);
|
|
211
|
+
await approveBy(specialistA, order.id);
|
|
212
|
+
} else {
|
|
213
|
+
if (placement === 'after') {
|
|
214
|
+
const waiting = await taskOf(specialistA, order.id, 'waiting');
|
|
215
|
+
await expect(engine.decide(waiting.id, specialistA.id, env.tenantId, 'approved', undefined, waiting.revision)).rejects.toMatchObject({ code: 'INVALID_TRANSITION' });
|
|
216
|
+
}
|
|
217
|
+
await approveBy(reviewer, order.id);
|
|
218
|
+
}
|
|
219
|
+
expect(await orderStatus(order.id)).toBe('pending_approval');
|
|
220
|
+
expect(await env.query('select id from core.inventory_lock where id=$1', [order.id])).toHaveLength(0);
|
|
221
|
+
await approveBy(placement === 'before' ? reviewer : specialistA, order.id);
|
|
222
|
+
expect(await orderStatus(order.id)).toBe('approved');
|
|
223
|
+
expect((await env.query('select status from core.inventory_lock where id=$1', [order.id]))[0].status).toBe('held');
|
|
224
|
+
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 采购单声明集中列出申请材料、专业复核名单、状态、范围、命令和事件,由 API、worker 和生成器统一消费。
|
|
3
|
+
* 草稿可编辑专业复核名单,提交重新核对协作名录;handler 仅读取声明依赖,数据库写入、版本与审计由内核完成。
|
|
4
|
+
* 审批结论仍以 process 命令执行职责分离、额度和库存检查;选入名录或路由不授予权限。
|
|
5
|
+
*/
|
|
3
6
|
import {
|
|
4
7
|
aggregate,
|
|
8
|
+
bool,
|
|
5
9
|
ceiling,
|
|
6
10
|
command,
|
|
7
11
|
emit,
|
|
@@ -17,9 +21,11 @@ import {
|
|
|
17
21
|
sod,
|
|
18
22
|
states,
|
|
19
23
|
text,
|
|
24
|
+
type ReadRepo,
|
|
20
25
|
} from '@yss123/cage-kernel';
|
|
21
26
|
import { z } from 'zod';
|
|
22
27
|
import { Inventory } from '../inventory/index.js';
|
|
28
|
+
import { ApprovalMember } from './approval-members.js';
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* 采购单生命周期:草稿提交后等待审批,通过后等待外部收货;退回修改重新进入草稿。
|
|
@@ -46,6 +52,10 @@ export const PurchaseOrder = aggregate('PurchaseOrder', {
|
|
|
46
52
|
amount: money(),
|
|
47
53
|
/** 可为空的用途说明;创建可先存草稿,AI 预审会判断是否足以支撑审批。 */
|
|
48
54
|
reason: text(2000).optional(),
|
|
55
|
+
/** 是否增加专业会签;旧记录 null/缺省按 false,不改变已经启动的审批实例。 */
|
|
56
|
+
needsSpecialistReview: bool().optional(),
|
|
57
|
+
/** 提交快照的专业复核用户 UUID;旧记录可为空,启用复核时必须为 1–5 个不同且非创建人的有效名录用户。 */
|
|
58
|
+
reviewerIds: list(id()).optional(),
|
|
49
59
|
/** 申请明细快照:sku 为物料编码,qty 为整数件数;输入额外要求每行数量为正。 */
|
|
50
60
|
items: list({ sku: text(64), qty: int() }),
|
|
51
61
|
/** ERP 成功响应后回填的外部单号,尚未同步时为空;不作为采购单生命周期状态。 */
|
|
@@ -76,6 +86,22 @@ export const OrderReceived = event('OrderReceived', { orderId: id() });
|
|
|
76
86
|
/** 命令输入比持久化 int 原语更具体:至少一行且每行数量为正,避免零数量申请进入审批。 */
|
|
77
87
|
const itemsInput = z.array(z.object({ sku: z.string(), qty: z.number().int().positive() })).min(1);
|
|
78
88
|
|
|
89
|
+
/**
|
|
90
|
+
* 在设置和提交时核对专业复核名单,防止界面被绕过、名录退役或重复账号减少会签人数。
|
|
91
|
+
* disabled/范围/批准能力仍由审批运行时处理;此处只按当前命令声明的名录读依赖判断业务成员 active。
|
|
92
|
+
* 关闭专业复核必须传空名单;旧记录 null 等价 false/[]。最多五人使逐人查询有明确上限。
|
|
93
|
+
*/
|
|
94
|
+
async function validateReviewers(needsReview: boolean | null | undefined, ids: string[] | null | undefined, creator: string, members: ReadRepo<typeof ApprovalMember>) {
|
|
95
|
+
const selected = ids ?? [];
|
|
96
|
+
if (!needsReview) return selected.length ? fail('REVIEWERS_NOT_REQUIRED', '未启用专业复核时请清空复核人员') : undefined;
|
|
97
|
+
if (selected.length < 1 || selected.length > 5) return fail('REVIEWERS_REQUIRED', '专业复核需选择 1–5 位协作人');
|
|
98
|
+
if (new Set(selected).size !== selected.length) return fail('DUPLICATE_REVIEWERS', '同一人不能重复承担专业会签');
|
|
99
|
+
if (selected.includes(creator)) return fail('REVIEWER_IS_APPLICANT', '提单人不能担任本单专业复核人');
|
|
100
|
+
for (const userId of selected) {
|
|
101
|
+
if (!await members.findOne({ userId, status: 'active' })) return fail('REVIEWER_UNAVAILABLE', '所选专业复核人不在有效协作名录中');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
/**
|
|
80
106
|
* 保存草稿;金额格式、字段长度和不变量由内核随后统一校验,handler 无额外副作用。
|
|
81
107
|
* deptId 可指定但仍须满足创建范围;attachments 为已完成上传 ID,省略时初始化为空集合。
|
|
@@ -89,10 +115,28 @@ export const create = command(PurchaseOrder, {
|
|
|
89
115
|
items: itemsInput,
|
|
90
116
|
supplierPhone: z.string().optional(),
|
|
91
117
|
reason: z.string().max(2000).optional(),
|
|
118
|
+
needsSpecialistReview: z.boolean().optional(),
|
|
119
|
+
reviewerIds: z.array(z.string().uuid()).max(5).optional(),
|
|
92
120
|
deptId: z.string().uuid().optional(),
|
|
93
121
|
attachments: z.array(z.string().uuid()).max(10).optional(),
|
|
94
122
|
}),
|
|
95
|
-
|
|
123
|
+
// 草稿允许先保存未选完的复核需求;提交才要求完整名单,新记录统一 false/[],旧记录兼容 null。
|
|
124
|
+
handler: (o) => ({ ...o, needsSpecialistReview: o.needsSpecialistReview ?? false, reviewerIds: o.reviewerIds ?? [] }),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 在 draft 中调整本单专业复核需求和名单;需要 PurchaseOrder.setReviewers,不能直接修改待审批快照。
|
|
129
|
+
* 输入保存前核对 1–5 位不同且非提单人的 active 名录成员;关闭时用 false/[],失败整次回滚。
|
|
130
|
+
* 提交还会重新查询名录,以拒绝保存后退役的成员;名录不授予对单据的读取、批准能力或额度。
|
|
131
|
+
*/
|
|
132
|
+
export const setReviewers = command(PurchaseOrder, {
|
|
133
|
+
name: 'setReviewers', description: '设置采购单专业复核需求和人员', from: 'draft', to: null,
|
|
134
|
+
input: z.object({ id: z.string().uuid(), needsSpecialistReview: z.boolean(), reviewerIds: z.array(z.string().uuid()).max(5) }),
|
|
135
|
+
reads: { members: ApprovalMember },
|
|
136
|
+
handler: async (draft, input, { members }) => {
|
|
137
|
+
const invalid = await validateReviewers(input.needsSpecialistReview, input.reviewerIds, draft.createdBy, members);
|
|
138
|
+
return invalid ?? { needsSpecialistReview: input.needsSpecialistReview, reviewerIds: input.reviewerIds };
|
|
139
|
+
},
|
|
96
140
|
});
|
|
97
141
|
|
|
98
142
|
// 退回后的说明只在草稿状态修改,保留提交时的版本校验和原始审计。
|
|
@@ -119,12 +163,19 @@ export const setAttachments = command(PurchaseOrder, {
|
|
|
119
163
|
handler: (_draft, input) => ({ attachments: input.attachments }),
|
|
120
164
|
});
|
|
121
165
|
|
|
122
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* 提交前重查专业复核名单,避免草稿保存后退役的成员进入新流程;缺省旧字段按无需复核处理。
|
|
168
|
+
* 成功只改变状态并记录 OrderSubmitted,实例由 worker 消费后创建;HTTP 成功不表示审批完成。
|
|
169
|
+
*/
|
|
123
170
|
export const submit = command(PurchaseOrder, {
|
|
124
171
|
from: 'draft',
|
|
125
172
|
to: 'pending_approval',
|
|
126
173
|
emits: [OrderSubmitted],
|
|
127
|
-
|
|
174
|
+
reads: { members: ApprovalMember },
|
|
175
|
+
handler: async (o, _input, { members }) => {
|
|
176
|
+
if (o.items.length === 0) return fail('NO_ITEMS');
|
|
177
|
+
return await validateReviewers(o.needsSpecialistReview, o.reviewerIds, o.createdBy, members) ?? o;
|
|
178
|
+
},
|
|
128
179
|
});
|
|
129
180
|
|
|
130
181
|
/**
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"build:app": "pnpm --filter @app/domain build && pnpm --filter @app/api build && pnpm --filter @app/web build"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@yss123/cage-cli": "0.3.
|
|
15
|
-
"@yss123/cage-testkit": "0.3.
|
|
16
|
-
"@yss123/cage-lint": "0.3.
|
|
14
|
+
"@yss123/cage-cli": "0.3.2",
|
|
15
|
+
"@yss123/cage-testkit": "0.3.2",
|
|
16
|
+
"@yss123/cage-lint": "0.3.2",
|
|
17
17
|
"@types/node": "^24.3.0",
|
|
18
18
|
"typescript": "~5.9.3",
|
|
19
19
|
"vitest": "^4.1.12",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@yss123/cage-kernel": "0.3.
|
|
18
|
-
"@yss123/cage-admin": "0.3.
|
|
19
|
-
"@yss123/cage-approval": "0.3.
|
|
20
|
-
"@yss123/cage-connectors": "0.3.
|
|
21
|
-
"@yss123/cage-ai": "0.3.
|
|
17
|
+
"@yss123/cage-kernel": "0.3.2",
|
|
18
|
+
"@yss123/cage-admin": "0.3.2",
|
|
19
|
+
"@yss123/cage-approval": "0.3.2",
|
|
20
|
+
"@yss123/cage-connectors": "0.3.2",
|
|
21
|
+
"@yss123/cage-ai": "0.3.2",
|
|
22
22
|
"zod": "^4.1.5"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# 业务开发指南(给 Codex 等编程工具)
|
|
2
|
-
|
|
3
|
-
这是 `create-cage-app` 产物自带的业务开发资料。目标是让 Codex 等编程工具只读当前项目,就能增加业务对象、操作、审批、报表和页面。先读根目录 `AGENTS.md`,再按任务定位本页资料;无需另行克隆 Cage 框架仓库。
|
|
4
|
-
|
|
5
|
-
## 先认识当前应用
|
|
6
|
-
|
|
7
|
-
| 目录 | 开发业务时放什么 |
|
|
8
|
-
| --- | --- |
|
|
9
|
-
| `packages/domain/src/modules/<业务>` | 按业务集中聚合、命令、事件、审批、查询与任务;跨模块反应放 workflows,外部协议放 integrations |
|
|
10
|
-
| `packages/contracts/src/aggregates`、`queries` | 按聚合目录生成 API 与类型;通过根 @app/contracts 使用,通过 gen 更新 |
|
|
11
|
-
| `apps/api/src` | API 与 worker 的薄启动入口;普通业务不在这里写路由 |
|
|
12
|
-
| `apps/web/src/app`、`modules`、`shared` | 应用组合 / 业务页面与交互 / 无具体业务归属的共用代码 |
|
|
13
|
-
| `db/migrations` | 生成后审查的迁移;更新已有数据库中的表 |
|
|
14
|
-
| `.env` / `.env.example` | 本机配置 / 不含真实凭证的配置说明 |
|
|
15
|
-
| `docs/ai`、`docs/errors` | 业务开发方法与错误排查 |
|
|
16
|
-
|
|
17
|
-
下文路径均相对**应用根目录**,即生成项目的 `package.json` 所在目录。框架包使用 `@yss123/cage-*`,作为依赖安装;当前项目不应复制或重新实现这些包。框架维护者在源码仓库读本页时,应用根目录为 `examples/purchase-admin`。
|
|
18
|
-
|
|
19
|
-
创建时可选 `blank`(空白业务项目)或 `purchase`(采购示例项目)。空白项目只有组织基础模型,新增客户业务写入根目录的 `packages/domain/src`;采购参考源码放在 `docs/examples/purchase-admin`,不参与注册、编译或建表。阅读本指南中的采购路径时,空白项目在该参考目录下查找,采购项目直接在应用根目录查找。先读本项目 AGENTS.md 的模板说明,不能把参考代码误当成已运行的业务。
|
|
20
|
-
|
|
21
|
-
## 推荐阅读顺序
|
|
22
|
-
|
|
23
|
-
| 顺序 | 资料 | 读完应能回答 |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| 1 | [架构与执行模型](architecture.md) | 聚合是什么?为什么写入必须走命令?数据库由谁连接? |
|
|
26
|
-
| 2 | [工程目录与代码组织](project-structure.md) | 新业务放哪里?页面如何拆分?哪些依赖会被门禁阻止? |
|
|
27
|
-
| 3 | [原语与最小业务](primitives.md) | 如何新增字段、CRUD 命令、读依赖、事件和查询? |
|
|
28
|
-
| 编码必读 | [详细中文注释规范](commenting.md) | 如何说明文件职责、API、字段、关键流程、权限/事务与失败边界,并同步维护注释? |
|
|
29
|
-
| 开发前 | [本体查询与 AI 上下文](ontology-context.md) | 按需求查哪些代码?为什么关联?候选、证据与子图边界怎么判断? |
|
|
30
|
-
| 开发与排错 | [能力发现与状态诊断](diagnostics.md) | 公开 API 有什么?注册/契约/进程哪里过期?如何查执行时间线和授权拒绝? |
|
|
31
|
-
| 按需 | [对象关系](relations.md) | 如何在 fields 中声明一对一、一对多、多对多,修改关联并分页读取? |
|
|
32
|
-
| 前端必读 | [业务路由与浏览器验收](routing.md) | 列表/详情如何获得独立 URL?登录回跳、404、/admin 和部署回退怎样验收? |
|
|
33
|
-
| 原生 AI | [智能助理与 AI 审批](intelligence.md) | 如何启用后端、配置三态审核、生成工具、传入页面快照和接入 SDK? |
|
|
34
|
-
| 4 | [标准采购范例导读](purchase-example.md) | 一次提交如何进入审批、锁库存、推 ERP、释放资源? |
|
|
35
|
-
| 5 | [开发步骤](workflow.md) | 改哪个文件?运行哪些命令?检查结果在哪里? |
|
|
36
|
-
| 6 | [业务任务配方](recipes.md) | 如何从采购演示换成自己的业务?常见需求如何交给 AI? |
|
|
37
|
-
| 7 | [业务页导入导出](transfers.md) | 如何提交任务、准备 CSV、查询状态、下载和展示报告? |
|
|
38
|
-
| 8 | [排错方法](troubleshooting.md)、[错误码册](../errors/README.md) | 如何区分配置、权限、业务失败和异步死信? |
|
|
39
|
-
| 按需 | [文件字段与断点续传](files.md) | 私有上传、分片恢复、命令绑定、下载授权和存储部署 |
|
|
40
|
-
|
|
41
|
-
第一次先读架构、目录规范和原语,再沿范例读代码。接到业务需求后先运行 `pnpm exec cage context "需求关键词"`,确认候选入口,再按源码位置阅读对应实现。
|
|
42
|
-
|
|
43
|
-
框架源码与脚手架应用都要求详细中文注释。新建或修改代码时按[注释规范](commenting.md)同步说明文件职责与调用链、导出 API/类型字段、组件/hooks 和关键流程的“做什么、为什么、怎么做、边界”;修改既有行为时连同相关上下游说明一起更新。注释属于交付内容,不能用“代码自解释”免除,也不能以逐行翻译或注释字数代替质量。
|
|
44
|
-
|
|
45
|
-
不确定框架支持什么时先用 `pnpm exec cage capabilities 关键词 --format json` 查看当前版本公开签名;功能未生效时用 `pnpm exec cage doctor` 区分注册、生成、构建、迁移、授权和实际进程。未检查项为 `unknown`,不能作为验证通过。
|
|
46
|
-
|
|
47
|
-
遇到 AI 审批、业务助理、随页助理、模型管理、性格或 Responses 需求,先读[原生智能化指南](intelligence.md)。这些是框架可选能力:后端循环、工具、会话与偏好记忆、HTTP 和 SDK 由框架提供;应用配置声明、策略、授权、页面快照及界面。入口不要求每次加载整份本体或全部指南,按本次任务读取相关章节即可。
|
|
48
|
-
|
|
49
|
-
## 按任务找入口
|
|
50
|
-
|
|
51
|
-
| 用户需求 | 优先修改 | 需要一起考虑 |
|
|
52
|
-
| --- | --- | --- |
|
|
53
|
-
| 增加业务对象、字段 | `packages/domain/src/modules/<业务>` | 模块公开入口、范围字段、不变量、契约、迁移、角色授权、图谱中文名 |
|
|
54
|
-
| 增加对象关联 | `fields` 的 ref / hasMany / manyToMany;见[关系指南](relations.md) | 归属命令、双侧权限与范围、目标分页、迁移;纯关联不手写中间表聚合 |
|
|
55
|
-
| 增加操作按钮 | 领域命令 + `apps/web/src/modules/<业务>` | 状态迁移、调用身份、错误展示、提交幂等键 |
|
|
56
|
-
| 增加列表、详情或新页面 | 模块 pages + app 路由表 | URL、查询条件、深链接、刷新、前进后退、登录回跳;见[路由指南](routing.md) |
|
|
57
|
-
| 增加报表 | 所属领域模块 `queries.ts` | `reads`、`safe.*`、参数化、调用人 read 权限 |
|
|
58
|
-
| 修改审批顺序/条件/选人 | 所属领域模块 `approvals.ts` | `routable`、角色/负责人数据、结论命令约束 |
|
|
59
|
-
| AI 预审、退回修改、转人工 | 现有 `step` 的 `kind: 'ai'` + `approval` 绑定 | 策略、取证字段/查询、`automatedBy` 角色、结论命令及 `handoff`;见[智能化指南](intelligence.md) |
|
|
60
|
-
| 业务助理、随页悬浮窗 | domain 导出 `ai()`;页面接 `useAssistant` 与 `useAiPageContext` | 业务权限、页面快照、流式 Markdown、写入确认;界面由应用实现 |
|
|
61
|
-
| 模型动态配置、Responses、性格 | `managedModel` 与框架 Admin 页面 | 部署加密密钥、管理权限、实际网关能力、性格仅影响表达 |
|
|
62
|
-
| 取消后释放占用 | `workflows/<相关业务>.ts` + 领域事件/命令 | 所有离开持有状态的路径、同事务/异步选择 |
|
|
63
|
-
| 接 ERP 或其他系统 | `integrations/<系统名>.ts` | schema、密钥名、白名单、幂等、回调验签、重试 |
|
|
64
|
-
| 业务页导入/导出 | 业务前端调用已有接口 | 任务状态、worker、权限、导入目标命令;不固定到系统管理页 |
|
|
65
|
-
| 文件上传/业务附件 | file/files 字段 + 用户命令 + 客户端上传任务 | 断点续传、当前权限、命令绑定、私有存储及清理;见[文件指南](files.md) |
|
|
66
|
-
| 新业务没出现在图或 API | domain 统一入口 | 顶层 `export const`、导出名唯一、入口 re-export |
|
|
67
|
-
| 框架无法表达业务 | 先核对公开导出与范例 | 说明能力缺口;不要编造 API 或把业务分支塞进内核 |
|
|
68
|
-
|
|
69
|
-
## 三份协作材料各管什么
|
|
70
|
-
|
|
71
|
-
- **AGENTS.md** 是工作入口:必守边界、目录、顺序和验证要求。`<!-- cage:begin ... -->` 到 `<!-- cage:end -->` 为框架托管区,`cage sync` 更新它;项目自己的规则放在外面。
|
|
72
|
-
- **标准范例** 是可运行证据:采购模板包含完整运行链路,空白模板保留离线参考源码。按需学习其中模式,客户业务仍写在本项目自己的 domain 和页面中。
|
|
73
|
-
- **错误码册** 是诊断索引:按真实错误码查位置、触发原因、修复步骤和正反例;不要靠放开权限或关闭门禁“修好”报错。
|
|
74
|
-
|
|
75
|
-
`docs/ai` 与 `docs/errors` 已放入分发模板,创建项目时一起复制,可离线阅读。框架仓库中的标准范例使用根目录的规范文档,资料位置由应用 AGENTS.md 指明。
|
|
76
|
-
|
|
77
|
-
## 当前实现边界
|
|
78
|
-
|
|
79
|
-
以下区分可避免 agent 把方案中的愿景写成不存在的功能。
|
|
80
|
-
|
|
81
|
-
| 已有能力 | 使用边界 |
|
|
82
|
-
| --- | --- |
|
|
83
|
-
| 真实 PostgreSQL 连接、分账号、表结构迁移 | 连接已有数据库;`init` 不建库;不会自动识别并接管任意历史表 |
|
|
84
|
-
| 聚合/命令、能力与范围、掩码、审计、outbox、worker | handler 不做数据库/网络 IO;生产角色不会自动拿到新能力 |
|
|
85
|
-
| fields 中的 ref / hasMany / manyToMany | hasMany 为只读反查;manyToMany 自动中间表并随主对象事务写入,公开读取独立分页;双向集合不会自动互为镜像 |
|
|
86
|
-
| 本体抽取、中文检索/节点/路径/AI 上下文、图规则、图谱、生成、影响分析与门禁 | 上下文注明证据、源码和覆盖边界;名称与测试匹配是候选线索,不能证明任意 JS 正确;跳过用例仍需补足 |
|
|
87
|
-
| 有界集合 reactionEach、显式步骤 order | 稳定唯一键排序后串行调用目标命令;同事务整体回滚,异步链按 outbox 恢复;不是并行写表接口 |
|
|
88
|
-
| capabilities、doctor、timeline、explain、snapshot | 实际运行诊断需要受控会话与权限;未取得的证据为 unknown,授权解释不执行 handler |
|
|
89
|
-
| 代码审批路由与适配接口 | 顺序步骤、条件步骤、步骤内任一/全部审批;没有通用可视化流程设计器、任意 JS 选人、转签/加签 API |
|
|
90
|
-
| 原生 AI 审批与领域助理 | 现有 `step` 支持 `kind: 'ai'`;三态审核、自动工具、会话及偏好记忆可复用;需显式启用、授权并做模型质量验收 |
|
|
91
|
-
| Admin 模型与性格、助理 SDK | 动态模型连接和性格管理;Chat Completions / Responses 兼容性以实际服务为准;SDK 提供流式状态与页面快照,悬浮窗和 Markdown 由应用实现 |
|
|
92
|
-
| 连接器 fake、出站、入站验签、同步记录 | fake 不等于真实 ERP 联调,也不等于完整请求录制/回放平台 |
|
|
93
|
-
| 异步 CSV 导入/导出接口与客户端任务查询 | 界面由业务项目决定;导出结果不是可直接回导的命令输入模板 |
|
|
94
|
-
| 私有文件字段、流式分片上传、暂停与续传、范围下载 | 默认本地存储,提供对象存储适配接口;尚无内置 S3/OSS、签名直传、病毒扫描及文件共享 |
|
|
95
|
-
| 组织模型、系统管理界面 | 暂无移动部门(改上级)的完整支持、菜单配置页 |
|
|
96
|
-
| 库存锁采购范例 | 演示资源生命周期;不是仓库级并发库存扣减、退货或 ERP 撤销补偿系统 |
|
|
97
|
-
|
|
98
|
-
文档与实现冲突时,以当前代码和检查结果为依据,指出冲突并修正文档;不要默默扩展公开 API。规划、代码存在、构建通过、真实联调通过是不同状态,交付时分别说明。
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# 业务开发指南(给 Codex 等编程工具)
|
|
2
|
-
|
|
3
|
-
这是 `create-cage-app` 产物自带的业务开发资料。目标是让 Codex 等编程工具只读当前项目,就能增加业务对象、操作、审批、报表和页面。先读根目录 `AGENTS.md`,再按任务定位本页资料;无需另行克隆 Cage 框架仓库。
|
|
4
|
-
|
|
5
|
-
## 先认识当前应用
|
|
6
|
-
|
|
7
|
-
| 目录 | 开发业务时放什么 |
|
|
8
|
-
| --- | --- |
|
|
9
|
-
| `packages/domain/src/modules/<业务>` | 按业务集中聚合、命令、事件、审批、查询与任务;跨模块反应放 workflows,外部协议放 integrations |
|
|
10
|
-
| `packages/contracts/src/aggregates`、`queries` | 按聚合目录生成 API 与类型;通过根 @app/contracts 使用,通过 gen 更新 |
|
|
11
|
-
| `apps/api/src` | API 与 worker 的薄启动入口;普通业务不在这里写路由 |
|
|
12
|
-
| `apps/web/src/app`、`modules`、`shared` | 应用组合 / 业务页面与交互 / 无具体业务归属的共用代码 |
|
|
13
|
-
| `db/migrations` | 生成后审查的迁移;更新已有数据库中的表 |
|
|
14
|
-
| `.env` / `.env.example` | 本机配置 / 不含真实凭证的配置说明 |
|
|
15
|
-
| `docs/ai`、`docs/errors` | 业务开发方法与错误排查 |
|
|
16
|
-
|
|
17
|
-
下文路径均相对**应用根目录**,即生成项目的 `package.json` 所在目录。框架包使用 `@yss123/cage-*`,作为依赖安装;当前项目不应复制或重新实现这些包。框架维护者在源码仓库读本页时,应用根目录为 `examples/purchase-admin`。
|
|
18
|
-
|
|
19
|
-
创建时可选 `blank`(空白业务项目)或 `purchase`(采购示例项目)。空白项目只有组织基础模型,新增客户业务写入根目录的 `packages/domain/src`;采购参考源码放在 `docs/examples/purchase-admin`,不参与注册、编译或建表。阅读本指南中的采购路径时,空白项目在该参考目录下查找,采购项目直接在应用根目录查找。先读本项目 AGENTS.md 的模板说明,不能把参考代码误当成已运行的业务。
|
|
20
|
-
|
|
21
|
-
## 推荐阅读顺序
|
|
22
|
-
|
|
23
|
-
| 顺序 | 资料 | 读完应能回答 |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| 1 | [架构与执行模型](architecture.md) | 聚合是什么?为什么写入必须走命令?数据库由谁连接? |
|
|
26
|
-
| 2 | [工程目录与代码组织](project-structure.md) | 新业务放哪里?页面如何拆分?哪些依赖会被门禁阻止? |
|
|
27
|
-
| 3 | [原语与最小业务](primitives.md) | 如何新增字段、CRUD 命令、读依赖、事件和查询? |
|
|
28
|
-
| 编码必读 | [详细中文注释规范](commenting.md) | 如何说明文件职责、API、字段、关键流程、权限/事务与失败边界,并同步维护注释? |
|
|
29
|
-
| 开发前 | [本体查询与 AI 上下文](ontology-context.md) | 按需求查哪些代码?为什么关联?候选、证据与子图边界怎么判断? |
|
|
30
|
-
| 开发与排错 | [能力发现与状态诊断](diagnostics.md) | 公开 API 有什么?注册/契约/进程哪里过期?如何查执行时间线和授权拒绝? |
|
|
31
|
-
| 按需 | [对象关系](relations.md) | 如何在 fields 中声明一对一、一对多、多对多,修改关联并分页读取? |
|
|
32
|
-
| 前端必读 | [业务路由与浏览器验收](routing.md) | 列表/详情如何获得独立 URL?登录回跳、404、/admin 和部署回退怎样验收? |
|
|
33
|
-
| 原生 AI | [智能助理与 AI 审批](intelligence.md) | 如何启用后端、配置三态审核、生成工具、传入页面快照和接入 SDK? |
|
|
34
|
-
| 4 | [标准采购范例导读](purchase-example.md) | 一次提交如何进入审批、锁库存、推 ERP、释放资源? |
|
|
35
|
-
| 5 | [开发步骤](workflow.md) | 改哪个文件?运行哪些命令?检查结果在哪里? |
|
|
36
|
-
| 6 | [业务任务配方](recipes.md) | 如何从采购演示换成自己的业务?常见需求如何交给 AI? |
|
|
37
|
-
| 7 | [业务页导入导出](transfers.md) | 如何提交任务、准备 CSV、查询状态、下载和展示报告? |
|
|
38
|
-
| 8 | [排错方法](troubleshooting.md)、[错误码册](../errors/README.md) | 如何区分配置、权限、业务失败和异步死信? |
|
|
39
|
-
| 按需 | [文件字段与断点续传](files.md) | 私有上传、分片恢复、命令绑定、下载授权和存储部署 |
|
|
40
|
-
|
|
41
|
-
第一次先读架构、目录规范和原语,再沿范例读代码。接到业务需求后先运行 `pnpm exec cage context "需求关键词"`,确认候选入口,再按源码位置阅读对应实现。
|
|
42
|
-
|
|
43
|
-
框架源码与脚手架应用都要求详细中文注释。新建或修改代码时按[注释规范](commenting.md)同步说明文件职责与调用链、导出 API/类型字段、组件/hooks 和关键流程的“做什么、为什么、怎么做、边界”;修改既有行为时连同相关上下游说明一起更新。注释属于交付内容,不能用“代码自解释”免除,也不能以逐行翻译或注释字数代替质量。
|
|
44
|
-
|
|
45
|
-
不确定框架支持什么时先用 `pnpm exec cage capabilities 关键词 --format json` 查看当前版本公开签名;功能未生效时用 `pnpm exec cage doctor` 区分注册、生成、构建、迁移、授权和实际进程。未检查项为 `unknown`,不能作为验证通过。
|
|
46
|
-
|
|
47
|
-
遇到 AI 审批、业务助理、随页助理、模型管理、性格或 Responses 需求,先读[原生智能化指南](intelligence.md)。这些是框架可选能力:后端循环、工具、会话与偏好记忆、HTTP 和 SDK 由框架提供;应用配置声明、策略、授权、页面快照及界面。入口不要求每次加载整份本体或全部指南,按本次任务读取相关章节即可。
|
|
48
|
-
|
|
49
|
-
## 按任务找入口
|
|
50
|
-
|
|
51
|
-
| 用户需求 | 优先修改 | 需要一起考虑 |
|
|
52
|
-
| --- | --- | --- |
|
|
53
|
-
| 增加业务对象、字段 | `packages/domain/src/modules/<业务>` | 模块公开入口、范围字段、不变量、契约、迁移、角色授权、图谱中文名 |
|
|
54
|
-
| 增加对象关联 | `fields` 的 ref / hasMany / manyToMany;见[关系指南](relations.md) | 归属命令、双侧权限与范围、目标分页、迁移;纯关联不手写中间表聚合 |
|
|
55
|
-
| 增加操作按钮 | 领域命令 + `apps/web/src/modules/<业务>` | 状态迁移、调用身份、错误展示、提交幂等键 |
|
|
56
|
-
| 增加列表、详情或新页面 | 模块 pages + app 路由表 | URL、查询条件、深链接、刷新、前进后退、登录回跳;见[路由指南](routing.md) |
|
|
57
|
-
| 增加报表 | 所属领域模块 `queries.ts` | `reads`、`safe.*`、参数化、调用人 read 权限 |
|
|
58
|
-
| 修改审批顺序/条件/选人 | 所属领域模块 `approvals.ts` | `routable`、角色/负责人数据、结论命令约束 |
|
|
59
|
-
| AI 预审、退回修改、转人工 | 现有 `step` 的 `kind: 'ai'` + `approval` 绑定 | 策略、取证字段/查询、`automatedBy` 角色、结论命令及 `handoff`;见[智能化指南](intelligence.md) |
|
|
60
|
-
| 业务助理、随页悬浮窗 | domain 导出 `ai()`;页面接 `useAssistant` 与 `useAiPageContext` | 业务权限、页面快照、流式 Markdown、写入确认;界面由应用实现 |
|
|
61
|
-
| 模型动态配置、Responses、性格 | `managedModel` 与框架 Admin 页面 | 部署加密密钥、管理权限、实际网关能力、性格仅影响表达 |
|
|
62
|
-
| 取消后释放占用 | `workflows/<相关业务>.ts` + 领域事件/命令 | 所有离开持有状态的路径、同事务/异步选择 |
|
|
63
|
-
| 接 ERP 或其他系统 | `integrations/<系统名>.ts` | schema、密钥名、白名单、幂等、回调验签、重试 |
|
|
64
|
-
| 业务页导入/导出 | 业务前端调用已有接口 | 任务状态、worker、权限、导入目标命令;不固定到系统管理页 |
|
|
65
|
-
| 文件上传/业务附件 | file/files 字段 + 用户命令 + 客户端上传任务 | 断点续传、当前权限、命令绑定、私有存储及清理;见[文件指南](files.md) |
|
|
66
|
-
| 新业务没出现在图或 API | domain 统一入口 | 顶层 `export const`、导出名唯一、入口 re-export |
|
|
67
|
-
| 框架无法表达业务 | 先核对公开导出与范例 | 说明能力缺口;不要编造 API 或把业务分支塞进内核 |
|
|
68
|
-
|
|
69
|
-
## 三份协作材料各管什么
|
|
70
|
-
|
|
71
|
-
- **AGENTS.md** 是工作入口:必守边界、目录、顺序和验证要求。`<!-- cage:begin ... -->` 到 `<!-- cage:end -->` 为框架托管区,`cage sync` 更新它;项目自己的规则放在外面。
|
|
72
|
-
- **标准范例** 是可运行证据:采购模板包含完整运行链路,空白模板保留离线参考源码。按需学习其中模式,客户业务仍写在本项目自己的 domain 和页面中。
|
|
73
|
-
- **错误码册** 是诊断索引:按真实错误码查位置、触发原因、修复步骤和正反例;不要靠放开权限或关闭门禁“修好”报错。
|
|
74
|
-
|
|
75
|
-
`docs/ai` 与 `docs/errors` 已放入分发模板,创建项目时一起复制,可离线阅读。框架仓库中的标准范例使用根目录的规范文档,资料位置由应用 AGENTS.md 指明。
|
|
76
|
-
|
|
77
|
-
## 当前实现边界
|
|
78
|
-
|
|
79
|
-
以下区分可避免 agent 把方案中的愿景写成不存在的功能。
|
|
80
|
-
|
|
81
|
-
| 已有能力 | 使用边界 |
|
|
82
|
-
| --- | --- |
|
|
83
|
-
| 真实 PostgreSQL 连接、分账号、表结构迁移 | 连接已有数据库;`init` 不建库;不会自动识别并接管任意历史表 |
|
|
84
|
-
| 聚合/命令、能力与范围、掩码、审计、outbox、worker | handler 不做数据库/网络 IO;生产角色不会自动拿到新能力 |
|
|
85
|
-
| fields 中的 ref / hasMany / manyToMany | hasMany 为只读反查;manyToMany 自动中间表并随主对象事务写入,公开读取独立分页;双向集合不会自动互为镜像 |
|
|
86
|
-
| 本体抽取、中文检索/节点/路径/AI 上下文、图规则、图谱、生成、影响分析与门禁 | 上下文注明证据、源码和覆盖边界;名称与测试匹配是候选线索,不能证明任意 JS 正确;跳过用例仍需补足 |
|
|
87
|
-
| 有界集合 reactionEach、显式步骤 order | 稳定唯一键排序后串行调用目标命令;同事务整体回滚,异步链按 outbox 恢复;不是并行写表接口 |
|
|
88
|
-
| capabilities、doctor、timeline、explain、snapshot | 实际运行诊断需要受控会话与权限;未取得的证据为 unknown,授权解释不执行 handler |
|
|
89
|
-
| 代码审批路由与适配接口 | 顺序步骤、条件步骤、步骤内任一/全部审批;没有通用可视化流程设计器、任意 JS 选人、转签/加签 API |
|
|
90
|
-
| 原生 AI 审批与领域助理 | 现有 `step` 支持 `kind: 'ai'`;三态审核、自动工具、会话及偏好记忆可复用;需显式启用、授权并做模型质量验收 |
|
|
91
|
-
| Admin 模型与性格、助理 SDK | 动态模型连接和性格管理;Chat Completions / Responses 兼容性以实际服务为准;SDK 提供流式状态与页面快照,悬浮窗和 Markdown 由应用实现 |
|
|
92
|
-
| 连接器 fake、出站、入站验签、同步记录 | fake 不等于真实 ERP 联调,也不等于完整请求录制/回放平台 |
|
|
93
|
-
| 异步 CSV 导入/导出接口与客户端任务查询 | 界面由业务项目决定;导出结果不是可直接回导的命令输入模板 |
|
|
94
|
-
| 私有文件字段、流式分片上传、暂停与续传、范围下载 | 默认本地存储,提供对象存储适配接口;尚无内置 S3/OSS、签名直传、病毒扫描及文件共享 |
|
|
95
|
-
| 组织模型、系统管理界面 | 暂无移动部门(改上级)的完整支持、菜单配置页 |
|
|
96
|
-
| 库存锁采购范例 | 演示资源生命周期;不是仓库级并发库存扣减、退货或 ERP 撤销补偿系统 |
|
|
97
|
-
|
|
98
|
-
文档与实现冲突时,以当前代码和检查结果为依据,指出冲突并修正文档;不要默默扩展公开 API。规划、代码存在、构建通过、真实联调通过是不同状态,交付时分别说明。
|