@rytass/bpm-core-nestjs-module 0.13.1 → 0.13.2

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 CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.13.2 (2026-09-04)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - **organization:** stop a deleted org unit from burning its code forever ([0f6897f](https://github.com/Rytass/BPMCore/commit/0f6897f))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Chia Yu Pai @fantasywind
10
+ - Claude Opus 5
11
+
1
12
  ## 0.13.1 (2026-09-04)
2
13
 
3
14
  ### 🩹 Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/bpm-core-nestjs-module",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "Embeddable NestJS BPM approval workflow module: workflow engine, approval templates, forms, organization/member contracts, attachments, signatures, notifications, delegation, and TypeORM migrations.",
5
5
  "keywords": [
6
6
  "bpm",
@@ -155,7 +155,7 @@
155
155
  "@nestjs/core": ">=10.0.0",
156
156
  "@nestjs/graphql": ">=12.0.0",
157
157
  "@nestjs/typeorm": ">=10.0.0",
158
- "@rytass/bpm-core-shared": "^0.13.1",
158
+ "@rytass/bpm-core-shared": "^0.13.2",
159
159
  "@rytass/secret-adapter-vault-nestjs": ">=0.4.5",
160
160
  "express": ">=4.0.0",
161
161
  "graphql": ">=16.0.0",
@@ -0,0 +1,26 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+ /**
3
+ * Scopes the org unit code uniqueness to rows that are not soft-deleted.
4
+ *
5
+ * `org_units.code` was created as a plain `UNIQUE` column while
6
+ * `deleteOrgUnit` only soft-deletes and `assertOrgUnitCodeAvailable` looks for
7
+ * a clash among rows with `deleted_at IS NULL`. The two disagreed: the service
8
+ * cleared a recycled code, and the insert was then rejected by the database
9
+ * with a raw `duplicate key value violates unique constraint
10
+ * "org_units_code_key"` — naming a row no query can return, because it is soft
11
+ * deleted. Every deleted unit permanently consumed its code, which broke the
12
+ * ordinary "close a department and recreate it under the same code" operation.
13
+ *
14
+ * The partial index makes the schema agree with the service. Nothing about
15
+ * `assertOrgUnitCodeAvailable` changes; it was already the intended contract.
16
+ */
17
+ export declare class OrgUnitCodeActiveUnique0000000022000 implements MigrationInterface {
18
+ name: string;
19
+ up(queryRunner: QueryRunner): Promise<void>;
20
+ /**
21
+ * Restoring the global constraint fails if any code has been reused since
22
+ * `up()` ran — which is precisely the operation this migration exists to
23
+ * allow. Free the duplicate codes first if you genuinely need to go back.
24
+ */
25
+ down(queryRunner: QueryRunner): Promise<void>;
26
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrgUnitCodeActiveUnique0000000022000 = void 0;
4
+ /**
5
+ * Scopes the org unit code uniqueness to rows that are not soft-deleted.
6
+ *
7
+ * `org_units.code` was created as a plain `UNIQUE` column while
8
+ * `deleteOrgUnit` only soft-deletes and `assertOrgUnitCodeAvailable` looks for
9
+ * a clash among rows with `deleted_at IS NULL`. The two disagreed: the service
10
+ * cleared a recycled code, and the insert was then rejected by the database
11
+ * with a raw `duplicate key value violates unique constraint
12
+ * "org_units_code_key"` — naming a row no query can return, because it is soft
13
+ * deleted. Every deleted unit permanently consumed its code, which broke the
14
+ * ordinary "close a department and recreate it under the same code" operation.
15
+ *
16
+ * The partial index makes the schema agree with the service. Nothing about
17
+ * `assertOrgUnitCodeAvailable` changes; it was already the intended contract.
18
+ */
19
+ class OrgUnitCodeActiveUnique0000000022000 {
20
+ constructor() {
21
+ this.name = 'OrgUnitCodeActiveUnique0000000022000';
22
+ }
23
+ async up(queryRunner) {
24
+ // The constraint is auto-named `org_units_code_key` by PostgreSQL, but it
25
+ // is looked up by shape rather than by that name so a database created
26
+ // through any other path still converges here.
27
+ await queryRunner.query(`
28
+ DO $$
29
+ DECLARE
30
+ target regclass := to_regclass('org_units');
31
+ existing_constraint text;
32
+ BEGIN
33
+ IF target IS NULL THEN
34
+ RETURN;
35
+ END IF;
36
+
37
+ SELECT con.conname INTO existing_constraint
38
+ FROM pg_constraint con
39
+ WHERE con.conrelid = target
40
+ AND con.contype = 'u'
41
+ AND con.conkey = ARRAY[(
42
+ SELECT att.attnum
43
+ FROM pg_attribute att
44
+ WHERE att.attrelid = target
45
+ AND att.attname = 'code'
46
+ AND NOT att.attisdropped
47
+ )]::smallint[];
48
+
49
+ IF existing_constraint IS NOT NULL THEN
50
+ EXECUTE format(
51
+ 'ALTER TABLE %s DROP CONSTRAINT %I', target::text, existing_constraint
52
+ );
53
+ END IF;
54
+ END $$
55
+ `);
56
+ // Safe on an existing database: the constraint just dropped already
57
+ // guaranteed global uniqueness, so the live rows cannot collide.
58
+ await queryRunner.query(`
59
+ CREATE UNIQUE INDEX IF NOT EXISTS "org_units_code_active_key"
60
+ ON "org_units" ("code")
61
+ WHERE "deleted_at" IS NULL
62
+ `);
63
+ }
64
+ /**
65
+ * Restoring the global constraint fails if any code has been reused since
66
+ * `up()` ran — which is precisely the operation this migration exists to
67
+ * allow. Free the duplicate codes first if you genuinely need to go back.
68
+ */
69
+ async down(queryRunner) {
70
+ await queryRunner.query(`
71
+ DROP INDEX IF EXISTS "org_units_code_active_key"
72
+ `);
73
+ await queryRunner.query(`
74
+ ALTER TABLE "org_units"
75
+ ADD CONSTRAINT "org_units_code_key" UNIQUE ("code")
76
+ `);
77
+ }
78
+ }
79
+ exports.OrgUnitCodeActiveUnique0000000022000 = OrgUnitCodeActiveUnique0000000022000;
80
+ //# sourceMappingURL=0000000022000-org-unit-code-active-unique.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0000000022000-org-unit-code-active-unique.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/migrations/0000000022000-org-unit-code-active-unique.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAa,oCAAoC;IAAjD;QACE,SAAI,GAAG,sCAAsC,CAAC;IA4DhD,CAAC;IA1DC,KAAK,CAAC,EAAE,CAAC,WAAwB;QAC/B,0EAA0E;QAC1E,uEAAuE;QACvE,+CAA+C;QAC/C,MAAM,WAAW,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4BvB,CAAC,CAAC;QAEH,oEAAoE;QACpE,iEAAiE;QACjE,MAAM,WAAW,CAAC,KAAK,CAAC;;;;KAIvB,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,WAAwB;QACjC,MAAM,WAAW,CAAC,KAAK,CAAC;;KAEvB,CAAC,CAAC;QAEH,MAAM,WAAW,CAAC,KAAK,CAAC;;;KAGvB,CAAC,CAAC;IACL,CAAC;CACF;AA7DD,oFA6DC"}
@@ -22,3 +22,4 @@ export * from './0000000018000-notification-archive';
22
22
  export * from './0000000019000-approval-template-activation';
23
23
  export * from './0000000020000-form-data-option-snapshots';
24
24
  export * from './0000000021000-notification-silenced';
25
+ export * from './0000000022000-org-unit-code-active-unique';
@@ -24,6 +24,7 @@ const _0000000018000_notification_archive_1 = require("./0000000018000-notificat
24
24
  const _0000000019000_approval_template_activation_1 = require("./0000000019000-approval-template-activation");
25
25
  const _0000000020000_form_data_option_snapshots_1 = require("./0000000020000-form-data-option-snapshots");
26
26
  const _0000000021000_notification_silenced_1 = require("./0000000021000-notification-silenced");
27
+ const _0000000022000_org_unit_code_active_unique_1 = require("./0000000022000-org-unit-code-active-unique");
27
28
  exports.BPM_CORE_MIGRATIONS = [
28
29
  _0000000000001_enable_postgres_extensions_1.EnablePostgresExtensions0000000000001,
29
30
  _0000000001000_identity_organization_foundation_1.IdentityOrganizationFoundation0000000001000,
@@ -47,6 +48,7 @@ exports.BPM_CORE_MIGRATIONS = [
47
48
  _0000000019000_approval_template_activation_1.ApprovalTemplateActivation0000000019000,
48
49
  _0000000020000_form_data_option_snapshots_1.FormDataOptionSnapshots0000000020000,
49
50
  _0000000021000_notification_silenced_1.NotificationSilenced0000000021000,
51
+ _0000000022000_org_unit_code_active_unique_1.OrgUnitCodeActiveUnique0000000022000,
50
52
  ];
51
53
  tslib_1.__exportStar(require("./0000000000001-enable-postgres-extensions"), exports);
52
54
  tslib_1.__exportStar(require("./0000000001000-identity-organization-foundation"), exports);
@@ -70,4 +72,5 @@ tslib_1.__exportStar(require("./0000000018000-notification-archive"), exports);
70
72
  tslib_1.__exportStar(require("./0000000019000-approval-template-activation"), exports);
71
73
  tslib_1.__exportStar(require("./0000000020000-form-data-option-snapshots"), exports);
72
74
  tslib_1.__exportStar(require("./0000000021000-notification-silenced"), exports);
75
+ tslib_1.__exportStar(require("./0000000022000-org-unit-code-active-unique"), exports);
73
76
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/migrations/index.ts"],"names":[],"mappings":";;;;AACA,0GAAmG;AACnG,sHAA+G;AAC/G,oGAA6F;AAC7F,8GAAuG;AACvG,0GAAmG;AACnG,sFAAgF;AAChF,wFAAkF;AAClF,kGAA4F;AAC5F,8GAAuG;AACvG,oFAA8E;AAC9E,4GAAqG;AACrG,sHAA8G;AAC9G,8GAAuG;AACvG,kGAA2F;AAC3F,oGAA8F;AAC9F,kIAA0H;AAC1H,8GAAsG;AACtG,sFAAgF;AAChF,8FAAwF;AACxF,8GAAuG;AACvG,0GAAkG;AAClG,gGAA0F;AAE7E,QAAA,mBAAmB,GAA8C;IAC5E,iFAAqC;IACrC,6FAA2C;IAC3C,2EAAkC;IAClC,qFAAuC;IACvC,iFAAqC;IACrC,8DAA4B;IAC5B,gEAA6B;IAC7B,0EAAkC;IAClC,qFAAuC;IACvC,4DAA2B;IAC3B,mFAAsC;IACtC,4FAA0C;IAC1C,qFAAuC;IACvC,yEAAiC;IACjC,4EAAmC;IACnC,wGAAgD;IAChD,oFAAsC;IACtC,8DAA4B;IAC5B,sEAAgC;IAChC,qFAAuC;IACvC,gFAAoC;IACpC,wEAAiC;CAClC,CAAC;AAEF,qFAA2D;AAC3D,2FAAiE;AACjE,kFAAwD;AACxD,uFAA6D;AAC7D,qFAA2D;AAC3D,2EAAiD;AACjD,4EAAkD;AAClD,iFAAuD;AACvD,uFAA6D;AAC7D,0EAAgD;AAChD,sFAA4D;AAC5D,2FAAiE;AACjE,uFAA6D;AAC7D,iFAAuD;AACvD,kFAAwD;AACxD,iGAAuE;AACvE,uFAA6D;AAC7D,2EAAiD;AACjD,+EAAqD;AACrD,uFAA6D;AAC7D,qFAA2D;AAC3D,gFAAsD"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/migrations/index.ts"],"names":[],"mappings":";;;;AACA,0GAAmG;AACnG,sHAA+G;AAC/G,oGAA6F;AAC7F,8GAAuG;AACvG,0GAAmG;AACnG,sFAAgF;AAChF,wFAAkF;AAClF,kGAA4F;AAC5F,8GAAuG;AACvG,oFAA8E;AAC9E,4GAAqG;AACrG,sHAA8G;AAC9G,8GAAuG;AACvG,kGAA2F;AAC3F,oGAA8F;AAC9F,kIAA0H;AAC1H,8GAAsG;AACtG,sFAAgF;AAChF,8FAAwF;AACxF,8GAAuG;AACvG,0GAAkG;AAClG,gGAA0F;AAC1F,4GAAmG;AAEtF,QAAA,mBAAmB,GAA8C;IAC5E,iFAAqC;IACrC,6FAA2C;IAC3C,2EAAkC;IAClC,qFAAuC;IACvC,iFAAqC;IACrC,8DAA4B;IAC5B,gEAA6B;IAC7B,0EAAkC;IAClC,qFAAuC;IACvC,4DAA2B;IAC3B,mFAAsC;IACtC,4FAA0C;IAC1C,qFAAuC;IACvC,yEAAiC;IACjC,4EAAmC;IACnC,wGAAgD;IAChD,oFAAsC;IACtC,8DAA4B;IAC5B,sEAAgC;IAChC,qFAAuC;IACvC,gFAAoC;IACpC,wEAAiC;IACjC,iFAAoC;CACrC,CAAC;AAEF,qFAA2D;AAC3D,2FAAiE;AACjE,kFAAwD;AACxD,uFAA6D;AAC7D,qFAA2D;AAC3D,2EAAiD;AACjD,4EAAkD;AAClD,iFAAuD;AACvD,uFAA6D;AAC7D,0EAAgD;AAChD,sFAA4D;AAC5D,2FAAiE;AACjE,uFAA6D;AAC7D,iFAAuD;AACvD,kFAAwD;AACxD,iGAAuE;AACvE,uFAA6D;AAC7D,2EAAiD;AACjD,+EAAqD;AACrD,uFAA6D;AAC7D,qFAA2D;AAC3D,gFAAsD;AACtD,sFAA4D"}
@@ -1,4 +1,13 @@
1
1
  import { OrgUnitTypeEnum } from './organization.enums';
2
+ /**
3
+ * Codes are unique among *live* units only.
4
+ *
5
+ * A plain `unique: true` on `code` would outlive the soft delete and burn the
6
+ * code forever, while `assertOrgUnitCodeAvailable` — which ignores
7
+ * soft-deleted rows — would keep clearing it and let the insert fail in the
8
+ * driver instead. Keep this partial index in step with migration
9
+ * `OrgUnitCodeActiveUnique0000000022000`.
10
+ */
2
11
  export declare class OrgUnitEntity {
3
12
  id: string;
4
13
  parentId: string | null;
@@ -5,6 +5,15 @@ const tslib_1 = require("tslib");
5
5
  const graphql_1 = require("@nestjs/graphql");
6
6
  const typeorm_1 = require("typeorm");
7
7
  const organization_enums_1 = require("./organization.enums");
8
+ /**
9
+ * Codes are unique among *live* units only.
10
+ *
11
+ * A plain `unique: true` on `code` would outlive the soft delete and burn the
12
+ * code forever, while `assertOrgUnitCodeAvailable` — which ignores
13
+ * soft-deleted rows — would keep clearing it and let the insert fail in the
14
+ * driver instead. Keep this partial index in step with migration
15
+ * `OrgUnitCodeActiveUnique0000000022000`.
16
+ */
8
17
  let OrgUnitEntity = class OrgUnitEntity {
9
18
  };
10
19
  exports.OrgUnitEntity = OrgUnitEntity;
@@ -19,7 +28,7 @@ tslib_1.__decorate([
19
28
  tslib_1.__metadata("design:type", Object)
20
29
  ], OrgUnitEntity.prototype, "parentId", void 0);
21
30
  tslib_1.__decorate([
22
- (0, typeorm_1.Column)('text', { unique: true }),
31
+ (0, typeorm_1.Column)('text'),
23
32
  (0, graphql_1.Field)(),
24
33
  tslib_1.__metadata("design:type", String)
25
34
  ], OrgUnitEntity.prototype, "code", void 0);
@@ -58,6 +67,10 @@ tslib_1.__decorate([
58
67
  tslib_1.__metadata("design:type", Date)
59
68
  ], OrgUnitEntity.prototype, "updatedAt", void 0);
60
69
  exports.OrgUnitEntity = OrgUnitEntity = tslib_1.__decorate([
70
+ (0, typeorm_1.Index)('org_units_code_active_key', ['code'], {
71
+ unique: true,
72
+ where: '"deleted_at" IS NULL',
73
+ }),
61
74
  (0, typeorm_1.Entity)('org_units'),
62
75
  (0, graphql_1.ObjectType)('OrgUnit')
63
76
  ], OrgUnitEntity);
@@ -1 +1 @@
1
- {"version":3,"file":"org-unit.entity.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/organization/org-unit.entity.ts"],"names":[],"mappings":";;;;AAAA,6CAAwD;AACxD,qCAOiB;AACjB,6DAAuD;AAIhD,IAAM,aAAa,GAAnB,MAAM,aAAa;CAuCzB,CAAA;AAvCY,sCAAa;AAGxB;IAFC,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,YAAE,CAAC;;yCACJ;AAIZ;IAFC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrD,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,YAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACX;AAIzB;IAFC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAChC,IAAA,eAAK,GAAE;;2CACM;AAId;IAFC,IAAA,gBAAM,EAAC,MAAM,CAAC;IACd,IAAA,eAAK,GAAE;;2CACM;AAId;IAFC,IAAA,gBAAM,EAAC,MAAM,CAAC;IACd,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,oCAAe,CAAC;;2CACN;AAIvB;IAFC,IAAA,gBAAM,EAAC,OAAO,CAAC;IACf,IAAA,eAAK,GAAE;;2CACM;AAGd;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;+CACY;AAI7C;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDACd;AAIxB;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,GAAE;sCACI,IAAI;gDAAC;AAIjB;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,GAAE;sCACI,IAAI;gDAAC;wBAtCN,aAAa;IAFzB,IAAA,gBAAM,EAAC,WAAW,CAAC;IACnB,IAAA,oBAAU,EAAC,SAAS,CAAC;GACT,aAAa,CAuCzB"}
1
+ {"version":3,"file":"org-unit.entity.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/organization/org-unit.entity.ts"],"names":[],"mappings":";;;;AAAA,6CAAwD;AACxD,qCAQiB;AACjB,6DAAuD;AAEvD;;;;;;;;GAQG;AAOI,IAAM,aAAa,GAAnB,MAAM,aAAa;CAuCzB,CAAA;AAvCY,sCAAa;AAGxB;IAFC,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,YAAE,CAAC;;yCACJ;AAIZ;IAFC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrD,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,YAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACX;AAIzB;IAFC,IAAA,gBAAM,EAAC,MAAM,CAAC;IACd,IAAA,eAAK,GAAE;;2CACM;AAId;IAFC,IAAA,gBAAM,EAAC,MAAM,CAAC;IACd,IAAA,eAAK,GAAE;;2CACM;AAId;IAFC,IAAA,gBAAM,EAAC,MAAM,CAAC;IACd,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,oCAAe,CAAC;;2CACN;AAIvB;IAFC,IAAA,gBAAM,EAAC,OAAO,CAAC;IACf,IAAA,eAAK,GAAE;;2CACM;AAGd;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;;+CACY;AAI7C;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDACd;AAIxB;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,GAAE;sCACI,IAAI;gDAAC;AAIjB;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC7D,IAAA,eAAK,GAAE;sCACI,IAAI;gDAAC;wBAtCN,aAAa;IANzB,IAAA,eAAK,EAAC,2BAA2B,EAAE,CAAC,MAAM,CAAC,EAAE;QAC5C,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,sBAAsB;KAC9B,CAAC;IACD,IAAA,gBAAM,EAAC,WAAW,CAAC;IACnB,IAAA,oBAAU,EAAC,SAAS,CAAC;GACT,aAAa,CAuCzB"}