@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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +67 -15
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +83 -53
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.spec.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +15 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +31 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +2 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +28 -29
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +16 -34
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +61 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +11 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +126 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +136 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.controller.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.dto.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.ts +94 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.types.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staffDirectory.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user.provider.ts +6 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +20 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +21 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +11 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +97 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +150 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.controller.ts +13 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.dto.ts +13 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.errors.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.spec.ts +125 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.ts +118 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +20 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +6 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +166 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +82 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +4 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +5 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +7 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +106 -37
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +59 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +33 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +10 -23
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.test.tsx +69 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.tsx +145 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/staff-directory.api.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +20 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +60 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +60 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +41 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/PeopleGroupModal.tsx +181 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +203 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/people-groups.api.ts +145 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/PersonModal.tsx +112 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +214 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/person-directory.api.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +7 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +7 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.test.tsx +46 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.tsx +14 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +97 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +87 -119
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +22 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +8 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +8 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +32 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +163 -106
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +8 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +26 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +13 -16
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +185 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +224 -105
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +21 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.test.tsx +140 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +124 -62
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +160 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +125 -55
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +78 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.test.tsx +33 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +28 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/event-catalog.api.ts +4 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/subject-provider.api.ts +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +14 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +0 -162
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +0 -72
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Prisma } from '@prisma/client';
|
|
1
2
|
import { UserGroupService } from './user-group.service';
|
|
2
3
|
|
|
3
4
|
const group = {
|
|
@@ -20,6 +21,7 @@ function createHarness() {
|
|
|
20
21
|
findMany: jest.fn(),
|
|
21
22
|
create: jest.fn(),
|
|
22
23
|
updateMany: jest.fn().mockResolvedValue({ count: 1 }),
|
|
24
|
+
delete: jest.fn().mockResolvedValue(group),
|
|
23
25
|
},
|
|
24
26
|
user: { findMany: jest.fn() },
|
|
25
27
|
userGroupUser: {
|
|
@@ -32,6 +34,8 @@ function createHarness() {
|
|
|
32
34
|
deleteMany: jest.fn().mockResolvedValue({ count: 0 }),
|
|
33
35
|
createMany: jest.fn().mockResolvedValue({ count: 1 }),
|
|
34
36
|
},
|
|
37
|
+
workflow: { findMany: jest.fn().mockResolvedValue([]) },
|
|
38
|
+
approvalRequestSubject: { count: jest.fn().mockResolvedValue(0) },
|
|
35
39
|
};
|
|
36
40
|
const prisma = {
|
|
37
41
|
...tx,
|
|
@@ -44,6 +48,19 @@ function createHarness() {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
describe('UserGroupService', () => {
|
|
51
|
+
it('can include inactive groups for the management page', async () => {
|
|
52
|
+
const { service, tx } = createHarness();
|
|
53
|
+
tx.userGroup.findMany.mockResolvedValue([]);
|
|
54
|
+
|
|
55
|
+
await service.search('finance', true);
|
|
56
|
+
|
|
57
|
+
expect(tx.userGroup.findMany).toHaveBeenCalledWith(
|
|
58
|
+
expect.objectContaining({
|
|
59
|
+
where: expect.not.objectContaining({ isActive: true }),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
47
64
|
it('returns direct users and child groups for editing', async () => {
|
|
48
65
|
const { service, tx } = createHarness();
|
|
49
66
|
tx.userGroup.findUnique
|
|
@@ -148,6 +165,31 @@ describe('UserGroupService', () => {
|
|
|
148
165
|
});
|
|
149
166
|
});
|
|
150
167
|
|
|
168
|
+
it('only accepts active non-API users as direct members', async () => {
|
|
169
|
+
const { service, tx } = createHarness();
|
|
170
|
+
tx.user.findMany.mockResolvedValue([{ userid: 'alice' }]);
|
|
171
|
+
tx.userGroup.findMany.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
|
|
172
|
+
|
|
173
|
+
await service.replaceMembers(
|
|
174
|
+
'finance',
|
|
175
|
+
{
|
|
176
|
+
expectedRevision: 4,
|
|
177
|
+
userIds: ['alice'],
|
|
178
|
+
childGroupCodes: [],
|
|
179
|
+
},
|
|
180
|
+
actor,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(tx.user.findMany).toHaveBeenCalledWith({
|
|
184
|
+
where: {
|
|
185
|
+
userid: { in: ['alice'] },
|
|
186
|
+
isActive: true,
|
|
187
|
+
isApiKey: false,
|
|
188
|
+
},
|
|
189
|
+
select: { userid: true },
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
151
193
|
it('returns 409 when expectedRevision is stale', async () => {
|
|
152
194
|
const { service } = createHarness();
|
|
153
195
|
await expect(
|
|
@@ -158,4 +200,87 @@ describe('UserGroupService', () => {
|
|
|
158
200
|
),
|
|
159
201
|
).rejects.toMatchObject({ status: 409 });
|
|
160
202
|
});
|
|
203
|
+
|
|
204
|
+
it('deletes an unreferenced group and its owned memberships', async () => {
|
|
205
|
+
const { service, tx } = createHarness();
|
|
206
|
+
tx.userGroupChild.findMany.mockResolvedValue([]);
|
|
207
|
+
|
|
208
|
+
await expect(service.deleteGroup('finance', 4)).resolves.toEqual({
|
|
209
|
+
count: 1,
|
|
210
|
+
});
|
|
211
|
+
expect(tx.userGroupUser.deleteMany).toHaveBeenCalledWith({
|
|
212
|
+
where: { groupId: group.id },
|
|
213
|
+
});
|
|
214
|
+
expect(tx.userGroupChild.deleteMany).toHaveBeenCalledWith({
|
|
215
|
+
where: { parentGroupId: group.id },
|
|
216
|
+
});
|
|
217
|
+
expect(tx.userGroup.delete).toHaveBeenCalledWith({
|
|
218
|
+
where: { id: group.id },
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('rejects deleting a group referenced by another group', async () => {
|
|
223
|
+
const { service, tx } = createHarness();
|
|
224
|
+
tx.userGroupChild.findMany.mockResolvedValue([
|
|
225
|
+
{ parentGroup: { code: 'all-finance', name: 'All Finance' } },
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
await expect(service.deleteGroup('finance', 4)).rejects.toMatchObject({
|
|
229
|
+
status: 409,
|
|
230
|
+
response: {
|
|
231
|
+
code: 'USER_GROUP_IN_USE',
|
|
232
|
+
message: expect.stringContaining('All Finance'),
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
expect(tx.userGroup.delete).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('rejects deleting a group referenced by a Workflow', async () => {
|
|
239
|
+
const { service, tx } = createHarness();
|
|
240
|
+
tx.userGroupChild.findMany.mockResolvedValue([]);
|
|
241
|
+
tx.workflow.findMany.mockResolvedValue([
|
|
242
|
+
{
|
|
243
|
+
id: BigInt(9),
|
|
244
|
+
name: 'Finance Approval',
|
|
245
|
+
dsl: {
|
|
246
|
+
nodes: [
|
|
247
|
+
{
|
|
248
|
+
config: {
|
|
249
|
+
subjects: [{ provider: 'USER_GROUP', refs: ['finance'] }],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
versions: [],
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
await expect(service.deleteGroup('finance', 4)).rejects.toMatchObject({
|
|
259
|
+
status: 409,
|
|
260
|
+
response: {
|
|
261
|
+
code: 'USER_GROUP_IN_USE',
|
|
262
|
+
message: expect.stringContaining('Finance Approval'),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
expect(tx.userGroup.delete).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('returns 409 when a concurrent reference makes the database reject deletion', async () => {
|
|
269
|
+
const { service, tx } = createHarness();
|
|
270
|
+
tx.userGroupChild.findMany.mockResolvedValue([]);
|
|
271
|
+
tx.userGroup.delete.mockRejectedValue(
|
|
272
|
+
new Prisma.PrismaClientKnownRequestError('foreign key conflict', {
|
|
273
|
+
code: 'P2003',
|
|
274
|
+
clientVersion: '6.19.3',
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
await expect(service.deleteGroup('finance', 4)).rejects.toMatchObject({
|
|
279
|
+
status: 409,
|
|
280
|
+
response: {
|
|
281
|
+
code: 'USER_GROUP_IN_USE',
|
|
282
|
+
message: expect.stringContaining('刷新后重试'),
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
});
|
|
161
286
|
});
|
|
@@ -138,11 +138,102 @@ export class UserGroupService {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
async deleteGroup(code: string, expectedRevision: number) {
|
|
142
|
+
try {
|
|
143
|
+
return await this.prisma.$transaction(async (tx) => {
|
|
144
|
+
const group = await this.findGroup(tx, code);
|
|
145
|
+
this.assertRevision(group.revision, expectedRevision);
|
|
146
|
+
|
|
147
|
+
const [parents, workflows, activeApprovalCount] = await Promise.all([
|
|
148
|
+
tx.userGroupChild.findMany({
|
|
149
|
+
where: { childGroupId: group.id },
|
|
150
|
+
select: {
|
|
151
|
+
parentGroup: { select: { code: true, name: true } },
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
tx.workflow.findMany({
|
|
155
|
+
select: {
|
|
156
|
+
id: true,
|
|
157
|
+
name: true,
|
|
158
|
+
dsl: true,
|
|
159
|
+
versions: { select: { dsl: true } },
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
tx.approvalRequestSubject.count({
|
|
163
|
+
where: {
|
|
164
|
+
provider: 'USER_GROUP',
|
|
165
|
+
ref: group.code,
|
|
166
|
+
request: { status: { in: ['PENDING', 'BLOCKED'] } },
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
]);
|
|
170
|
+
const parentGroups = parents.map(({ parentGroup }) => parentGroup);
|
|
171
|
+
const referencedWorkflows = workflows
|
|
172
|
+
.filter(
|
|
173
|
+
(workflow) =>
|
|
174
|
+
workflowDslReferencesGroup(workflow.dsl, group.code) ||
|
|
175
|
+
workflow.versions.some((version) =>
|
|
176
|
+
workflowDslReferencesGroup(version.dsl, group.code),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
.map((workflow) => ({
|
|
180
|
+
id: String(workflow.id),
|
|
181
|
+
name: workflow.name,
|
|
182
|
+
}));
|
|
183
|
+
const reasons = [
|
|
184
|
+
...(parentGroups.length
|
|
185
|
+
? [
|
|
186
|
+
`被人员组引用:${parentGroups
|
|
187
|
+
.map((parent) => parent.name)
|
|
188
|
+
.join('、')}`,
|
|
189
|
+
]
|
|
190
|
+
: []),
|
|
191
|
+
...(referencedWorkflows.length
|
|
192
|
+
? [
|
|
193
|
+
`被 Workflow 引用:${referencedWorkflows
|
|
194
|
+
.map((workflow) => workflow.name)
|
|
195
|
+
.join('、')}`,
|
|
196
|
+
]
|
|
197
|
+
: []),
|
|
198
|
+
...(activeApprovalCount
|
|
199
|
+
? [`仍有 ${activeApprovalCount} 个进行中的审批引用`]
|
|
200
|
+
: []),
|
|
201
|
+
];
|
|
202
|
+
if (reasons.length) {
|
|
203
|
+
throw new UserGroupDomainError(
|
|
204
|
+
'USER_GROUP_IN_USE',
|
|
205
|
+
`无法删除该人员组:${reasons.join(';')}`,
|
|
206
|
+
409,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await Promise.all([
|
|
211
|
+
tx.userGroupUser.deleteMany({ where: { groupId: group.id } }),
|
|
212
|
+
tx.userGroupChild.deleteMany({ where: { parentGroupId: group.id } }),
|
|
213
|
+
]);
|
|
214
|
+
await tx.userGroup.delete({ where: { id: group.id } });
|
|
215
|
+
return { count: 1 };
|
|
216
|
+
});
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (
|
|
219
|
+
error instanceof Prisma.PrismaClientKnownRequestError &&
|
|
220
|
+
error.code === 'P2003'
|
|
221
|
+
) {
|
|
222
|
+
throw new UserGroupDomainError(
|
|
223
|
+
'USER_GROUP_IN_USE',
|
|
224
|
+
'该人员组刚被其他数据引用,请刷新后重试',
|
|
225
|
+
409,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
search(query: string, includeInactive = false) {
|
|
142
233
|
const q = query.trim();
|
|
143
234
|
return this.prisma.userGroup.findMany({
|
|
144
235
|
where: {
|
|
145
|
-
isActive: true,
|
|
236
|
+
...(!includeInactive && { isActive: true }),
|
|
146
237
|
...(q && {
|
|
147
238
|
OR: [
|
|
148
239
|
{ code: { contains: q, mode: 'insensitive' } },
|
|
@@ -156,6 +247,8 @@ export class UserGroupService {
|
|
|
156
247
|
name: true,
|
|
157
248
|
description: true,
|
|
158
249
|
revision: true,
|
|
250
|
+
isActive: true,
|
|
251
|
+
updatedAt: true,
|
|
159
252
|
},
|
|
160
253
|
take: 50,
|
|
161
254
|
});
|
|
@@ -220,7 +313,7 @@ export class UserGroupService {
|
|
|
220
313
|
where: { id: { in: ids }, isActive: true },
|
|
221
314
|
select: {
|
|
222
315
|
users: {
|
|
223
|
-
where: { user: { isActive: true } },
|
|
316
|
+
where: { user: { isActive: true, isApiKey: false } },
|
|
224
317
|
select: {
|
|
225
318
|
user: { select: { userid: true, username: true } },
|
|
226
319
|
},
|
|
@@ -267,13 +360,13 @@ export class UserGroupService {
|
|
|
267
360
|
private async requireActiveUsers(tx: Transaction, userIds: string[]) {
|
|
268
361
|
if (userIds.length === 0) return [];
|
|
269
362
|
const users = await tx.user.findMany({
|
|
270
|
-
where: { userid: { in: userIds }, isActive: true },
|
|
363
|
+
where: { userid: { in: userIds }, isActive: true, isApiKey: false },
|
|
271
364
|
select: { userid: true },
|
|
272
365
|
});
|
|
273
366
|
if (users.length !== userIds.length) {
|
|
274
367
|
throw new UserGroupDomainError(
|
|
275
368
|
'USER_GROUP_MEMBER_INVALID',
|
|
276
|
-
'Every direct user must exist and be
|
|
369
|
+
'Every direct user must exist, be active, and not be an API key user',
|
|
277
370
|
);
|
|
278
371
|
}
|
|
279
372
|
return users;
|
|
@@ -388,3 +481,23 @@ export class UserGroupService {
|
|
|
388
481
|
);
|
|
389
482
|
}
|
|
390
483
|
}
|
|
484
|
+
|
|
485
|
+
function workflowDslReferencesGroup(
|
|
486
|
+
value: Prisma.JsonValue | null,
|
|
487
|
+
groupCode: string,
|
|
488
|
+
): boolean {
|
|
489
|
+
if (Array.isArray(value))
|
|
490
|
+
return value.some((item) => workflowDslReferencesGroup(item, groupCode));
|
|
491
|
+
if (!value || typeof value !== 'object') return false;
|
|
492
|
+
|
|
493
|
+
const record = value as Record<string, Prisma.JsonValue>;
|
|
494
|
+
const refs = Array.isArray(record.refs) ? record.refs.map(String) : [];
|
|
495
|
+
if (
|
|
496
|
+
refs.includes(`group:${groupCode}`) ||
|
|
497
|
+
(record.provider === 'USER_GROUP' && refs.includes(groupCode))
|
|
498
|
+
)
|
|
499
|
+
return true;
|
|
500
|
+
return Object.values(record).some((item) =>
|
|
501
|
+
workflowDslReferencesGroup(item, groupCode),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
@@ -69,6 +69,7 @@ describe('Workflow cancellation', () => {
|
|
|
69
69
|
runtime.cancelExecution({
|
|
70
70
|
tx: tx as any,
|
|
71
71
|
instanceId: BigInt(11),
|
|
72
|
+
requestedBy: 'business-user',
|
|
72
73
|
reason: 'business cancellation',
|
|
73
74
|
}),
|
|
74
75
|
).resolves.toEqual({ instanceId: '11', status: 'CANCELLED' });
|
|
@@ -79,6 +80,8 @@ describe('Workflow cancellation', () => {
|
|
|
79
80
|
data: expect.objectContaining({
|
|
80
81
|
status: 'CANCELLED',
|
|
81
82
|
cancellationStatus: 'PENDING',
|
|
83
|
+
cancellationRequestedAt: expect.any(Date),
|
|
84
|
+
cancellationRequestedBy: 'business-user',
|
|
82
85
|
cancellationReason: 'business cancellation',
|
|
83
86
|
}),
|
|
84
87
|
});
|
|
@@ -112,6 +115,7 @@ describe('Workflow cancellation', () => {
|
|
|
112
115
|
await runtime.cancelExecution({
|
|
113
116
|
tx: tx as any,
|
|
114
117
|
instanceId: execution!.instanceId,
|
|
118
|
+
requestedBy: 'applicant-user',
|
|
115
119
|
reason: 'applicant cancellation',
|
|
116
120
|
});
|
|
117
121
|
|
|
@@ -150,7 +154,11 @@ describe('Workflow cancellation', () => {
|
|
|
150
154
|
const { runtime, temporal, tx } = setup({ status: 'COMPLETED' });
|
|
151
155
|
|
|
152
156
|
await expect(
|
|
153
|
-
runtime.cancelExecution({
|
|
157
|
+
runtime.cancelExecution({
|
|
158
|
+
tx: tx as any,
|
|
159
|
+
instanceId: BigInt(11),
|
|
160
|
+
requestedBy: 'admin-user',
|
|
161
|
+
}),
|
|
154
162
|
).rejects.toBeInstanceOf(ConflictException);
|
|
155
163
|
expect(tx.workflowInstance.update).not.toHaveBeenCalled();
|
|
156
164
|
expect(tx.approvalRequest.updateMany).not.toHaveBeenCalled();
|
|
@@ -164,7 +172,11 @@ describe('Workflow cancellation', () => {
|
|
|
164
172
|
});
|
|
165
173
|
|
|
166
174
|
await expect(
|
|
167
|
-
runtime.cancelExecution({
|
|
175
|
+
runtime.cancelExecution({
|
|
176
|
+
tx: tx as any,
|
|
177
|
+
instanceId: BigInt(11),
|
|
178
|
+
requestedBy: 'admin-user',
|
|
179
|
+
}),
|
|
168
180
|
).rejects.toMatchObject({
|
|
169
181
|
response: {
|
|
170
182
|
code: 'WORKFLOW_CANCELLATION_BLOCKED',
|
|
@@ -180,7 +192,11 @@ describe('Workflow cancellation', () => {
|
|
|
180
192
|
});
|
|
181
193
|
|
|
182
194
|
await expect(
|
|
183
|
-
runtime.cancelExecution({
|
|
195
|
+
runtime.cancelExecution({
|
|
196
|
+
tx: tx as any,
|
|
197
|
+
instanceId: BigInt(11),
|
|
198
|
+
requestedBy: 'second-user',
|
|
199
|
+
}),
|
|
184
200
|
).resolves.toEqual({ instanceId: '11', status: 'CANCELLED' });
|
|
185
201
|
expect(tx.workflowInstance.update).not.toHaveBeenCalled();
|
|
186
202
|
expect(temporal.cancelWorkflow).not.toHaveBeenCalled();
|
|
@@ -193,6 +209,7 @@ describe('Workflow cancellation', () => {
|
|
|
193
209
|
runtime.cancelExecution({
|
|
194
210
|
tx: transactionClient,
|
|
195
211
|
instanceId: BigInt(11),
|
|
212
|
+
requestedBy: `${reason} user`,
|
|
196
213
|
reason,
|
|
197
214
|
}),
|
|
198
215
|
);
|
|
@@ -279,7 +279,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
279
279
|
type: 'approval',
|
|
280
280
|
label: '部门负责人审批',
|
|
281
281
|
config: {
|
|
282
|
-
subjects: [{ source: 'STATIC',
|
|
282
|
+
subjects: [{ source: 'STATIC', refs: [' '] }],
|
|
283
283
|
},
|
|
284
284
|
},
|
|
285
285
|
],
|
|
@@ -426,7 +426,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
426
426
|
code: 'FINANCE_APPROVAL',
|
|
427
427
|
nodeTypeId: BigInt(7),
|
|
428
428
|
isEnabled: true,
|
|
429
|
-
config: { subjects: [{
|
|
429
|
+
config: { subjects: [{ source: 'STATIC', refs: ['alice'] }] },
|
|
430
430
|
};
|
|
431
431
|
const prisma = {
|
|
432
432
|
workflowNodeType: {
|
|
@@ -483,7 +483,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
483
483
|
});
|
|
484
484
|
instance.config.subjects[0].refs[0] = 'bob';
|
|
485
485
|
expect(compiled.nodes[0].config).toEqual({
|
|
486
|
-
subjects: [{
|
|
486
|
+
subjects: [{ source: 'STATIC', refs: ['alice'] }],
|
|
487
487
|
});
|
|
488
488
|
});
|
|
489
489
|
|
|
@@ -529,9 +529,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
529
529
|
config: instance.config,
|
|
530
530
|
configOverride: {
|
|
531
531
|
timeoutSeconds: 3600,
|
|
532
|
-
subjects: [
|
|
533
|
-
{ source: 'STATIC', provider: 'USER', refs: ['alice'] },
|
|
534
|
-
],
|
|
532
|
+
subjects: [{ source: 'STATIC', refs: ['alice'] }],
|
|
535
533
|
},
|
|
536
534
|
instanceRef: {
|
|
537
535
|
instanceId: '12',
|
|
@@ -546,7 +544,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
546
544
|
|
|
547
545
|
const expectedOverride = {
|
|
548
546
|
timeoutSeconds: 3600,
|
|
549
|
-
subjects: [{ source: 'STATIC',
|
|
547
|
+
subjects: [{ source: 'STATIC', refs: ['alice'] }],
|
|
550
548
|
};
|
|
551
549
|
expect(compiled.nodes[0].config).toEqual(expectedOverride);
|
|
552
550
|
expect(compiled.nodes[0].source?.configHash).toBe(
|
|
@@ -571,7 +569,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
571
569
|
},
|
|
572
570
|
details: [],
|
|
573
571
|
},
|
|
574
|
-
subjects: [{ source: 'STATIC',
|
|
572
|
+
subjects: [{ source: 'STATIC', refs: ['alice'] }],
|
|
575
573
|
approvalRule: { mode: 'ANY' },
|
|
576
574
|
rejectionRule: { mode: 'ANY' },
|
|
577
575
|
timeout: { durationSeconds: 0, action: 'REJECTED' },
|
|
@@ -655,7 +653,6 @@ describe('WorkflowCompilerService', () => {
|
|
|
655
653
|
subjects: [
|
|
656
654
|
{
|
|
657
655
|
source: 'DYNAMIC',
|
|
658
|
-
provider: 'USER',
|
|
659
656
|
inputKey: 'direct_manager',
|
|
660
657
|
label: '查询直属领导',
|
|
661
658
|
lookup: { request: { connectionRef: 'erp' } },
|