@yss123/cage-cli 0.0.0-stage → 0.1.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 +18 -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 +26 -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 +47 -0
- package/dist/config.js.map +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +47 -0
- package/dist/create.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/database.js +70 -0
- package/dist/database.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 +155 -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 +167 -0
- package/dist/gen/contracts.js.map +1 -0
- package/dist/gen/index.d.ts +19 -0
- package/dist/gen/index.js +137 -0
- package/dist/gen/index.js.map +1 -0
- package/dist/gen/zod-code.d.ts +7 -0
- package/dist/gen/zod-code.js +161 -0
- package/dist/gen/zod-code.js.map +1 -0
- package/dist/graph-snapshot.d.ts +13 -0
- package/dist/graph-snapshot.js +46 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +87 -0
- package/dist/graph.js.map +1 -0
- package/dist/impact.d.ts +28 -0
- package/dist/impact.js +223 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/index.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 +176 -0
- package/dist/main.js.map +1 -0
- package/dist/migrate.d.ts +7 -0
- package/dist/migrate.js +69 -0
- package/dist/migrate.js.map +1 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +270 -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/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 +225 -0
- package/dist/static-check.js.map +1 -0
- package/dist/sync.d.ts +5 -0
- package/dist/sync.js +46 -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 +1065 -0
- package/graph-ui/index.html +197 -0
- package/graph-ui/model.js +141 -0
- package/graph-ui/relations.js +125 -0
- package/graph-ui/structure.js +74 -0
- package/graph-ui/style.css +1698 -0
- package/managed/errors/CAGE-APPR-001.md +32 -0
- package/managed/errors/CAGE-APPR-002.md +32 -0
- package/managed/errors/CAGE-APPR-003.md +32 -0
- package/managed/errors/CAGE-APPR-004.md +32 -0
- package/managed/errors/CAGE-APPR-005.md +32 -0
- package/managed/errors/CAGE-APPR-006.md +32 -0
- package/managed/errors/CAGE-APPR-007.md +32 -0
- package/managed/errors/CAGE-APPR-010.md +32 -0
- package/managed/errors/CAGE-AUTH-001.md +32 -0
- package/managed/errors/CAGE-BUDGET-001.md +32 -0
- package/managed/errors/CAGE-BUDGET-002.md +32 -0
- package/managed/errors/CAGE-BUDGET-003.md +32 -0
- package/managed/errors/CAGE-CMD-002.md +32 -0
- package/managed/errors/CAGE-CMD-003.md +34 -0
- package/managed/errors/CAGE-CONN-001.md +32 -0
- package/managed/errors/CAGE-CONN-002.md +32 -0
- package/managed/errors/CAGE-CONN-003.md +32 -0
- package/managed/errors/CAGE-CONN-004.md +32 -0
- package/managed/errors/CAGE-CONN-005.md +32 -0
- package/managed/errors/CAGE-CONN-006.md +32 -0
- package/managed/errors/CAGE-CONN-007.md +32 -0
- package/managed/errors/CAGE-CONN-008.md +32 -0
- package/managed/errors/CAGE-CONN-009.md +32 -0
- package/managed/errors/CAGE-CONN-010.md +32 -0
- package/managed/errors/CAGE-CONN-011.md +32 -0
- package/managed/errors/CAGE-CONN-012.md +32 -0
- package/managed/errors/CAGE-CREATE-001.md +32 -0
- package/managed/errors/CAGE-DECL-001.md +34 -0
- package/managed/errors/CAGE-DECL-002.md +34 -0
- package/managed/errors/CAGE-DECL-003.md +34 -0
- package/managed/errors/CAGE-DECL-004.md +34 -0
- package/managed/errors/CAGE-DEP-001.md +34 -0
- package/managed/errors/CAGE-DEP-002.md +34 -0
- package/managed/errors/CAGE-DEP-003.md +34 -0
- package/managed/errors/CAGE-DEP-004.md +34 -0
- package/managed/errors/CAGE-DEP-005.md +34 -0
- package/managed/errors/CAGE-DLQ-001.md +32 -0
- package/managed/errors/CAGE-ENV-001.md +32 -0
- package/managed/errors/CAGE-EVENT-001.md +32 -0
- package/managed/errors/CAGE-EVENT-002.md +32 -0
- package/managed/errors/CAGE-FIELD-001.md +32 -0
- package/managed/errors/CAGE-FIELD-002.md +32 -0
- package/managed/errors/CAGE-FIELD-003.md +32 -0
- package/managed/errors/CAGE-FIELD-004.md +32 -0
- package/managed/errors/CAGE-GEN-001.md +33 -0
- package/managed/errors/CAGE-GEN-002.md +33 -0
- package/managed/errors/CAGE-GRANT-001.md +32 -0
- package/managed/errors/CAGE-GRANT-002.md +32 -0
- package/managed/errors/CAGE-GRANT-003.md +32 -0
- package/managed/errors/CAGE-IMPACT-001.md +32 -0
- package/managed/errors/CAGE-JOB-001.md +32 -0
- package/managed/errors/CAGE-JOB-002.md +32 -0
- package/managed/errors/CAGE-JOB-003.md +32 -0
- package/managed/errors/CAGE-LINT-001.md +32 -0
- package/managed/errors/CAGE-MIG-001.md +32 -0
- package/managed/errors/CAGE-MIG-002.md +32 -0
- package/managed/errors/CAGE-MIG-003.md +32 -0
- package/managed/errors/CAGE-MIG-004.md +32 -0
- package/managed/errors/CAGE-MIG-005.md +32 -0
- package/managed/errors/CAGE-NAME-001.md +32 -0
- package/managed/errors/CAGE-NAME-002.md +32 -0
- package/managed/errors/CAGE-NAME-003.md +32 -0
- package/managed/errors/CAGE-NAME-004.md +32 -0
- package/managed/errors/CAGE-NAME-005.md +32 -0
- package/managed/errors/CAGE-NAME-006.md +32 -0
- package/managed/errors/CAGE-ORG-001.md +32 -0
- package/managed/errors/CAGE-PAIR-002.md +33 -0
- package/managed/errors/CAGE-QUERY-001.md +32 -0
- package/managed/errors/CAGE-QUERY-002.md +32 -0
- package/managed/errors/CAGE-QUERY-003.md +32 -0
- package/managed/errors/CAGE-REACT-001.md +32 -0
- package/managed/errors/CAGE-REACT-002.md +32 -0
- package/managed/errors/CAGE-READS-001.md +32 -0
- package/managed/errors/CAGE-REG-001.md +32 -0
- package/managed/errors/CAGE-REG-002.md +32 -0
- package/managed/errors/CAGE-REG-003.md +32 -0
- package/managed/errors/CAGE-ROUTE-001.md +32 -0
- package/managed/errors/CAGE-SCOPE-001.md +32 -0
- package/managed/errors/CAGE-SCOPE-002.md +32 -0
- package/managed/errors/CAGE-SECRET-001.md +32 -0
- package/managed/errors/CAGE-SECRET-002.md +32 -0
- package/managed/errors/CAGE-SENS-001.md +32 -0
- package/managed/errors/CAGE-SQL-001.md +32 -0
- package/managed/errors/CAGE-SQL-002.md +32 -0
- package/managed/errors/CAGE-STATE-001.md +32 -0
- package/managed/errors/CAGE-STATE-002.md +32 -0
- package/managed/errors/CAGE-STATE-003.md +32 -0
- package/managed/errors/CAGE-STATE-004.md +32 -0
- package/managed/errors/CAGE-STATE-005.md +32 -0
- package/managed/errors/CAGE-STATE-010.md +32 -0
- package/managed/errors/CAGE-STATE-011.md +32 -0
- package/managed/errors/CAGE-SYNC-001.md +35 -0
- package/managed/errors/CAGE-TEST-001.md +33 -0
- package/managed/errors/CAGE-UNSAFE-001.md +32 -0
- package/managed/errors/CAGE-UPGRADE-001.md +32 -0
- package/managed/errors/README.md +99 -0
- package/package.json +60 -3
- package/templates/app/.dockerignore +10 -0
- package/templates/app/.env.example +17 -0
- package/templates/app/.github/workflows/cage.yml +44 -0
- package/templates/app/AGENTS.md +13 -0
- package/templates/app/Dockerfile +23 -0
- package/templates/app/README.md +58 -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/main.tsx +552 -0
- package/templates/app/apps/web/src/style.css +663 -0
- package/templates/app/apps/web/tsconfig.json +11 -0
- package/templates/app/apps/web/vite.config.ts +16 -0
- package/templates/app/cage.config.json +114 -0
- package/templates/app/compose.yaml +39 -0
- package/templates/app/db/migrations/0001_init.sql +112 -0
- package/templates/app/package.json +25 -0
- package/templates/app/packages/contracts/package.json +16 -0
- package/templates/app/packages/contracts/src/dept.ts +74 -0
- package/templates/app/packages/contracts/src/index.ts +81 -0
- package/templates/app/packages/contracts/src/inventory-lock.ts +52 -0
- package/templates/app/packages/contracts/src/inventory.ts +57 -0
- package/templates/app/packages/contracts/src/purchase-order.ts +72 -0
- package/templates/app/packages/contracts/src/queries.ts +17 -0
- package/templates/app/packages/contracts/src/role.ts +85 -0
- package/templates/app/packages/contracts/src/user.ts +89 -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/approvals.ts +18 -0
- package/templates/app/packages/domain/src/erp.ts +36 -0
- package/templates/app/packages/domain/src/index.ts +9 -0
- package/templates/app/packages/domain/src/inventory.ts +43 -0
- package/templates/app/packages/domain/src/jobs.ts +13 -0
- package/templates/app/packages/domain/src/purchase-order.test.ts +137 -0
- package/templates/app/packages/domain/src/purchase-order.ts +122 -0
- package/templates/app/packages/domain/src/queries.ts +10 -0
- package/templates/app/packages/domain/src/reactions.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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domain": "packages/domain/src/index.ts",
|
|
3
|
+
"contracts": "packages/contracts/src",
|
|
4
|
+
"typecheck": ["packages/domain", "packages/contracts", "apps/api", "apps/web"],
|
|
5
|
+
"devDbPort": 54329,
|
|
6
|
+
"graph": {
|
|
7
|
+
"title": "采购业务图谱",
|
|
8
|
+
"labels": {
|
|
9
|
+
"aggregate:PurchaseOrder": "采购单",
|
|
10
|
+
"aggregate:Inventory": "库存台账",
|
|
11
|
+
"aggregate:InventoryLock": "库存锁",
|
|
12
|
+
"aggregate:Dept": "部门",
|
|
13
|
+
"aggregate:User": "用户",
|
|
14
|
+
"aggregate:Role": "角色",
|
|
15
|
+
"command:PurchaseOrder.create": "新建采购单",
|
|
16
|
+
"command:PurchaseOrder.submit": "提交审批",
|
|
17
|
+
"command:PurchaseOrder.approve": "通过审批",
|
|
18
|
+
"command:PurchaseOrder.reject": "驳回审批",
|
|
19
|
+
"command:PurchaseOrder.cancel": "取消采购单",
|
|
20
|
+
"command:PurchaseOrder.markReceived": "确认收货",
|
|
21
|
+
"command:PurchaseOrder.recordErpNo": "回写外部单号",
|
|
22
|
+
"command:Inventory.create": "登记库存",
|
|
23
|
+
"command:InventoryLock.acquire": "锁定库存",
|
|
24
|
+
"command:InventoryLock.release": "释放库存锁",
|
|
25
|
+
"event:OrderSubmitted": "采购单已提交",
|
|
26
|
+
"event:OrderApproved": "采购单已批准",
|
|
27
|
+
"event:OrderCancelled": "采购单已取消",
|
|
28
|
+
"event:OrderReceived": "采购单已收货",
|
|
29
|
+
"reaction:lockOnApprove": "审批通过后锁库存",
|
|
30
|
+
"reaction:releaseOnCancel": "取消后释放库存锁",
|
|
31
|
+
"reaction:releaseOnReceive": "收货后释放库存锁",
|
|
32
|
+
"approval:poApproval": "采购审批流程",
|
|
33
|
+
"route:route:po-approval": "采购审批分支",
|
|
34
|
+
"query:poByStatus": "按状态汇总采购单",
|
|
35
|
+
"job:expireStaleOrders": "清理过期采购草稿",
|
|
36
|
+
"connector:connector:erp": "企业资源系统",
|
|
37
|
+
"outbound:pushToErp": "推送采购单至外部",
|
|
38
|
+
"inbound:erpReceived": "接收外部收货通知",
|
|
39
|
+
"state:PurchaseOrder.draft": "草稿",
|
|
40
|
+
"state:PurchaseOrder.pending_approval": "待审批",
|
|
41
|
+
"state:PurchaseOrder.approved": "已批准",
|
|
42
|
+
"state:PurchaseOrder.received": "已收货",
|
|
43
|
+
"state:PurchaseOrder.rejected": "已驳回",
|
|
44
|
+
"state:PurchaseOrder.cancelled": "已取消",
|
|
45
|
+
"state:Inventory.active": "有效库存",
|
|
46
|
+
"state:Inventory.deleted": "已删除",
|
|
47
|
+
"state:InventoryLock.held": "锁定中",
|
|
48
|
+
"state:InventoryLock.released": "已释放",
|
|
49
|
+
"field:PurchaseOrder.amount": "采购金额",
|
|
50
|
+
"field:PurchaseOrder.deptId": "所属部门",
|
|
51
|
+
"field:PurchaseOrder.createdBy": "提单人",
|
|
52
|
+
"field:PurchaseOrder.supplierPhone": "供应商电话",
|
|
53
|
+
"field:PurchaseOrder.items": "采购明细",
|
|
54
|
+
"field:PurchaseOrder.erpNo": "外部系统单号",
|
|
55
|
+
"field:Inventory.sku": "物料编码",
|
|
56
|
+
"field:Inventory.available": "台账数量",
|
|
57
|
+
"field:InventoryLock.orderId": "采购单编号",
|
|
58
|
+
"field:InventoryLock.items": "锁定明细",
|
|
59
|
+
"capability:PurchaseOrder.read": "查看采购单",
|
|
60
|
+
"capability:PurchaseOrder.export": "导出采购单",
|
|
61
|
+
"capability:Inventory.read": "查看库存台账",
|
|
62
|
+
"capability:Inventory.export": "导出库存台账",
|
|
63
|
+
"capability:InventoryLock.read": "查看库存锁",
|
|
64
|
+
"capability:InventoryLock.export": "导出库存锁",
|
|
65
|
+
"command:Dept.create": "新建部门",
|
|
66
|
+
"command:Dept.update": "修改部门信息",
|
|
67
|
+
"command:Dept.archive": "停用部门",
|
|
68
|
+
"state:Dept.active": "启用中",
|
|
69
|
+
"state:Dept.archived": "已停用",
|
|
70
|
+
"field:Dept.name": "部门名称",
|
|
71
|
+
"field:Dept.code": "部门编码",
|
|
72
|
+
"field:Dept.parentId": "上级部门",
|
|
73
|
+
"field:Dept.ancestors": "部门层级路径",
|
|
74
|
+
"field:Dept.leaderId": "部门负责人",
|
|
75
|
+
"capability:Dept.read": "查看部门",
|
|
76
|
+
"capability:Dept.export": "导出部门",
|
|
77
|
+
"command:User.create": "新建用户",
|
|
78
|
+
"command:User.update": "修改用户信息",
|
|
79
|
+
"command:User.assignRoles": "分配角色",
|
|
80
|
+
"command:User.disable": "停用用户",
|
|
81
|
+
"command:User.enable": "启用用户",
|
|
82
|
+
"command:User.setPassword": "设置密码",
|
|
83
|
+
"state:User.active": "启用中",
|
|
84
|
+
"state:User.disabled": "已停用",
|
|
85
|
+
"field:User.username": "登录账号",
|
|
86
|
+
"field:User.name": "用户姓名",
|
|
87
|
+
"field:User.deptId": "所属部门",
|
|
88
|
+
"field:User.roleIds": "拥有的角色",
|
|
89
|
+
"field:User.phone": "手机号码",
|
|
90
|
+
"field:User.email": "电子邮箱",
|
|
91
|
+
"field:User.ssoSubject": "单点登录标识",
|
|
92
|
+
"field:User.passwordHash": "密码摘要",
|
|
93
|
+
"capability:User.read": "查看用户",
|
|
94
|
+
"capability:User.export": "导出用户",
|
|
95
|
+
"command:Role.create": "新建角色",
|
|
96
|
+
"command:Role.rename": "修改角色名称",
|
|
97
|
+
"command:Role.archive": "停用角色",
|
|
98
|
+
"command:Role.grantCapability": "授予能力",
|
|
99
|
+
"command:Role.revokeCapability": "收回能力",
|
|
100
|
+
"command:Role.setScope": "设置数据范围",
|
|
101
|
+
"state:Role.active": "启用中",
|
|
102
|
+
"state:Role.archived": "已停用",
|
|
103
|
+
"field:Role.code": "角色编码",
|
|
104
|
+
"field:Role.name": "角色名称",
|
|
105
|
+
"field:Role.capabilities": "能力点与额度",
|
|
106
|
+
"field:Role.scopes": "数据访问范围",
|
|
107
|
+
"capability:Role.read": "查看角色",
|
|
108
|
+
"capability:Role.export": "导出角色",
|
|
109
|
+
"resource:inventory.lock": "库存锁",
|
|
110
|
+
"operation:pushOrder": "推送采购单",
|
|
111
|
+
"event:goodsReceived": "外部收货通知"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 开发库和测试库使用不同 PostgreSQL 集群。testkit 会初始化集群角色,
|
|
2
|
+
# 因此不能把测试超级用户连接串指向持久化的开发库或生产库。
|
|
3
|
+
services:
|
|
4
|
+
postgres:
|
|
5
|
+
image: postgres:18-bookworm
|
|
6
|
+
restart: unless-stopped
|
|
7
|
+
environment:
|
|
8
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?先运行 pnpm db:setup 生成本地凭证}
|
|
9
|
+
POSTGRES_DB: postgres
|
|
10
|
+
ports:
|
|
11
|
+
- '127.0.0.1:${CAGE_POSTGRES_PORT:-54329}:5432'
|
|
12
|
+
volumes:
|
|
13
|
+
# PostgreSQL 18 官方镜像把数据目录放在此父目录的 18/docker 中。
|
|
14
|
+
- postgres-data:/var/lib/postgresql
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ['CMD-SHELL', 'pg_isready -U postgres -d postgres']
|
|
17
|
+
interval: 2s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 30
|
|
20
|
+
|
|
21
|
+
postgres-test:
|
|
22
|
+
image: postgres:18-bookworm
|
|
23
|
+
profiles: [test]
|
|
24
|
+
environment:
|
|
25
|
+
POSTGRES_PASSWORD: ${CAGE_TEST_POSTGRES_PASSWORD:?先运行 pnpm db:setup}
|
|
26
|
+
POSTGRES_DB: postgres
|
|
27
|
+
ports:
|
|
28
|
+
- '127.0.0.1:${CAGE_TEST_POSTGRES_PORT:-54330}:5432'
|
|
29
|
+
# 测试数据可丢弃:停止并重新创建容器后,由测试环境重新建临时库。
|
|
30
|
+
tmpfs:
|
|
31
|
+
- /var/lib/postgresql
|
|
32
|
+
healthcheck:
|
|
33
|
+
test: ['CMD-SHELL', 'pg_isready -U postgres -d postgres']
|
|
34
|
+
interval: 2s
|
|
35
|
+
timeout: 5s
|
|
36
|
+
retries: 30
|
|
37
|
+
|
|
38
|
+
volumes:
|
|
39
|
+
postgres-data:
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
-- 迁移草稿:由 cage gen 生成,人审后提交;已发布的迁移不要修改
|
|
2
|
+
-- 破坏性变更按「先扩展再收缩」分多次发布
|
|
3
|
+
|
|
4
|
+
-- 新表:来自聚合 Inventory
|
|
5
|
+
CREATE TABLE IF NOT EXISTS "core"."inventory" (
|
|
6
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
"tenant_id" uuid NOT NULL,
|
|
8
|
+
"sku" text NOT NULL,
|
|
9
|
+
"available" integer NOT NULL,
|
|
10
|
+
"status" text NOT NULL,
|
|
11
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
12
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
13
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
14
|
+
CONSTRAINT "inventory_status_check" CHECK (status IN ('active', 'deleted'))
|
|
15
|
+
);
|
|
16
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "inventory_sku_uq" ON "core"."inventory" (tenant_id, "sku");
|
|
17
|
+
CREATE INDEX IF NOT EXISTS "inventory_status_idx" ON "core"."inventory" ("tenant_id", "status");
|
|
18
|
+
|
|
19
|
+
-- 新表:来自聚合 InventoryLock
|
|
20
|
+
CREATE TABLE IF NOT EXISTS "core"."inventory_lock" (
|
|
21
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
22
|
+
"tenant_id" uuid NOT NULL,
|
|
23
|
+
"order_id" uuid NOT NULL,
|
|
24
|
+
"items" jsonb NOT NULL,
|
|
25
|
+
"status" text NOT NULL,
|
|
26
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
27
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
29
|
+
CONSTRAINT "inventory_lock_status_check" CHECK (status IN ('held', 'released'))
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS "inventory_lock_status_idx" ON "core"."inventory_lock" ("tenant_id", "status");
|
|
32
|
+
|
|
33
|
+
-- 新表:来自聚合 Dept
|
|
34
|
+
CREATE TABLE IF NOT EXISTS "sys"."dept" (
|
|
35
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
36
|
+
"tenant_id" uuid NOT NULL,
|
|
37
|
+
"name" text NOT NULL,
|
|
38
|
+
"code" text,
|
|
39
|
+
"parent_id" uuid REFERENCES "sys"."dept"(id),
|
|
40
|
+
"ancestors" text NOT NULL,
|
|
41
|
+
"leader_id" uuid,
|
|
42
|
+
"status" text NOT NULL,
|
|
43
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
44
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
45
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
CONSTRAINT "dept_status_check" CHECK (status IN ('active', 'archived'))
|
|
47
|
+
);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS "dept_status_idx" ON "sys"."dept" ("tenant_id", "status");
|
|
49
|
+
CREATE INDEX IF NOT EXISTS "dept_parent_id_idx" ON "sys"."dept" ("parent_id");
|
|
50
|
+
|
|
51
|
+
-- 新表:来自聚合 User
|
|
52
|
+
CREATE TABLE IF NOT EXISTS "sys"."user" (
|
|
53
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
54
|
+
"tenant_id" uuid NOT NULL,
|
|
55
|
+
"username" text NOT NULL,
|
|
56
|
+
"name" text NOT NULL,
|
|
57
|
+
"dept_id" uuid NOT NULL REFERENCES "sys"."dept"(id),
|
|
58
|
+
"role_ids" jsonb NOT NULL,
|
|
59
|
+
"phone" text,
|
|
60
|
+
"email" text,
|
|
61
|
+
"sso_subject" text,
|
|
62
|
+
"password_hash" text,
|
|
63
|
+
"status" text NOT NULL,
|
|
64
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
65
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
66
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
67
|
+
CONSTRAINT "user_status_check" CHECK (status IN ('active', 'disabled'))
|
|
68
|
+
);
|
|
69
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "user_username_uq" ON "sys"."user" (tenant_id, "username");
|
|
70
|
+
CREATE INDEX IF NOT EXISTS "user_tenant_dept_id_idx" ON "sys"."user" ("tenant_id", "dept_id");
|
|
71
|
+
CREATE INDEX IF NOT EXISTS "user_status_idx" ON "sys"."user" ("tenant_id", "status");
|
|
72
|
+
|
|
73
|
+
-- 新表:来自聚合 PurchaseOrder
|
|
74
|
+
CREATE TABLE IF NOT EXISTS "core"."purchase_order" (
|
|
75
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
76
|
+
"tenant_id" uuid NOT NULL,
|
|
77
|
+
"dept_id" uuid NOT NULL REFERENCES "sys"."dept"(id),
|
|
78
|
+
"created_by" uuid NOT NULL REFERENCES "sys"."user"(id),
|
|
79
|
+
"supplier_phone" text,
|
|
80
|
+
"amount" numeric(18,2) NOT NULL,
|
|
81
|
+
"items" jsonb NOT NULL,
|
|
82
|
+
"erp_no" text,
|
|
83
|
+
"status" text NOT NULL,
|
|
84
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
85
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
86
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
87
|
+
CONSTRAINT "purchase_order_status_check" CHECK (status IN ('draft', 'pending_approval', 'approved', 'received', 'rejected', 'cancelled'))
|
|
88
|
+
);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS "purchase_order_tenant_dept_id_idx" ON "core"."purchase_order" ("tenant_id", "dept_id");
|
|
90
|
+
CREATE INDEX IF NOT EXISTS "purchase_order_tenant_created_by_idx" ON "core"."purchase_order" ("tenant_id", "created_by");
|
|
91
|
+
CREATE INDEX IF NOT EXISTS "purchase_order_status_idx" ON "core"."purchase_order" ("tenant_id", "status");
|
|
92
|
+
|
|
93
|
+
-- 新表:来自聚合 Role
|
|
94
|
+
CREATE TABLE IF NOT EXISTS "sys"."role" (
|
|
95
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
96
|
+
"tenant_id" uuid NOT NULL,
|
|
97
|
+
"code" text NOT NULL,
|
|
98
|
+
"name" text NOT NULL,
|
|
99
|
+
"capabilities" jsonb NOT NULL,
|
|
100
|
+
"scopes" jsonb NOT NULL,
|
|
101
|
+
"status" text NOT NULL,
|
|
102
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
103
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
104
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
105
|
+
CONSTRAINT "role_status_check" CHECK (status IN ('active', 'archived'))
|
|
106
|
+
);
|
|
107
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "role_code_uq" ON "sys"."role" (tenant_id, "code");
|
|
108
|
+
CREATE INDEX IF NOT EXISTS "role_status_idx" ON "sys"."role" ("tenant_id", "status");
|
|
109
|
+
|
|
110
|
+
/* cage:schema
|
|
111
|
+
{"version":1,"tables":[{"schema":"core","name":"inventory","aggregate":"Inventory","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"sku","type":"text","notNull":true,"unique":true},{"name":"available","type":"integer","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","deleted"],"indexes":[{"name":"inventory_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"inventory_lock","aggregate":"InventoryLock","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"order_id","type":"uuid","notNull":true},{"name":"items","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["held","released"],"indexes":[{"name":"inventory_lock_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"dept","aggregate":"Dept","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"name","type":"text","notNull":true},{"name":"code","type":"text","notNull":false},{"name":"parent_id","type":"uuid","notNull":false,"references":"sys.dept"},{"name":"ancestors","type":"text","notNull":true},{"name":"leader_id","type":"uuid","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"dept_status_idx","columns":["tenant_id","status"]},{"name":"dept_parent_id_idx","columns":["parent_id"]}]},{"schema":"sys","name":"user","aggregate":"User","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"username","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"role_ids","type":"jsonb","notNull":true},{"name":"phone","type":"text","notNull":false},{"name":"email","type":"text","notNull":false},{"name":"sso_subject","type":"text","notNull":false},{"name":"password_hash","type":"text","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","disabled"],"indexes":[{"name":"user_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"user_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"purchase_order","aggregate":"PurchaseOrder","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"created_by","type":"uuid","notNull":true,"references":"sys.user"},{"name":"supplier_phone","type":"text","notNull":false},{"name":"amount","type":"numeric(18,2)","notNull":true},{"name":"items","type":"jsonb","notNull":true},{"name":"erp_no","type":"text","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["draft","pending_approval","approved","received","rejected","cancelled"],"indexes":[{"name":"purchase_order_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"purchase_order_tenant_created_by_idx","columns":["tenant_id","created_by"]},{"name":"purchase_order_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"role","aggregate":"Role","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"code","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"capabilities","type":"jsonb","notNull":true},{"name":"scopes","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"role_status_idx","columns":["tenant_id","status"]}]}]}
|
|
112
|
+
*/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/purchase-admin",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@10.14.0",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "cage dev",
|
|
8
|
+
"db:setup": "cage db setup",
|
|
9
|
+
"db:up": "cage db up",
|
|
10
|
+
"db:down": "cage db down",
|
|
11
|
+
"db:status": "cage db status",
|
|
12
|
+
"verify": "cage verify",
|
|
13
|
+
"gen": "cage gen",
|
|
14
|
+
"build:app": "pnpm --filter @app/domain build && pnpm --filter @app/api build && pnpm --filter @app/web build"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@yss123/cage-cli": "0.1.0",
|
|
18
|
+
"@yss123/cage-testkit": "0.1.0",
|
|
19
|
+
"@yss123/cage-lint": "0.1.0",
|
|
20
|
+
"@types/node": "^24.3.0",
|
|
21
|
+
"typescript": "~5.9.3",
|
|
22
|
+
"vitest": "^4.1.12",
|
|
23
|
+
"tsx": "^4.20.5"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/contracts",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@yss123/cage-client": "0.1.0",
|
|
14
|
+
"zod": "^4.1.5"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** 部门:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const DeptSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
name: z.string(),
|
|
11
|
+
code: z.string().nullable().optional(),
|
|
12
|
+
parentId: z.string().uuid().nullable().optional(),
|
|
13
|
+
ancestors: z.string(),
|
|
14
|
+
leaderId: z.string().uuid().nullable().optional(),
|
|
15
|
+
status: z.enum(["active", "archived"]),
|
|
16
|
+
version: z.number().int(),
|
|
17
|
+
createdAt: z.string(),
|
|
18
|
+
updatedAt: z.string(),
|
|
19
|
+
});
|
|
20
|
+
export type Dept = z.infer<typeof DeptSchema>;
|
|
21
|
+
export type DeptStatus = Dept['status'];
|
|
22
|
+
export const DeptStates = ['active', 'archived'] as const;
|
|
23
|
+
|
|
24
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
25
|
+
export const DeptInputs = {
|
|
26
|
+
archive: z.object({ id: z.string().uuid() }),
|
|
27
|
+
create: z.object({ name: z.string().min(1).max(100), code: z.string().max(64).optional(), parentId: z.string().uuid().nullable().optional(), leaderId: z.string().uuid().nullable().optional() }),
|
|
28
|
+
update: z.object({ id: z.string().uuid(), name: z.string().min(1).max(100).optional(), code: z.string().max(64).nullable().optional(), leaderId: z.string().uuid().nullable().optional() }),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Dept.archive 可能返回的错误码 */
|
|
32
|
+
export type DeptArchiveError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
33
|
+
/** Dept.create 可能返回的错误码 */
|
|
34
|
+
export type DeptCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT';
|
|
35
|
+
/** Dept.update 可能返回的错误码 */
|
|
36
|
+
export type DeptUpdateError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
37
|
+
|
|
38
|
+
export type DeptSortKey = 'createdAt' | 'updatedAt' | 'name' | 'code' | 'parentId' | 'ancestors' | 'leaderId';
|
|
39
|
+
export interface DeptListParams {
|
|
40
|
+
page?: number;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
43
|
+
sort?: DeptSortKey | `-${DeptSortKey}`;
|
|
44
|
+
/** 可以逗号分隔多个状态 */
|
|
45
|
+
status?: string;
|
|
46
|
+
createdAfter?: string;
|
|
47
|
+
createdBefore?: string;
|
|
48
|
+
name?: string | number | boolean;
|
|
49
|
+
code?: string | number | boolean;
|
|
50
|
+
parentId?: string | number | boolean;
|
|
51
|
+
ancestors?: string | number | boolean;
|
|
52
|
+
leaderId?: string | number | boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
56
|
+
export const dept = {
|
|
57
|
+
/** 停用部门(没有在职用户和下级部门时) */
|
|
58
|
+
archive: (id: string, expectedVersion?: number) => client.post<Dept, DeptArchiveError>(`/api/dept/${encodeURIComponent(id)}/archive`, { expectedVersion }),
|
|
59
|
+
/** 新建部门 */
|
|
60
|
+
create: (input: z.input<typeof DeptInputs.create>) => client.post<Dept, DeptCreateError>('/api/dept/create', input),
|
|
61
|
+
/** 修改部门名称、编码、负责人 */
|
|
62
|
+
update: (id: string, input: Omit<z.input<typeof DeptInputs.update>, 'id'>, expectedVersion?: number) => client.post<Dept, DeptUpdateError>(`/api/dept/${encodeURIComponent(id)}/update`, { ...input, expectedVersion }),
|
|
63
|
+
/** 导出 CSV:需要 Dept.export 能力 */
|
|
64
|
+
export: (params: DeptListParams = {}) => client.post<string, never>('/api/dept/export', params),
|
|
65
|
+
get: (id: string) => client.get<Dept>(`/api/dept/${encodeURIComponent(id)}`),
|
|
66
|
+
list: (params: DeptListParams = {}) => client.get<{ items: Dept[]; total: number; page: number; pageSize: number }>('/api/dept', params as Record<string, unknown>),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
70
|
+
export const useDept = (id: string) => useCageQuery(['dept', id], `/api/dept/${encodeURIComponent(id)}`, DeptSchema, { enabled: !!id });
|
|
71
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
72
|
+
export const useDeptList = (params: DeptListParams = {}) => useCageList(['dept'], '/api/dept', params as Record<string, unknown>, DeptSchema);
|
|
73
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
74
|
+
export const deptKeys = { all: ['dept'] as const, one: (id: string) => ['dept', id] as const };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export * from './dept';
|
|
5
|
+
export * from './inventory';
|
|
6
|
+
export * from './inventory-lock';
|
|
7
|
+
export * from './purchase-order';
|
|
8
|
+
export * from './role';
|
|
9
|
+
export * from './user';
|
|
10
|
+
export * from './queries';
|
|
11
|
+
import { can as baseCan, useCan as baseUseCan } from '@yss123/cage-client';
|
|
12
|
+
import { DeptInputs } from './dept';
|
|
13
|
+
import { InventoryInputs } from './inventory';
|
|
14
|
+
import { InventoryLockInputs } from './inventory-lock';
|
|
15
|
+
import { PurchaseOrderInputs } from './purchase-order';
|
|
16
|
+
import { RoleInputs } from './role';
|
|
17
|
+
import { UserInputs } from './user';
|
|
18
|
+
export { client, configureClient, loadMe, currentUser, CageQueryError, type CageResult, type CommonError } 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:schemas.PurchaseOrder.create 可以直接给表单库做校验 */
|
|
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,52 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** InventoryLock:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const InventoryLockSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
orderId: z.string().uuid(),
|
|
11
|
+
items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),
|
|
12
|
+
status: z.enum(["held", "released"]),
|
|
13
|
+
version: z.number().int(),
|
|
14
|
+
createdAt: z.string(),
|
|
15
|
+
updatedAt: z.string(),
|
|
16
|
+
});
|
|
17
|
+
export type InventoryLock = z.infer<typeof InventoryLockSchema>;
|
|
18
|
+
export type InventoryLockStatus = InventoryLock['status'];
|
|
19
|
+
export const InventoryLockStates = ['held', 'released'] as const;
|
|
20
|
+
|
|
21
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
22
|
+
export const InventoryLockInputs = {
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export type InventoryLockSortKey = 'createdAt' | 'updatedAt' | 'orderId';
|
|
27
|
+
export interface InventoryLockListParams {
|
|
28
|
+
page?: number;
|
|
29
|
+
pageSize?: number;
|
|
30
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
31
|
+
sort?: InventoryLockSortKey | `-${InventoryLockSortKey}`;
|
|
32
|
+
/** 可以逗号分隔多个状态 */
|
|
33
|
+
status?: string;
|
|
34
|
+
createdAfter?: string;
|
|
35
|
+
createdBefore?: string;
|
|
36
|
+
orderId?: string | number | boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
40
|
+
export const inventoryLock = {
|
|
41
|
+
/** 导出 CSV:需要 InventoryLock.export 能力 */
|
|
42
|
+
export: (params: InventoryLockListParams = {}) => client.post<string, never>('/api/inventory-lock/export', params),
|
|
43
|
+
get: (id: string) => client.get<InventoryLock>(`/api/inventory-lock/${encodeURIComponent(id)}`),
|
|
44
|
+
list: (params: InventoryLockListParams = {}) => client.get<{ items: InventoryLock[]; total: number; page: number; pageSize: number }>('/api/inventory-lock', params as Record<string, unknown>),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
48
|
+
export const useInventoryLock = (id: string) => useCageQuery(['inventory-lock', id], `/api/inventory-lock/${encodeURIComponent(id)}`, InventoryLockSchema, { enabled: !!id });
|
|
49
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
50
|
+
export const useInventoryLockList = (params: InventoryLockListParams = {}) => useCageList(['inventory-lock'], '/api/inventory-lock', params as Record<string, unknown>, InventoryLockSchema);
|
|
51
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
52
|
+
export const inventoryLockKeys = { all: ['inventory-lock'] as const, one: (id: string) => ['inventory-lock', id] as const };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** Inventory:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const InventorySchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
sku: z.string(),
|
|
11
|
+
available: z.number().int(),
|
|
12
|
+
status: z.enum(["active", "deleted"]),
|
|
13
|
+
version: z.number().int(),
|
|
14
|
+
createdAt: z.string(),
|
|
15
|
+
updatedAt: z.string(),
|
|
16
|
+
});
|
|
17
|
+
export type Inventory = z.infer<typeof InventorySchema>;
|
|
18
|
+
export type InventoryStatus = Inventory['status'];
|
|
19
|
+
export const InventoryStates = ['active', 'deleted'] as const;
|
|
20
|
+
|
|
21
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
22
|
+
export const InventoryInputs = {
|
|
23
|
+
create: z.object({ sku: z.string(), available: z.number().int().min(-9007199254740991).max(9007199254740991) }),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Inventory.create 可能返回的错误码 */
|
|
27
|
+
export type InventoryCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT';
|
|
28
|
+
|
|
29
|
+
export type InventorySortKey = 'createdAt' | 'updatedAt' | 'sku' | 'available';
|
|
30
|
+
export interface InventoryListParams {
|
|
31
|
+
page?: number;
|
|
32
|
+
pageSize?: number;
|
|
33
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
34
|
+
sort?: InventorySortKey | `-${InventorySortKey}`;
|
|
35
|
+
/** 可以逗号分隔多个状态 */
|
|
36
|
+
status?: string;
|
|
37
|
+
createdAfter?: string;
|
|
38
|
+
createdBefore?: string;
|
|
39
|
+
sku?: string | number | boolean;
|
|
40
|
+
available?: string | number | boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
44
|
+
export const inventory = {
|
|
45
|
+
create: (input: z.input<typeof InventoryInputs.create>) => client.post<Inventory, InventoryCreateError>('/api/inventory/create', input),
|
|
46
|
+
/** 导出 CSV:需要 Inventory.export 能力 */
|
|
47
|
+
export: (params: InventoryListParams = {}) => client.post<string, never>('/api/inventory/export', params),
|
|
48
|
+
get: (id: string) => client.get<Inventory>(`/api/inventory/${encodeURIComponent(id)}`),
|
|
49
|
+
list: (params: InventoryListParams = {}) => client.get<{ items: Inventory[]; total: number; page: number; pageSize: number }>('/api/inventory', params as Record<string, unknown>),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
53
|
+
export const useInventory = (id: string) => useCageQuery(['inventory', id], `/api/inventory/${encodeURIComponent(id)}`, InventorySchema, { enabled: !!id });
|
|
54
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
55
|
+
export const useInventoryList = (params: InventoryListParams = {}) => useCageList(['inventory'], '/api/inventory', params as Record<string, unknown>, InventorySchema);
|
|
56
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
57
|
+
export const inventoryKeys = { all: ['inventory'] as const, one: (id: string) => ['inventory', id] as const };
|