@yss123/cage-cli 0.3.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/dist/approval-tree.d.ts +88 -0
- package/dist/approval-tree.js +18 -0
- package/dist/approval-tree.js.map +1 -0
- package/dist/capabilities.js +16 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/graph-approvals.d.ts +11 -1
- package/dist/graph-approvals.js +18 -5
- package/dist/graph-approvals.js.map +1 -1
- package/dist/graph-relations.js +10 -8
- package/dist/graph-relations.js.map +1 -1
- package/dist/graph.js +2 -0
- package/dist/graph.js.map +1 -1
- package/dist/ontology.js +8 -5
- package/dist/ontology.js.map +1 -1
- package/dist/sync.js +1 -1
- package/graph-ui/approval-flow.js +28 -8
- package/graph-ui/approval-parallel.js +110 -0
- package/graph-ui/approval-tree.js +17 -0
- package/graph-ui/business.css +26 -0
- package/graph-ui/business.js +30 -13
- package/graph-ui/graph-approvals.js +18 -5
- package/graph-ui/graph-relations.js +10 -8
- package/package.json +6 -6
- package/templates/app/AGENTS.md +2 -2
- package/templates/app/README.md +5 -4
- package/templates/app/apps/api/package.json +2 -2
- package/templates/app/apps/web/package.json +1 -1
- package/templates/app/apps/web/src/modules/approval/components/task-operation-dialog.tsx +61 -0
- package/templates/app/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +6 -9
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +81 -49
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks.css +18 -0
- package/templates/app/apps/web/src/modules/purchase/components/approval-history.tsx +39 -4
- package/templates/app/apps/web/src/modules/purchase/components/order-reviewers.tsx +38 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +9 -2
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-intelligence.css +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +2 -0
- package/templates/app/apps/web/src/shared/lib/error-message.ts +6 -1
- package/templates/app/apps/web/src/shared/ui/approval-member-picker.css +10 -0
- package/templates/app/apps/web/src/shared/ui/approval-member-picker.tsx +51 -0
- package/templates/app/cage.config.json +14 -0
- package/templates/app/db/migrations/0004_draft.sql +27 -0
- package/templates/app/docs/ai/README.md +7 -2
- package/templates/app/docs/ai/approvals.md +260 -0
- package/templates/app/docs/ai/primitives.md +1 -1
- package/templates/app/docs/ai/purchase-example.md +19 -5
- package/templates/app/docs/ai/recipes.md +1 -1
- package/templates/app/docs/errors/CAGE-AI-001.md +2 -2
- package/templates/app/docs/errors/CAGE-APPR-001.md +12 -9
- package/templates/app/docs/errors/CAGE-APPR-005.md +4 -4
- package/templates/app/docs/errors/CAGE-APPR-006.md +24 -9
- package/templates/app/docs/errors/CAGE-APPR-007.md +8 -6
- package/templates/app/docs/errors/CAGE-EVENT-001.md +1 -1
- package/templates/app/docs/errors/CAGE-REG-001.md +1 -1
- package/templates/app/docs/errors/README.md +2 -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/purchase-order/index.ts +10 -2
- package/templates/app/packages/contracts/src/index.ts +9 -0
- package/templates/app/packages/domain/package.json +5 -5
- package/templates/app/packages/domain/src/modules/purchase/approval-members.ts +54 -0
- package/templates/app/packages/domain/src/modules/purchase/approvals.ts +19 -8
- package/templates/app/packages/domain/src/modules/purchase/index.ts +2 -1
- package/templates/app/packages/domain/src/modules/purchase/intelligence.test.ts +169 -7
- package/templates/app/packages/domain/src/modules/purchase/purchase-order.ts +56 -5
- package/templates/blank/AGENTS.md +2 -2
- package/templates/blank/apps/api/package.json +1 -1
- package/templates/blank/apps/web/package.json +1 -1
- package/templates/blank/docs/ai/README.md +7 -2
- package/templates/blank/docs/ai/approvals.md +260 -0
- package/templates/blank/docs/ai/primitives.md +1 -1
- package/templates/blank/docs/ai/purchase-example.md +19 -5
- package/templates/blank/docs/ai/recipes.md +1 -1
- package/templates/blank/docs/errors/CAGE-AI-001.md +2 -2
- package/templates/blank/docs/errors/CAGE-APPR-001.md +12 -9
- package/templates/blank/docs/errors/CAGE-APPR-005.md +4 -4
- package/templates/blank/docs/errors/CAGE-APPR-006.md +24 -9
- package/templates/blank/docs/errors/CAGE-APPR-007.md +8 -6
- package/templates/blank/docs/errors/CAGE-EVENT-001.md +1 -1
- package/templates/blank/docs/errors/CAGE-REG-001.md +1 -1
- package/templates/blank/docs/errors/README.md +2 -2
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/components/task-operation-dialog.tsx +61 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +6 -9
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +81 -49
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks.css +18 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/approval-history.tsx +39 -4
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/order-reviewers.tsx +38 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +9 -2
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-intelligence.css +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +6 -1
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/approval-member-picker.css +10 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/approval-member-picker.tsx +51 -0
- package/templates/blank/docs/examples/purchase-admin/cage.config.json +14 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/approval-member/index.ts +70 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +10 -2
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +9 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approval-members.ts +54 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts +19 -8
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +2 -1
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/intelligence.test.ts +169 -7
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +56 -5
- package/templates/blank/package.json +3 -3
- package/templates/blank/packages/contracts/package.json +1 -1
- package/templates/blank/packages/domain/package.json +5 -5
package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts
CHANGED
|
@@ -14,10 +14,15 @@ export function errorMessage(r: Extract<CageResult<unknown, string>, { ok: false
|
|
|
14
14
|
const labels: Record<string, string> = {
|
|
15
15
|
FORBIDDEN: '你没有执行此操作的权限',
|
|
16
16
|
INVALID_INPUT: '请检查填写内容',
|
|
17
|
-
CONFLICT: '
|
|
17
|
+
CONFLICT: '数据或任务已更新,请刷新后重试',
|
|
18
18
|
UNAUTHENTICATED: '登录已过期',
|
|
19
19
|
CONSTRAINT_VIOLATED: '未满足审批条件,请检查审批人或额度',
|
|
20
20
|
STOCK_SHORT: '库存不足',
|
|
21
|
+
REVIEWERS_REQUIRED: '请为专业会签选择 1–5 位协作人',
|
|
22
|
+
REVIEWERS_NOT_REQUIRED: '取消专业会签时请清空复核人员',
|
|
23
|
+
DUPLICATE_REVIEWERS: '专业会签人员不能重复',
|
|
24
|
+
REVIEWER_IS_APPLICANT: '申请人不能担任本单专业会签人',
|
|
25
|
+
REVIEWER_UNAVAILABLE: '所选人员已不可用,请重新核对协作名录',
|
|
21
26
|
NETWORK: '连接失败,请稍后重试',
|
|
22
27
|
};
|
|
23
28
|
return `${labels[r.domainCode ?? r.code] ?? r.domainCode ?? r.code}${r.requestId ? ` · 请求 ${r.requestId.slice(0, 8)}` : ''}`;
|
|
@@ -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; }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购参与者共用的审批协作名录选择器,由采购表单和审批管理弹窗消费。
|
|
3
|
+
* 仅通过生成契约读取 ApprovalMember 安全视图,不读取完整 User 或凭账号名猜测权限。
|
|
4
|
+
* 目录是公开协作身份,不保证被选者对当前单据有审批权限;实际提交由服务端复核,空/失败不能伪装成可选名单。
|
|
5
|
+
*/
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { useQuery } from '@tanstack/react-query';
|
|
8
|
+
import { approvalMember } from '@app/contracts';
|
|
9
|
+
import { errorMessage } from '../lib/error-message';
|
|
10
|
+
import './approval-member-picker.css';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 在有名录读取权限的界面加载当前页,可按显示名/职责就地筛选;最多读取 200 项,不声称返回完整组织。
|
|
14
|
+
* page 进入查询缓存键,退出账号时由应用会话层清理;retired 默认不用于选人。
|
|
15
|
+
*/
|
|
16
|
+
export function useApprovalMembers(page = 1, includeRetired = false) {
|
|
17
|
+
return useQuery({ queryKey: ['approval-members', page, includeRetired], queryFn: async () => {
|
|
18
|
+
const result = await approvalMember.list({ page, pageSize: 200, ...(includeRetired ? {} : { status: 'active' }) });
|
|
19
|
+
if (!result.ok) throw new Error(errorMessage(result));
|
|
20
|
+
return result.data;
|
|
21
|
+
} });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 单选/多选均输出真实 userId,value 与 onChange 由父表单持有,exclude 只改善体验,不替代服务端重复票检查。
|
|
26
|
+
* max 限制本次选择数量,已选项始终可取消;翻页保留已选 ID,页面展示本次已选数量。
|
|
27
|
+
* disabled 用于在父级提交时冻结编辑;刷新与失败保留用户选择,不自动选择或发起写操作。
|
|
28
|
+
*/
|
|
29
|
+
export function ApprovalMemberPicker({ value, onChange, max = 5, exclude = [], disabled = false }: {
|
|
30
|
+
value: string[]; onChange: (ids: string[]) => void; max?: number; exclude?: string[]; disabled?: boolean;
|
|
31
|
+
}) {
|
|
32
|
+
const [search, setSearch] = useState(''), [page, setPage] = useState(1);
|
|
33
|
+
const query = useApprovalMembers(page);
|
|
34
|
+
const entries = query.data?.items.filter(member => !exclude.includes(member.userId) &&
|
|
35
|
+
`${member.displayName} ${member.specialty}`.includes(search.trim())) ?? [];
|
|
36
|
+
return <fieldset className="approval-member-picker" disabled={disabled}>
|
|
37
|
+
<legend>选择协作人员 · 已选 {value.length}/{max}</legend>
|
|
38
|
+
<input aria-label="筛选协作人员" placeholder="按姓名或职责筛选当前页" value={search} onChange={event => setSearch(event.target.value)} />
|
|
39
|
+
{query.isPending && <p role="status">正在读取协作名录…</p>}
|
|
40
|
+
{query.isError && <p role="alert">{query.error.message} <button type="button" onClick={() => void query.refetch()}>重试</button></p>}
|
|
41
|
+
<div className="approval-member-options">{entries.map(member => <label key={member.id}>
|
|
42
|
+
<input type="checkbox" checked={value.includes(member.userId)} disabled={disabled || (!value.includes(member.userId) && value.length >= max && max !== 1)}
|
|
43
|
+
onChange={event => onChange(event.target.checked ? max === 1 ? [member.userId] : [...value, member.userId] : value.filter(id => id !== member.userId))} />
|
|
44
|
+
<span><strong>{member.displayName}</strong><small>{member.specialty}</small></span>
|
|
45
|
+
</label>)}</div>
|
|
46
|
+
{query.data && entries.length === 0 && <p className="muted">当前页没有匹配人员,请调整筛选或联系名录维护人。</p>}
|
|
47
|
+
{(query.data?.total ?? 0) > 200 && <div className="actions"><button type="button" disabled={page === 1} onClick={() => setPage(page - 1)}>上一页</button>
|
|
48
|
+
<span>第 {page} 页</span><button type="button" disabled={page * 200 >= query.data!.total} onClick={() => setPage(page + 1)}>下一页</button></div>}
|
|
49
|
+
<small className="muted">名录只提供选人信息,处理权限、数据范围和最终额度仍以服务端校验为准。</small>
|
|
50
|
+
</fieldset>;
|
|
51
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"module:purchase": "采购管理",
|
|
10
10
|
"module:inventory": "库存管理",
|
|
11
11
|
"aggregate:PurchaseOrder": "采购单",
|
|
12
|
+
"aggregate:ApprovalMember": "采购审批协作名录",
|
|
12
13
|
"aggregate:Inventory": "库存台账",
|
|
13
14
|
"aggregate:InventoryLock": "库存锁",
|
|
14
15
|
"aggregate:Dept": "部门",
|
|
@@ -16,8 +17,21 @@
|
|
|
16
17
|
"aggregate:Role": "角色",
|
|
17
18
|
"command:PurchaseOrder.create": "新建采购单",
|
|
18
19
|
"command:PurchaseOrder.explainPurpose": "补充采购用途",
|
|
20
|
+
"command:PurchaseOrder.setReviewers": "设置专业复核人员",
|
|
19
21
|
"command:PurchaseOrder.returnForChanges": "退回修改",
|
|
20
22
|
"field:PurchaseOrder.reason": "采购用途",
|
|
23
|
+
"field:PurchaseOrder.needsSpecialistReview": "需要专业复核",
|
|
24
|
+
"field:PurchaseOrder.reviewerIds": "专业复核人员",
|
|
25
|
+
"command:ApprovalMember.create": "登记采购协作人",
|
|
26
|
+
"command:ApprovalMember.update": "修改协作人公开资料",
|
|
27
|
+
"command:ApprovalMember.retire": "退役采购协作人",
|
|
28
|
+
"field:ApprovalMember.userId": "协作账号",
|
|
29
|
+
"field:ApprovalMember.displayName": "公开业务姓名",
|
|
30
|
+
"field:ApprovalMember.specialty": "专业领域",
|
|
31
|
+
"state:ApprovalMember.active": "可选协作人",
|
|
32
|
+
"state:ApprovalMember.retired": "已退役协作人",
|
|
33
|
+
"capability:ApprovalMember.read": "查看采购协作名录",
|
|
34
|
+
"capability:ApprovalMember.export": "导出采购协作名录",
|
|
21
35
|
"capability:Sys.ai.use": "使用智能助理",
|
|
22
36
|
"capability:Sys.ai.models.manage": "管理 AI 模型",
|
|
23
37
|
"command:PurchaseOrder.setAttachments": "修改采购附件",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { client, useCageList, useCageQuery, type TransferTask, type RequestOptions } from '@yss123/cage-client';
|
|
6
|
+
|
|
7
|
+
/** 采购审批协作名录:读侧快照(敏感字段是按权限掩码后的值) */
|
|
8
|
+
export const ApprovalMemberSchema = z.object({
|
|
9
|
+
id: z.string().uuid(),
|
|
10
|
+
userId: z.string().uuid(),
|
|
11
|
+
displayName: z.string(),
|
|
12
|
+
specialty: z.string(),
|
|
13
|
+
status: z.enum(["active", "retired"]),
|
|
14
|
+
version: z.number().int(),
|
|
15
|
+
createdAt: z.string(),
|
|
16
|
+
updatedAt: z.string(),
|
|
17
|
+
});
|
|
18
|
+
export type ApprovalMember = z.infer<typeof ApprovalMemberSchema>;
|
|
19
|
+
export type ApprovalMemberStatus = ApprovalMember['status'];
|
|
20
|
+
export const ApprovalMemberStates = ['active', 'retired'] as const;
|
|
21
|
+
|
|
22
|
+
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
23
|
+
export const ApprovalMemberInputs = {
|
|
24
|
+
create: z.object({ userId: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")), displayName: z.unknown(), specialty: z.unknown() }),
|
|
25
|
+
retire: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")) }),
|
|
26
|
+
update: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")), displayName: z.unknown(), specialty: z.unknown() }),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** ApprovalMember.create 可能返回的错误码 */
|
|
30
|
+
export type ApprovalMemberCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT';
|
|
31
|
+
/** ApprovalMember.retire 可能返回的错误码 */
|
|
32
|
+
export type ApprovalMemberRetireError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
33
|
+
/** ApprovalMember.update 可能返回的错误码 */
|
|
34
|
+
export type ApprovalMemberUpdateError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'NOT_FOUND';
|
|
35
|
+
|
|
36
|
+
export type ApprovalMemberSortKey = 'createdAt' | 'updatedAt' | 'userId' | 'displayName' | 'specialty';
|
|
37
|
+
export interface ApprovalMemberListParams {
|
|
38
|
+
page?: number;
|
|
39
|
+
pageSize?: number;
|
|
40
|
+
/** 排序:字段名,前面加 - 表示倒序 */
|
|
41
|
+
sort?: ApprovalMemberSortKey | `-${ApprovalMemberSortKey}`;
|
|
42
|
+
/** 可以逗号分隔多个状态 */
|
|
43
|
+
status?: string;
|
|
44
|
+
createdAfter?: string;
|
|
45
|
+
createdBefore?: string;
|
|
46
|
+
userId?: string | number | boolean;
|
|
47
|
+
displayName?: string | number | boolean;
|
|
48
|
+
specialty?: string | number | boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 调用方法:每个命令一个;client 自动生成并附带幂等键和 CSRF 令牌 */
|
|
52
|
+
export const approvalMember = {
|
|
53
|
+
/** 登记采购审批协作人 */
|
|
54
|
+
create: (input: z.input<typeof ApprovalMemberInputs.create>, options: RequestOptions = {}) => client.post<ApprovalMember, ApprovalMemberCreateError>('/api/approval-member/create', input, options),
|
|
55
|
+
/** 停用采购审批协作名录成员 */
|
|
56
|
+
retire: (id: string, expectedVersion?: number, options: RequestOptions = {}) => client.post<ApprovalMember, ApprovalMemberRetireError>(`/api/approval-member/${encodeURIComponent(id)}/retire`, { expectedVersion }, options),
|
|
57
|
+
/** 修改采购协作人的公开资料 */
|
|
58
|
+
update: (id: string, input: Omit<z.input<typeof ApprovalMemberInputs.update>, 'id'>, expectedVersion?: number, options: RequestOptions = {}) => client.post<ApprovalMember, ApprovalMemberUpdateError>(`/api/approval-member/${encodeURIComponent(id)}/update`, { ...input, expectedVersion }, options),
|
|
59
|
+
/** 异步导出 CSV:返回任务,使用 client.transfer(id) 查状态,完成后下载。需要 ApprovalMember.export 能力。 */
|
|
60
|
+
export: (params: ApprovalMemberListParams = {}, options: RequestOptions = {}) => client.post<TransferTask, 'CONFLICT'>('/api/approval-member/export', params, options),
|
|
61
|
+
get: (id: string, options: RequestOptions = {}) => client.get<ApprovalMember>(`/api/approval-member/${encodeURIComponent(id)}`, undefined, options),
|
|
62
|
+
list: (params: ApprovalMemberListParams = {}, options: RequestOptions = {}) => client.get<{ items: ApprovalMember[]; total: number; page: number; pageSize: number }>('/api/approval-member', params as Record<string, unknown>, options),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** 详情查询 hook:响应也用 schema 校验;范围外返回 NOT_FOUND */
|
|
66
|
+
export const useApprovalMember = (id: string) => useCageQuery(['approval-member', id], `/api/approval-member/${encodeURIComponent(id)}`, ApprovalMemberSchema, { enabled: !!id });
|
|
67
|
+
/** 列表查询 hook:走读侧账号与安全视图 */
|
|
68
|
+
export const useApprovalMemberList = (params: ApprovalMemberListParams = {}) => useCageList(['approval-member'], '/api/approval-member', params as Record<string, unknown>, ApprovalMemberSchema);
|
|
69
|
+
/** 查询缓存键前缀:命令成功后让它失效 */
|
|
70
|
+
export const approvalMemberKeys = { all: ['approval-member'] as const, one: (id: string) => ['approval-member', id] as const };
|
|
@@ -12,6 +12,8 @@ export const PurchaseOrderSchema = z.object({
|
|
|
12
12
|
supplierPhone: z.string().nullable().optional(),
|
|
13
13
|
amount: z.string(),
|
|
14
14
|
reason: z.string().nullable().optional(),
|
|
15
|
+
needsSpecialistReview: z.boolean().nullable().optional(),
|
|
16
|
+
reviewerIds: z.array(z.string().uuid()).nullable().optional(),
|
|
15
17
|
items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),
|
|
16
18
|
erpNo: z.string().nullable().optional(),
|
|
17
19
|
attachments: z.array(z.string().uuid()).max(10),
|
|
@@ -29,9 +31,10 @@ export const PurchaseOrderFileFields = {"attachments":{"maxBytes":2147483648,"ac
|
|
|
29
31
|
/** 命令输入:和后端命令的输入校验完全一致 */
|
|
30
32
|
export const PurchaseOrderInputs = {
|
|
31
33
|
cancel: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")) }),
|
|
32
|
-
create: z.object({ amount: z.string(), items: z.array(z.object({ sku: z.string(), qty: z.number().int().max(9007199254740991).gt(0) })).min(1), supplierPhone: z.string().optional(), reason: z.string().max(2000).optional(), deptId: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).optional(), attachments: z.array(z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"))).max(10).optional() }),
|
|
34
|
+
create: z.object({ amount: z.string(), items: z.array(z.object({ sku: z.string(), qty: z.number().int().max(9007199254740991).gt(0) })).min(1), supplierPhone: z.string().optional(), reason: z.string().max(2000).optional(), needsSpecialistReview: z.boolean().optional(), reviewerIds: z.array(z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"))).max(5).optional(), deptId: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).optional(), attachments: z.array(z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"))).max(10).optional() }),
|
|
33
35
|
explainPurpose: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")), reason: z.unknown() }),
|
|
34
36
|
setAttachments: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")), attachments: z.array(z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"))).max(10) }),
|
|
37
|
+
setReviewers: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")), needsSpecialistReview: z.boolean(), reviewerIds: z.array(z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"))).max(5) }),
|
|
35
38
|
submit: z.object({ id: z.string().uuid().regex(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")) }),
|
|
36
39
|
};
|
|
37
40
|
|
|
@@ -43,11 +46,13 @@ export type PurchaseOrderCreateError = 'CONFLICT' | 'CONSTRAINT_VIOLATED' | 'DOM
|
|
|
43
46
|
export type PurchaseOrderExplainPurposeError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'INVARIANT_VIOLATED' | 'NOT_FOUND';
|
|
44
47
|
/** PurchaseOrder.setAttachments 可能返回的错误码 */
|
|
45
48
|
export type PurchaseOrderSetAttachmentsError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'INVARIANT_VIOLATED' | 'NOT_FOUND';
|
|
49
|
+
/** PurchaseOrder.setReviewers 可能返回的错误码 */
|
|
50
|
+
export type PurchaseOrderSetReviewersError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'INVARIANT_VIOLATED' | 'NOT_FOUND';
|
|
46
51
|
/** PurchaseOrder.submit 可能返回的错误码;业务码(domainCode):NO_ITEMS */
|
|
47
52
|
export type PurchaseOrderSubmitError = 'CONFLICT' | 'DOMAIN_ERROR' | 'FORBIDDEN' | 'INVALID_INPUT' | 'INVALID_TRANSITION' | 'INVARIANT_VIOLATED' | 'NOT_FOUND';
|
|
48
53
|
export type PurchaseOrderSubmitDomainCode = 'NO_ITEMS';
|
|
49
54
|
|
|
50
|
-
export type PurchaseOrderSortKey = 'createdAt' | 'updatedAt' | 'deptId' | 'createdBy' | 'supplierPhone' | 'amount' | 'reason' | 'erpNo';
|
|
55
|
+
export type PurchaseOrderSortKey = 'createdAt' | 'updatedAt' | 'deptId' | 'createdBy' | 'supplierPhone' | 'amount' | 'reason' | 'needsSpecialistReview' | 'erpNo';
|
|
51
56
|
export interface PurchaseOrderListParams {
|
|
52
57
|
page?: number;
|
|
53
58
|
pageSize?: number;
|
|
@@ -61,6 +66,7 @@ export interface PurchaseOrderListParams {
|
|
|
61
66
|
createdBy?: string | number | boolean;
|
|
62
67
|
amount?: string | number | boolean;
|
|
63
68
|
reason?: string | number | boolean;
|
|
69
|
+
needsSpecialistReview?: string | number | boolean;
|
|
64
70
|
erpNo?: string | number | boolean;
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -71,6 +77,8 @@ export const purchaseOrder = {
|
|
|
71
77
|
/** 补充采购用途与必要性,保存后可以重新提交审核 */
|
|
72
78
|
explainPurpose: (id: string, input: Omit<z.input<typeof PurchaseOrderInputs.explainPurpose>, 'id'>, expectedVersion?: number, options: RequestOptions = {}) => client.post<PurchaseOrder, PurchaseOrderExplainPurposeError>(`/api/purchase-order/${encodeURIComponent(id)}/explain-purpose`, { ...input, expectedVersion }, options),
|
|
73
79
|
setAttachments: (id: string, input: Omit<z.input<typeof PurchaseOrderInputs.setAttachments>, 'id'>, expectedVersion?: number, options: RequestOptions = {}) => client.post<PurchaseOrder, PurchaseOrderSetAttachmentsError>(`/api/purchase-order/${encodeURIComponent(id)}/set-attachments`, { ...input, expectedVersion }, options),
|
|
80
|
+
/** 设置采购单专业复核需求和人员 */
|
|
81
|
+
setReviewers: (id: string, input: Omit<z.input<typeof PurchaseOrderInputs.setReviewers>, 'id'>, expectedVersion?: number, options: RequestOptions = {}) => client.post<PurchaseOrder, PurchaseOrderSetReviewersError>(`/api/purchase-order/${encodeURIComponent(id)}/set-reviewers`, { ...input, expectedVersion }, options),
|
|
74
82
|
submit: (id: string, expectedVersion?: number, options: RequestOptions = {}) => client.post<PurchaseOrder, PurchaseOrderSubmitError>(`/api/purchase-order/${encodeURIComponent(id)}/submit`, { expectedVersion }, options),
|
|
75
83
|
/** 异步导出 CSV:返回任务,使用 client.transfer(id) 查状态,完成后下载。需要 PurchaseOrder.export 能力。 */
|
|
76
84
|
export: (params: PurchaseOrderListParams = {}, options: RequestOptions = {}) => client.post<TransferTask, 'CONFLICT'>('/api/purchase-order/export', params, options),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// 生成物:由 cage gen 从 domain 声明生成,不要手改。
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
|
+
export * from './aggregates/approval-member';
|
|
4
5
|
export * from './aggregates/dept';
|
|
5
6
|
export * from './aggregates/inventory';
|
|
6
7
|
export * from './aggregates/inventory-lock';
|
|
@@ -9,6 +10,7 @@ export * from './aggregates/role';
|
|
|
9
10
|
export * from './aggregates/user';
|
|
10
11
|
export * from './queries';
|
|
11
12
|
import { can as baseCan, useCan as baseUseCan } from '@yss123/cage-client';
|
|
13
|
+
import { ApprovalMemberInputs } from './aggregates/approval-member';
|
|
12
14
|
import { DeptInputs } from './aggregates/dept';
|
|
13
15
|
import { InventoryInputs } from './aggregates/inventory';
|
|
14
16
|
import { InventoryLockInputs } from './aggregates/inventory-lock';
|
|
@@ -19,6 +21,11 @@ export { client, configureClient, loadMe, currentUser, CageQueryError, CageComma
|
|
|
19
21
|
|
|
20
22
|
/** 能力目录(由代码派生):写错能力点名会编译失败 */
|
|
21
23
|
export const capabilities = [
|
|
24
|
+
'ApprovalMember.create',
|
|
25
|
+
'ApprovalMember.export',
|
|
26
|
+
'ApprovalMember.read',
|
|
27
|
+
'ApprovalMember.retire',
|
|
28
|
+
'ApprovalMember.update',
|
|
22
29
|
'Dept.archive',
|
|
23
30
|
'Dept.create',
|
|
24
31
|
'Dept.export',
|
|
@@ -42,6 +49,7 @@ export const capabilities = [
|
|
|
42
49
|
'PurchaseOrder.reject',
|
|
43
50
|
'PurchaseOrder.returnForChanges',
|
|
44
51
|
'PurchaseOrder.setAttachments',
|
|
52
|
+
'PurchaseOrder.setReviewers',
|
|
45
53
|
'PurchaseOrder.submit',
|
|
46
54
|
'PurchaseOrder.unmask.phone',
|
|
47
55
|
'Role.archive',
|
|
@@ -78,6 +86,7 @@ export const useCan = (capability: Capability): boolean => baseUseCan(capability
|
|
|
78
86
|
|
|
79
87
|
/** 各聚合的命令输入 schema:按聚合名、命令名访问,可直接供表单库校验 */
|
|
80
88
|
export const schemas = {
|
|
89
|
+
ApprovalMember: ApprovalMemberInputs,
|
|
81
90
|
Dept: DeptInputs,
|
|
82
91
|
Inventory: InventoryInputs,
|
|
83
92
|
InventoryLock: InventoryLockInputs,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 采购协作名录:由业务管理员登记可参与采购复核的账号、公开业务姓名和专业领域,供草稿选人及任务操作使用。
|
|
3
|
+
* 聚合通过标准命令维护,浏览器读取 safe.approval_member;名录与 sys.user 的私密资料、角色授权分离。
|
|
4
|
+
* 名录在同租户内向具 ApprovalMember.read 且显式获 all 范围的采购参与者公开;加入名录不授予审批权限、范围或额度。
|
|
5
|
+
*/
|
|
6
|
+
import { aggregate, command, ref, scope, states, text } from '@yss123/cage-kernel';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 一位账号对应一个采购协作身份,显示姓名是管理员明确登记的业务公开值,不读取 User.name 明文。
|
|
11
|
+
* active 可供新申请选择,retired 保留旧单据的人员证据;退役不取消已经承担的审批责任或撤销真实角色。
|
|
12
|
+
*/
|
|
13
|
+
export const ApprovalMember = aggregate('ApprovalMember', {
|
|
14
|
+
description: '采购审批协作名录',
|
|
15
|
+
fields: {
|
|
16
|
+
/** 当前租户的真实用户引用;租户内唯一,禁止用多条名录为同一账号制造多个会签名额。 */
|
|
17
|
+
userId: ref('User').unique(),
|
|
18
|
+
/** 对采购协作者公开的展示姓名;与登录姓名独立维护,不包含电话、邮箱或其他个人资料。 */
|
|
19
|
+
displayName: text(80),
|
|
20
|
+
/** 供选人判断的专业领域或岗位说明,例如设备技术、合同审查;不是角色编码或权限表达式。 */
|
|
21
|
+
specialty: text(120),
|
|
22
|
+
},
|
|
23
|
+
states: states('active', 'retired', { terminal: ['retired'] }),
|
|
24
|
+
// 名录没有部门或创建人字段,因此采购参与者必须由管理员显式授予 ApprovalMember 的 all 范围。
|
|
25
|
+
scope: scope.none(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 登记采购协作人,要求 ApprovalMember.create;真实账号引用和租户约束由内核处理。
|
|
30
|
+
* 只保存公开业务名录,不改 User 的角色或密码,不代表所选用户可以批准任何采购单。
|
|
31
|
+
*/
|
|
32
|
+
export const createApprovalMember = command(ApprovalMember, {
|
|
33
|
+
name: 'create', description: '登记采购审批协作人', from: 'new', to: 'active',
|
|
34
|
+
input: z.object({ userId: z.string().uuid(), displayName: z.string().trim().min(1).max(80), specialty: z.string().trim().min(1).max(120) }),
|
|
35
|
+
handler: draft => draft,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 仅更新有效成员的展示姓名与专业说明,版本、范围和审计由内核维护。
|
|
40
|
+
* 不接受 userId,避免已被采购单引用的协作身份悄悄换人;需要另一个账号时登记新成员。
|
|
41
|
+
*/
|
|
42
|
+
export const updateApprovalMember = command(ApprovalMember, {
|
|
43
|
+
name: 'update', description: '修改采购协作人的公开资料', from: 'active', to: null,
|
|
44
|
+
input: z.object({ id: z.string().uuid(), displayName: z.string().trim().min(1).max(80), specialty: z.string().trim().min(1).max(120) }),
|
|
45
|
+
handler: (_draft, input) => ({ displayName: input.displayName, specialty: input.specialty }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 退役成员使后续设置/提交专业复核名单拒绝此人,保留记录供历史展示。
|
|
50
|
+
* 此动作不停用登录账号、不收回角色,也不修改在途流程快照;在途处理仍按当前真实权限核验。
|
|
51
|
+
*/
|
|
52
|
+
export const retireApprovalMember = command(ApprovalMember, {
|
|
53
|
+
name: 'retire', description: '停用采购审批协作名录成员', from: 'active', to: 'retired', handler: draft => draft,
|
|
54
|
+
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 采购实际审批路由:AI 预审、部门审核、可选专业会签和大额财务候选受理,共用一个采购流程实例。
|
|
3
|
+
* 人工节点显式开放转办、委派和前后/并行加签;步骤/政策随提交保存,旧实例保持自己的快照。
|
|
4
|
+
* 路由只决定任务分配与签序,最终结论仍执行领域命令的职责分离、额度与库存约束。
|
|
5
|
+
*/
|
|
2
6
|
import { approval } from '@yss123/cage-kernel';
|
|
3
|
-
import { approver, gt, route, step, when } from '@yss123/cage-approval';
|
|
7
|
+
import { approver, eq, gt, route, step, when } from '@yss123/cage-approval';
|
|
4
8
|
import { PurchaseOrder, OrderSubmitted, OrderCancelled, approve, reject, returnForChanges } from './purchase-order.js';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
|
-
* 把提交/取消事件与审批生命周期关联;routable
|
|
11
|
+
* 把提交/取消事件与审批生命周期关联;routable 仅开放金额、部门和专业复核需求/名单。
|
|
8
12
|
* 通过、驳回、退回分别调用已有 process 命令,领域状态与最终审批结论由引擎在事务内落实。
|
|
9
13
|
* automatedBy 是自动结论执行角色代码,必须由部署管理员显式配置能力和范围,声明不自动授予权限。
|
|
10
14
|
*/
|
|
@@ -12,7 +16,7 @@ export const poApproval = approval(PurchaseOrder, {
|
|
|
12
16
|
process: 'po-approval',
|
|
13
17
|
start: OrderSubmitted,
|
|
14
18
|
cancelOn: [OrderCancelled],
|
|
15
|
-
routable: ['amount', 'deptId'],
|
|
19
|
+
routable: ['amount', 'deptId', 'needsSpecialistReview', 'reviewerIds'],
|
|
16
20
|
onApproved: approve,
|
|
17
21
|
onRejected: reject,
|
|
18
22
|
onReturned: returnForChanges,
|
|
@@ -20,10 +24,12 @@ export const poApproval = approval(PurchaseOrder, {
|
|
|
20
24
|
});
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
|
-
* 顺序为 AI
|
|
27
|
+
* 顺序为 AI 材料预审、部门经理、按单据指定人员的专业会签、条件财务候选受理。
|
|
28
|
+
* needsSpecialistReview=true 才增加专业会签,名单来自提交快照且全部人必须通过;金额严格大于 50000 元才增加财务节点。
|
|
24
29
|
* AI 只读取 reason/amount/items,pass 仅推进到经理,return 回到草稿,manual 在当前节点交部门负责人。
|
|
25
30
|
* 模型失败同样转人工,最多四轮/五次工具调用/120 秒;不以失败兜底为自动批准。
|
|
26
|
-
*
|
|
31
|
+
* 财务共享一项未分配任务,finance 当前成员须先领取;所有人工动作仍需真实命令能力、范围、额度和职责分离。
|
|
32
|
+
* AI 人工接管保持原节点规则;经理、专业和财务才开放三项管理动作,新实例自动保存允许的加签位置。
|
|
27
33
|
*/
|
|
28
34
|
export const poRoute = route('po-approval', [
|
|
29
35
|
step('AI 材料预审', {
|
|
@@ -34,6 +40,11 @@ export const poRoute = route('po-approval', [
|
|
|
34
40
|
limits: { maxSteps: 4, maxToolCalls: 5, timeoutMs: 120_000 },
|
|
35
41
|
onFailure: 'manual',
|
|
36
42
|
}),
|
|
37
|
-
step('部门经理审批', approver.deptLeader('deptId')),
|
|
38
|
-
when(
|
|
43
|
+
step('部门经理审批', approver.deptLeader('deptId'), { actions: { transfer: true, delegate: true, addSign: true } }),
|
|
44
|
+
when(eq('needsSpecialistReview', true), step('专业人员会签', approver.field('reviewerIds'), {
|
|
45
|
+
mode: 'all', actions: { transfer: true, delegate: true, addSign: true },
|
|
46
|
+
})),
|
|
47
|
+
when(gt('amount', 50000), step('财务候选受理', approver.role('finance'), {
|
|
48
|
+
assignment: 'candidate', mode: 'any', actions: { transfer: true, delegate: true, addSign: true },
|
|
49
|
+
})),
|
|
39
50
|
]);
|
package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// 采购模块的公开声明;其他模块通过本入口依赖它,不穿透内部文件。
|
|
2
2
|
// index 只转导出,避免在组装入口里隐藏业务行为或产生初始化副作用。
|
|
3
|
-
//
|
|
3
|
+
// 主对象、协作名录、审批、只读统计和定时任务统一由此公开;测试文件不导出,运行时不注册测试夹具。
|
|
4
4
|
export * from './purchase-order.js';
|
|
5
|
+
export * from './approval-members.js';
|
|
5
6
|
export * from './approvals.js';
|
|
6
7
|
export * from './queries.js';
|
|
7
8
|
export * from './jobs.js';
|