@yss123/cage-cli 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +40 -2
- package/bin/cage.js +6 -0
- package/bin/create-cage-app.js +6 -0
- package/dist/analyze.d.ts +18 -0
- package/dist/analyze.js +28 -0
- package/dist/analyze.js.map +1 -0
- package/dist/baseline.d.ts +5 -0
- package/dist/baseline.js +50 -0
- package/dist/baseline.js.map +1 -0
- package/dist/budget.d.ts +7 -0
- package/dist/budget.js +52 -0
- package/dist/budget.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +46 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +72 -0
- package/dist/context.js +202 -0
- package/dist/context.js.map +1 -0
- package/dist/create-prompt.d.ts +5 -0
- package/dist/create-prompt.js +50 -0
- package/dist/create-prompt.js.map +1 -0
- package/dist/create.d.ts +18 -0
- package/dist/create.js +64 -0
- package/dist/create.js.map +1 -0
- package/dist/derive.d.ts +14 -0
- package/dist/derive.js +163 -0
- package/dist/derive.js.map +1 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +116 -0
- package/dist/dev.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +15 -0
- package/dist/env.js.map +1 -0
- package/dist/findings.d.ts +11 -0
- package/dist/findings.js +13 -0
- package/dist/findings.js.map +1 -0
- package/dist/gen/contracts.d.ts +6 -0
- package/dist/gen/contracts.js +188 -0
- package/dist/gen/contracts.js.map +1 -0
- package/dist/gen/index.d.ts +19 -0
- package/dist/gen/index.js +177 -0
- package/dist/gen/index.js.map +1 -0
- package/dist/gen/zod-code.d.ts +7 -0
- package/dist/gen/zod-code.js +163 -0
- package/dist/gen/zod-code.js.map +1 -0
- package/dist/graph-command.d.ts +11 -0
- package/dist/graph-command.js +67 -0
- package/dist/graph-command.js.map +1 -0
- package/dist/graph-model.d.ts +21 -0
- package/dist/graph-model.js +101 -0
- package/dist/graph-model.js.map +1 -0
- package/dist/graph-presentation.d.ts +11 -0
- package/dist/graph-presentation.js +25 -0
- package/dist/graph-presentation.js.map +1 -0
- package/dist/graph-query.d.ts +171 -0
- package/dist/graph-query.js +242 -0
- package/dist/graph-query.js.map +1 -0
- package/dist/graph-relations.d.ts +14 -0
- package/dist/graph-relations.js +142 -0
- package/dist/graph-relations.js.map +1 -0
- package/dist/graph-search.d.ts +21 -0
- package/dist/graph-search.js +63 -0
- package/dist/graph-search.js.map +1 -0
- package/dist/graph-snapshot.d.ts +11 -0
- package/dist/graph-snapshot.js +29 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +105 -0
- package/dist/graph.js.map +1 -0
- package/dist/impact.d.ts +34 -0
- package/dist/impact.js +235 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +6 -0
- package/dist/init.js +131 -0
- package/dist/init.js.map +1 -0
- package/dist/load.d.ts +6 -0
- package/dist/load.js +17 -0
- package/dist/load.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +221 -0
- package/dist/main.js.map +1 -0
- package/dist/migrate.d.ts +4 -0
- package/dist/migrate.js +30 -0
- package/dist/migrate.js.map +1 -0
- package/dist/ontology.d.ts +43 -0
- package/dist/ontology.js +280 -0
- package/dist/ontology.js.map +1 -0
- package/dist/process.d.ts +6 -0
- package/dist/process.js +39 -0
- package/dist/process.js.map +1 -0
- package/dist/project-structure.d.ts +8 -0
- package/dist/project-structure.js +94 -0
- package/dist/project-structure.js.map +1 -0
- package/dist/rules.d.ts +23 -0
- package/dist/rules.js +251 -0
- package/dist/rules.js.map +1 -0
- package/dist/static-check.d.ts +23 -0
- package/dist/static-check.js +222 -0
- package/dist/static-check.js.map +1 -0
- package/dist/sync.d.ts +5 -0
- package/dist/sync.js +118 -0
- package/dist/sync.js.map +1 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/upgrade.js +81 -0
- package/dist/upgrade.js.map +1 -0
- package/dist/verify.d.ts +7 -0
- package/dist/verify.js +88 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/graph-ui/app.js +1103 -0
- package/graph-ui/graph-model.js +100 -0
- package/graph-ui/graph-relations.js +141 -0
- package/graph-ui/graph-search.js +62 -0
- package/graph-ui/index.html +197 -0
- package/graph-ui/model.js +83 -0
- package/graph-ui/relations.js +2 -0
- package/graph-ui/structure.js +77 -0
- package/graph-ui/style.css +1719 -0
- package/package.json +60 -3
- package/templates/app/.dockerignore +10 -0
- package/templates/app/.env.example +61 -0
- package/templates/app/.github/workflows/cage.yml +44 -0
- package/templates/app/AGENTS.md +96 -0
- package/templates/app/Dockerfile +25 -0
- package/templates/app/README.md +88 -0
- package/templates/app/_gitignore +8 -0
- package/templates/app/apps/api/package.json +16 -0
- package/templates/app/apps/api/src/main.ts +10 -0
- package/templates/app/apps/api/src/worker.ts +14 -0
- package/templates/app/apps/api/tsconfig.json +5 -0
- package/templates/app/apps/web/index.html +13 -0
- package/templates/app/apps/web/package.json +22 -0
- package/templates/app/apps/web/src/app/app.tsx +18 -0
- package/templates/app/apps/web/src/app/styles/global.css +663 -0
- package/templates/app/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/app/apps/web/src/main.tsx +13 -0
- package/templates/app/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +20 -0
- package/templates/app/apps/web/src/modules/approval/index.ts +2 -0
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +54 -0
- package/templates/app/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/app/apps/web/src/modules/auth/pages/login-page.tsx +59 -0
- package/templates/app/apps/web/src/modules/inventory/index.ts +1 -0
- package/templates/app/apps/web/src/modules/inventory/pages/inventory-page.tsx +74 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +60 -0
- package/templates/app/apps/web/src/modules/purchase/index.ts +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +210 -0
- package/templates/app/apps/web/src/shared/hooks/use-command.ts +29 -0
- package/templates/app/apps/web/src/shared/lib/error-message.ts +15 -0
- package/templates/app/apps/web/src/shared/ui/notice.tsx +13 -0
- package/templates/app/apps/web/src/shared/ui/page-heading.tsx +28 -0
- package/templates/app/apps/web/tsconfig.json +11 -0
- package/templates/app/apps/web/vite.config.ts +19 -0
- package/templates/app/cage.config.json +114 -0
- package/templates/app/db/migrations/0001_init.sql +112 -0
- package/templates/app/docs/ai/README.md +77 -0
- package/templates/app/docs/ai/architecture.md +122 -0
- package/templates/app/docs/ai/ontology-context.md +117 -0
- package/templates/app/docs/ai/primitives.md +249 -0
- package/templates/app/docs/ai/project-structure.md +123 -0
- package/templates/app/docs/ai/purchase-example.md +97 -0
- package/templates/app/docs/ai/recipes.md +81 -0
- package/templates/app/docs/ai/relations.md +160 -0
- package/templates/app/docs/ai/transfers.md +92 -0
- package/templates/app/docs/ai/troubleshooting.md +60 -0
- package/templates/app/docs/ai/workflow.md +139 -0
- package/templates/app/docs/errors/CAGE-APPR-001.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-002.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-003.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-004.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-005.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-006.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-007.md +50 -0
- package/templates/app/docs/errors/CAGE-APPR-010.md +48 -0
- package/templates/app/docs/errors/CAGE-AUTH-001.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-001.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-002.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CMD-002.md +47 -0
- package/templates/app/docs/errors/CAGE-CMD-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-001.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-002.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-004.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-005.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-006.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-007.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-008.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-009.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-010.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-011.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-012.md +47 -0
- package/templates/app/docs/errors/CAGE-CREATE-001.md +52 -0
- package/templates/app/docs/errors/CAGE-DECL-001.md +48 -0
- package/templates/app/docs/errors/CAGE-DECL-002.md +47 -0
- package/templates/app/docs/errors/CAGE-DECL-003.md +47 -0
- package/templates/app/docs/errors/CAGE-DECL-004.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-001.md +48 -0
- package/templates/app/docs/errors/CAGE-DEP-002.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-003.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-004.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-005.md +47 -0
- package/templates/app/docs/errors/CAGE-DLQ-001.md +48 -0
- package/templates/app/docs/errors/CAGE-ENV-001.md +52 -0
- package/templates/app/docs/errors/CAGE-EVENT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-EVENT-002.md +50 -0
- package/templates/app/docs/errors/CAGE-FIELD-001.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-002.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-003.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-004.md +47 -0
- package/templates/app/docs/errors/CAGE-GEN-001.md +48 -0
- package/templates/app/docs/errors/CAGE-GEN-002.md +47 -0
- package/templates/app/docs/errors/CAGE-GRANT-001.md +49 -0
- package/templates/app/docs/errors/CAGE-GRANT-002.md +48 -0
- package/templates/app/docs/errors/CAGE-GRANT-003.md +47 -0
- package/templates/app/docs/errors/CAGE-IMPACT-001.md +48 -0
- package/templates/app/docs/errors/CAGE-INIT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-INIT-002.md +49 -0
- package/templates/app/docs/errors/CAGE-INIT-003.md +48 -0
- package/templates/app/docs/errors/CAGE-INIT-004.md +48 -0
- package/templates/app/docs/errors/CAGE-JOB-001.md +47 -0
- package/templates/app/docs/errors/CAGE-JOB-002.md +47 -0
- package/templates/app/docs/errors/CAGE-JOB-003.md +47 -0
- package/templates/app/docs/errors/CAGE-LINT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-001.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-002.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-003.md +5 -0
- package/templates/app/docs/errors/CAGE-MIG-004.md +5 -0
- package/templates/app/docs/errors/CAGE-MIG-005.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-001.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-002.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-003.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-004.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-005.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-006.md +47 -0
- package/templates/app/docs/errors/CAGE-ORG-001.md +49 -0
- package/templates/app/docs/errors/CAGE-PAIR-002.md +50 -0
- package/templates/app/docs/errors/CAGE-QUERY-001.md +47 -0
- package/templates/app/docs/errors/CAGE-QUERY-002.md +47 -0
- package/templates/app/docs/errors/CAGE-QUERY-003.md +49 -0
- package/templates/app/docs/errors/CAGE-REACT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-REACT-002.md +47 -0
- package/templates/app/docs/errors/CAGE-READS-001.md +47 -0
- package/templates/app/docs/errors/CAGE-REG-001.md +53 -0
- package/templates/app/docs/errors/CAGE-REG-002.md +48 -0
- package/templates/app/docs/errors/CAGE-REG-003.md +47 -0
- package/templates/app/docs/errors/CAGE-REL-001.md +64 -0
- package/templates/app/docs/errors/CAGE-ROUTE-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SCOPE-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SCOPE-002.md +47 -0
- package/templates/app/docs/errors/CAGE-SECRET-001.md +48 -0
- package/templates/app/docs/errors/CAGE-SECRET-002.md +47 -0
- package/templates/app/docs/errors/CAGE-SENS-001.md +48 -0
- package/templates/app/docs/errors/CAGE-SQL-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SQL-002.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-001.md +49 -0
- package/templates/app/docs/errors/CAGE-STATE-002.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-003.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-004.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-005.md +48 -0
- package/templates/app/docs/errors/CAGE-STATE-010.md +48 -0
- package/templates/app/docs/errors/CAGE-STATE-011.md +48 -0
- package/templates/app/docs/errors/CAGE-STRUCT-001.md +51 -0
- package/templates/app/docs/errors/CAGE-STRUCT-002.md +51 -0
- package/templates/app/docs/errors/CAGE-SYNC-001.md +48 -0
- package/templates/app/docs/errors/CAGE-TEST-001.md +47 -0
- package/templates/app/docs/errors/CAGE-UNSAFE-001.md +48 -0
- package/templates/app/docs/errors/CAGE-UPGRADE-001.md +48 -0
- package/templates/app/docs/errors/README.md +307 -0
- package/templates/app/docs/errors/runtime.md +50 -0
- package/templates/app/package.json +22 -0
- package/templates/app/packages/contracts/package.json +16 -0
- package/templates/app/packages/contracts/src/aggregates/dept/index.ts +74 -0
- package/templates/app/packages/contracts/src/aggregates/inventory/index.ts +57 -0
- package/templates/app/packages/contracts/src/aggregates/inventory-lock/index.ts +52 -0
- package/templates/app/packages/contracts/src/aggregates/purchase-order/index.ts +72 -0
- package/templates/app/packages/contracts/src/aggregates/role/index.ts +85 -0
- package/templates/app/packages/contracts/src/aggregates/user/index.ts +89 -0
- package/templates/app/packages/contracts/src/index.ts +81 -0
- package/templates/app/packages/contracts/src/queries/index.ts +17 -0
- package/templates/app/packages/contracts/tsconfig.json +5 -0
- package/templates/app/packages/domain/package.json +23 -0
- package/templates/app/packages/domain/src/index.ts +6 -0
- package/templates/app/packages/domain/src/integrations/erp.ts +36 -0
- package/templates/app/packages/domain/src/modules/inventory/index.ts +2 -0
- package/templates/app/packages/domain/src/modules/inventory/inventory.ts +43 -0
- package/templates/app/packages/domain/src/modules/purchase/approvals.ts +18 -0
- package/templates/app/packages/domain/src/modules/purchase/index.ts +6 -0
- package/templates/app/packages/domain/src/modules/purchase/jobs.ts +13 -0
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.test.ts +137 -0
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.ts +122 -0
- package/templates/app/packages/domain/src/modules/purchase/queries.ts +10 -0
- package/templates/app/packages/domain/src/workflows/purchase-inventory.ts +24 -0
- package/templates/app/packages/domain/tsconfig.json +5 -0
- package/templates/app/pnpm-workspace.yaml +10 -0
- package/templates/app/renovate.json +5 -0
- package/templates/app/tsconfig.base.json +8 -0
- package/templates/blank/.dockerignore +10 -0
- package/templates/blank/.env.example +61 -0
- package/templates/blank/.github/workflows/cage.yml +44 -0
- package/templates/blank/AGENTS.md +96 -0
- package/templates/blank/Dockerfile +25 -0
- package/templates/blank/README.md +37 -0
- package/templates/blank/_gitignore +8 -0
- package/templates/blank/apps/api/package.json +15 -0
- package/templates/blank/apps/api/src/main.ts +11 -0
- package/templates/blank/apps/api/src/worker.ts +8 -0
- package/templates/blank/apps/api/tsconfig.json +5 -0
- package/templates/blank/apps/web/index.html +13 -0
- package/templates/blank/apps/web/package.json +22 -0
- package/templates/blank/apps/web/src/app/app.tsx +44 -0
- package/templates/blank/apps/web/src/app/styles/global.css +516 -0
- package/templates/blank/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/blank/apps/web/src/main.tsx +13 -0
- package/templates/blank/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/blank/apps/web/src/modules/auth/pages/login-page.tsx +87 -0
- package/templates/blank/apps/web/src/modules/workspace/index.ts +1 -0
- package/templates/blank/apps/web/src/modules/workspace/pages/home-page.tsx +45 -0
- package/templates/blank/apps/web/src/shared/lib/error-message.ts +11 -0
- package/templates/blank/apps/web/src/shared/ui/brand.tsx +11 -0
- package/templates/blank/apps/web/tsconfig.json +11 -0
- package/templates/blank/apps/web/vite.config.ts +19 -0
- package/templates/blank/cage.config.json +14 -0
- package/templates/blank/db/migrations/0001_init.sql +63 -0
- package/templates/blank/docs/ai/README.md +77 -0
- package/templates/blank/docs/ai/architecture.md +122 -0
- package/templates/blank/docs/ai/ontology-context.md +117 -0
- package/templates/blank/docs/ai/primitives.md +249 -0
- package/templates/blank/docs/ai/project-structure.md +123 -0
- package/templates/blank/docs/ai/purchase-example.md +97 -0
- package/templates/blank/docs/ai/recipes.md +81 -0
- package/templates/blank/docs/ai/relations.md +160 -0
- package/templates/blank/docs/ai/transfers.md +92 -0
- package/templates/blank/docs/ai/troubleshooting.md +60 -0
- package/templates/blank/docs/ai/workflow.md +139 -0
- package/templates/blank/docs/errors/CAGE-APPR-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-007.md +50 -0
- package/templates/blank/docs/errors/CAGE-APPR-010.md +48 -0
- package/templates/blank/docs/errors/CAGE-AUTH-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CMD-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-CMD-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-007.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-008.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-009.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-010.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-011.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-012.md +47 -0
- package/templates/blank/docs/errors/CAGE-CREATE-001.md +52 -0
- package/templates/blank/docs/errors/CAGE-DECL-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-DECL-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-DECL-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-DECL-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-DEP-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-DLQ-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-ENV-001.md +52 -0
- package/templates/blank/docs/errors/CAGE-EVENT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-EVENT-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-FIELD-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-GEN-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-GEN-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-GRANT-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-GRANT-002.md +48 -0
- package/templates/blank/docs/errors/CAGE-GRANT-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-IMPACT-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-INIT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-INIT-002.md +49 -0
- package/templates/blank/docs/errors/CAGE-INIT-003.md +48 -0
- package/templates/blank/docs/errors/CAGE-INIT-004.md +48 -0
- package/templates/blank/docs/errors/CAGE-JOB-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-JOB-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-JOB-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-LINT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-003.md +5 -0
- package/templates/blank/docs/errors/CAGE-MIG-004.md +5 -0
- package/templates/blank/docs/errors/CAGE-MIG-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-ORG-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-PAIR-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-QUERY-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-QUERY-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-QUERY-003.md +49 -0
- package/templates/blank/docs/errors/CAGE-REACT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-REACT-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-READS-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-REG-001.md +53 -0
- package/templates/blank/docs/errors/CAGE-REG-002.md +48 -0
- package/templates/blank/docs/errors/CAGE-REG-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-REL-001.md +64 -0
- package/templates/blank/docs/errors/CAGE-ROUTE-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SCOPE-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SCOPE-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-SECRET-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-SECRET-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-SENS-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-SQL-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SQL-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-STATE-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-005.md +48 -0
- package/templates/blank/docs/errors/CAGE-STATE-010.md +48 -0
- package/templates/blank/docs/errors/CAGE-STATE-011.md +48 -0
- package/templates/blank/docs/errors/CAGE-STRUCT-001.md +51 -0
- package/templates/blank/docs/errors/CAGE-STRUCT-002.md +51 -0
- package/templates/blank/docs/errors/CAGE-SYNC-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-TEST-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-UNSAFE-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-UPGRADE-001.md +48 -0
- package/templates/blank/docs/errors/README.md +307 -0
- package/templates/blank/docs/errors/runtime.md +50 -0
- package/templates/blank/docs/examples/purchase-admin/README.md +5 -0
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/main.ts +10 -0
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/worker.ts +14 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/app.tsx +18 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/styles/global.css +663 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/main.tsx +13 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +20 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +54 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/pages/login-page.tsx +59 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/index.ts +1 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/pages/inventory-page.tsx +74 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +60 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +210 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/hooks/use-command.ts +29 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +15 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/notice.tsx +13 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-heading.tsx +28 -0
- package/templates/blank/docs/examples/purchase-admin/cage.config.json +114 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/dept/index.ts +74 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory/index.ts +57 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory-lock/index.ts +52 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +72 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/role/index.ts +85 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/user/index.ts +89 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +81 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/queries/index.ts +17 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/index.ts +6 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/erp.ts +36 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/inventory.ts +43 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts +18 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +6 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/jobs.ts +13 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.test.ts +137 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +122 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/queries.ts +10 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-inventory.ts +24 -0
- package/templates/blank/package.json +22 -0
- package/templates/blank/packages/contracts/package.json +16 -0
- package/templates/blank/packages/contracts/src/aggregates/dept/index.ts +74 -0
- package/templates/blank/packages/contracts/src/aggregates/role/index.ts +85 -0
- package/templates/blank/packages/contracts/src/aggregates/user/index.ts +89 -0
- package/templates/blank/packages/contracts/src/index.ts +55 -0
- package/templates/blank/packages/contracts/src/queries/index.ts +13 -0
- package/templates/blank/packages/contracts/tsconfig.json +5 -0
- package/templates/blank/packages/domain/package.json +23 -0
- package/templates/blank/packages/domain/src/index.ts +4 -0
- package/templates/blank/packages/domain/tsconfig.json +5 -0
- package/templates/blank/pnpm-workspace.yaml +10 -0
- package/templates/blank/renovate.json +5 -0
- package/templates/blank/tsconfig.base.json +8 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery, type TransferTask } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** 角色:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const RoleSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
code: z.string(),
|
|
11
|
+
name: z.string(),
|
|
12
|
+
capabilities: z.array(z.object({ capability: z.string(), params: z.record(z.string(), z.unknown()).nullable().optional() })),
|
|
13
|
+
scopes: z.array(z.object({ aggregate: z.string(), combinator: z.enum(["all", "self", "dept", "deptSubtree", "deptSet"]), deptIds: z.array(z.string().uuid()).nullable().optional() })),
|
|
14
|
+
status: z.enum(["active", "archived"]),
|
|
15
|
+
version: z.number().int(),
|
|
16
|
+
createdAt: z.string(),
|
|
17
|
+
updatedAt: z.string(),
|
|
18
|
+
});
|
|
19
|
+
export type Role = z.infer<typeof RoleSchema>;
|
|
20
|
+
export type RoleStatus = Role['status'];
|
|
21
|
+
export const RoleStates = ['active', 'archived'] as const;
|
|
22
|
+
|
|
23
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
24
|
+
export const RoleInputs = {
|
|
25
|
+
archive: z.object({ id: z.string().uuid() }),
|
|
26
|
+
create: z.object({ code: z.string().min(1).max(64).regex(new RegExp("^[a-z][a-z0-9_]*$")), name: z.string().min(1).max(100) }),
|
|
27
|
+
grantCapability: z.object({ id: z.string().uuid(), capability: z.string().min(1).max(200), params: z.record(z.string(), z.unknown()).optional() }),
|
|
28
|
+
rename: z.object({ id: z.string().uuid(), name: z.string().min(1).max(100) }),
|
|
29
|
+
revokeCapability: z.object({ id: z.string().uuid(), capability: z.string().min(1).max(200) }),
|
|
30
|
+
setScope: z.object({ id: z.string().uuid(), aggregate: z.string().min(1).max(100), combinator: z.enum(["all", "self", "dept", "deptSubtree", "deptSet"]).nullable(), deptIds: z.array(z.string().uuid()).optional() }),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Role.archive 可能返回的错误码 */
|
|
34
|
+
export type RoleArchiveError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
35
|
+
/** Role.create 可能返回的错误码 */
|
|
36
|
+
export type RoleCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT';
|
|
37
|
+
/** Role.grantCapability 可能返回的错误码 */
|
|
38
|
+
export type RoleGrantCapabilityError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
39
|
+
/** Role.rename 可能返回的错误码 */
|
|
40
|
+
export type RoleRenameError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
41
|
+
/** Role.revokeCapability 可能返回的错误码 */
|
|
42
|
+
export type RoleRevokeCapabilityError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
43
|
+
/** Role.setScope 可能返回的错误码 */
|
|
44
|
+
export type RoleSetScopeError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
45
|
+
|
|
46
|
+
export type RoleSortKey = 'createdAt' | 'updatedAt' | 'code' | 'name';
|
|
47
|
+
export interface RoleListParams {
|
|
48
|
+
page?: number;
|
|
49
|
+
pageSize?: number;
|
|
50
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
51
|
+
sort?: RoleSortKey | `-${RoleSortKey}`;
|
|
52
|
+
/** 可以逗号分隔多个状态 */
|
|
53
|
+
status?: string;
|
|
54
|
+
createdAfter?: string;
|
|
55
|
+
createdBefore?: string;
|
|
56
|
+
code?: string | number | boolean;
|
|
57
|
+
name?: string | number | boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
61
|
+
export const role = {
|
|
62
|
+
/** 停用角色 */
|
|
63
|
+
archive: (id: string, expectedVersion?: number) => client.post<Role, RoleArchiveError>(`/api/role/${encodeURIComponent(id)}/archive`, { expectedVersion }),
|
|
64
|
+
/** 新建角色(默认没有任何能力) */
|
|
65
|
+
create: (input: z.input<typeof RoleInputs.create>) => client.post<Role, RoleCreateError>('/api/role/create', input),
|
|
66
|
+
/** 给角色授予能力点(不能授出自己没有的能力点) */
|
|
67
|
+
grantCapability: (id: string, input: Omit<z.input<typeof RoleInputs.grantCapability>, 'id'>, expectedVersion?: number) => client.post<Role, RoleGrantCapabilityError>(`/api/role/${encodeURIComponent(id)}/grant-capability`, { ...input, expectedVersion }),
|
|
68
|
+
/** 修改角色名称 */
|
|
69
|
+
rename: (id: string, input: Omit<z.input<typeof RoleInputs.rename>, 'id'>, expectedVersion?: number) => client.post<Role, RoleRenameError>(`/api/role/${encodeURIComponent(id)}/rename`, { ...input, expectedVersion }),
|
|
70
|
+
/** 收回角色的能力点 */
|
|
71
|
+
revokeCapability: (id: string, input: Omit<z.input<typeof RoleInputs.revokeCapability>, 'id'>, expectedVersion?: number) => client.post<Role, RoleRevokeCapabilityError>(`/api/role/${encodeURIComponent(id)}/revoke-capability`, { ...input, expectedVersion }),
|
|
72
|
+
/** 设置角色在某个聚合上的数据范围(不能授出比自己更宽的范围) */
|
|
73
|
+
setScope: (id: string, input: Omit<z.input<typeof RoleInputs.setScope>, 'id'>, expectedVersion?: number) => client.post<Role, RoleSetScopeError>(`/api/role/${encodeURIComponent(id)}/set-scope`, { ...input, expectedVersion }),
|
|
74
|
+
/** 异步导出 CSV:返回任务,使用 client.transfer(id) 查状态,完成后下载。需要 Role.export 能力。 */
|
|
75
|
+
export: (params: RoleListParams = {}, options: { idempotencyKey?: string } = {}) => client.post<TransferTask, 'CONFLICT'>('/api/role/export', params, options),
|
|
76
|
+
get: (id: string) => client.get<Role>(`/api/role/${encodeURIComponent(id)}`),
|
|
77
|
+
list: (params: RoleListParams = {}) => client.get<{ items: Role[]; total: number; page: number; pageSize: number }>('/api/role', params as Record<string, unknown>),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
81
|
+
export const useRole = (id: string) => useCageQuery(['role', id], `/api/role/${encodeURIComponent(id)}`, RoleSchema, { enabled: !!id });
|
|
82
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
83
|
+
export const useRoleList = (params: RoleListParams = {}) => useCageList(['role'], '/api/role', params as Record<string, unknown>, RoleSchema);
|
|
84
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
85
|
+
export const roleKeys = { all: ['role'] as const, one: (id: string) => ['role', id] as const };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery, type TransferTask } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** 用户:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const UserSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
username: z.string(),
|
|
11
|
+
name: z.string(),
|
|
12
|
+
deptId: z.string().uuid(),
|
|
13
|
+
roleIds: z.array(z.string().uuid()),
|
|
14
|
+
phone: z.string().nullable().optional(),
|
|
15
|
+
email: z.string().nullable().optional(),
|
|
16
|
+
ssoSubject: z.string().nullable().optional(),
|
|
17
|
+
status: z.enum(["active", "disabled"]),
|
|
18
|
+
version: z.number().int(),
|
|
19
|
+
createdAt: z.string(),
|
|
20
|
+
updatedAt: z.string(),
|
|
21
|
+
});
|
|
22
|
+
export type User = z.infer<typeof UserSchema>;
|
|
23
|
+
export type UserStatus = User['status'];
|
|
24
|
+
export const UserStates = ['active', 'disabled'] as const;
|
|
25
|
+
|
|
26
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
27
|
+
export const UserInputs = {
|
|
28
|
+
assignRoles: z.object({ id: z.string().uuid(), roleIds: z.array(z.string().uuid()).max(50) }),
|
|
29
|
+
create: z.object({ username: z.string().min(2).max(64).regex(new RegExp("^[A-Za-z0-9_.@-]+$")), name: z.string().min(1).max(100), deptId: z.string().uuid(), phone: z.string().max(32).nullable().optional(), email: z.string().email().max(200).nullable().optional(), ssoSubject: z.string().max(200).nullable().optional() }),
|
|
30
|
+
disable: z.object({ id: z.string().uuid() }),
|
|
31
|
+
enable: z.object({ id: z.string().uuid() }),
|
|
32
|
+
setPassword: z.object({ id: z.string().uuid(), password: z.string().min(8).max(200) }),
|
|
33
|
+
update: z.object({ id: z.string().uuid(), name: z.string().min(1).max(100).optional(), deptId: z.string().uuid().optional(), phone: z.string().max(32).nullable().optional(), email: z.string().email().max(200).nullable().optional() }),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** User.assignRoles 可能返回的错误码 */
|
|
37
|
+
export type UserAssignRolesError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
38
|
+
/** User.create 可能返回的错误码 */
|
|
39
|
+
export type UserCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT';
|
|
40
|
+
/** User.disable 可能返回的错误码 */
|
|
41
|
+
export type UserDisableError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
42
|
+
/** User.enable 可能返回的错误码 */
|
|
43
|
+
export type UserEnableError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
44
|
+
/** User.setPassword 可能返回的错误码 */
|
|
45
|
+
export type UserSetPasswordError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
46
|
+
/** User.update 可能返回的错误码 */
|
|
47
|
+
export type UserUpdateError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
48
|
+
|
|
49
|
+
export type UserSortKey = 'createdAt' | 'updatedAt' | 'username' | 'name' | 'deptId' | 'phone' | 'email' | 'ssoSubject';
|
|
50
|
+
export interface UserListParams {
|
|
51
|
+
page?: number;
|
|
52
|
+
pageSize?: number;
|
|
53
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
54
|
+
sort?: UserSortKey | `-${UserSortKey}`;
|
|
55
|
+
/** 可以逗号分隔多个状态 */
|
|
56
|
+
status?: string;
|
|
57
|
+
createdAfter?: string;
|
|
58
|
+
createdBefore?: string;
|
|
59
|
+
username?: string | number | boolean;
|
|
60
|
+
deptId?: string | number | boolean;
|
|
61
|
+
ssoSubject?: string | number | boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
65
|
+
export const user = {
|
|
66
|
+
/** 设置用户的角色(不能分配包含自己没有的能力的角色) */
|
|
67
|
+
assignRoles: (id: string, input: Omit<z.input<typeof UserInputs.assignRoles>, 'id'>, expectedVersion?: number) => client.post<User, UserAssignRolesError>(`/api/user/${encodeURIComponent(id)}/assign-roles`, { ...input, expectedVersion }),
|
|
68
|
+
/** 新建用户(默认没有任何角色) */
|
|
69
|
+
create: (input: z.input<typeof UserInputs.create>) => client.post<User, UserCreateError>('/api/user/create', input),
|
|
70
|
+
/** 停用用户 */
|
|
71
|
+
disable: (id: string, expectedVersion?: number) => client.post<User, UserDisableError>(`/api/user/${encodeURIComponent(id)}/disable`, { expectedVersion }),
|
|
72
|
+
/** 启用用户 */
|
|
73
|
+
enable: (id: string, expectedVersion?: number) => client.post<User, UserEnableError>(`/api/user/${encodeURIComponent(id)}/enable`, { expectedVersion }),
|
|
74
|
+
/** 重置密码 */
|
|
75
|
+
setPassword: (id: string, input: Omit<z.input<typeof UserInputs.setPassword>, 'id'>, expectedVersion?: number) => client.post<User, UserSetPasswordError>(`/api/user/${encodeURIComponent(id)}/set-password`, { ...input, expectedVersion }),
|
|
76
|
+
/** 修改用户信息 */
|
|
77
|
+
update: (id: string, input: Omit<z.input<typeof UserInputs.update>, 'id'>, expectedVersion?: number) => client.post<User, UserUpdateError>(`/api/user/${encodeURIComponent(id)}/update`, { ...input, expectedVersion }),
|
|
78
|
+
/** 异步导出 CSV:返回任务,使用 client.transfer(id) 查状态,完成后下载。需要 User.export 能力。 */
|
|
79
|
+
export: (params: UserListParams = {}, options: { idempotencyKey?: string } = {}) => client.post<TransferTask, 'CONFLICT'>('/api/user/export', params, options),
|
|
80
|
+
get: (id: string) => client.get<User>(`/api/user/${encodeURIComponent(id)}`),
|
|
81
|
+
list: (params: UserListParams = {}) => client.get<{ items: User[]; total: number; page: number; pageSize: number }>('/api/user', params as Record<string, unknown>),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
85
|
+
export const useUser = (id: string) => useCageQuery(['user', id], `/api/user/${encodeURIComponent(id)}`, UserSchema, { enabled: !!id });
|
|
86
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
87
|
+
export const useUserList = (params: UserListParams = {}) => useCageList(['user'], '/api/user', params as Record<string, unknown>, UserSchema);
|
|
88
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
89
|
+
export const userKeys = { all: ['user'] as const, one: (id: string) => ['user', id] as const };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export * from './aggregates/dept';
|
|
5
|
+
export * from './aggregates/inventory';
|
|
6
|
+
export * from './aggregates/inventory-lock';
|
|
7
|
+
export * from './aggregates/purchase-order';
|
|
8
|
+
export * from './aggregates/role';
|
|
9
|
+
export * from './aggregates/user';
|
|
10
|
+
export * from './queries';
|
|
11
|
+
import { can as baseCan, useCan as baseUseCan } from '@yss123/cage-client';
|
|
12
|
+
import { DeptInputs } from './aggregates/dept';
|
|
13
|
+
import { InventoryInputs } from './aggregates/inventory';
|
|
14
|
+
import { InventoryLockInputs } from './aggregates/inventory-lock';
|
|
15
|
+
import { PurchaseOrderInputs } from './aggregates/purchase-order';
|
|
16
|
+
import { RoleInputs } from './aggregates/role';
|
|
17
|
+
import { UserInputs } from './aggregates/user';
|
|
18
|
+
export { client, configureClient, loadMe, currentUser, CageQueryError, useTransfer, type CageResult, type CommonError, type TransferTask, type ImportReport } from '@yss123/cage-client';
|
|
19
|
+
|
|
20
|
+
/** 能力目录(由代码派生):写错能力点名会编译失败 */
|
|
21
|
+
export const capabilities = [
|
|
22
|
+
'Dept.archive',
|
|
23
|
+
'Dept.create',
|
|
24
|
+
'Dept.export',
|
|
25
|
+
'Dept.read',
|
|
26
|
+
'Dept.update',
|
|
27
|
+
'Inventory.create',
|
|
28
|
+
'Inventory.export',
|
|
29
|
+
'Inventory.read',
|
|
30
|
+
'InventoryLock.acquire',
|
|
31
|
+
'InventoryLock.export',
|
|
32
|
+
'InventoryLock.read',
|
|
33
|
+
'InventoryLock.release',
|
|
34
|
+
'PurchaseOrder.approve',
|
|
35
|
+
'PurchaseOrder.cancel',
|
|
36
|
+
'PurchaseOrder.create',
|
|
37
|
+
'PurchaseOrder.export',
|
|
38
|
+
'PurchaseOrder.markReceived',
|
|
39
|
+
'PurchaseOrder.read',
|
|
40
|
+
'PurchaseOrder.recordErpNo',
|
|
41
|
+
'PurchaseOrder.reject',
|
|
42
|
+
'PurchaseOrder.submit',
|
|
43
|
+
'PurchaseOrder.unmask.phone',
|
|
44
|
+
'Role.archive',
|
|
45
|
+
'Role.create',
|
|
46
|
+
'Role.export',
|
|
47
|
+
'Role.grantCapability',
|
|
48
|
+
'Role.read',
|
|
49
|
+
'Role.rename',
|
|
50
|
+
'Role.revokeCapability',
|
|
51
|
+
'Role.setScope',
|
|
52
|
+
'Sys.audit.read',
|
|
53
|
+
'Sys.ops.manage',
|
|
54
|
+
'Sys.ops.read',
|
|
55
|
+
'User.assignRoles',
|
|
56
|
+
'User.create',
|
|
57
|
+
'User.disable',
|
|
58
|
+
'User.enable',
|
|
59
|
+
'User.export',
|
|
60
|
+
'User.read',
|
|
61
|
+
'User.setPassword',
|
|
62
|
+
'User.unmask.email',
|
|
63
|
+
'User.unmask.name',
|
|
64
|
+
'User.unmask.phone',
|
|
65
|
+
'User.update',
|
|
66
|
+
] as const;
|
|
67
|
+
export type Capability = (typeof capabilities)[number];
|
|
68
|
+
|
|
69
|
+
/** 当前用户有没有某个能力点。只影响界面显示:绕过前端直接调接口,内核一样会拒绝 */
|
|
70
|
+
export const can = (capability: Capability): boolean => baseCan(capability);
|
|
71
|
+
export const useCan = (capability: Capability): boolean => baseUseCan(capability);
|
|
72
|
+
|
|
73
|
+
/** 各聚合的命令输入 schema:按聚合名、命令名访问,可直接供表单库校验 */
|
|
74
|
+
export const schemas = {
|
|
75
|
+
Dept: DeptInputs,
|
|
76
|
+
Inventory: InventoryInputs,
|
|
77
|
+
InventoryLock: InventoryLockInputs,
|
|
78
|
+
PurchaseOrder: PurchaseOrderInputs,
|
|
79
|
+
Role: RoleInputs,
|
|
80
|
+
User: UserInputs,
|
|
81
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageQuery } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** 查询输入 */
|
|
8
|
+
export const QueryInputs = {
|
|
9
|
+
poByStatus: z.object({ status: z.string() }),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const queries = {
|
|
13
|
+
/** poByStatus(需要 PurchaseOrder.read) */
|
|
14
|
+
poByStatus: (input: z.input<typeof QueryInputs.poByStatus> = {} as any) => client.get<Record<string, unknown>[]>('/api/queries/po-by-status', input as Record<string, unknown>),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const usePoByStatus = (input: z.input<typeof QueryInputs.poByStatus> = {} as any) => useCageQuery<Record<string, unknown>[]>(['query', 'poByStatus'], '/api/queries/po-by-status', undefined, { query: input as Record<string, unknown> });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/domain",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"development": "./src/index.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@yss123/cage-kernel": "0.2.0",
|
|
18
|
+
"@yss123/cage-admin": "0.2.0",
|
|
19
|
+
"@yss123/cage-approval": "0.2.0",
|
|
20
|
+
"@yss123/cage-connectors": "0.2.0",
|
|
21
|
+
"zod": "^4.1.5"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// 单一导出入口:CLI 抽取器、API 和 worker 都加载同一套声明。
|
|
2
|
+
export * from '@yss123/cage-admin/domain';
|
|
3
|
+
export * from './modules/inventory/index.js';
|
|
4
|
+
export * from './modules/purchase/index.js';
|
|
5
|
+
export * from './workflows/purchase-inventory.js';
|
|
6
|
+
export * from './integrations/erp.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ERP 的两个边界:出站只发送声明允许的字段,入站回调必须通过 HMAC 验签。
|
|
2
|
+
// 本地由 worker 注册 fake;这里始终只写声明,不发送请求或读取环境变量。
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { connector, hmacSha256, inbound, op, outbound } from '@yss123/cage-connectors';
|
|
5
|
+
import { OrderApproved, recordErpNo, markReceived } from '../modules/purchase/index.js';
|
|
6
|
+
|
|
7
|
+
export const erp = connector('erp', {
|
|
8
|
+
hosts: { test: 'erp-sandbox.example.com', prod: 'erp.example.com' },
|
|
9
|
+
secret: 'ERP_API_TOKEN',
|
|
10
|
+
timeoutMs: 5000,
|
|
11
|
+
operations: {
|
|
12
|
+
pushOrder: op({
|
|
13
|
+
effect: 'retryableWrite',
|
|
14
|
+
method: 'POST',
|
|
15
|
+
path: '/api/purchase-orders',
|
|
16
|
+
request: z.object({
|
|
17
|
+
externalId: z.string(),
|
|
18
|
+
lines: z.array(z.object({ sku: z.string(), qty: z.number() })),
|
|
19
|
+
}),
|
|
20
|
+
response: z.object({ erpNo: z.string() }),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const pushToErp = outbound(OrderApproved, erp.operations.pushOrder, {
|
|
26
|
+
idempotencyKey: (e) => `po-${e.orderId}`,
|
|
27
|
+
map: (e) => ({ externalId: e.orderId, lines: e.items.map((i) => ({ sku: i.sku, qty: i.qty })) }),
|
|
28
|
+
onSuccess: { command: recordErpNo, map: (res, e) => ({ id: e.orderId, erpNo: res.erpNo }) },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const erpReceived = inbound(erp, 'goodsReceived', {
|
|
32
|
+
verify: hmacSha256('ERP_WEBHOOK_SECRET'),
|
|
33
|
+
payload: z.object({ externalId: z.string().uuid() }),
|
|
34
|
+
command: markReceived,
|
|
35
|
+
map: (p) => ({ id: p.externalId }),
|
|
36
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// 最简聚合示例:Inventory 维护物料台账,InventoryLock 记录订单的资源占用。
|
|
2
|
+
// 此范例验证锁的生命周期;available 是登记数量,不包含仓库出入库核算。
|
|
3
|
+
import { aggregate, command, id, int, list, scope, states, text } from '@yss123/cage-kernel';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export const Inventory = aggregate('Inventory', {
|
|
7
|
+
fields: { sku: text(64).unique(), available: int() },
|
|
8
|
+
scope: scope.none(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const createInventory = command(Inventory, {
|
|
12
|
+
name: 'create',
|
|
13
|
+
from: 'new',
|
|
14
|
+
to: 'active',
|
|
15
|
+
input: z.object({ sku: z.string(), available: z.number().int() }),
|
|
16
|
+
handler: (o) => o,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const InventoryLock = aggregate('InventoryLock', {
|
|
20
|
+
fields: { orderId: id(), items: list({ sku: text(64), qty: int() }) },
|
|
21
|
+
states: states('held', 'released', { terminal: ['released'] }),
|
|
22
|
+
scope: scope.none(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 锁以订单 id 为主键,同一事务反应确保审批和建锁一起成功或回滚。
|
|
26
|
+
const itemsInput = z.array(z.object({ sku: z.string(), qty: z.number().int().positive() })).min(1);
|
|
27
|
+
|
|
28
|
+
export const acquireLock = command(InventoryLock, {
|
|
29
|
+
name: 'acquire',
|
|
30
|
+
from: 'new',
|
|
31
|
+
to: 'held',
|
|
32
|
+
invokedBy: 'process',
|
|
33
|
+
input: z.object({ id: z.string().uuid(), orderId: z.string().uuid(), items: itemsInput }),
|
|
34
|
+
handler: (o) => o,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const releaseLock = command(InventoryLock, {
|
|
38
|
+
name: 'release',
|
|
39
|
+
from: 'held',
|
|
40
|
+
to: 'released',
|
|
41
|
+
invokedBy: 'process',
|
|
42
|
+
handler: (o) => o,
|
|
43
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 路由只决定由谁审批;最终结论仍调用领域命令,执行职责分离和额度约束。
|
|
2
|
+
import { approval } from '@yss123/cage-kernel';
|
|
3
|
+
import { approver, gt, route, step, when } from '@yss123/cage-approval';
|
|
4
|
+
import { PurchaseOrder, OrderSubmitted, OrderCancelled, approve, reject } from './purchase-order.js';
|
|
5
|
+
|
|
6
|
+
export const poApproval = approval(PurchaseOrder, {
|
|
7
|
+
process: 'po-approval',
|
|
8
|
+
start: OrderSubmitted,
|
|
9
|
+
cancelOn: [OrderCancelled],
|
|
10
|
+
routable: ['amount', 'deptId'],
|
|
11
|
+
onApproved: approve,
|
|
12
|
+
onRejected: reject,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const poRoute = route('po-approval', [
|
|
16
|
+
step('部门经理审批', approver.deptLeader('deptId')),
|
|
17
|
+
when(gt('amount', 50000), step('财务审批', approver.role('finance'), { mode: 'any' })),
|
|
18
|
+
]);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// 定时任务只调用显式列出的命令;取消操作和用户操作共用内核及审计。
|
|
2
|
+
import { daysAgo, job } from '@yss123/cage-kernel';
|
|
3
|
+
import { PurchaseOrder, cancel } from './purchase-order.js';
|
|
4
|
+
|
|
5
|
+
export const expireStaleOrders = job('expireStaleOrders', {
|
|
6
|
+
cron: '0 2 * * *',
|
|
7
|
+
reads: [PurchaseOrder],
|
|
8
|
+
calls: [cancel],
|
|
9
|
+
run: async ({ query, call }) => {
|
|
10
|
+
const stale = await query.list(PurchaseOrder, { status: 'draft', createdBefore: daysAgo(30) });
|
|
11
|
+
for (const po of stale) await call(cancel, { id: po.id });
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// 定向业务测试补足通用造数无法准备的库存、审批人和角色参数。
|
|
2
|
+
// 使用真实 PostgreSQL 及示例迁移,确保示例不是仅能通过抽取的静态声明。
|
|
3
|
+
import { afterAll, beforeAll, expect, it } from 'vitest';
|
|
4
|
+
import { createTestEnv, type TestEnv, type TestUser } from '@yss123/cage-testkit';
|
|
5
|
+
import { readMigrations } from '@yss123/cage-kernel/db';
|
|
6
|
+
import { systemCtx } from '@yss123/cage-kernel';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import * as domain from '../../index.js';
|
|
9
|
+
|
|
10
|
+
let env: TestEnv;
|
|
11
|
+
let buyer: TestUser;
|
|
12
|
+
let manager: TestUser;
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
env = await createTestEnv({
|
|
15
|
+
domain,
|
|
16
|
+
migrations: readMigrations(fileURLToPath(new URL('../../../../../db/migrations/', import.meta.url))),
|
|
17
|
+
});
|
|
18
|
+
buyer = await env.user({
|
|
19
|
+
caps: ['PurchaseOrder.create', 'PurchaseOrder.submit', 'PurchaseOrder.cancel', 'PurchaseOrder.read'],
|
|
20
|
+
});
|
|
21
|
+
manager = await env.user({
|
|
22
|
+
caps: ['PurchaseOrder.approve'],
|
|
23
|
+
params: { 'PurchaseOrder.approve': { ceiling: 50000 } },
|
|
24
|
+
});
|
|
25
|
+
await env.kernel.execute('Dept.update', { id: env.rootDeptId, leaderId: manager.id }, env.system());
|
|
26
|
+
await env.kernel.execute('Inventory.create', { sku: 'A-01', available: 100 }, env.system());
|
|
27
|
+
});
|
|
28
|
+
afterAll(() => env?.close());
|
|
29
|
+
|
|
30
|
+
async function submitted() {
|
|
31
|
+
const ctx = await buyer.ctx();
|
|
32
|
+
const order = await env.kernel.execute(
|
|
33
|
+
'PurchaseOrder.create',
|
|
34
|
+
{ amount: '1200.00', items: [{ sku: 'A-01', qty: 2 }] },
|
|
35
|
+
ctx,
|
|
36
|
+
);
|
|
37
|
+
return env.kernel.execute('PurchaseOrder.submit', { id: order.id }, ctx);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it('审批成功同事务建锁;取消后 worker 释放锁,且每步都有审计', async () => {
|
|
41
|
+
const order = await submitted();
|
|
42
|
+
const process = env.kernel.processCtx({
|
|
43
|
+
process: 'po-approval',
|
|
44
|
+
tenantId: env.tenantId,
|
|
45
|
+
onBehalfOf: manager.id,
|
|
46
|
+
capabilities: ['PurchaseOrder.approve'],
|
|
47
|
+
});
|
|
48
|
+
await env.kernel.execute('PurchaseOrder.approve', { id: order.id }, process);
|
|
49
|
+
expect(
|
|
50
|
+
(await env.query('select status from core.inventory_lock where id = $1', [order.id]))[0].status,
|
|
51
|
+
).toBe('held');
|
|
52
|
+
// 先完成审批事件及 ERP 回写,再验证后续取消的资源释放。
|
|
53
|
+
// 同一单据的 outbox 严格按顺序执行,不能越过尚未成功的前序事件。
|
|
54
|
+
await env.drain();
|
|
55
|
+
await env.kernel.execute('PurchaseOrder.cancel', { id: order.id }, await buyer.ctx());
|
|
56
|
+
await env.drain();
|
|
57
|
+
expect(
|
|
58
|
+
(await env.query('select status from core.inventory_lock where id = $1', [order.id]))[0].status,
|
|
59
|
+
).toBe('released');
|
|
60
|
+
expect(
|
|
61
|
+
await env.audit({ aggregateId: order.id, command: 'PurchaseOrder.approve', kind: 'write' }),
|
|
62
|
+
).toHaveLength(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('提单人不能借流程身份审批自己的采购单', async () => {
|
|
66
|
+
const order = await submitted();
|
|
67
|
+
const process = env.kernel.processCtx({
|
|
68
|
+
process: 'po-approval',
|
|
69
|
+
tenantId: env.tenantId,
|
|
70
|
+
onBehalfOf: buyer.id,
|
|
71
|
+
capabilities: ['PurchaseOrder.approve'],
|
|
72
|
+
});
|
|
73
|
+
expect((await env.callAs(process, 'PurchaseOrder.approve', { id: order.id })).code).toBe(
|
|
74
|
+
'CONSTRAINT_VIOLATED',
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it.each(['cancelled', 'received'] as const)(
|
|
79
|
+
'采购单进入 %s 后,延迟的 ERP 回写保留终态且不会阻塞解锁',
|
|
80
|
+
async (status) => {
|
|
81
|
+
const order = await submitted();
|
|
82
|
+
const process = env.kernel.processCtx({
|
|
83
|
+
process: 'po-approval',
|
|
84
|
+
tenantId: env.tenantId,
|
|
85
|
+
onBehalfOf: manager.id,
|
|
86
|
+
capabilities: ['PurchaseOrder.approve'],
|
|
87
|
+
});
|
|
88
|
+
await env.kernel.execute('PurchaseOrder.approve', { id: order.id }, process);
|
|
89
|
+
// 有意在处理 outbox 之前取消:数据库命令与第三方响应之间没有完成顺序保证。
|
|
90
|
+
// 断言最终业务状态和资源状态,避免仅验证命令返回成功而漏掉死信阻塞。
|
|
91
|
+
if (status === 'cancelled') {
|
|
92
|
+
await env.kernel.execute('PurchaseOrder.cancel', { id: order.id }, await buyer.ctx());
|
|
93
|
+
} else {
|
|
94
|
+
const external = systemCtx({
|
|
95
|
+
kind: 'external',
|
|
96
|
+
name: 'erp',
|
|
97
|
+
tenantId: env.tenantId,
|
|
98
|
+
capabilities: ['PurchaseOrder.markReceived'],
|
|
99
|
+
});
|
|
100
|
+
await env.kernel.execute('PurchaseOrder.markReceived', { id: order.id }, external);
|
|
101
|
+
}
|
|
102
|
+
await env.drain();
|
|
103
|
+
const saved = (
|
|
104
|
+
await env.query('select status, erp_no, version from core.purchase_order where id = $1', [order.id])
|
|
105
|
+
)[0];
|
|
106
|
+
expect(saved.status).toBe(status);
|
|
107
|
+
expect(saved.erp_no).toBeTruthy();
|
|
108
|
+
expect(saved.version).toBe(5);
|
|
109
|
+
expect(
|
|
110
|
+
(await env.query('select status from core.inventory_lock where id = $1', [order.id]))[0].status,
|
|
111
|
+
).toBe('released');
|
|
112
|
+
expect(
|
|
113
|
+
await env.query('select id from sys.dead_letter where aggregate_id = $1', [order.id]),
|
|
114
|
+
).toHaveLength(0);
|
|
115
|
+
expect(
|
|
116
|
+
await env.audit({ aggregateId: order.id, command: 'PurchaseOrder.recordErpNo', kind: 'write' }),
|
|
117
|
+
).toHaveLength(1);
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
it('审批额度不够时保留待审批状态,不创建库存锁', async () => {
|
|
122
|
+
const order = await submitted();
|
|
123
|
+
const lowLimit = await env.user({
|
|
124
|
+
caps: ['PurchaseOrder.approve'],
|
|
125
|
+
params: { 'PurchaseOrder.approve': { ceiling: 500 } },
|
|
126
|
+
});
|
|
127
|
+
const process = env.kernel.processCtx({
|
|
128
|
+
process: 'po-approval',
|
|
129
|
+
tenantId: env.tenantId,
|
|
130
|
+
onBehalfOf: lowLimit.id,
|
|
131
|
+
capabilities: ['PurchaseOrder.approve'],
|
|
132
|
+
});
|
|
133
|
+
expect((await env.callAs(process, 'PurchaseOrder.approve', { id: order.id })).code).toBe(
|
|
134
|
+
'CONSTRAINT_VIOLATED',
|
|
135
|
+
);
|
|
136
|
+
expect(await env.query('select id from core.inventory_lock where id = $1', [order.id])).toHaveLength(0);
|
|
137
|
+
});
|