@gadmin2n/schematics 0.0.147 → 0.0.149

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.
Files changed (106) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +2 -1
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +67 -15
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +83 -53
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +5 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.spec.ts +24 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +15 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +4 -0
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +31 -4
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +2 -6
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +28 -29
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +16 -34
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +3 -3
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +61 -6
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +2 -2
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +11 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +126 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +136 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.controller.ts +24 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.dto.ts +8 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +62 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.ts +94 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.types.ts +6 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staffDirectory.module.ts +10 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user.provider.ts +6 -2
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +20 -12
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +21 -19
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +11 -0
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +97 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +150 -2
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.controller.ts +13 -1
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.dto.ts +13 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.errors.ts +1 -0
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.spec.ts +125 -0
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.ts +118 -5
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +20 -3
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +6 -9
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +166 -17
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +82 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +2 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +4 -1
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +5 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +7 -2
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +44 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +106 -37
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +59 -3
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +33 -22
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +10 -23
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.test.tsx +69 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.tsx +145 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/index.ts +2 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/staff-directory.api.ts +16 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +20 -2
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +6 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +60 -14
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +60 -14
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +41 -1
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/PeopleGroupModal.tsx +181 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +203 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/people-groups.api.ts +145 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/PersonModal.tsx +112 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +214 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/person-directory.api.ts +79 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +7 -11
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +7 -7
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.test.tsx +46 -5
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.tsx +14 -10
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.test.tsx +1 -1
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +2 -1
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.test.tsx +1 -1
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +2 -2
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +20 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +97 -28
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +87 -119
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +22 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +8 -2
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +8 -46
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +7 -0
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +32 -3
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +163 -106
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +8 -1
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +26 -0
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -6
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +13 -16
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +185 -0
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +224 -105
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +1 -1
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +21 -12
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.test.tsx +140 -0
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +124 -62
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +160 -8
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +125 -55
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +78 -17
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.test.tsx +33 -1
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +28 -46
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/event-catalog.api.ts +4 -2
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +16 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/subject-provider.api.ts +0 -6
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +24 -0
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +14 -17
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
  104. package/package.json +1 -1
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +0 -162
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +0 -72
@@ -149,6 +149,7 @@ model WorkflowInstance {
149
149
  retryFromNodeId String? @db.VarChar(64) @map("retry_from_node_id") // 本实例从哪个失败节点继续执行
150
150
  cancellationStatus WorkflowCancellationStatus @default(NONE) @map("cancellation_status")
151
151
  cancellationRequestedAt DateTime? @map("cancellation_requested_at") @db.Timestamptz(3)
152
+ cancellationRequestedBy String? @map("cancellation_requested_by") @db.VarChar(128)
152
153
  cancellationReason String? @map("cancellation_reason") @db.VarChar(512)
153
154
  cancellationLastError String? @map("cancellation_last_error") @db.Text
154
155
  cancellationBlockedAt DateTime? @map("cancellation_blocked_at") @db.Timestamptz(3)
@@ -377,7 +378,7 @@ model WorkflowBusinessEventRegistration {
377
378
  registrationKey String @unique @map("registration_key") @db.Char(64)
378
379
  schemaName String @map("schema_name") @db.VarChar(63)
379
380
  tableName String @map("table_name") @db.VarChar(63)
380
- operation WorkflowEventOperation
381
+ operations WorkflowEventOperation[]
381
382
  watchedFields Json @map("watched_fields")
382
383
  primaryKeyFields Json @map("primary_key_fields")
383
384
  isEnabled Boolean @default(true) @map("is_enabled")
@@ -12,8 +12,9 @@ import {
12
12
  *
13
13
  * 职责(仅本模块自家):
14
14
  * 1. permission 父分组 page (顶级 admin/administration → permission)
15
- * 2. 5 个 leaf page: permission_readme / user / role / page / resource
16
- * 3. 7 个 resource: user / role / rolePages / roleResource / page / pageResource / resource
15
+ * 2. 7 个 leaf page: permission_readme / person_directory / user /
16
+ * people_group / role / page / resource
17
+ * 3. permission 模块所需 resource 及页面绑定
17
18
  * 4. PageResource 绑定(page ↔ resource,多对多)
18
19
  * 5. SYSTEM_ADMIN 对上述全部 page + resource 的全量授权
19
20
  *
@@ -54,14 +55,31 @@ export async function seedPermissionModule(
54
55
  enName: 'Menu Guide',
55
56
  sortOrder: 1005,
56
57
  },
57
- { code: 'user', zhName: '用户管理', enName: 'User Mgnt', sortOrder: 1010 },
58
- { code: 'role', zhName: '角色管理', enName: 'Role Mgnt', sortOrder: 1020 },
59
- { code: 'page', zhName: '页面管理', enName: 'Page Mgnt', sortOrder: 1030 },
58
+ {
59
+ code: 'person_directory',
60
+ zhName: '人员目录',
61
+ enName: 'People Directory',
62
+ sortOrder: 1010,
63
+ },
64
+ {
65
+ code: 'user',
66
+ zhName: '访问授权',
67
+ enName: 'Access Grants',
68
+ sortOrder: 1020,
69
+ },
70
+ {
71
+ code: 'people_group',
72
+ zhName: '人员组',
73
+ enName: 'People Groups',
74
+ sortOrder: 1030,
75
+ },
76
+ { code: 'role', zhName: '角色管理', enName: 'Role Mgnt', sortOrder: 1040 },
77
+ { code: 'page', zhName: '页面管理', enName: 'Page Mgnt', sortOrder: 1050 },
60
78
  {
61
79
  code: 'resource',
62
80
  zhName: '资源管理',
63
81
  enName: 'Resource Management',
64
- sortOrder: 1040,
82
+ sortOrder: 1060,
65
83
  },
66
84
  ];
67
85
 
@@ -69,14 +87,23 @@ export async function seedPermissionModule(
69
87
  pagesByCode.set(permissionPage.code, permissionPage.id);
70
88
 
71
89
  for (const def of leafDefs) {
72
- const p = await upsertPage(prisma, {
73
- code: def.code,
74
- name: def.code,
75
- zhName: def.zhName,
76
- enName: def.enName,
77
- sortOrder: def.sortOrder,
78
- parentId: permissionPage.id,
79
- });
90
+ const p = await upsertPage(
91
+ prisma,
92
+ {
93
+ code: def.code,
94
+ name: def.code,
95
+ zhName: def.zhName,
96
+ enName: def.enName,
97
+ sortOrder: def.sortOrder,
98
+ parentId: permissionPage.id,
99
+ },
100
+ {
101
+ zhName: def.zhName,
102
+ enName: def.enName,
103
+ sortOrder: def.sortOrder,
104
+ parentId: permissionPage.id,
105
+ },
106
+ );
80
107
  pagesByCode.set(p.code, p.id);
81
108
  console.log(`[permission] Page: ${p.code} (id=${p.id})`);
82
109
  }
@@ -87,7 +114,32 @@ export async function seedPermissionModule(
87
114
  description: string;
88
115
  pageCode: string;
89
116
  }[] = [
90
- { code: 'user', description: 'User management', pageCode: 'user' },
117
+ {
118
+ code: 'user',
119
+ description: 'People directory',
120
+ pageCode: 'person_directory',
121
+ },
122
+ {
123
+ code: 'staffDirectory',
124
+ description: 'Search the shared staff directory',
125
+ pageCode: 'person_directory',
126
+ },
127
+ { code: 'user', description: 'Access grants', pageCode: 'user' },
128
+ {
129
+ code: 'userGroup:read',
130
+ description: 'Read reusable people groups',
131
+ pageCode: 'people_group',
132
+ },
133
+ {
134
+ code: 'userGroup:create',
135
+ description: 'Maintain reusable people groups',
136
+ pageCode: 'people_group',
137
+ },
138
+ {
139
+ code: 'user',
140
+ description: 'Read people for group membership',
141
+ pageCode: 'people_group',
142
+ },
91
143
  { code: 'role', description: 'Role management', pageCode: 'role' },
92
144
  {
93
145
  code: 'rolePages',
@@ -202,65 +202,82 @@ const NODE_DEFINITIONS: BaseNodeTypeSeed[] = [
202
202
  minLength: 1,
203
203
  description: '前端登记的 PostgreSQL 表字段监听标识',
204
204
  },
205
- match: {
206
- type: 'object',
207
- properties: {
208
- combineOperation: {
209
- type: 'string',
210
- enum: ['AND', 'OR'],
211
- },
212
- conditions: {
213
- type: 'array',
214
- minItems: 1,
215
- items: {
205
+ operationRules: {
206
+ type: 'array',
207
+ minItems: 1,
208
+ maxItems: 3,
209
+ description: '按数据操作配置启动规则;未启用的操作不启动',
210
+ items: {
211
+ type: 'object',
212
+ properties: {
213
+ operation: {
214
+ type: 'string',
215
+ enum: ['INSERT', 'UPDATE', 'DELETE'],
216
+ },
217
+ match: {
216
218
  type: 'object',
217
219
  properties: {
218
- id: { type: 'string', minLength: 1 },
219
- left: {
220
- type: 'object',
221
- properties: {
222
- source: { const: 'WORKFLOW' },
223
- path: { type: 'string', minLength: 1 },
224
- },
225
- required: ['source', 'path'],
226
- additionalProperties: false,
227
- },
228
- operator: {
220
+ combineOperation: {
229
221
  type: 'string',
230
- enum: ['EQ', 'NEQ', 'EMPTY', 'NOT_EMPTY'],
231
- },
232
- right: {
233
- type: 'object',
234
- properties: {
235
- source: { const: 'FIXED' },
236
- valueType: {
237
- type: 'string',
238
- enum: ['STRING', 'NUMBER', 'BOOLEAN'],
239
- },
240
- value: { type: 'string' },
241
- },
242
- required: ['source', 'valueType', 'value'],
243
- additionalProperties: false,
222
+ enum: ['AND', 'OR'],
244
223
  },
245
- },
246
- required: ['id', 'left', 'operator'],
247
- allOf: [
248
- {
249
- if: {
224
+ conditions: {
225
+ type: 'array',
226
+ minItems: 1,
227
+ items: {
228
+ type: 'object',
250
229
  properties: {
251
- operator: { enum: ['EQ', 'NEQ'] },
230
+ id: { type: 'string', minLength: 1 },
231
+ left: {
232
+ type: 'object',
233
+ properties: {
234
+ source: { const: 'WORKFLOW' },
235
+ path: { type: 'string', minLength: 1 },
236
+ },
237
+ required: ['source', 'path'],
238
+ additionalProperties: false,
239
+ },
240
+ operator: {
241
+ type: 'string',
242
+ enum: ['EQ', 'NEQ', 'IN', 'EMPTY', 'NOT_EMPTY'],
243
+ },
244
+ right: {
245
+ type: 'object',
246
+ properties: {
247
+ source: { const: 'FIXED' },
248
+ valueType: {
249
+ type: 'string',
250
+ enum: ['STRING', 'NUMBER', 'BOOLEAN'],
251
+ },
252
+ value: { type: 'string' },
253
+ },
254
+ required: ['source', 'valueType', 'value'],
255
+ additionalProperties: false,
256
+ },
252
257
  },
253
- required: ['operator'],
258
+ required: ['id', 'left', 'operator'],
259
+ allOf: [
260
+ {
261
+ if: {
262
+ properties: {
263
+ operator: { enum: ['EQ', 'NEQ', 'IN'] },
264
+ },
265
+ required: ['operator'],
266
+ },
267
+ then: { required: ['right'] },
268
+ },
269
+ ],
270
+ additionalProperties: false,
254
271
  },
255
- then: { required: ['right'] },
256
272
  },
257
- ],
273
+ },
274
+ required: ['combineOperation', 'conditions'],
258
275
  additionalProperties: false,
259
276
  },
260
277
  },
278
+ required: ['operation'],
279
+ additionalProperties: false,
261
280
  },
262
- required: ['combineOperation', 'conditions'],
263
- additionalProperties: false,
264
281
  },
265
282
  processingMode: {
266
283
  type: 'string',
@@ -281,7 +298,7 @@ const NODE_DEFINITIONS: BaseNodeTypeSeed[] = [
281
298
  description: '顺序调度器最多同时处理的业务对象数量',
282
299
  },
283
300
  },
284
- required: ['registrationCode', 'processingMode'],
301
+ required: ['registrationCode', 'operationRules', 'processingMode'],
285
302
  allOf: [
286
303
  {
287
304
  if: {
@@ -980,7 +997,11 @@ const HUMAN_APPROVAL_CONFIG_SCHEMA: NodeTypeSeed['configSchema'] = {
980
997
  type: 'object',
981
998
  properties: {
982
999
  source: { type: 'string', enum: ['STATIC', 'DYNAMIC'] },
983
- provider: { type: 'string', enum: ['USER', 'USER_GROUP'] },
1000
+ provider: {
1001
+ type: 'string',
1002
+ enum: ['USER', 'USER_GROUP'],
1003
+ default: 'USER',
1004
+ },
984
1005
  refs: {
985
1006
  type: 'array',
986
1007
  minItems: 1,
@@ -998,7 +1019,7 @@ const HUMAN_APPROVAL_CONFIG_SCHEMA: NodeTypeSeed['configSchema'] = {
998
1019
  additionalProperties: false,
999
1020
  },
1000
1021
  },
1001
- required: ['source', 'provider'],
1022
+ required: ['source'],
1002
1023
  allOf: [
1003
1024
  {
1004
1025
  if: { properties: { source: { const: 'STATIC' } } },
@@ -1093,7 +1114,7 @@ const humanApprovalNode: NodeTypeSeed = {
1093
1114
  configSchema: HUMAN_APPROVAL_CONFIG_SCHEMA,
1094
1115
  outputs: ['true', 'false'],
1095
1116
  runtimeKey: 'human_approval',
1096
- schemaVersion: 20,
1117
+ schemaVersion: 22,
1097
1118
  instancePolicy: 'OPTIONAL',
1098
1119
  uiSchema: HUMAN_APPROVAL_UI_SCHEMA,
1099
1120
  };
@@ -1323,7 +1344,7 @@ const dedicatedEditorNodes: NodeTypeSeed[] = [
1323
1344
  ),
1324
1345
  schemaVersion:
1325
1346
  type === 'event_trigger'
1326
- ? 4
1347
+ ? 5
1327
1348
  : type === 'db_execute'
1328
1349
  ? 4
1329
1350
  : type === 'parallel'
@@ -1361,7 +1382,16 @@ export const NODE_TYPES: NodeTypeSeed[] = [
1361
1382
 
1362
1383
  export async function seedNodeTypes(prisma: PrismaClient) {
1363
1384
  console.log('Seeding workflow node types...');
1364
- for (const removedType of ['return_result', 'db_query']) {
1385
+ for (const removedType of [
1386
+ 'return_result',
1387
+ 'db_query',
1388
+ 'approval_request_trigger',
1389
+ 'send_notification',
1390
+ 'set_variable',
1391
+ 'submit_approval_result',
1392
+ 'submit_external_result',
1393
+ 'error_handler',
1394
+ ]) {
1365
1395
  const removedNodeType = await prisma.workflowNodeType.findUnique({
1366
1396
  where: { type: removedType },
1367
1397
  select: { id: true },
@@ -60,6 +60,11 @@ const config: Record<string, any> = {
60
60
  integration: {
61
61
  secretKey: process.env.INTEGRATION_SECRET_KEY ?? '',
62
62
  },
63
+ staffDirectory: {
64
+ baseUrl: process.env.STAFF_DIRECTORY_BASE_URL ?? '',
65
+ apiKey: process.env.STAFF_DIRECTORY_API_KEY ?? '',
66
+ searchPath: process.env.STAFF_DIRECTORY_SEARCH_PATH ?? '/api/staff/search',
67
+ },
63
68
  };
64
69
 
65
70
  export default () => config;
@@ -1,4 +1,5 @@
1
1
  import { ArgumentsHost, BadRequestException } from '@nestjs/common';
2
+ import { Prisma } from '@prisma/client';
2
3
  import { StructuredAllExceptionsFilter } from './structured-all-exceptions.filter';
3
4
 
4
5
  function httpHost(url = '/api/workflowNodeInstance') {
@@ -51,4 +52,27 @@ describe('StructuredAllExceptionsFilter', () => {
51
52
  path: '/api/workflowNodeInstance',
52
53
  });
53
54
  });
55
+
56
+ it('returns a safe 409 response for an unhandled Prisma unique conflict', () => {
57
+ const filter = new StructuredAllExceptionsFilter();
58
+ const structured = httpHost('/api/user/createOne');
59
+
60
+ filter.catch(
61
+ new Prisma.PrismaClientKnownRequestError('sensitive database details', {
62
+ code: 'P2002',
63
+ clientVersion: '6.19.3',
64
+ meta: { target: ['userid'] },
65
+ }),
66
+ structured.host,
67
+ );
68
+
69
+ expect(structured.response.status).toHaveBeenCalledWith(409);
70
+ expect(structured.response.json).toHaveBeenCalledWith({
71
+ statusCode: 409,
72
+ code: 'RESOURCE_ALREADY_EXISTS',
73
+ message: '记录已存在,请勿重复提交',
74
+ error: 'Conflict',
75
+ path: '/api/user/createOne',
76
+ });
77
+ });
54
78
  });
@@ -4,6 +4,7 @@ import {
4
4
  ExceptionFilter,
5
5
  HttpStatus,
6
6
  } from '@nestjs/common';
7
+ import { Prisma } from '@prisma/client';
7
8
 
8
9
  interface ControlledException {
9
10
  getStatus?: () => number;
@@ -24,6 +25,20 @@ export class StructuredAllExceptionsFilter implements ExceptionFilter {
24
25
  const error = controlled?.name ?? 'Error';
25
26
  const path = request.url ?? '';
26
27
 
28
+ if (
29
+ exception instanceof Prisma.PrismaClientKnownRequestError &&
30
+ exception.code === 'P2002'
31
+ ) {
32
+ response.status(HttpStatus.CONFLICT).json({
33
+ statusCode: HttpStatus.CONFLICT,
34
+ code: 'RESOURCE_ALREADY_EXISTS',
35
+ message: '记录已存在,请勿重复提交',
36
+ error: 'Conflict',
37
+ path,
38
+ });
39
+ return;
40
+ }
41
+
27
42
  if (isRecord(payload)) {
28
43
  response.status(statusCode).json({
29
44
  ...payload,
@@ -22,6 +22,8 @@ class JobsQueryDto {
22
22
  toggle?: string;
23
23
  module?: string;
24
24
  q?: string;
25
+ dateFrom?: string;
26
+ dateTo?: string;
25
27
  skip?: number;
26
28
  limit?: number;
27
29
  }
@@ -55,6 +57,8 @@ export class AgendaController {
55
57
  toggle: query.toggle,
56
58
  module: query.module,
57
59
  q: query.q,
60
+ dateFrom: query.dateFrom,
61
+ dateTo: query.dateTo,
58
62
  skip: query.skip ? Number(query.skip) : 0,
59
63
  limit: query.limit ? Number(query.limit) : 20,
60
64
  });
@@ -16,6 +16,8 @@ interface GetJobsParams {
16
16
  toggle?: string; // 'active' | 'paused'
17
17
  module?: string;
18
18
  q?: string;
19
+ dateFrom?: string; // ISO:按 lastRunAt 过滤区间下界
20
+ dateTo?: string; // ISO:按 lastRunAt 过滤区间上界
19
21
  skip?: number;
20
22
  limit?: number;
21
23
  }
@@ -364,7 +366,17 @@ export class AgendaService
364
366
  totalPages: 0,
365
367
  };
366
368
  }
367
- const { job, state, toggle, module, q, skip = 0, limit = 20 } = params;
369
+ const {
370
+ job,
371
+ state,
372
+ toggle,
373
+ module,
374
+ q,
375
+ dateFrom,
376
+ dateTo,
377
+ skip = 0,
378
+ limit = 20,
379
+ } = params;
368
380
 
369
381
  const allQuery: JobsQueryOptions = {};
370
382
  if (job) allQuery.name = job;
@@ -381,6 +393,21 @@ export class AgendaService
381
393
  )
382
394
  : allResult.jobs;
383
395
 
396
+ // 应用层过滤 lastRunAt 时间区间(Agenda queryJobs 同样不支持日期过滤)。
397
+ // 未执行过(lastRunAt 为空)的任务在设置了时间区间时不纳入。
398
+ const fromTs = dateFrom ? new Date(dateFrom).getTime() : null;
399
+ const toTs = dateTo ? new Date(dateTo).getTime() : null;
400
+ const dateFiltered =
401
+ fromTs !== null || toTs !== null
402
+ ? moduleFiltered.filter((j) => {
403
+ if (!j.lastRunAt) return false;
404
+ const t = new Date(j.lastRunAt).getTime();
405
+ if (fromTs !== null && t < fromTs) return false;
406
+ if (toTs !== null && t > toTs) return false;
407
+ return true;
408
+ })
409
+ : moduleFiltered;
410
+
384
411
  const overview: Record<string, number> = {
385
412
  running: 0,
386
413
  scheduled: 0,
@@ -392,15 +419,15 @@ export class AgendaService
392
419
  active: 0,
393
420
  };
394
421
 
395
- for (const j of moduleFiltered) {
422
+ for (const j of dateFiltered) {
396
423
  const s = j.state;
397
424
  if (s in overview) overview[s]++;
398
425
  }
399
426
 
400
427
  // 第一步:按 job 原始状态过滤(running/queued 等)
401
428
  const stateFiltered = state
402
- ? moduleFiltered.filter((j) => j.state === state)
403
- : moduleFiltered;
429
+ ? dateFiltered.filter((j) => j.state === state)
430
+ : dateFiltered;
404
431
 
405
432
  // 从状态过滤后的数据计算 active/paused 数量
406
433
  for (const j of stateFiltered) {
@@ -28,12 +28,11 @@ describe('CodeRuntimeController', () => {
28
28
  subjects: [
29
29
  {
30
30
  source: 'DYNAMIC',
31
- provider: 'USER',
32
31
  inputKey: 'direct_manager',
33
32
  lookup: {
34
33
  result: {
35
34
  script:
36
- "return { subjects: [{ provider: 'USER', refs: [response.data.leader] }], requester: input.user_id };",
35
+ 'return [response.data.leader, input.user_id];',
37
36
  },
38
37
  },
39
38
  },
@@ -89,10 +88,7 @@ describe('CodeRuntimeController', () => {
89
88
  workflow: {},
90
89
  }),
91
90
  ).resolves.toEqual({
92
- result: {
93
- subjects: [{ provider: 'USER', refs: ['leader-a'] }],
94
- requester: 'applicant-a',
95
- },
91
+ result: ['leader-a', 'applicant-a'],
96
92
  });
97
93
  });
98
94
  });
@@ -5,36 +5,22 @@ describe('materializeApprovalSubjects', () => {
5
5
  expect(
6
6
  materializeApprovalSubjects(
7
7
  [
8
- { source: 'STATIC', provider: 'USER', refs: ['alice'] },
8
+ { source: 'STATIC', refs: ['alice', 'bob'] },
9
9
  {
10
10
  source: 'DYNAMIC',
11
- provider: 'USER',
12
11
  inputKey: 'request_owner',
13
12
  },
14
13
  {
15
14
  source: 'DYNAMIC',
16
- provider: 'USER_GROUP',
17
- inputKey: 'review_groups',
15
+ inputKey: 'backup_approvers',
18
16
  },
19
17
  ],
20
18
  {
21
- request_owner: {
22
- subjects: [{ provider: 'USER', refs: ['bob'] }],
23
- },
24
- review_groups: {
25
- subjects: [
26
- {
27
- provider: 'USER_GROUP',
28
- refs: ['finance', 'risk', 'finance'],
29
- },
30
- ],
31
- },
19
+ request_owner: ['bob', 'carol'],
20
+ backup_approvers: ['alice', 'dave', 'carol'],
32
21
  },
33
22
  ),
34
- ).toEqual([
35
- { provider: 'USER', refs: ['alice', 'bob'] },
36
- { provider: 'USER_GROUP', refs: ['finance', 'risk'] },
37
- ]);
23
+ ).toEqual([{ provider: 'USER', refs: ['alice', 'bob', 'carol', 'dave'] }]);
38
24
  });
39
25
 
40
26
  it('rejects a missing dynamic approver input', () => {
@@ -43,7 +29,6 @@ describe('materializeApprovalSubjects', () => {
43
29
  [
44
30
  {
45
31
  source: 'DYNAMIC',
46
- provider: 'USER',
47
32
  inputKey: 'request_owner',
48
33
  },
49
34
  ],
@@ -58,32 +43,46 @@ describe('materializeApprovalSubjects', () => {
58
43
  [
59
44
  {
60
45
  source: 'DYNAMIC',
61
- provider: 'USER',
62
46
  inputKey: 'direct_manager',
63
47
  },
64
48
  ],
65
- {
66
- direct_manager: {
67
- subjects: [{ provider: 'USER', refs: ['leader-a'] }],
68
- },
69
- },
49
+ { direct_manager: ['leader-a'] },
70
50
  ),
71
51
  ).toEqual([{ provider: 'USER', refs: ['leader-a'] }]);
72
52
  });
73
53
 
74
- it('rejects a lookup result that does not use the configured provider', () => {
54
+ it('keeps fixed user groups for runtime expansion while deduplicating users', () => {
55
+ expect(
56
+ materializeApprovalSubjects(
57
+ [
58
+ { source: 'STATIC', provider: 'USER', refs: ['alice'] },
59
+ {
60
+ source: 'STATIC',
61
+ provider: 'USER_GROUP',
62
+ refs: ['finance'],
63
+ },
64
+ { source: 'DYNAMIC', inputKey: 'backup_approvers' },
65
+ ],
66
+ { backup_approvers: ['alice', 'bob', 'bob'] },
67
+ ),
68
+ ).toEqual([
69
+ { provider: 'USER', refs: ['alice', 'bob'] },
70
+ { provider: 'USER_GROUP', refs: ['finance'] },
71
+ ]);
72
+ });
73
+
74
+ it('rejects a lookup result that is not a user ID array', () => {
75
75
  expect(() =>
76
76
  materializeApprovalSubjects(
77
77
  [
78
78
  {
79
79
  source: 'DYNAMIC',
80
- provider: 'USER',
81
80
  inputKey: 'direct_manager',
82
81
  },
83
82
  ],
84
83
  {
85
84
  direct_manager: {
86
- subjects: [{ provider: 'USER_GROUP', refs: ['leaders'] }],
85
+ subjects: [{ provider: 'USER', refs: ['leader-a'] }],
87
86
  },
88
87
  },
89
88
  ),