@rytass/bpm-core-nestjs-module 0.20.1 → 0.21.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/CHANGELOG.md +81 -0
- package/README.md +109 -5
- package/package.json +2 -2
- package/src/lib/attachment/attachment.service.js +2 -10
- package/src/lib/attachment/attachment.service.js.map +1 -1
- package/src/lib/attachment/file-upload-value-ids.d.ts +8 -0
- package/src/lib/attachment/file-upload-value-ids.js +15 -0
- package/src/lib/attachment/file-upload-value-ids.js.map +1 -0
- package/src/lib/bpm/bpm-root-options.d.ts +18 -0
- package/src/lib/bpm/bpm-root-options.js.map +1 -1
- package/src/lib/bpm/bpm-root.module.d.ts +11 -0
- package/src/lib/bpm/bpm-root.module.js +9 -4
- package/src/lib/bpm/bpm-root.module.js.map +1 -1
- package/src/lib/form/form-data-issue.d.ts +34 -0
- package/src/lib/form/form-data-issue.js +59 -0
- package/src/lib/form/form-data-issue.js.map +1 -0
- package/src/lib/form/form-member-value.validator.d.ts +2 -2
- package/src/lib/form/form-member-value.validator.js +2 -3
- package/src/lib/form/form-member-value.validator.js.map +1 -1
- package/src/lib/form/form-org-unit-value.validator.d.ts +2 -2
- package/src/lib/form/form-org-unit-value.validator.js +4 -1
- package/src/lib/form/form-org-unit-value.validator.js.map +1 -1
- package/src/lib/form/form-schema.validator.js +40 -0
- package/src/lib/form/form-schema.validator.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.module.d.ts +8 -0
- package/src/lib/form-data-source/form-data-source.module.js +1 -1
- package/src/lib/form-data-source/form-data-source.module.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.service.js +1 -1
- package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
- package/src/lib/migrations/0000000031000-approval-instance-form-data-gin.d.ts +29 -0
- package/src/lib/migrations/0000000031000-approval-instance-form-data-gin.js +44 -0
- package/src/lib/migrations/0000000031000-approval-instance-form-data-gin.js.map +1 -0
- package/src/lib/migrations/index.d.ts +1 -0
- package/src/lib/migrations/index.js +3 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/notification.service.js +6 -0
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/organization/organization-time-zone.d.ts +4 -2
- package/src/lib/organization/organization-time-zone.js +3 -6
- package/src/lib/organization/organization-time-zone.js.map +1 -1
- package/src/lib/template/filterable-field-guard.d.ts +49 -0
- package/src/lib/template/filterable-field-guard.js +141 -0
- package/src/lib/template/filterable-field-guard.js.map +1 -0
- package/src/lib/template/filterable-field-issue.object.d.ts +21 -0
- package/src/lib/template/filterable-field-issue.object.js +65 -0
- package/src/lib/template/filterable-field-issue.object.js.map +1 -0
- package/src/lib/template/filterable-field.queries.d.ts +20 -0
- package/src/lib/template/filterable-field.queries.js +80 -0
- package/src/lib/template/filterable-field.queries.js.map +1 -0
- package/src/lib/template/index.d.ts +1 -0
- package/src/lib/template/index.js +1 -0
- package/src/lib/template/index.js.map +1 -1
- package/src/lib/template/template.module.js +2 -0
- package/src/lib/template/template.module.js.map +1 -1
- package/src/lib/template/template.service.d.ts +39 -0
- package/src/lib/template/template.service.js +218 -49
- package/src/lib/template/template.service.js.map +1 -1
- package/src/lib/template/workflow-definition.validator.js +84 -2
- package/src/lib/template/workflow-definition.validator.js.map +1 -1
- package/src/lib/workflow-engine/approval-instance-field-filter.compiler.d.ts +49 -0
- package/src/lib/workflow-engine/approval-instance-field-filter.compiler.js +501 -0
- package/src/lib/workflow-engine/approval-instance-field-filter.compiler.js.map +1 -0
- package/src/lib/workflow-engine/approval-instance-filterable-field.policy.d.ts +15 -0
- package/src/lib/workflow-engine/approval-instance-filterable-field.policy.js +20 -0
- package/src/lib/workflow-engine/approval-instance-filterable-field.policy.js.map +1 -0
- package/src/lib/workflow-engine/dto/approval-instance-field-filter.input.d.ts +15 -0
- package/src/lib/workflow-engine/dto/approval-instance-field-filter.input.js +55 -0
- package/src/lib/workflow-engine/dto/approval-instance-field-filter.input.js.map +1 -0
- package/src/lib/workflow-engine/dto/approval-instance-list-args.d.ts +9 -0
- package/src/lib/workflow-engine/dto/approval-instance-list-args.js +24 -0
- package/src/lib/workflow-engine/dto/approval-instance-list-args.js.map +1 -0
- package/src/lib/workflow-engine/dto/approval-instance-sort.input.d.ts +10 -0
- package/src/lib/workflow-engine/dto/approval-instance-sort.input.js +32 -0
- package/src/lib/workflow-engine/dto/approval-instance-sort.input.js.map +1 -0
- package/src/lib/workflow-engine/index.d.ts +1 -0
- package/src/lib/workflow-engine/index.js +1 -0
- package/src/lib/workflow-engine/index.js.map +1 -1
- package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.d.ts +108 -0
- package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.js +9 -0
- package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.enums.d.ts +13 -0
- package/src/lib/workflow-engine/workflow-engine.enums.js +22 -1
- package/src/lib/workflow-engine/workflow-engine.enums.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.module.d.ts +2 -0
- package/src/lib/workflow-engine/workflow-engine.module.js +9 -3
- package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.queries.d.ts +6 -4
- package/src/lib/workflow-engine/workflow-engine.queries.js +35 -12
- package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +166 -18
- package/src/lib/workflow-engine/workflow-engine.service.js +918 -139
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterable-field.queries.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/template/filterable-field.queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ export * from './dto/approval-template.input';
|
|
|
6
6
|
export * from './dto/compose-approval-template.input';
|
|
7
7
|
export * from './dto/expression-designer.dto';
|
|
8
8
|
export * from './expression-designer.service';
|
|
9
|
+
export * from './filterable-field-issue.object';
|
|
9
10
|
export * from './template-observer.token';
|
|
10
11
|
export * from './template.enums';
|
|
11
12
|
export * from './template.module';
|
|
@@ -9,6 +9,7 @@ tslib_1.__exportStar(require("./dto/approval-template.input"), exports);
|
|
|
9
9
|
tslib_1.__exportStar(require("./dto/compose-approval-template.input"), exports);
|
|
10
10
|
tslib_1.__exportStar(require("./dto/expression-designer.dto"), exports);
|
|
11
11
|
tslib_1.__exportStar(require("./expression-designer.service"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./filterable-field-issue.object"), exports);
|
|
12
13
|
tslib_1.__exportStar(require("./template-observer.token"), exports);
|
|
13
14
|
tslib_1.__exportStar(require("./template.enums"), exports);
|
|
14
15
|
tslib_1.__exportStar(require("./template.module"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/template/index.ts"],"names":[],"mappings":";;;AAAA,8EAAoD;AACpD,6EAAmD;AACnD,qEAA2C;AAC3C,6EAAmD;AACnD,wEAA8C;AAC9C,gFAAsD;AACtD,wEAA8C;AAC9C,wEAA8C;AAC9C,oEAA0C;AAC1C,2DAAiC;AACjC,4DAAkC;AAClC,6DAAmC;AACnC,0EAAgD"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/template/index.ts"],"names":[],"mappings":";;;AAAA,8EAAoD;AACpD,6EAAmD;AACnD,qEAA2C;AAC3C,6EAAmD;AACnD,wEAA8C;AAC9C,gFAAsD;AACtD,wEAA8C;AAC9C,wEAA8C;AAC9C,0EAAgD;AAChD,oEAA0C;AAC1C,2DAAiC;AACjC,4DAAkC;AAClC,6DAAmC;AACnC,0EAAgD"}
|
|
@@ -13,6 +13,7 @@ const approval_template_version_entity_1 = require("./approval-template-version.
|
|
|
13
13
|
const compose_template_mutations_1 = require("./compose-template.mutations");
|
|
14
14
|
const expression_designer_queries_1 = require("./expression-designer.queries");
|
|
15
15
|
const expression_designer_service_1 = require("./expression-designer.service");
|
|
16
|
+
const filterable_field_queries_1 = require("./filterable-field.queries");
|
|
16
17
|
const template_mutations_1 = require("./template.mutations");
|
|
17
18
|
const template_queries_1 = require("./template.queries");
|
|
18
19
|
const template_service_1 = require("./template.service");
|
|
@@ -35,6 +36,7 @@ exports.TemplateModule = TemplateModule = tslib_1.__decorate([
|
|
|
35
36
|
compose_template_mutations_1.ComposeTemplateMutations,
|
|
36
37
|
expression_designer_queries_1.ExpressionDesignerQueries,
|
|
37
38
|
expression_designer_service_1.ExpressionDesignerService,
|
|
39
|
+
filterable_field_queries_1.FilterableFieldQueries,
|
|
38
40
|
template_mutations_1.TemplateMutations,
|
|
39
41
|
template_queries_1.TemplateQueries,
|
|
40
42
|
template_service_1.TemplateService,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.module.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/template/template.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,oEAAgE;AAChE,2FAAqF;AACrF,qDAAiD;AACjD,2FAAqF;AACrF,yEAAoE;AACpE,yFAAmF;AACnF,6EAAwE;AACxE,+EAA0E;AAC1E,+EAA0E;AAC1E,6DAAyD;AACzD,yDAAqD;AACrD,yDAAqD;
|
|
1
|
+
{"version":3,"file":"template.module.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/template/template.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,oEAAgE;AAChE,2FAAqF;AACrF,qDAAiD;AACjD,2FAAqF;AACrF,yEAAoE;AACpE,yFAAmF;AACnF,6EAAwE;AACxE,+EAA0E;AAC1E,+EAA0E;AAC1E,yEAAoE;AACpE,6DAAyD;AACzD,yDAAqD;AACrD,yDAAqD;AAwB9C,IAAM,cAAc,GAApB,MAAM,cAAc;CAAG,CAAA;AAAjB,wCAAc;yBAAd,cAAc;IAtB1B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,kCAAe;YACf,wBAAU;YACV,uBAAa,CAAC,UAAU,CAAC;gBACvB,kEAA8B;gBAC9B,iDAAsB;gBACtB,gEAA6B;gBAC7B,4DAA2B;aAC5B,CAAC;SACH;QACD,SAAS,EAAE;YACT,qDAAwB;YACxB,uDAAyB;YACzB,uDAAyB;YACzB,iDAAsB;YACtB,sCAAiB;YACjB,kCAAe;YACf,kCAAe;SAChB;QACD,OAAO,EAAE,CAAC,kCAAe,CAAC;KAC3B,CAAC;GACW,cAAc,CAAG"}
|
|
@@ -73,7 +73,19 @@ export declare class TemplateService {
|
|
|
73
73
|
listApprovalTemplateVersions(templateId: string): Promise<readonly ApprovalTemplateVersionEntity[]>;
|
|
74
74
|
getApprovalTemplateVersion(id: string): Promise<ApprovalTemplateVersionEntity>;
|
|
75
75
|
updateApprovalTemplateDraft(input: UpdateApprovalTemplateDraftInput, manager?: EntityManager): Promise<ApprovalTemplateVersionEntity>;
|
|
76
|
+
/**
|
|
77
|
+
* Runs `run` in the caller's transaction when one is passed, otherwise in a
|
|
78
|
+
* new one, so a template lock taken inside it is held until the write
|
|
79
|
+
* commits.
|
|
80
|
+
*/
|
|
81
|
+
private runInTemplateTransaction;
|
|
76
82
|
forkApprovalTemplate(templateId: string, manager?: EntityManager): Promise<ApprovalTemplateVersionEntity>;
|
|
83
|
+
/**
|
|
84
|
+
* Forks under the template row lock (ADR 25 §15): the draft check, the
|
|
85
|
+
* source read and the insert see the template as no concurrent publish or
|
|
86
|
+
* fork can change it until this commits.
|
|
87
|
+
*/
|
|
88
|
+
private forkApprovalTemplateLocked;
|
|
77
89
|
publishApprovalTemplateVersion(versionId: string, publishedByMemberId?: string, manager?: EntityManager): Promise<ApprovalTemplateVersionEntity>;
|
|
78
90
|
/**
|
|
79
91
|
* The publish itself, without the observer notification. Split out so
|
|
@@ -81,6 +93,11 @@ export declare class TemplateService {
|
|
|
81
93
|
* and still report a single `COMPOSED` event rather than two.
|
|
82
94
|
*/
|
|
83
95
|
private publishVersion;
|
|
96
|
+
/**
|
|
97
|
+
* Reads one template row, optionally locked. Locks are only taken inside a
|
|
98
|
+
* transaction; the caller passes the transaction's manager.
|
|
99
|
+
*/
|
|
100
|
+
private lockTemplateRow;
|
|
84
101
|
private publishApprovalTemplateVersionWithManager;
|
|
85
102
|
rollbackApprovalTemplateVersion(versionId: string, rolledBackByMemberId?: string): Promise<ApprovalTemplateVersionEntity>;
|
|
86
103
|
/**
|
|
@@ -90,6 +107,10 @@ export declare class TemplateService {
|
|
|
90
107
|
*/
|
|
91
108
|
private notifyTemplateObserver;
|
|
92
109
|
private readHostObserver;
|
|
110
|
+
/**
|
|
111
|
+
* Returns the bound form schema so the publish can run the filterable-field
|
|
112
|
+
* check against it without loading the form version a second time.
|
|
113
|
+
*/
|
|
93
114
|
private validatePublishableVersion;
|
|
94
115
|
/** The cel-legacy publish lint, unchanged (docs/24 §1). */
|
|
95
116
|
private lintCelLegacyExpressions;
|
|
@@ -121,9 +142,27 @@ export declare class TemplateService {
|
|
|
121
142
|
private templateVersions;
|
|
122
143
|
private templateCategories;
|
|
123
144
|
private formVersions;
|
|
145
|
+
/**
|
|
146
|
+
* The form schema of the template's current version: the one whose
|
|
147
|
+
* filterable fields the stored instances already satisfy. Null when the
|
|
148
|
+
* template has no published version yet. Read under the caller's template
|
|
149
|
+
* row lock, so no write can change what it guarantees before the publish
|
|
150
|
+
* commits.
|
|
151
|
+
*/
|
|
152
|
+
private readInForceFormSchema;
|
|
153
|
+
private readFormSchemaOfVersion;
|
|
124
154
|
private getTemplateOrThrow;
|
|
125
155
|
private getTemplateCategoryOrThrow;
|
|
126
156
|
private setApprovalTemplateActive;
|
|
157
|
+
/**
|
|
158
|
+
* Locks the template row FOR UPDATE, the lock a publish, compose or rollback
|
|
159
|
+
* takes (ADR 25 §15), then reads the row with its category. A template edit
|
|
160
|
+
* saves the whole entity it read, so reading it unlocked would let a publish
|
|
161
|
+
* that commits in between be overwritten with the pre-publish
|
|
162
|
+
* `currentVersionId`; under the lock the read is the latest committed row
|
|
163
|
+
* and a publish waits for this edit instead.
|
|
164
|
+
*/
|
|
165
|
+
private readLockedTemplateOrThrow;
|
|
127
166
|
private setApprovalTemplateCategoryActive;
|
|
128
167
|
private getTemplateVersionOrThrow;
|
|
129
168
|
private readNextVersionNumber;
|
|
@@ -22,6 +22,7 @@ const form_enums_1 = require("../form/form.enums");
|
|
|
22
22
|
const form_service_1 = require("../form/form.service");
|
|
23
23
|
const workflow_webhook_validator_1 = require("../workflow-webhook/workflow-webhook.validator");
|
|
24
24
|
const workflow_webhook_service_1 = require("../workflow-webhook/workflow-webhook.service");
|
|
25
|
+
const filterable_field_guard_1 = require("./filterable-field-guard");
|
|
25
26
|
const approval_template_category_entity_1 = require("./approval-template-category.entity");
|
|
26
27
|
const approval_template_entity_1 = require("./approval-template.entity");
|
|
27
28
|
const approval_template_version_entity_1 = require("./approval-template-version.entity");
|
|
@@ -108,6 +109,14 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
108
109
|
*/
|
|
109
110
|
async composeApprovalTemplateWithForm(input, currentMemberId) {
|
|
110
111
|
const { event, result } = await this.templateRepository.manager.transaction(async (manager) => {
|
|
112
|
+
// ADR 25 §15: an existing template's row is locked before anything
|
|
113
|
+
// else, so this compose takes the template lock before it forks or
|
|
114
|
+
// writes the draft row, the same order a standalone publish uses.
|
|
115
|
+
// Taking it later (after the draft write, inside publishVersion) let a
|
|
116
|
+
// concurrent publish of the same draft deadlock against this one.
|
|
117
|
+
if (input.templateId) {
|
|
118
|
+
await this.readLockedTemplateOrThrow(manager, input.templateId);
|
|
119
|
+
}
|
|
111
120
|
const formDefinitionVersion = await this.resolveComposedFormVersion(manager, input, currentMemberId);
|
|
112
121
|
const templateDraftVersion = await this.resolveComposedTemplateDraftVersion(manager, input, currentMemberId);
|
|
113
122
|
let templateVersion = await this.updateApprovalTemplateDraft({
|
|
@@ -218,27 +227,30 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
218
227
|
});
|
|
219
228
|
}
|
|
220
229
|
async updateApprovalTemplate(input) {
|
|
221
|
-
const existing = await this.getTemplateOrThrow(input.id);
|
|
222
230
|
const category = input.categoryId === undefined
|
|
223
231
|
? undefined
|
|
224
232
|
: await this.validateOptionalTemplateCategory(input.categoryId);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
await this.templateRepository.manager.transaction(async (manager) => {
|
|
234
|
+
const existing = await this.readLockedTemplateOrThrow(manager, input.id);
|
|
235
|
+
const templates = manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity);
|
|
236
|
+
const next = templates.merge(existing, {
|
|
237
|
+
category: input.category !== undefined
|
|
238
|
+
? input.category
|
|
239
|
+
: category !== undefined
|
|
240
|
+
? (category?.name ?? null)
|
|
241
|
+
: existing.category,
|
|
242
|
+
description: input.description ?? existing.description,
|
|
243
|
+
name: input.name ?? existing.name,
|
|
244
|
+
});
|
|
245
|
+
// Assigned outside `merge` because the relation, not the `categoryId`
|
|
246
|
+
// scalar, owns `category_id`. Leaving `categoryDetail` untouched when the
|
|
247
|
+
// caller omitted `categoryId` keeps the loaded relation, which persists
|
|
248
|
+
// the current category unchanged.
|
|
249
|
+
if (category !== undefined) {
|
|
250
|
+
next.categoryDetail = category;
|
|
251
|
+
}
|
|
252
|
+
await templates.save(next);
|
|
233
253
|
});
|
|
234
|
-
// Assigned outside `merge` because the relation, not the `categoryId`
|
|
235
|
-
// scalar, owns `category_id`. Leaving `categoryDetail` untouched when the
|
|
236
|
-
// caller omitted `categoryId` keeps the loaded relation, which persists
|
|
237
|
-
// the current category unchanged.
|
|
238
|
-
if (category !== undefined) {
|
|
239
|
-
next.categoryDetail = category;
|
|
240
|
-
}
|
|
241
|
-
await this.templateRepository.save(next);
|
|
242
254
|
// Re-read rather than returning the saved entity: `categoryId` is not
|
|
243
255
|
// written by `save()` any more, so the in-memory copy would still carry
|
|
244
256
|
// the previous category alongside the new `categoryDetail` — the same
|
|
@@ -364,25 +376,51 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
364
376
|
return this.getTemplateVersionOrThrow(id);
|
|
365
377
|
}
|
|
366
378
|
async updateApprovalTemplateDraft(input, manager) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
379
|
+
return this.runInTemplateTransaction(manager, async (txManager) => {
|
|
380
|
+
const versionRepository = this.templateVersions(txManager);
|
|
381
|
+
// Read once to learn the template, lock its row (ADR 25 §15), then read
|
|
382
|
+
// the draft again: a publish that committed before the lock has turned
|
|
383
|
+
// it PUBLISHED, and saving the copy read before would write the old
|
|
384
|
+
// DRAFT status and empty publish fields back over it.
|
|
385
|
+
const unlocked = await this.getTemplateVersionOrThrow(input.versionId, txManager);
|
|
386
|
+
await this.readLockedTemplateOrThrow(txManager, unlocked.templateId);
|
|
387
|
+
const existing = await this.getTemplateVersionOrThrow(input.versionId, txManager);
|
|
388
|
+
if (existing.status !== template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT) {
|
|
389
|
+
throw new common_1.ConflictException('Only draft approval template versions can be updated');
|
|
390
|
+
}
|
|
391
|
+
await this.validateOptionalFormDefinitionVersion(input.formDefinitionVersionId, txManager);
|
|
392
|
+
const workflowDefinition = this.parseWorkflowDefinitionOrThrow(input.workflowDefinitionJson);
|
|
393
|
+
const next = versionRepository.merge(existing, {
|
|
394
|
+
formDefinitionVersionId: input.formDefinitionVersionId,
|
|
395
|
+
initiatorPolicyCel: input.initiatorPolicyCel,
|
|
396
|
+
notificationConfig: parseOptionalJsonObject(input.notificationConfigJson),
|
|
397
|
+
slaDefaults: parseOptionalJsonObject(input.slaDefaultsJson),
|
|
398
|
+
workflowDefinition,
|
|
399
|
+
});
|
|
400
|
+
return versionRepository.save(next);
|
|
380
401
|
});
|
|
381
|
-
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Runs `run` in the caller's transaction when one is passed, otherwise in a
|
|
405
|
+
* new one, so a template lock taken inside it is held until the write
|
|
406
|
+
* commits.
|
|
407
|
+
*/
|
|
408
|
+
runInTemplateTransaction(manager, run) {
|
|
409
|
+
return manager
|
|
410
|
+
? run(manager)
|
|
411
|
+
: this.templateRepository.manager.transaction(run);
|
|
382
412
|
}
|
|
383
413
|
async forkApprovalTemplate(templateId, manager) {
|
|
414
|
+
return this.runInTemplateTransaction(manager, (txManager) => this.forkApprovalTemplateLocked(templateId, txManager));
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Forks under the template row lock (ADR 25 §15): the draft check, the
|
|
418
|
+
* source read and the insert see the template as no concurrent publish or
|
|
419
|
+
* fork can change it until this commits.
|
|
420
|
+
*/
|
|
421
|
+
async forkApprovalTemplateLocked(templateId, manager) {
|
|
384
422
|
const versionRepository = this.templateVersions(manager);
|
|
385
|
-
const template = await this.
|
|
423
|
+
const template = await this.readLockedTemplateOrThrow(manager, templateId);
|
|
386
424
|
const existingDraft = await versionRepository.findOne({
|
|
387
425
|
where: {
|
|
388
426
|
status: template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT,
|
|
@@ -433,18 +471,45 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
433
471
|
if (version.status !== template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT) {
|
|
434
472
|
throw new common_1.ConflictException('Only draft approval template versions can be published');
|
|
435
473
|
}
|
|
474
|
+
// Early refusals only: they run outside the lock and are repeated below.
|
|
436
475
|
await this.validatePublishableVersion(version, manager);
|
|
437
|
-
|
|
476
|
+
// ADR 25 §5.4 and §15: the template row is locked before the enable check
|
|
477
|
+
// and before the draft is read again, so a concurrent form-data write
|
|
478
|
+
// (which takes FOR SHARE on the same row) cannot slip between the check
|
|
479
|
+
// and the commit, and the draft is judged as it is under the lock.
|
|
480
|
+
const run = async (txManager) => {
|
|
481
|
+
const lockedTemplate = await this.lockTemplateRow(txManager, version.templateId, 'pessimistic_write');
|
|
482
|
+
if (!lockedTemplate) {
|
|
483
|
+
throw new common_1.NotFoundException(`Approval template ${version.templateId} was not found`);
|
|
484
|
+
}
|
|
485
|
+
const lockedVersion = await this.getTemplateVersionOrThrow(versionId, txManager);
|
|
486
|
+
if (lockedVersion.status !== template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT) {
|
|
487
|
+
throw new common_1.ConflictException('Only draft approval template versions can be published');
|
|
488
|
+
}
|
|
489
|
+
const formSchema = await this.validatePublishableVersion(lockedVersion, txManager);
|
|
490
|
+
return this.publishApprovalTemplateVersionWithManager(txManager, lockedVersion, publishedByMemberId, formSchema);
|
|
491
|
+
};
|
|
438
492
|
return manager
|
|
439
493
|
? run(manager)
|
|
440
494
|
: this.templateRepository.manager.transaction(run);
|
|
441
495
|
}
|
|
442
|
-
|
|
496
|
+
/**
|
|
497
|
+
* Reads one template row, optionally locked. Locks are only taken inside a
|
|
498
|
+
* transaction; the caller passes the transaction's manager.
|
|
499
|
+
*/
|
|
500
|
+
async lockTemplateRow(manager, templateId, mode) {
|
|
501
|
+
return manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity).findOne({
|
|
502
|
+
lock: { mode },
|
|
503
|
+
where: { deletedAt: (0, typeorm_2.IsNull)(), id: templateId },
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async publishApprovalTemplateVersionWithManager(manager, version, publishedByMemberId, formSchema) {
|
|
443
507
|
const templateRepository = manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity);
|
|
444
508
|
const versionRepository = manager.getRepository(approval_template_version_entity_1.ApprovalTemplateVersionEntity);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
509
|
+
// Already locked by the caller (`publishVersion`); the lock is taken again
|
|
510
|
+
// here so this method is safe on its own, and the lock is re-entrant
|
|
511
|
+
// within one transaction.
|
|
512
|
+
const template = await this.lockTemplateRow(manager, version.templateId, 'pessimistic_write');
|
|
448
513
|
if (!template) {
|
|
449
514
|
throw new common_1.NotFoundException(`Approval template ${version.templateId} was not found`);
|
|
450
515
|
}
|
|
@@ -454,6 +519,11 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
454
519
|
if (!template.isActive) {
|
|
455
520
|
throw new common_1.ConflictException('Approval template is deactivated');
|
|
456
521
|
}
|
|
522
|
+
// ADR 25 §5.4: a field that becomes filterable must not hold values the
|
|
523
|
+
// template's existing instances cannot satisfy. Checked here, inside the
|
|
524
|
+
// publish transaction, so the instances it reads are the ones it archives.
|
|
525
|
+
// Only what this publish makes filterable is scanned (ADR 25 §15).
|
|
526
|
+
await (0, filterable_field_guard_1.assertTemplateFilterableFieldsEnabled)(manager, template.id, formSchema, () => this.readInForceFormSchema(manager, template));
|
|
457
527
|
const previousVersionId = template.currentVersionId;
|
|
458
528
|
if (template.currentVersionId) {
|
|
459
529
|
await versionRepository.update({ id: template.currentVersionId }, {
|
|
@@ -486,6 +556,8 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
486
556
|
// and is not linted again, since a rule tightened since could refuse it
|
|
487
557
|
// (ADR 22 §5, docs/24 §1). The one check is that every function it calls
|
|
488
558
|
// is still registered; without it every case would fail on evaluation.
|
|
559
|
+
// The filterable-field check (ADR 25 §5.4) is the exception: it reads the
|
|
560
|
+
// instances stored now, so it runs again inside the transaction below.
|
|
489
561
|
const unregisteredFunctionErrors = this.lintRegisteredFunctions(target.workflowDefinition, target.initiatorPolicyCel);
|
|
490
562
|
if (unregisteredFunctionErrors.length) {
|
|
491
563
|
throw new common_1.BadRequestException(unregisteredFunctionErrors.join('; '));
|
|
@@ -493,9 +565,9 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
493
565
|
const rolledBack = await this.templateRepository.manager.transaction(async (manager) => {
|
|
494
566
|
const templateRepository = manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity);
|
|
495
567
|
const versionRepository = manager.getRepository(approval_template_version_entity_1.ApprovalTemplateVersionEntity);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
568
|
+
// ADR 25 §5.4 and §15: lock the template row before the enable check,
|
|
569
|
+
// the same lock a publish takes (see `publishVersion`).
|
|
570
|
+
const template = await this.lockTemplateRow(manager, target.templateId, 'pessimistic_write');
|
|
499
571
|
if (!template) {
|
|
500
572
|
throw new common_1.NotFoundException(`Approval template ${target.templateId} was not found`);
|
|
501
573
|
}
|
|
@@ -504,6 +576,9 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
504
576
|
if (!template.isActive) {
|
|
505
577
|
throw new common_1.ConflictException('Approval template is deactivated');
|
|
506
578
|
}
|
|
579
|
+
// The target becomes the current version again, so its filterable
|
|
580
|
+
// fields are checked against the instances stored now (ADR 25 §5.4).
|
|
581
|
+
await (0, filterable_field_guard_1.assertTemplateFilterableFieldsEnabled)(manager, template.id, await this.readFormSchemaOfVersion(manager, target.formDefinitionVersionId), () => this.readInForceFormSchema(manager, template));
|
|
507
582
|
const previousVersionId = template.currentVersionId;
|
|
508
583
|
if (template.currentVersionId &&
|
|
509
584
|
template.currentVersionId !== target.id) {
|
|
@@ -561,6 +636,10 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
561
636
|
return null;
|
|
562
637
|
}
|
|
563
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Returns the bound form schema so the publish can run the filterable-field
|
|
641
|
+
* check against it without loading the form version a second time.
|
|
642
|
+
*/
|
|
564
643
|
async validatePublishableVersion(version, manager) {
|
|
565
644
|
if (!version.formDefinitionVersionId) {
|
|
566
645
|
throw new common_1.BadRequestException('Approval template version must bind a form definition version before publishing');
|
|
@@ -599,6 +678,9 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
599
678
|
const nodeActorSourceErrors = workflowResult.valid
|
|
600
679
|
? lintWorkflowNodeActorSources(version.workflowDefinition)
|
|
601
680
|
: [];
|
|
681
|
+
const excludeNodeActorsErrors = workflowResult.valid
|
|
682
|
+
? lintWorkflowExcludeNodeActors(version.workflowDefinition)
|
|
683
|
+
: [];
|
|
602
684
|
const approverFilledFieldErrors = (0, bpm_core_shared_1.readFormFieldAccessIssues)(version.workflowDefinition, formVersion.schema).map((issue) => issue.message);
|
|
603
685
|
const approverFilledReadErrors = lintApproverFilledFieldReads(version.workflowDefinition, fieldReader);
|
|
604
686
|
const webhookErrors = await this.lintWorkflowWebhooks(version.workflowDefinition, formVersion.schema);
|
|
@@ -610,6 +692,7 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
610
692
|
...memberReferenceErrors,
|
|
611
693
|
...orgUnitReferenceErrors,
|
|
612
694
|
...nodeActorSourceErrors,
|
|
695
|
+
...excludeNodeActorsErrors,
|
|
613
696
|
...approverFilledFieldErrors,
|
|
614
697
|
...approverFilledReadErrors,
|
|
615
698
|
...webhookErrors,
|
|
@@ -617,6 +700,7 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
617
700
|
if (errors.length) {
|
|
618
701
|
throw new common_1.BadRequestException(errors.join('; '));
|
|
619
702
|
}
|
|
703
|
+
return formVersion.schema;
|
|
620
704
|
}
|
|
621
705
|
/** The cel-legacy publish lint, unchanged (docs/24 §1). */
|
|
622
706
|
lintCelLegacyExpressions(version) {
|
|
@@ -748,6 +832,31 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
748
832
|
? manager.getRepository(form_definition_version_entity_1.FormDefinitionVersionEntity)
|
|
749
833
|
: this.formDefinitionVersionRepository;
|
|
750
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The form schema of the template's current version: the one whose
|
|
837
|
+
* filterable fields the stored instances already satisfy. Null when the
|
|
838
|
+
* template has no published version yet. Read under the caller's template
|
|
839
|
+
* row lock, so no write can change what it guarantees before the publish
|
|
840
|
+
* commits.
|
|
841
|
+
*/
|
|
842
|
+
async readInForceFormSchema(manager, template) {
|
|
843
|
+
if (!template.currentVersionId) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
const current = await manager
|
|
847
|
+
.getRepository(approval_template_version_entity_1.ApprovalTemplateVersionEntity)
|
|
848
|
+
.findOne({ where: { id: template.currentVersionId } });
|
|
849
|
+
return this.readFormSchemaOfVersion(manager, current?.formDefinitionVersionId ?? null);
|
|
850
|
+
}
|
|
851
|
+
async readFormSchemaOfVersion(manager, formDefinitionVersionId) {
|
|
852
|
+
if (!formDefinitionVersionId) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
const formVersion = await manager
|
|
856
|
+
.getRepository(form_definition_version_entity_1.FormDefinitionVersionEntity)
|
|
857
|
+
.findOne({ where: { id: formDefinitionVersionId } });
|
|
858
|
+
return formVersion?.schema ?? null;
|
|
859
|
+
}
|
|
751
860
|
async getTemplateOrThrow(id, manager) {
|
|
752
861
|
const entity = await this.templates(manager).findOne({
|
|
753
862
|
relations: { categoryDetail: true },
|
|
@@ -768,10 +877,28 @@ let TemplateService = TemplateService_1 = class TemplateService {
|
|
|
768
877
|
return entity;
|
|
769
878
|
}
|
|
770
879
|
async setApprovalTemplateActive(id, isActive) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
880
|
+
return this.templateRepository.manager.transaction(async (manager) => {
|
|
881
|
+
const template = await this.readLockedTemplateOrThrow(manager, id);
|
|
882
|
+
const templates = manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity);
|
|
883
|
+
return templates.save(templates.merge(template, { isActive }));
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Locks the template row FOR UPDATE, the lock a publish, compose or rollback
|
|
888
|
+
* takes (ADR 25 §15), then reads the row with its category. A template edit
|
|
889
|
+
* saves the whole entity it read, so reading it unlocked would let a publish
|
|
890
|
+
* that commits in between be overwritten with the pre-publish
|
|
891
|
+
* `currentVersionId`; under the lock the read is the latest committed row
|
|
892
|
+
* and a publish waits for this edit instead.
|
|
893
|
+
*/
|
|
894
|
+
async readLockedTemplateOrThrow(manager, id) {
|
|
895
|
+
// The lock is taken without the category join: Postgres refuses FOR
|
|
896
|
+
// UPDATE on the nullable side of an outer join.
|
|
897
|
+
const locked = await this.lockTemplateRow(manager, id, 'pessimistic_write');
|
|
898
|
+
if (!locked) {
|
|
899
|
+
throw new common_1.NotFoundException(`Approval template ${id} was not found`);
|
|
900
|
+
}
|
|
901
|
+
return this.getTemplateOrThrow(id, manager);
|
|
775
902
|
}
|
|
776
903
|
async setApprovalTemplateCategoryActive(id, isActive) {
|
|
777
904
|
const category = await this.getTemplateCategoryOrThrow(id);
|
|
@@ -937,12 +1064,15 @@ function lintWorkflowTableReferences(definition, initiatorPolicyCel, schema, fie
|
|
|
937
1064
|
* approver resolver reads one member id there. A path that names no
|
|
938
1065
|
* member field of the bound form version resolves to nothing at runtime, which
|
|
939
1066
|
* throws while the task is created and can strand the case — so it is caught
|
|
940
|
-
* here instead. The same throw happens when the field is merely empty
|
|
1067
|
+
* here instead. The same throw happens when the field is merely empty (a
|
|
1068
|
+
* manager chain with a `FORM_MEMBER` start is the exception: an empty field
|
|
1069
|
+
* sends it to its fallback, and it throws only when it has none), and on a
|
|
941
1070
|
* node past the first one it lands inside the previous approver's decision,
|
|
942
1071
|
* which then can never commit. So the field must also be one the submit
|
|
943
1072
|
* validation guarantees a value for: required, with no `visibleWhen` that can
|
|
944
1073
|
* hide it and not a store-only `hidden` field. `FORM_MEMBER_MANAGER` looks one
|
|
945
|
-
* person's manager up, so its field must not be `multiple` either
|
|
1074
|
+
* person's manager up, so its field must not be `multiple` either; so does an
|
|
1075
|
+
* `ORG_MANAGER_CHAIN` whose `startFrom` is a `FORM_MEMBER`.
|
|
946
1076
|
*/
|
|
947
1077
|
function lintWorkflowMemberReferences(definition, schema) {
|
|
948
1078
|
const memberFields = new Map((schema?.fields ?? [])
|
|
@@ -961,6 +1091,19 @@ function lintWorkflowMemberReferences(definition, schema) {
|
|
|
961
1091
|
},
|
|
962
1092
|
];
|
|
963
1093
|
}
|
|
1094
|
+
// A manager chain that starts from a form pick reads one person there,
|
|
1095
|
+
// the same as FORM_MEMBER_MANAGER.
|
|
1096
|
+
if (node.type === 'userTask' &&
|
|
1097
|
+
node.data.approverResolver?.type === 'ORG_MANAGER_CHAIN' &&
|
|
1098
|
+
node.data.approverResolver.startFrom?.type === 'FORM_MEMBER') {
|
|
1099
|
+
return [
|
|
1100
|
+
{
|
|
1101
|
+
formPath: node.data.approverResolver.startFrom.formPath,
|
|
1102
|
+
label: `workflow.nodes.${node.id}.approverResolver.startFrom.formPath`,
|
|
1103
|
+
single: true,
|
|
1104
|
+
},
|
|
1105
|
+
];
|
|
1106
|
+
}
|
|
964
1107
|
if (node.type === 'serviceTask' &&
|
|
965
1108
|
node.data.action?.type === 'NOTIFY' &&
|
|
966
1109
|
node.data.action.recipients?.type === 'DYNAMIC_FORM') {
|
|
@@ -1032,19 +1175,45 @@ function lintWorkflowOrgUnitReferences(definition, schema) {
|
|
|
1032
1175
|
});
|
|
1033
1176
|
}
|
|
1034
1177
|
/**
|
|
1035
|
-
* The graph checks behind `NODE_ACTOR` / `NODE_ACTOR_MANAGER` nodes
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1178
|
+
* The graph checks behind `NODE_ACTOR` / `NODE_ACTOR_MANAGER` nodes, and the
|
|
1179
|
+
* `NODE_ACTOR` start of an `ORG_MANAGER_CHAIN` (shared with the designer):
|
|
1180
|
+
* every source is an unskippable, upstream, non-parallel user task and every
|
|
1181
|
+
* path to the node passes one.
|
|
1038
1182
|
*/
|
|
1039
1183
|
function lintWorkflowNodeActorSources(definition) {
|
|
1040
1184
|
return definition.nodes.flatMap((node) => {
|
|
1041
1185
|
const resolver = node.type === 'userTask' ? node.data.approverResolver : null;
|
|
1186
|
+
// A manager chain that starts from an earlier stage reads that stage's
|
|
1187
|
+
// signer the way NODE_ACTOR does, so it meets the same graph rules. It
|
|
1188
|
+
// also needs that signer to be one person: when the last of several
|
|
1189
|
+
// signers approves, the engine's "which one?" Conflict would land in that
|
|
1190
|
+
// approval's own transaction and it could never commit. NODE_ACTOR and
|
|
1191
|
+
// NODE_ACTOR_MANAGER take every signer and are not held to this.
|
|
1192
|
+
if (resolver?.type === 'ORG_MANAGER_CHAIN' &&
|
|
1193
|
+
resolver.startFrom?.type === 'NODE_ACTOR') {
|
|
1194
|
+
return [
|
|
1195
|
+
...(0, workflow_graph_1.readNodeActorSourceIssues)(definition, node.id, resolver.startFrom.nodeIds),
|
|
1196
|
+
...(0, workflow_graph_1.readSingleApproverSourceIssues)(definition, resolver.startFrom.nodeIds),
|
|
1197
|
+
].map((issue) => `workflow.nodes.${node.id}.approverResolver.startFrom.nodeIds: ${issue}`);
|
|
1198
|
+
}
|
|
1042
1199
|
if (resolver?.type !== 'NODE_ACTOR' && resolver?.type !== 'NODE_ACTOR_MANAGER') {
|
|
1043
1200
|
return [];
|
|
1044
1201
|
}
|
|
1045
1202
|
return (0, workflow_graph_1.readNodeActorSourceIssues)(definition, node.id, resolver.nodeIds).map((issue) => `workflow.nodes.${node.id}.approverResolver.nodeIds: ${issue}`);
|
|
1046
1203
|
});
|
|
1047
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* The graph checks behind `excludeNodeActors` (shared with the designer): every
|
|
1207
|
+
* listed node, on its own, is an upstream user task that is on every path to
|
|
1208
|
+
* the node and always has a person sign (the `NODE_ACTOR` source rules, worded
|
|
1209
|
+
* for the exclusion). Runs after the structural lint, like the `NODE_ACTOR`
|
|
1210
|
+
* checks.
|
|
1211
|
+
*/
|
|
1212
|
+
function lintWorkflowExcludeNodeActors(definition) {
|
|
1213
|
+
return definition.nodes.flatMap((node) => node.type === 'userTask'
|
|
1214
|
+
? (0, workflow_graph_1.readExcludeNodeActorsIssues)(definition, node.id, node.data.excludeNodeActors).map((issue) => `workflow.nodes.${node.id}.excludeNodeActors: ${issue}`)
|
|
1215
|
+
: []);
|
|
1216
|
+
}
|
|
1048
1217
|
/**
|
|
1049
1218
|
* A condition that reads a field an approver fills in, before that approver
|
|
1050
1219
|
* has filled it in, finds the field absent: the applicant's form is submitted
|