@yss123/cage-cli 0.2.0 → 0.3.1
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 +47 -3
- package/bin/cage.js +5 -0
- package/bin/create-cage-app.js +5 -0
- package/dist/analyze.d.ts +16 -0
- package/dist/analyze.js +18 -2
- package/dist/analyze.js.map +1 -1
- 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/baseline.d.ts +8 -0
- package/dist/baseline.js +13 -0
- package/dist/baseline.js.map +1 -1
- package/dist/bootstrap.d.ts +20 -0
- package/dist/bootstrap.js +75 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/budget.d.ts +7 -0
- package/dist/budget.js +49 -5
- package/dist/budget.js.map +1 -1
- package/dist/capabilities.d.ts +38 -0
- package/dist/capabilities.js +148 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/config.d.ts +31 -2
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +14 -0
- package/dist/context.js +35 -2
- package/dist/context.js.map +1 -1
- package/dist/create-prompt.d.ts +7 -0
- package/dist/create-prompt.js +12 -0
- package/dist/create-prompt.js.map +1 -1
- package/dist/create.d.ts +21 -1
- package/dist/create.js +23 -1
- package/dist/create.js.map +1 -1
- package/dist/derive.d.ts +23 -0
- package/dist/derive.js +40 -0
- package/dist/derive.js.map +1 -1
- package/dist/dev.d.ts +6 -0
- package/dist/dev.js +11 -0
- package/dist/dev.js.map +1 -1
- package/dist/doctor.d.ts +98 -0
- package/dist/doctor.js +93 -0
- package/dist/doctor.js.map +1 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.js +11 -0
- package/dist/env.js.map +1 -1
- package/dist/findings.d.ts +27 -0
- package/dist/findings.js +20 -0
- package/dist/findings.js.map +1 -1
- package/dist/gen/contracts.d.ts +28 -0
- package/dist/gen/contracts.js +52 -12
- package/dist/gen/contracts.js.map +1 -1
- package/dist/gen/index.d.ts +26 -0
- package/dist/gen/index.js +69 -1
- package/dist/gen/index.js.map +1 -1
- package/dist/gen/zod-code.d.ts +42 -0
- package/dist/gen/zod-code.js +178 -16
- package/dist/gen/zod-code.js.map +1 -1
- package/dist/graph-approvals.d.ts +70 -0
- package/dist/graph-approvals.js +93 -0
- package/dist/graph-approvals.js.map +1 -0
- package/dist/graph-business.d.ts +135 -0
- package/dist/graph-business.js +404 -0
- package/dist/graph-business.js.map +1 -0
- package/dist/graph-command.d.ts +17 -0
- package/dist/graph-command.js +21 -0
- package/dist/graph-command.js.map +1 -1
- package/dist/graph-model.d.ts +44 -0
- package/dist/graph-model.js +36 -3
- package/dist/graph-model.js.map +1 -1
- package/dist/graph-presentation.d.ts +16 -0
- package/dist/graph-presentation.js +10 -1
- package/dist/graph-presentation.js.map +1 -1
- package/dist/graph-query.d.ts +95 -0
- package/dist/graph-query.js +62 -0
- package/dist/graph-query.js.map +1 -1
- package/dist/graph-relations.d.ts +20 -0
- package/dist/graph-relations.js +42 -10
- package/dist/graph-relations.js.map +1 -1
- package/dist/graph-search.d.ts +35 -0
- package/dist/graph-search.js +36 -0
- package/dist/graph-search.js.map +1 -1
- package/dist/graph-snapshot.d.ts +26 -0
- package/dist/graph-snapshot.js +30 -1
- package/dist/graph-snapshot.js.map +1 -1
- package/dist/graph.d.ts +13 -2
- package/dist/graph.js +61 -4
- package/dist/graph.js.map +1 -1
- package/dist/impact.d.ts +50 -0
- package/dist/impact.js +62 -2
- package/dist/impact.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +12 -0
- package/dist/init.js +15 -0
- package/dist/init.js.map +1 -1
- package/dist/load.d.ts +10 -0
- package/dist/load.js +15 -0
- package/dist/load.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.js +93 -5
- package/dist/main.js.map +1 -1
- package/dist/migrate.d.ts +12 -0
- package/dist/migrate.js +17 -0
- package/dist/migrate.js.map +1 -1
- package/dist/ontology.d.ts +67 -2
- package/dist/ontology.js +94 -4
- package/dist/ontology.js.map +1 -1
- package/dist/process-rules.d.ts +21 -0
- package/dist/process-rules.js +100 -0
- package/dist/process-rules.js.map +1 -0
- package/dist/process.d.ts +32 -0
- package/dist/process.js +32 -0
- package/dist/process.js.map +1 -1
- package/dist/project-structure.d.ts +5 -0
- package/dist/project-structure.js +10 -0
- package/dist/project-structure.js.map +1 -1
- package/dist/registration.d.ts +16 -0
- package/dist/registration.js +56 -0
- package/dist/registration.js.map +1 -0
- package/dist/rules.d.ts +52 -0
- package/dist/rules.js +58 -0
- package/dist/rules.js.map +1 -1
- package/dist/runtime-diagnostics.d.ts +27 -0
- package/dist/runtime-diagnostics.js +60 -0
- package/dist/runtime-diagnostics.js.map +1 -0
- package/dist/snapshots.d.ts +40 -0
- package/dist/snapshots.js +62 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/static-check.d.ts +33 -0
- package/dist/static-check.js +37 -1
- package/dist/static-check.js.map +1 -1
- package/dist/sync.d.ts +11 -0
- package/dist/sync.js +66 -9
- package/dist/sync.js.map +1 -1
- package/dist/upgrade.d.ts +16 -0
- package/dist/upgrade.js +21 -0
- package/dist/upgrade.js.map +1 -1
- package/dist/verify.d.ts +14 -0
- package/dist/verify.js +19 -0
- package/dist/verify.js.map +1 -1
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -1
- package/graph-ui/app.js +321 -27
- package/graph-ui/approval-flow.js +185 -0
- package/graph-ui/approval-parallel.js +110 -0
- package/graph-ui/approval-tree.js +17 -0
- package/graph-ui/business.css +1011 -0
- package/graph-ui/business.js +566 -0
- package/graph-ui/flowchart-interaction.js +177 -0
- package/graph-ui/flowchart.js +1059 -0
- package/graph-ui/graph-approvals.js +92 -0
- package/graph-ui/graph-business.js +403 -0
- package/graph-ui/graph-model.js +36 -3
- package/graph-ui/graph-relations.js +42 -10
- package/graph-ui/graph-search.js +36 -0
- package/graph-ui/index.html +26 -12
- package/graph-ui/model.js +28 -0
- package/graph-ui/relations.js +4 -0
- package/graph-ui/structure.js +44 -0
- package/graph-ui/style.css +12 -0
- package/package.json +6 -4
- package/templates/app/.dockerignore +2 -0
- package/templates/app/.env.example +10 -0
- package/templates/app/AGENTS.md +50 -10
- package/templates/app/README.md +9 -5
- package/templates/app/_gitignore +1 -0
- package/templates/app/apps/api/package.json +2 -2
- package/templates/app/apps/api/src/main.ts +5 -1
- package/templates/app/apps/api/src/worker.ts +3 -0
- package/templates/app/apps/web/index.html +1 -0
- package/templates/app/apps/web/package.json +7 -3
- package/templates/app/apps/web/src/app/app.tsx +122 -12
- package/templates/app/apps/web/src/app/styles/global.css +88 -8
- package/templates/app/apps/web/src/app/use-session.ts +43 -0
- package/templates/app/apps/web/src/app/workspace-layout.tsx +58 -19
- package/templates/app/apps/web/src/main.tsx +5 -0
- 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 +13 -5
- package/templates/app/apps/web/src/modules/approval/index.ts +4 -0
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +89 -38
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks.css +18 -0
- package/templates/app/apps/web/src/modules/auth/index.ts +4 -0
- package/templates/app/apps/web/src/modules/auth/pages/login-page.tsx +12 -0
- package/templates/app/apps/web/src/modules/inventory/index.ts +4 -0
- package/templates/app/apps/web/src/modules/inventory/pages/inventory-page.tsx +29 -1
- package/templates/app/apps/web/src/modules/purchase/components/approval-history.tsx +67 -0
- package/templates/app/apps/web/src/modules/purchase/components/order-attachments.tsx +29 -0
- package/templates/app/apps/web/src/modules/purchase/components/order-reviewers.tsx +38 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-assistant-context.tsx +17 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.css +6 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +32 -3
- package/templates/app/apps/web/src/modules/purchase/index.ts +5 -0
- package/templates/app/apps/web/src/modules/purchase/lib/presentation.ts +20 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-intelligence.css +17 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +156 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +54 -22
- package/templates/app/apps/web/src/modules/workspace/index.ts +5 -0
- package/templates/app/apps/web/src/modules/workspace/pages/home-page.tsx +44 -0
- package/templates/app/apps/web/src/shared/hooks/use-command.ts +13 -0
- package/templates/app/apps/web/src/shared/lib/error-message.ts +15 -1
- package/templates/app/apps/web/src/shared/lib/navigation.ts +21 -0
- 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/apps/web/src/shared/ui/assistant-markdown.tsx +19 -0
- package/templates/app/apps/web/src/shared/ui/assistant-panel.css +134 -0
- package/templates/app/apps/web/src/shared/ui/assistant-panel.tsx +200 -0
- package/templates/app/apps/web/src/shared/ui/assistant-scope.tsx +72 -0
- package/templates/app/apps/web/src/shared/ui/attachment-upload.css +28 -0
- package/templates/app/apps/web/src/shared/ui/attachment-upload.tsx +163 -0
- package/templates/app/apps/web/src/shared/ui/notice.tsx +7 -0
- package/templates/app/apps/web/src/shared/ui/page-heading.tsx +7 -0
- package/templates/app/apps/web/src/shared/ui/page-state.css +47 -0
- package/templates/app/apps/web/src/shared/ui/page-state.tsx +76 -0
- package/templates/app/apps/web/src/shared/ui/require-capability.tsx +34 -0
- package/templates/app/apps/web/vite.config.ts +5 -0
- package/templates/app/cage.config.json +23 -0
- package/templates/app/db/migrations/0002_draft.sql +12 -0
- package/templates/app/db/migrations/0003_draft.sql +8 -0
- package/templates/app/db/migrations/0004_draft.sql +27 -0
- package/templates/app/deploy/nginx.conf +52 -0
- package/templates/app/docs/ai/README.md +28 -2
- package/templates/app/docs/ai/approvals.md +260 -0
- package/templates/app/docs/ai/architecture.md +26 -0
- package/templates/app/docs/ai/commenting.md +203 -0
- package/templates/app/docs/ai/diagnostics.md +80 -0
- package/templates/app/docs/ai/files.md +93 -0
- package/templates/app/docs/ai/intelligence.md +348 -0
- package/templates/app/docs/ai/ontology-context.md +59 -4
- package/templates/app/docs/ai/primitives.md +34 -1
- package/templates/app/docs/ai/project-structure.md +8 -3
- package/templates/app/docs/ai/purchase-example.md +35 -7
- package/templates/app/docs/ai/recipes.md +10 -2
- package/templates/app/docs/ai/routing.md +86 -0
- package/templates/app/docs/ai/troubleshooting.md +5 -0
- package/templates/app/docs/ai/workflow.md +24 -11
- package/templates/app/docs/errors/CAGE-AI-001.md +61 -0
- package/templates/app/docs/errors/CAGE-APPR-001.md +13 -9
- package/templates/app/docs/errors/CAGE-APPR-002.md +2 -2
- package/templates/app/docs/errors/CAGE-APPR-003.md +1 -1
- package/templates/app/docs/errors/CAGE-APPR-004.md +4 -4
- package/templates/app/docs/errors/CAGE-APPR-005.md +4 -4
- package/templates/app/docs/errors/CAGE-APPR-006.md +29 -8
- package/templates/app/docs/errors/CAGE-APPR-007.md +9 -7
- package/templates/app/docs/errors/CAGE-APPR-010.md +5 -5
- package/templates/app/docs/errors/CAGE-AUDIT-001.md +53 -0
- package/templates/app/docs/errors/CAGE-AUTH-001.md +1 -1
- package/templates/app/docs/errors/CAGE-BIZ-001.md +51 -0
- package/templates/app/docs/errors/CAGE-BIZ-002.md +49 -0
- 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 +3 -3
- 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 +6 -6
- package/templates/app/docs/errors/CAGE-CONN-008.md +5 -5
- package/templates/app/docs/errors/CAGE-CONN-009.md +6 -6
- package/templates/app/docs/errors/CAGE-CONN-010.md +6 -6
- 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 +49 -0
- package/templates/app/docs/errors/CAGE-CONN-014.md +48 -0
- package/templates/app/docs/errors/CAGE-CONN-015.md +49 -0
- package/templates/app/docs/errors/CAGE-CREATE-001.md +10 -9
- package/templates/app/docs/errors/CAGE-DECL-001.md +2 -2
- package/templates/app/docs/errors/CAGE-DECL-002.md +7 -6
- 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 +2 -2
- package/templates/app/docs/errors/CAGE-DEP-002.md +1 -1
- package/templates/app/docs/errors/CAGE-DEP-003.md +2 -2
- 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 +53 -0
- package/templates/app/docs/errors/CAGE-DLQ-001.md +2 -2
- package/templates/app/docs/errors/CAGE-ENV-001.md +4 -4
- package/templates/app/docs/errors/CAGE-ENV-002.md +51 -0
- package/templates/app/docs/errors/CAGE-ENV-003.md +48 -0
- package/templates/app/docs/errors/CAGE-EVENT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-EVENT-002.md +7 -7
- package/templates/app/docs/errors/CAGE-FIELD-001.md +2 -2
- 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 +56 -0
- package/templates/app/docs/errors/CAGE-GEN-001.md +2 -2
- package/templates/app/docs/errors/CAGE-GEN-002.md +1 -1
- package/templates/app/docs/errors/CAGE-GEN-003.md +53 -0
- package/templates/app/docs/errors/CAGE-GRANT-001.md +3 -3
- package/templates/app/docs/errors/CAGE-GRANT-002.md +2 -2
- package/templates/app/docs/errors/CAGE-GRANT-003.md +7 -6
- package/templates/app/docs/errors/CAGE-IMPACT-001.md +2 -2
- package/templates/app/docs/errors/CAGE-IMPACT-002.md +52 -0
- 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 +2 -2
- package/templates/app/docs/errors/CAGE-INIT-004.md +1 -1
- package/templates/app/docs/errors/CAGE-INIT-005.md +52 -0
- 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 +3 -3
- 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 +7 -6
- package/templates/app/docs/errors/CAGE-PAIR-002.md +6 -5
- package/templates/app/docs/errors/CAGE-QUERY-001.md +1 -1
- package/templates/app/docs/errors/CAGE-QUERY-002.md +2 -2
- package/templates/app/docs/errors/CAGE-QUERY-003.md +7 -6
- package/templates/app/docs/errors/CAGE-REACT-001.md +3 -3
- package/templates/app/docs/errors/CAGE-REACT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-REACT-003.md +50 -0
- package/templates/app/docs/errors/CAGE-READS-001.md +9 -6
- package/templates/app/docs/errors/CAGE-REG-001.md +13 -12
- package/templates/app/docs/errors/CAGE-REG-002.md +2 -1
- package/templates/app/docs/errors/CAGE-REG-003.md +1 -1
- package/templates/app/docs/errors/CAGE-REL-001.md +14 -14
- package/templates/app/docs/errors/CAGE-ROUTE-001.md +7 -6
- package/templates/app/docs/errors/CAGE-SCOPE-001.md +1 -1
- package/templates/app/docs/errors/CAGE-SCOPE-002.md +2 -2
- package/templates/app/docs/errors/CAGE-SECRET-001.md +2 -2
- package/templates/app/docs/errors/CAGE-SECRET-002.md +1 -1
- package/templates/app/docs/errors/CAGE-SENS-001.md +5 -5
- 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 +3 -3
- 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 +3 -2
- package/templates/app/docs/errors/CAGE-STATE-010.md +5 -5
- package/templates/app/docs/errors/CAGE-STATE-011.md +5 -5
- package/templates/app/docs/errors/CAGE-STRUCT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-STRUCT-002.md +1 -1
- package/templates/app/docs/errors/CAGE-SYNC-001.md +3 -2
- package/templates/app/docs/errors/CAGE-TEST-001.md +1 -1
- package/templates/app/docs/errors/CAGE-TEST-002.md +50 -0
- package/templates/app/docs/errors/CAGE-TEST-003.md +52 -0
- package/templates/app/docs/errors/CAGE-UNSAFE-001.md +3 -2
- package/templates/app/docs/errors/CAGE-UPGRADE-001.md +2 -2
- package/templates/app/docs/errors/CAGE-WORKER-001.md +48 -0
- package/templates/app/docs/errors/CAGE-WORKER-002.md +50 -0
- package/templates/app/docs/errors/CAGE-WORKER-003.md +48 -0
- package/templates/app/docs/errors/README.md +52 -2
- 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/dept/index.ts +10 -10
- package/templates/app/packages/contracts/src/aggregates/inventory/index.ts +5 -5
- package/templates/app/packages/contracts/src/aggregates/inventory-lock/index.ts +4 -4
- package/templates/app/packages/contracts/src/aggregates/purchase-order/index.ts +34 -12
- package/templates/app/packages/contracts/src/aggregates/role/index.ts +15 -15
- package/templates/app/packages/contracts/src/aggregates/user/index.ts +16 -16
- package/templates/app/packages/contracts/src/index.ts +16 -1
- package/templates/app/packages/contracts/src/queries/index.ts +2 -2
- package/templates/app/packages/domain/package.json +5 -4
- package/templates/app/packages/domain/src/index.ts +4 -0
- package/templates/app/packages/domain/src/integrations/erp.ts +15 -0
- package/templates/app/packages/domain/src/integrations/intelligence.ts +20 -0
- package/templates/app/packages/domain/src/modules/inventory/index.ts +1 -0
- package/templates/app/packages/domain/src/modules/inventory/inventory.ts +12 -0
- package/templates/app/packages/domain/src/modules/purchase/approval-members.ts +54 -0
- package/templates/app/packages/domain/src/modules/purchase/approvals.ts +38 -6
- package/templates/app/packages/domain/src/modules/purchase/index.ts +2 -0
- package/templates/app/packages/domain/src/modules/purchase/intelligence.test.ts +224 -0
- package/templates/app/packages/domain/src/modules/purchase/jobs.ts +5 -0
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.test.ts +9 -0
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.ts +125 -4
- package/templates/app/packages/domain/src/modules/purchase/queries.ts +5 -0
- package/templates/app/packages/domain/src/workflows/purchase-inventory.ts +6 -0
- package/templates/app/packages/domain/src/workflows/purchase-process.ts +42 -0
- package/templates/blank/.dockerignore +2 -0
- package/templates/blank/.env.example +10 -0
- package/templates/blank/AGENTS.md +50 -10
- package/templates/blank/README.md +4 -1
- package/templates/blank/_gitignore +1 -0
- package/templates/blank/apps/api/package.json +1 -1
- package/templates/blank/apps/api/src/main.ts +4 -1
- package/templates/blank/apps/api/src/worker.ts +2 -0
- package/templates/blank/apps/web/index.html +1 -0
- package/templates/blank/apps/web/package.json +7 -3
- package/templates/blank/apps/web/src/app/app.tsx +80 -18
- package/templates/blank/apps/web/src/app/styles/global.css +90 -0
- package/templates/blank/apps/web/src/app/use-session.ts +43 -0
- package/templates/blank/apps/web/src/app/workspace-layout.tsx +30 -5
- package/templates/blank/apps/web/src/main.tsx +5 -0
- package/templates/blank/apps/web/src/modules/auth/index.ts +4 -0
- package/templates/blank/apps/web/src/modules/auth/pages/login-page.tsx +18 -1
- package/templates/blank/apps/web/src/modules/directory/index.ts +7 -0
- package/templates/blank/apps/web/src/modules/directory/pages/user-detail-page.tsx +78 -0
- package/templates/blank/apps/web/src/modules/directory/pages/users-page.tsx +93 -0
- package/templates/blank/apps/web/src/modules/workspace/index.ts +4 -0
- package/templates/blank/apps/web/src/modules/workspace/pages/home-page.tsx +17 -0
- package/templates/blank/apps/web/src/shared/lib/error-message.ts +11 -0
- package/templates/blank/apps/web/src/shared/lib/navigation.ts +21 -0
- package/templates/blank/apps/web/src/shared/ui/brand.tsx +10 -2
- package/templates/blank/apps/web/src/shared/ui/page-state.css +47 -0
- package/templates/blank/apps/web/src/shared/ui/page-state.tsx +76 -0
- package/templates/blank/apps/web/src/shared/ui/require-capability.tsx +34 -0
- package/templates/blank/apps/web/vite.config.ts +5 -0
- package/templates/blank/deploy/nginx.conf +52 -0
- package/templates/blank/docs/ai/README.md +28 -2
- package/templates/blank/docs/ai/approvals.md +260 -0
- package/templates/blank/docs/ai/architecture.md +26 -0
- package/templates/blank/docs/ai/commenting.md +203 -0
- package/templates/blank/docs/ai/diagnostics.md +80 -0
- package/templates/blank/docs/ai/files.md +93 -0
- package/templates/blank/docs/ai/intelligence.md +348 -0
- package/templates/blank/docs/ai/ontology-context.md +59 -4
- package/templates/blank/docs/ai/primitives.md +34 -1
- package/templates/blank/docs/ai/project-structure.md +8 -3
- package/templates/blank/docs/ai/purchase-example.md +35 -7
- package/templates/blank/docs/ai/recipes.md +10 -2
- package/templates/blank/docs/ai/routing.md +86 -0
- package/templates/blank/docs/ai/troubleshooting.md +5 -0
- package/templates/blank/docs/ai/workflow.md +24 -11
- package/templates/blank/docs/errors/CAGE-AI-001.md +61 -0
- package/templates/blank/docs/errors/CAGE-APPR-001.md +13 -9
- package/templates/blank/docs/errors/CAGE-APPR-002.md +2 -2
- package/templates/blank/docs/errors/CAGE-APPR-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-APPR-004.md +4 -4
- package/templates/blank/docs/errors/CAGE-APPR-005.md +4 -4
- package/templates/blank/docs/errors/CAGE-APPR-006.md +29 -8
- package/templates/blank/docs/errors/CAGE-APPR-007.md +9 -7
- package/templates/blank/docs/errors/CAGE-APPR-010.md +5 -5
- package/templates/blank/docs/errors/CAGE-AUDIT-001.md +53 -0
- package/templates/blank/docs/errors/CAGE-AUTH-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-BIZ-001.md +51 -0
- package/templates/blank/docs/errors/CAGE-BIZ-002.md +49 -0
- 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 +3 -3
- 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 +6 -6
- package/templates/blank/docs/errors/CAGE-CONN-008.md +5 -5
- package/templates/blank/docs/errors/CAGE-CONN-009.md +6 -6
- package/templates/blank/docs/errors/CAGE-CONN-010.md +6 -6
- 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 +49 -0
- package/templates/blank/docs/errors/CAGE-CONN-014.md +48 -0
- package/templates/blank/docs/errors/CAGE-CONN-015.md +49 -0
- package/templates/blank/docs/errors/CAGE-CREATE-001.md +10 -9
- package/templates/blank/docs/errors/CAGE-DECL-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-DECL-002.md +7 -6
- 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 +2 -2
- package/templates/blank/docs/errors/CAGE-DEP-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-DEP-003.md +2 -2
- 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 +53 -0
- package/templates/blank/docs/errors/CAGE-DLQ-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-ENV-001.md +4 -4
- package/templates/blank/docs/errors/CAGE-ENV-002.md +51 -0
- package/templates/blank/docs/errors/CAGE-ENV-003.md +48 -0
- package/templates/blank/docs/errors/CAGE-EVENT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-EVENT-002.md +7 -7
- package/templates/blank/docs/errors/CAGE-FIELD-001.md +2 -2
- 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 +56 -0
- package/templates/blank/docs/errors/CAGE-GEN-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-GEN-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-GEN-003.md +53 -0
- package/templates/blank/docs/errors/CAGE-GRANT-001.md +3 -3
- package/templates/blank/docs/errors/CAGE-GRANT-002.md +2 -2
- package/templates/blank/docs/errors/CAGE-GRANT-003.md +7 -6
- package/templates/blank/docs/errors/CAGE-IMPACT-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-IMPACT-002.md +52 -0
- 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 +2 -2
- package/templates/blank/docs/errors/CAGE-INIT-004.md +1 -1
- package/templates/blank/docs/errors/CAGE-INIT-005.md +52 -0
- 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 +3 -3
- 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 +7 -6
- package/templates/blank/docs/errors/CAGE-PAIR-002.md +6 -5
- package/templates/blank/docs/errors/CAGE-QUERY-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-QUERY-002.md +2 -2
- package/templates/blank/docs/errors/CAGE-QUERY-003.md +7 -6
- package/templates/blank/docs/errors/CAGE-REACT-001.md +3 -3
- package/templates/blank/docs/errors/CAGE-REACT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-REACT-003.md +50 -0
- package/templates/blank/docs/errors/CAGE-READS-001.md +9 -6
- package/templates/blank/docs/errors/CAGE-REG-001.md +13 -12
- package/templates/blank/docs/errors/CAGE-REG-002.md +2 -1
- package/templates/blank/docs/errors/CAGE-REG-003.md +1 -1
- package/templates/blank/docs/errors/CAGE-REL-001.md +14 -14
- package/templates/blank/docs/errors/CAGE-ROUTE-001.md +7 -6
- package/templates/blank/docs/errors/CAGE-SCOPE-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-SCOPE-002.md +2 -2
- package/templates/blank/docs/errors/CAGE-SECRET-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-SECRET-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-SENS-001.md +5 -5
- 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 +3 -3
- 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 +3 -2
- package/templates/blank/docs/errors/CAGE-STATE-010.md +5 -5
- package/templates/blank/docs/errors/CAGE-STATE-011.md +5 -5
- package/templates/blank/docs/errors/CAGE-STRUCT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-STRUCT-002.md +1 -1
- package/templates/blank/docs/errors/CAGE-SYNC-001.md +3 -2
- package/templates/blank/docs/errors/CAGE-TEST-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-TEST-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-TEST-003.md +52 -0
- package/templates/blank/docs/errors/CAGE-UNSAFE-001.md +3 -2
- package/templates/blank/docs/errors/CAGE-UPGRADE-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-WORKER-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-WORKER-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-WORKER-003.md +48 -0
- package/templates/blank/docs/errors/README.md +52 -2
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/main.ts +5 -1
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/worker.ts +3 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/app.tsx +122 -12
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/styles/global.css +88 -8
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/use-session.ts +43 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/workspace-layout.tsx +58 -19
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/main.tsx +5 -0
- 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 +13 -5
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/index.ts +4 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +89 -38
- 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/auth/index.ts +4 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/pages/login-page.tsx +12 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/index.ts +4 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/pages/inventory-page.tsx +29 -1
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/approval-history.tsx +67 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/order-attachments.tsx +29 -0
- 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-assistant-context.tsx +17 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.css +6 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +32 -3
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/index.ts +5 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/lib/presentation.ts +20 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-intelligence.css +17 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +156 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +54 -22
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/workspace/index.ts +5 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/workspace/pages/home-page.tsx +44 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/hooks/use-command.ts +13 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +15 -1
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/navigation.ts +21 -0
- 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/apps/web/src/shared/ui/assistant-markdown.tsx +19 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-panel.css +134 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-panel.tsx +200 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-scope.tsx +72 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/attachment-upload.css +28 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/attachment-upload.tsx +163 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/notice.tsx +7 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-heading.tsx +7 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-state.css +47 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-state.tsx +76 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/require-capability.tsx +34 -0
- package/templates/blank/docs/examples/purchase-admin/cage.config.json +23 -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/dept/index.ts +10 -10
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory/index.ts +5 -5
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory-lock/index.ts +4 -4
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +34 -12
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/role/index.ts +15 -15
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/user/index.ts +16 -16
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +16 -1
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/queries/index.ts +2 -2
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/index.ts +4 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/erp.ts +15 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/intelligence.ts +20 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/index.ts +1 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/inventory.ts +12 -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 +38 -6
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/intelligence.test.ts +224 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/jobs.ts +5 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.test.ts +9 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +125 -4
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/queries.ts +5 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-inventory.ts +6 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-process.ts +42 -0
- package/templates/blank/package.json +3 -3
- package/templates/blank/packages/contracts/package.json +1 -1
- package/templates/blank/packages/contracts/src/aggregates/dept/index.ts +10 -10
- package/templates/blank/packages/contracts/src/aggregates/role/index.ts +15 -15
- package/templates/blank/packages/contracts/src/aggregates/user/index.ts +16 -16
- package/templates/blank/packages/contracts/src/index.ts +1 -1
- package/templates/blank/packages/contracts/src/queries/index.ts +1 -1
- package/templates/blank/packages/domain/package.json +5 -4
- package/templates/blank/packages/domain/src/index.ts +2 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购详情中的专业复核配置,申请人在草稿或退回后选择实际会签人。
|
|
3
|
+
* 配置通过 setReviewers 命令保存,提交后只读;页面不能更改已经生成的审批实例快照。
|
|
4
|
+
* 名录只用于展示与选人,服务端重查有效名录、提单人隔离、状态、版本及命令权限。
|
|
5
|
+
*/
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { can, purchaseOrder, type PurchaseOrder } from '@app/contracts';
|
|
8
|
+
import { useCommand } from '../../../shared/hooks/use-command';
|
|
9
|
+
import { ApprovalMemberPicker, useApprovalMembers } from '../../../shared/ui/approval-member-picker';
|
|
10
|
+
import { Notice } from '../../../shared/ui/notice';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* order 为安全读取的最新单据;null 草稿表示未编辑,避免后台刷新覆盖用户尚未保存的选人。
|
|
14
|
+
* 保存携带当前业务版本,成功清空本地草稿并刷新缓存;失败保留选择和提示,不重试写入。
|
|
15
|
+
* 名录首页外或已不可读人员保留 UUID,不能把查不到名称解释为已删除或无需签署。
|
|
16
|
+
*/
|
|
17
|
+
export function OrderReviewers({ order }: { order: PurchaseOrder }) {
|
|
18
|
+
const [draft, setDraft] = useState<{ enabled: boolean; ids: string[] } | null>(null);
|
|
19
|
+
const members = useApprovalMembers(1, true);
|
|
20
|
+
const { run, busy, message, clearMessage } = useCommand();
|
|
21
|
+
const editable = order.status === 'draft' && can('PurchaseOrder.setReviewers');
|
|
22
|
+
const selection = draft ?? { enabled: order.needsSpecialistReview ?? false, ids: order.reviewerIds ?? [] };
|
|
23
|
+
const names = (order.reviewerIds ?? []).map(id => members.data?.items.find(member => member.userId === id)?.displayName ?? id);
|
|
24
|
+
return <section className="purpose-editor">
|
|
25
|
+
<h2>审批安排{draft && editable && <small className="muted"> · 尚未保存</small>}</h2>
|
|
26
|
+
<p className="muted">{selection.enabled ? '需要所选专业人员全部签署。' : '本单不要求专业人员会签。'}具体流转与其他审批环节以提交后的流程记录为准。</p>
|
|
27
|
+
{message && <Notice onClose={clearMessage}>{message}</Notice>}
|
|
28
|
+
{editable ? <>
|
|
29
|
+
<label className="review-checkbox"><input type="checkbox" checked={selection.enabled} disabled={busy}
|
|
30
|
+
onChange={event => setDraft({ ...selection, enabled: event.target.checked })} />需要专业会签(技术、质量等)</label>
|
|
31
|
+
{selection.enabled && <ApprovalMemberPicker value={selection.ids} onChange={ids => setDraft({ ...selection, ids })} exclude={[order.createdBy]} disabled={busy} />}
|
|
32
|
+
<div className="actions"><span className="muted">退回后可重新安排,重提将开启新一轮审批。</span>
|
|
33
|
+
<button disabled={busy || draft === null || (selection.enabled && !selection.ids.length)} onClick={async () => {
|
|
34
|
+
if (await run(() => purchaseOrder.setReviewers(order.id, { needsSpecialistReview: selection.enabled, reviewerIds: selection.enabled ? selection.ids : [] }, order.version), '审批安排已保存')) setDraft(null);
|
|
35
|
+
}}>保存审批安排</button></div>
|
|
36
|
+
</> : <p>{order.needsSpecialistReview ? `专业会签:${names.join('、')}` : '无需专业会签'}</p>}
|
|
37
|
+
</section>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购模块与公共助理之间的语义适配层,提供采购字段、命令的中文展示名称。
|
|
3
|
+
* 页面通过 context 显式传入当前所见,不导入领域运行时或自行获取越权数据。
|
|
4
|
+
*/
|
|
5
|
+
import type { PageContextInput } from '@yss123/cage-client';
|
|
6
|
+
import { AssistantPage } from '../../../shared/ui/assistant-scope';
|
|
7
|
+
|
|
8
|
+
// 页面只补充业务文案与上下文,工具发现、授权及命令执行全部使用框架 SDK。
|
|
9
|
+
/**
|
|
10
|
+
* 将页面快照与建议问题注册到最近的 AssistantScope,不再创建第二个面板。
|
|
11
|
+
* context 中的记录/草稿来源由页面负责;标签仅用于确认展示,工具与授权仍来自框架。
|
|
12
|
+
*/
|
|
13
|
+
export function PurchaseAssistantContext({ context, suggestions }: { context: PageContextInput; suggestions: string[] }) {
|
|
14
|
+
return <AssistantPage context={context} suggestions={suggestions}
|
|
15
|
+
fieldLabels={{ reason: '采购用途', amount: '金额', items: '物料', key: '偏好名称', value: '偏好内容', comment: '审批意见', outcome: '审批结果' }}
|
|
16
|
+
commandLabels={{ 'PurchaseOrder.submit': '提交采购审批', 'PurchaseOrder.explainPurpose': '保存采购用途', 'PurchaseOrder.cancel': '取消采购单', 'PurchaseOrder.create': '新建采购单' }} />;
|
|
17
|
+
}
|
|
@@ -1,12 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购草稿创建表单,收集物料、数量、元金额字符串、用途、电话和已上传附件。
|
|
3
|
+
* 创建人、部门和初始状态由领域及内核确定;浏览器表单限制只承担输入体验。
|
|
4
|
+
*/
|
|
1
5
|
// 只处理采购草稿输入;状态迁移、创建人和权限仍由领域命令决定。
|
|
2
6
|
import { useState, type FormEvent } from 'react';
|
|
3
|
-
import { purchaseOrder } from '@app/contracts';
|
|
7
|
+
import { purchaseOrder, PurchaseOrderFileFields, currentUser } from '@app/contracts';
|
|
4
8
|
import type { RunCommand } from '../../../shared/hooks/use-command';
|
|
9
|
+
import { AttachmentUpload } from '../../../shared/ui/attachment-upload';
|
|
10
|
+
import './purchase-order-form.css';
|
|
11
|
+
import { ApprovalMemberPicker } from '../../../shared/ui/approval-member-picker';
|
|
5
12
|
|
|
13
|
+
/**
|
|
14
|
+
* 在弹窗中维护附件列表、上传状态和提交状态。
|
|
15
|
+
* close 由列表控制弹窗关闭;run 使用 useCommand 协议返回成功布尔值,失败保留输入。
|
|
16
|
+
* 上传中的文件不能随表单提交,上传完成仍需 create 命令正式绑定。
|
|
17
|
+
*/
|
|
6
18
|
export function PurchaseOrderForm({ close, run }: { close: () => void; run: RunCommand }) {
|
|
7
19
|
const [busy, setBusy] = useState(false);
|
|
20
|
+
const [attachments, setAttachments] = useState<string[]>([]);
|
|
21
|
+
const [uploading, setUploading] = useState(false);
|
|
22
|
+
const [needsSpecialistReview, setNeedsSpecialistReview] = useState(false);
|
|
23
|
+
const [reviewerIds, setReviewerIds] = useState<string[]>([]);
|
|
24
|
+
/**
|
|
25
|
+
* 读取 FormData 并构造创建输入:金额保留字符串,数量转数值,可选空文本转 undefined。
|
|
26
|
+
* 等待 run 返回后解除提交态,只有成功才关闭;依赖 run 返回结果,不额外捕获任意抛出异常。
|
|
27
|
+
*/
|
|
8
28
|
async function submit(event: FormEvent<HTMLFormElement>) {
|
|
9
29
|
event.preventDefault();
|
|
30
|
+
if (uploading || busy) return;
|
|
10
31
|
setBusy(true);
|
|
11
32
|
const data = new FormData(event.currentTarget);
|
|
12
33
|
const ok = await run(
|
|
@@ -14,7 +35,10 @@ export function PurchaseOrderForm({ close, run }: { close: () => void; run: RunC
|
|
|
14
35
|
purchaseOrder.create({
|
|
15
36
|
amount: String(data.get('amount')),
|
|
16
37
|
supplierPhone: String(data.get('phone')) || undefined,
|
|
38
|
+
reason: String(data.get('reason')) || undefined,
|
|
17
39
|
items: [{ sku: String(data.get('sku')), qty: Number(data.get('qty')) }],
|
|
40
|
+
attachments,
|
|
41
|
+
needsSpecialistReview, reviewerIds: needsSpecialistReview ? reviewerIds : [],
|
|
18
42
|
}),
|
|
19
43
|
'采购单已保存为草稿',
|
|
20
44
|
);
|
|
@@ -23,7 +47,7 @@ export function PurchaseOrderForm({ close, run }: { close: () => void; run: RunC
|
|
|
23
47
|
}
|
|
24
48
|
return (
|
|
25
49
|
<div className="modal-backdrop">
|
|
26
|
-
<section role="dialog" aria-modal="true" aria-labelledby="new-order-title" className="modal">
|
|
50
|
+
<section role="dialog" aria-modal="true" aria-labelledby="new-order-title" className="modal purchase-order-dialog">
|
|
27
51
|
<div className="section-head">
|
|
28
52
|
<h2 id="new-order-title">新建采购单</h2>
|
|
29
53
|
<button onClick={close} aria-label="关闭">
|
|
@@ -50,7 +74,12 @@ export function PurchaseOrderForm({ close, run }: { close: () => void; run: RunC
|
|
|
50
74
|
<input name="phone" type="tel" maxLength={32} />
|
|
51
75
|
</label>
|
|
52
76
|
<p className="muted">保存后可在列表提交审批,创建人和部门会自动填写。</p>
|
|
53
|
-
<
|
|
77
|
+
<label>采购用途与必要性<textarea name="reason" rows={3} maxLength={2000} placeholder="说明具体用途;提交后先进行 AI 材料预审" /></label>
|
|
78
|
+
<label className="review-checkbox"><input type="checkbox" checked={needsSpecialistReview} onChange={event => setNeedsSpecialistReview(event.target.checked)} />需要专业会签(技术、质量等)</label>
|
|
79
|
+
{needsSpecialistReview && <ApprovalMemberPicker value={reviewerIds} onChange={setReviewerIds} exclude={currentUser() ? [currentUser()!.id] : []} disabled={busy} />}
|
|
80
|
+
<p className="muted">{needsSpecialistReview ? '所选专业人员需要全部签署。' : '本次不要求专业会签。'}提交后可在审批记录查看实际环节和办理进度。</p>
|
|
81
|
+
<AttachmentUpload command="PurchaseOrder.create" field="attachments" policy={PurchaseOrderFileFields.attachments} value={attachments} onChange={setAttachments} onBusyChange={setUploading} />
|
|
82
|
+
<button className="primary" disabled={busy || uploading || (needsSpecialistReview && reviewerIds.length === 0)}>
|
|
54
83
|
{busy ? '保存中…' : '保存草稿'}
|
|
55
84
|
</button>
|
|
56
85
|
</form>
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* purchase 模块的公开入口,对 app 暴露采购列表和独立详情页。
|
|
3
|
+
* 外部组合通过本入口导入,页面、组件与内部 helper 的组织由模块自行维护。
|
|
4
|
+
*/
|
|
1
5
|
// 模块公开入口:应用壳只需要页面,不依赖内部表单与本地状态。
|
|
2
6
|
export { PurchaseOrdersPage } from './pages/purchase-orders-page';
|
|
7
|
+
export { PurchaseOrderDetailPage } from './pages/purchase-order-detail-page';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购页面共享的展示映射与金额格式;不承载状态迁移、权限或精确金额计算。
|
|
3
|
+
*/
|
|
4
|
+
// 同一业务的列表与详情共用展示口径;不把采购状态塞入全局 shared。
|
|
5
|
+
/**
|
|
6
|
+
* 采购记录状态到中文名称的界面映射,状态合法性与迁移规则仍以领域声明为准。
|
|
7
|
+
*/
|
|
8
|
+
export const statusLabels: Record<string, string> = {
|
|
9
|
+
draft: '草稿',
|
|
10
|
+
pending_approval: '待审批',
|
|
11
|
+
approved: '已通过',
|
|
12
|
+
received: '已收货',
|
|
13
|
+
rejected: '已驳回',
|
|
14
|
+
cancelled: '已取消',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 把以元表示的金额字符串转为人民币格式显示;Number 转换只用于展示,不能用于账务计算。
|
|
18
|
+
*/
|
|
19
|
+
export const currency = (value: string) =>
|
|
20
|
+
new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(Number(value));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购详情的用途编辑区和审批轮次轨迹样式;实际审批状态来自 ApprovalHistory 的接口结果。
|
|
3
|
+
* 纵向步骤线表示展示层次,颜色不产生通过/退回判断;文本框允许用户按内容调整高度。
|
|
4
|
+
*/
|
|
5
|
+
.purchase-detail-content { min-width: 0; }
|
|
6
|
+
.purchase-detail-content .panel { margin-bottom: 22px; }
|
|
7
|
+
.purpose-editor { border-top: 1px solid var(--line, #e4e8df); padding: 20px 0; }
|
|
8
|
+
.purpose-editor > label { display: block; font-weight: 600; margin-bottom: 12px; }
|
|
9
|
+
.purpose-editor textarea { width: 100%; box-sizing: border-box; padding: 12px; border: 1px solid #d9e0d8; border-radius: 8px; font: inherit; resize: vertical; }
|
|
10
|
+
.purpose-editor .actions { margin-top: 12px; justify-content: space-between; }
|
|
11
|
+
.review-round { margin-top: 20px; }
|
|
12
|
+
.review-round h3 { font-size: 14px; color: #36594d; }
|
|
13
|
+
.review-step { margin: 14px 0; border-left: 2px solid #bbd0bd; padding: 4px 0 4px 18px; font-size: 13px; line-height: 1.8; }
|
|
14
|
+
.review-step p { margin: 6px 0; }
|
|
15
|
+
.review-suggestion { color: #956128; }
|
|
16
|
+
.review-actions { margin-top: 24px; border-top: 1px solid var(--line, #e4e8df); padding-top: 12px; }
|
|
17
|
+
.review-actions small { display: block; }
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购详情路由,直接从地址栏校验单据 ID 并独立读取,支持刷新与深链接。
|
|
3
|
+
* 详情由真实契约数据构建,附件、审批轨迹和助理上下文通过同模块组件组合。
|
|
4
|
+
*/
|
|
5
|
+
// 详情通过 URL 中的记录 ID 重新读取安全接口;刷新或新标签页不依赖列表的内存状态。
|
|
6
|
+
import { Link, useParams } from 'react-router-dom';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { can, PurchaseOrderSchema, purchaseOrder, usePurchaseOrder } from '@app/contracts';
|
|
9
|
+
import { useCommand } from '../../../shared/hooks/use-command';
|
|
10
|
+
import { PageState, QueryFailure } from '../../../shared/ui/page-state';
|
|
11
|
+
import { PageHeading } from '../../../shared/ui/page-heading';
|
|
12
|
+
import { Notice } from '../../../shared/ui/notice';
|
|
13
|
+
import { currency, statusLabels } from '../lib/presentation';
|
|
14
|
+
import { OrderAttachments } from '../components/order-attachments';
|
|
15
|
+
import { PurchaseAssistantContext } from '../components/purchase-assistant-context';
|
|
16
|
+
import { ApprovalHistory } from '../components/approval-history';
|
|
17
|
+
import { OrderReviewers } from '../components/order-reviewers';
|
|
18
|
+
import './purchase-intelligence.css';
|
|
19
|
+
|
|
20
|
+
const list = '/procurement/orders';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 从 URL 参数获取 id,使用生成 schema 校验;无效地址给返回列表入口。
|
|
24
|
+
* 有效 ID 作为 OrderDetail 的 key,使切换记录时重置本地未保存用途草稿。
|
|
25
|
+
*/
|
|
26
|
+
export function PurchaseOrderDetailPage() {
|
|
27
|
+
const { id } = useParams();
|
|
28
|
+
const parsed = PurchaseOrderSchema.shape.id.safeParse(id);
|
|
29
|
+
if (!parsed.success)
|
|
30
|
+
return <PageState title="单据地址无效" description="请从采购单列表选择有效单据。" to={list} />;
|
|
31
|
+
return <OrderDetail key={parsed.data} id={parsed.data} />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 按 id 读取单据;待审批期间定时刷新,错误不作为空记录处理。
|
|
36
|
+
* 用途草稿用 null 区分尚未编辑与主动清空;保存成功后回到服务端数据,命令使用当前 version。
|
|
37
|
+
* 助理收到明确区分的已保存 record 与未保存 draft;审批历史及附件仍各自按权限读取。
|
|
38
|
+
*/
|
|
39
|
+
function OrderDetail({ id }: { id: string }) {
|
|
40
|
+
const query = usePurchaseOrder(id);
|
|
41
|
+
const [draftReason, setDraftReason] = useState<string | null>(null);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (query.data?.status !== 'pending_approval') return;
|
|
44
|
+
const timer = setInterval(() => void query.refetch(), 1500);
|
|
45
|
+
return () => clearInterval(timer);
|
|
46
|
+
}, [query.data?.status, query.refetch]);
|
|
47
|
+
const { run, busy, message, clearMessage } = useCommand();
|
|
48
|
+
if (query.isPending)
|
|
49
|
+
return (
|
|
50
|
+
<p className="empty" role="status">
|
|
51
|
+
正在读取采购单…
|
|
52
|
+
</p>
|
|
53
|
+
);
|
|
54
|
+
if (query.isError) return <QueryFailure error={query.error} to={list} retry={() => void query.refetch()} />;
|
|
55
|
+
const order = query.data;
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<PageHeading
|
|
59
|
+
eyebrow={`PO-${order.id.slice(0, 8).toUpperCase()}`}
|
|
60
|
+
title="采购单详情"
|
|
61
|
+
description="单据内容与最新状态,以服务端记录为准。"
|
|
62
|
+
>
|
|
63
|
+
<Link className="route-link" to={list}>
|
|
64
|
+
← 返回采购单
|
|
65
|
+
</Link>
|
|
66
|
+
</PageHeading>
|
|
67
|
+
{message && <Notice onClose={clearMessage}>{message}</Notice>}
|
|
68
|
+
<div className="purchase-detail-content"><section className="panel">
|
|
69
|
+
<div className="section-head">
|
|
70
|
+
<span className={`badge ${order.status}`}>{statusLabels[order.status]}</span>
|
|
71
|
+
<div className="actions">
|
|
72
|
+
{order.status === 'draft' && can('PurchaseOrder.submit') && (
|
|
73
|
+
<button
|
|
74
|
+
className="primary"
|
|
75
|
+
disabled={busy}
|
|
76
|
+
onClick={() => run(() => purchaseOrder.submit(id, order.version), '已提交审批')}
|
|
77
|
+
>
|
|
78
|
+
提交审批
|
|
79
|
+
</button>
|
|
80
|
+
)}
|
|
81
|
+
{['draft', 'pending_approval', 'approved'].includes(order.status) &&
|
|
82
|
+
can('PurchaseOrder.cancel') && (
|
|
83
|
+
<button
|
|
84
|
+
disabled={busy}
|
|
85
|
+
onClick={() => run(() => purchaseOrder.cancel(id, order.version), '采购单已取消')}
|
|
86
|
+
>
|
|
87
|
+
取消采购单
|
|
88
|
+
</button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<dl className="record-facts">
|
|
93
|
+
<div>
|
|
94
|
+
<dt>采购金额</dt>
|
|
95
|
+
<dd className="amount">{currency(order.amount)}</dd>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<dt>ERP 单号</dt>
|
|
99
|
+
<dd>{order.erpNo ?? '尚未同步'}</dd>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<dt>供应商电话</dt>
|
|
103
|
+
<dd>{order.supplierPhone ?? '未填写'}</dd>
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<dt>创建时间</dt>
|
|
107
|
+
<dd>{new Date(order.createdAt).toLocaleString('zh-CN')}</dd>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<dt>单据 ID</dt>
|
|
111
|
+
<dd className="mono">{order.id}</dd>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<dt>当前版本</dt>
|
|
115
|
+
<dd>{order.version}</dd>
|
|
116
|
+
</div>
|
|
117
|
+
</dl>
|
|
118
|
+
<div className="purpose-editor"><label htmlFor="purchase-purpose">采购用途与必要性</label>
|
|
119
|
+
{order.status === 'draft' && can('PurchaseOrder.explainPurpose') ? <>
|
|
120
|
+
<textarea id="purchase-purpose" rows={4} maxLength={2000} value={draftReason ?? order.reason ?? ''} onChange={e => setDraftReason(e.target.value)} placeholder="说明用于什么工作、需要哪些物料以及采购原因" />
|
|
121
|
+
<div className="actions"><small className="muted">助理可检查此处尚未保存的内容。</small><button disabled={busy || draftReason === null || !draftReason.trim()} onClick={async () => {
|
|
122
|
+
if (await run(() => purchaseOrder.explainPurpose(id, { reason: draftReason! }, order.version), '采购用途已保存')) setDraftReason(null);
|
|
123
|
+
}}>保存用途</button></div>
|
|
124
|
+
</> : <p>{order.reason || '尚未填写用途'}</p>}
|
|
125
|
+
</div>
|
|
126
|
+
<OrderReviewers order={order} />
|
|
127
|
+
<OrderAttachments order={order} />
|
|
128
|
+
<div className="section-head">
|
|
129
|
+
<h2>采购物料</h2>
|
|
130
|
+
<span className="muted">{order.items.length} 项</span>
|
|
131
|
+
</div>
|
|
132
|
+
<table>
|
|
133
|
+
<thead>
|
|
134
|
+
<tr>
|
|
135
|
+
<th>物料编码</th>
|
|
136
|
+
<th>采购数量</th>
|
|
137
|
+
</tr>
|
|
138
|
+
</thead>
|
|
139
|
+
<tbody>
|
|
140
|
+
{order.items.map((item, index) => (
|
|
141
|
+
<tr key={`${item.sku}-${index}`}>
|
|
142
|
+
<td className="mono">{item.sku}</td>
|
|
143
|
+
<td>{item.qty}</td>
|
|
144
|
+
</tr>
|
|
145
|
+
))}
|
|
146
|
+
</tbody>
|
|
147
|
+
</table>
|
|
148
|
+
</section>
|
|
149
|
+
<ApprovalHistory id={id} version={order.version} pending={order.status === 'pending_approval'} /></div>
|
|
150
|
+
<PurchaseAssistantContext context={{ page: { id: 'purchase-order-detail', title: '采购单详情', kind: 'detail', route: `/procurement/orders/${id}` },
|
|
151
|
+
subject: { aggregate: 'PurchaseOrder', id, observedVersion: order.version },
|
|
152
|
+
...(draftReason !== null && order.status === 'draft' ? { draft: { command: 'PurchaseOrder.explainPurpose', values: { reason: draftReason }, baseVersion: order.version } } : {}),
|
|
153
|
+
}} suggestions={['检查当前采购用途是否符合审核要求', '这张采购单现在到哪一步了?', '帮我提交当前采购单']} />
|
|
154
|
+
</>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -1,26 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购列表页:从 URL 恢复状态筛选与页码,查询真实单据,提供创建、提交、取消和后台导出入口。
|
|
3
|
+
* 通过采购模块入口进入路由;审批待办统计由 app 注入,查询和写入均消费生成契约。
|
|
4
|
+
*/
|
|
1
5
|
// 采购页面拥有筛选、分页、新建和导出状态;其他模块不用了解这些实现细节。
|
|
2
6
|
import { useState } from 'react';
|
|
7
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
3
8
|
import { can, client, purchaseOrder, usePurchaseOrderList, useTransfer } from '@app/contracts';
|
|
4
9
|
import { useCommand } from '../../../shared/hooks/use-command';
|
|
5
10
|
import { errorMessage } from '../../../shared/lib/error-message';
|
|
6
11
|
import { Notice } from '../../../shared/ui/notice';
|
|
7
12
|
import { PageHeading } from '../../../shared/ui/page-heading';
|
|
13
|
+
import { QueryFailure } from '../../../shared/ui/page-state';
|
|
8
14
|
import { PurchaseOrderForm } from '../components/purchase-order-form';
|
|
15
|
+
import { currency, statusLabels } from '../lib/presentation';
|
|
16
|
+
import { PurchaseAssistantContext } from '../components/purchase-assistant-context';
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
rejected: '已驳回',
|
|
16
|
-
cancelled: '已取消',
|
|
17
|
-
};
|
|
18
|
-
const currency = (value: string) =>
|
|
19
|
-
new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(Number(value));
|
|
20
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 按状态及每页十条读取服务端列表,加载、失败和空结果分别呈现。
|
|
20
|
+
* pendingTasks 可省略,表示待办数量尚不可用;创建弹窗和导出任务 ID 只保存在当前页面状态。
|
|
21
|
+
* 提交/取消携带读取到的 version,服务端负责并发、状态及授权检查;导出完成后才显示下载入口。
|
|
22
|
+
*/
|
|
21
23
|
export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number }) {
|
|
22
|
-
|
|
23
|
-
const [
|
|
24
|
+
// 可分享、刷新恢复的列表条件进入 URL;弹窗和提交中的临时状态继续留在组件里。
|
|
25
|
+
const [search, setSearch] = useSearchParams();
|
|
26
|
+
const status = search.get('status') ?? '';
|
|
27
|
+
const requestedPage = Number(search.get('page') ?? 1);
|
|
28
|
+
const page = Number.isSafeInteger(requestedPage) && requestedPage > 0 ? requestedPage : 1;
|
|
29
|
+
/**
|
|
30
|
+
* 更新 status/page 查询参数并保留其他参数;第一页省略 page。切换条件由调用方传入第一页。
|
|
31
|
+
*/
|
|
32
|
+
function filter(nextStatus: string, nextPage: number) {
|
|
33
|
+
const params = new URLSearchParams(search);
|
|
34
|
+
nextStatus ? params.set('status', nextStatus) : params.delete('status');
|
|
35
|
+
nextPage === 1 ? params.delete('page') : params.set('page', String(nextPage));
|
|
36
|
+
setSearch(params);
|
|
37
|
+
}
|
|
24
38
|
const [creating, setCreating] = useState(false);
|
|
25
39
|
const [exporting, setExporting] = useState(false);
|
|
26
40
|
const [exportId, setExportId] = useState<string | null>(null);
|
|
@@ -29,6 +43,10 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
29
43
|
const { run, busy, message, clearMessage } = useCommand();
|
|
30
44
|
// 列表和统计来自服务端;待办数量由 app 组合层传入,采购模块不依赖审批页面。
|
|
31
45
|
const orders = usePurchaseOrderList({ status, page, pageSize: 10, sort: '-createdAt' });
|
|
46
|
+
/**
|
|
47
|
+
* 提交当前状态筛选的后台导出任务。成功只取得任务 ID,由 useTransfer 继续观察完成/失败/过期。
|
|
48
|
+
* 不把任务接受当作 CSV 已可下载,也不只导出当前十条表格数据。
|
|
49
|
+
*/
|
|
32
50
|
async function exportCsv() {
|
|
33
51
|
setExporting(true);
|
|
34
52
|
setExportError('');
|
|
@@ -37,6 +55,15 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
37
55
|
if (result.ok) setExportId(result.data.id);
|
|
38
56
|
else setExportError(errorMessage(result));
|
|
39
57
|
}
|
|
58
|
+
if (orders.isError)
|
|
59
|
+
return (
|
|
60
|
+
<QueryFailure
|
|
61
|
+
error={orders.error}
|
|
62
|
+
to="/workspace"
|
|
63
|
+
label="返回工作台"
|
|
64
|
+
retry={() => void orders.refetch()}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
40
67
|
return (
|
|
41
68
|
<>
|
|
42
69
|
<PageHeading eyebrow="PURCHASE ORDERS" title="采购单" description="每一张单据的进展,清晰可见。">
|
|
@@ -104,8 +131,7 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
104
131
|
className={status === value ? 'active' : ''}
|
|
105
132
|
key={value}
|
|
106
133
|
onClick={() => {
|
|
107
|
-
|
|
108
|
-
setPage(1);
|
|
134
|
+
filter(value, 1);
|
|
109
135
|
}}
|
|
110
136
|
>
|
|
111
137
|
{label}
|
|
@@ -120,10 +146,6 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
120
146
|
</div>
|
|
121
147
|
{orders.isPending ? (
|
|
122
148
|
<p className="empty">正在读取采购单…</p>
|
|
123
|
-
) : orders.isError ? (
|
|
124
|
-
<p role="alert" className="error">
|
|
125
|
-
读取失败:{orders.error.message}
|
|
126
|
-
</p>
|
|
127
149
|
) : (
|
|
128
150
|
<>
|
|
129
151
|
<div className="table-wrap">
|
|
@@ -142,7 +164,14 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
142
164
|
{orders.data.items.map((order) => (
|
|
143
165
|
<tr key={order.id}>
|
|
144
166
|
<td>
|
|
145
|
-
<
|
|
167
|
+
<Link
|
|
168
|
+
className="record-link mono"
|
|
169
|
+
to={`/procurement/orders/${encodeURIComponent(order.id)}`}
|
|
170
|
+
>
|
|
171
|
+
PO-{order.id.slice(0, 8).toUpperCase()}
|
|
172
|
+
</Link>
|
|
173
|
+
{/* 用途摘要帮助采购员辨认单据;完整材料仍在详情页按安全读权限展示。 */}
|
|
174
|
+
{order.reason && <small title={order.reason}>{order.reason.length > 48 ? `${order.reason.slice(0, 48)}…` : order.reason}</small>}
|
|
146
175
|
<small>{new Date(order.createdAt).toLocaleString('zh-CN')}</small>
|
|
147
176
|
</td>
|
|
148
177
|
<td>{order.items.map((i) => `${i.sku} × ${i.qty}`).join('、')}</td>
|
|
@@ -193,10 +222,10 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
193
222
|
共 {orders.data.total} 单 · 第 {page} 页
|
|
194
223
|
</span>
|
|
195
224
|
<div>
|
|
196
|
-
<button disabled={page === 1} onClick={() =>
|
|
225
|
+
<button disabled={page === 1} onClick={() => filter(status, page - 1)}>
|
|
197
226
|
上一页
|
|
198
227
|
</button>
|
|
199
|
-
<button disabled={page * 10 >= orders.data.total} onClick={() =>
|
|
228
|
+
<button disabled={page * 10 >= orders.data.total} onClick={() => filter(status, page + 1)}>
|
|
200
229
|
下一页
|
|
201
230
|
</button>
|
|
202
231
|
</div>
|
|
@@ -205,6 +234,9 @@ export function PurchaseOrdersPage({ pendingTasks }: { pendingTasks?: number })
|
|
|
205
234
|
)}
|
|
206
235
|
</section>
|
|
207
236
|
{creating && <PurchaseOrderForm close={() => setCreating(false)} run={run} />}
|
|
237
|
+
<PurchaseAssistantContext context={{ page: { id: 'purchase-orders', title: '采购单列表', kind: 'list', route: `/procurement/orders?${search}` },
|
|
238
|
+
view: { aggregate: 'PurchaseOrder', input: { status, page, pageSize: 10 }, visibleIds: orders.data?.items.map(o => o.id) ?? [] },
|
|
239
|
+
}} suggestions={['汇总本月我有权限查看的采购金额', '采购单的审批规则是什么?']} />
|
|
208
240
|
</>
|
|
209
241
|
);
|
|
210
242
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购工作台首页,提供采购、审批和库存的固定业务入口,不伪造业务统计。
|
|
3
|
+
* 实际记录与待办在对应页面通过生成契约读取,系统管理由独立页面负责。
|
|
4
|
+
*/
|
|
5
|
+
import { Link } from 'react-router-dom';
|
|
6
|
+
import { PageHeading } from '../../../shared/ui/page-heading';
|
|
7
|
+
import { AssistantPage } from '../../../shared/ui/assistant-scope';
|
|
8
|
+
|
|
9
|
+
// 工作台展示实际配置的业务入口,不制造演示统计;访问权限由目标路由及接口检查。
|
|
10
|
+
/**
|
|
11
|
+
* 渲染导航卡片,链接进入各模块独立 URL。
|
|
12
|
+
* 本页不按能力隐藏卡片,进入页面后仍需通过路由守卫和服务端检查。
|
|
13
|
+
*/
|
|
14
|
+
export function HomePage() {
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<AssistantPage context={{ page: { id: 'workspace', title: '采购工作台', kind: 'dashboard', route: '/workspace' },
|
|
18
|
+
visibleContent: [{ id: 'workspace-entries', text: '工作台提供采购单、审批待办和库存台账入口。' }],
|
|
19
|
+
}} suggestions={['我有哪些待处理的审批?', '了解采购单的完整处理流程', '汇总本月我能查看的采购金额']} />
|
|
20
|
+
<PageHeading
|
|
21
|
+
eyebrow="WORKSPACE"
|
|
22
|
+
title="采购工作台"
|
|
23
|
+
description="从采购、审批到库存,每个页面都有可以收藏和分享的地址。"
|
|
24
|
+
/>
|
|
25
|
+
<section className="entry-grid">
|
|
26
|
+
<Link to="/procurement/orders">
|
|
27
|
+
<small>01 / PROCUREMENT</small>
|
|
28
|
+
<h2>采购单 ↗</h2>
|
|
29
|
+
<p>查看单据、新建采购与跟踪处理进度。</p>
|
|
30
|
+
</Link>
|
|
31
|
+
<Link to="/approvals">
|
|
32
|
+
<small>02 / APPROVALS</small>
|
|
33
|
+
<h2>审批待办 ↗</h2>
|
|
34
|
+
<p>处理分配给你的任务,直接打开关联单据。</p>
|
|
35
|
+
</Link>
|
|
36
|
+
<Link to="/inventory">
|
|
37
|
+
<small>03 / INVENTORY</small>
|
|
38
|
+
<h2>库存台账 ↗</h2>
|
|
39
|
+
<p>查看物料与登记数量。</p>
|
|
40
|
+
</Link>
|
|
41
|
+
</section>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 页面命令调用的轻量状态封装,统一提交中状态、成功反馈和领域错误文案。
|
|
3
|
+
* 消费契约客户端返回的 CageResult;不会自行写数据库或将前端显示状态当作授权。
|
|
4
|
+
*/
|
|
1
5
|
import { useState } from 'react';
|
|
2
6
|
import { useQueryClient } from '@tanstack/react-query';
|
|
3
7
|
import type { CageResult } from '@app/contracts';
|
|
4
8
|
import { errorMessage } from '../lib/error-message';
|
|
5
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 页面传入 action 执行一次命令,success 为业务成功提示。
|
|
12
|
+
* Promise<boolean> 只表示本次结果是否 ok,供表单决定关闭或保留草稿;调用者仍需提供目标和版本。
|
|
13
|
+
*/
|
|
6
14
|
export type RunCommand = (
|
|
7
15
|
action: () => Promise<CageResult<unknown, string>>,
|
|
8
16
|
success: string,
|
|
@@ -12,6 +20,11 @@ export type RunCommand = (
|
|
|
12
20
|
* 页面间复用提交状态和结果提示,不包含采购或库存的业务判断。
|
|
13
21
|
* 成功后使查询缓存失效,让列表从服务端重新读取版本和状态;写入仍由生成 SDK 完成。
|
|
14
22
|
*/
|
|
23
|
+
/**
|
|
24
|
+
* 返回 run、busy、message 和 clearMessage,供按钮禁用与结果提示复用。
|
|
25
|
+
* run 等待 action 返回 CageResult 后解除 busy,成功时请求刷新查询缓存,失败时保留错误文案。
|
|
26
|
+
* 本封装没有捕获 action 抛出的异常;action 应遵守客户端的结果协议,不能把它当作任意异步任务的异常屏障。
|
|
27
|
+
*/
|
|
15
28
|
export function useCommand() {
|
|
16
29
|
const cache = useQueryClient();
|
|
17
30
|
const [busy, setBusy] = useState(false);
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把契约命令的失败结果转换为页面提示,集中维护本应用的错误码文案。
|
|
3
|
+
* 请求追踪标识只用于排错关联,不能用显示文案代替对 ok/code 的控制流判断。
|
|
4
|
+
*/
|
|
1
5
|
import type { CageResult } from '@app/contracts';
|
|
2
6
|
|
|
3
7
|
/** 将框架错误码转换成操作提示,保留 requestId 供用户向维护人员反馈。 */
|
|
8
|
+
/**
|
|
9
|
+
* 选取 domainCode(缺省时取 code)查文案,未知值保留错误码,并在存在 requestId 时附上短追踪标识。
|
|
10
|
+
* @param r 已经判定 ok 为 false 的客户端结果,不接收成功数据。
|
|
11
|
+
* @returns 适合 Notice 展示的错误描述;不会重试命令或修改结果。
|
|
12
|
+
*/
|
|
4
13
|
export function errorMessage(r: Extract<CageResult<unknown, string>, { ok: false }>): string {
|
|
5
14
|
const labels: Record<string, string> = {
|
|
6
15
|
FORBIDDEN: '你没有执行此操作的权限',
|
|
7
16
|
INVALID_INPUT: '请检查填写内容',
|
|
8
|
-
CONFLICT: '
|
|
17
|
+
CONFLICT: '数据或任务已更新,请刷新后重试',
|
|
9
18
|
UNAUTHENTICATED: '登录已过期',
|
|
10
19
|
CONSTRAINT_VIOLATED: '未满足审批条件,请检查审批人或额度',
|
|
11
20
|
STOCK_SHORT: '库存不足',
|
|
21
|
+
REVIEWERS_REQUIRED: '请为专业会签选择 1–5 位协作人',
|
|
22
|
+
REVIEWERS_NOT_REQUIRED: '取消专业会签时请清空复核人员',
|
|
23
|
+
DUPLICATE_REVIEWERS: '专业会签人员不能重复',
|
|
24
|
+
REVIEWER_IS_APPLICANT: '申请人不能担任本单专业会签人',
|
|
25
|
+
REVIEWER_UNAVAILABLE: '所选人员已不可用,请重新核对协作名录',
|
|
12
26
|
NETWORK: '连接失败,请稍后重试',
|
|
13
27
|
};
|
|
14
28
|
return `${labels[r.domainCode ?? r.code] ?? r.domainCode ?? r.code}${r.requestId ? ` · 请求 ${r.requestId.slice(0, 8)}` : ''}`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 登录回跳的应用边界:只接受本应用的业务路径,阻止外站及认证/API/静态资源路径回跳。
|
|
3
|
+
* 由路由组合层调用,纯解析 URL 字符串,不发起导航或网络请求。
|
|
4
|
+
*/
|
|
5
|
+
/** 登录回跳只接受本应用页面;固定基址只用于解析,不会发起网络请求。 */
|
|
6
|
+
/**
|
|
7
|
+
* 提取 search 中的 returnTo,使用固定解析基准检查同源路径并排除保留前缀。
|
|
8
|
+
* @param search 登录页查询字符串,可含 URL 编码的 returnTo。
|
|
9
|
+
* @param fallback 缺失、格式不合法或越界时返回的可信业务首页。
|
|
10
|
+
* @returns 可回跳路径,保留其查询参数与 hash;不返回外站 URL。
|
|
11
|
+
*/
|
|
12
|
+
export function loginReturnTo(search: string, fallback: string): string {
|
|
13
|
+
const requested = new URLSearchParams(search).get('returnTo');
|
|
14
|
+
const origin = 'https://cage.local';
|
|
15
|
+
if (!requested?.startsWith('/') || !URL.canParse(requested, origin)) return fallback;
|
|
16
|
+
const url = new URL(requested, origin);
|
|
17
|
+
// 保留查询条件和锚点;阻止外站跳转、登录循环及把服务端入口当成 React 页面。
|
|
18
|
+
if (url.origin !== origin || /^\/(login|admin|api|auth|hooks|assets)(\/|$)/.test(url.pathname))
|
|
19
|
+
return fallback;
|
|
20
|
+
return url.pathname + url.search + url.hash;
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 协作名录的复选行布局供采购和审批模块复用;不覆盖其他表单,窄屏单列保持可点击标签。 */
|
|
2
|
+
.approval-member-picker { border: 1px solid #dbe3d9; border-radius: 8px; padding: 14px; margin: 14px 0; min-width: 0; }
|
|
3
|
+
.approval-member-picker legend { font-size: 12px; padding: 0 6px; font-weight: 600; }
|
|
4
|
+
.approval-member-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px; margin: 12px 0; max-height: 250px; overflow-y: auto; }
|
|
5
|
+
.approval-member-options label { display: flex; align-items: center; gap: 10px; padding: 10px; border: 1px solid #e5e9e2; border-radius: 6px; cursor: pointer; }
|
|
6
|
+
.approval-member-options input[type='checkbox'] { width: 16px; height: 16px; flex: 0 0 auto; }
|
|
7
|
+
.approval-member-options small { display: block; margin-top: 3px; color: #647365; }
|
|
8
|
+
/* 开关与选人复选框保留原生尺寸,避免通用表单的 width:100% 让标签错位。 */
|
|
9
|
+
label.review-checkbox { display: flex; align-items: center; gap: 10px; }
|
|
10
|
+
label.review-checkbox input { width: auto; flex: none; }
|