@things-factory/shell 10.1.206 → 10.1.208
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-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/reversibility/reversibility.d.ts +1 -1
- package/dist-server/reversibility/reversibility.js +1 -1
- package/dist-server/reversibility/reversibility.js.map +1 -1
- package/dist-server/row-ties/index.d.ts +1 -0
- package/dist-server/row-ties/index.js +5 -0
- package/dist-server/row-ties/index.js.map +1 -0
- package/dist-server/row-ties/row-ties.d.ts +32 -0
- package/dist-server/row-ties/row-ties.js +66 -0
- package/dist-server/row-ties/row-ties.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/tests/row-ties.test.ts +144 -0
- package/translations/en.json +12 -0
- package/translations/es.json +12 -0
- package/translations/ja.json +12 -0
- package/translations/ko.json +12 -0
- package/translations/ms.json +12 -0
- package/translations/zh.json +12 -0
package/dist-server/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './bootstrap-hooks.js';
|
|
|
9
9
|
export * from './graphql-local-client.js';
|
|
10
10
|
export * from './service/index.js';
|
|
11
11
|
export * from './refusal/index.js';
|
|
12
|
+
export * from './row-ties/index.js';
|
|
12
13
|
export * from './reversibility/index.js';
|
|
13
14
|
export * from './typeorm/long-text-column.js';
|
|
14
15
|
export * from './typeorm/encrypt-transform.js';
|
package/dist-server/index.js
CHANGED
|
@@ -12,6 +12,7 @@ tslib_1.__exportStar(require("./bootstrap-hooks.js"), exports);
|
|
|
12
12
|
tslib_1.__exportStar(require("./graphql-local-client.js"), exports);
|
|
13
13
|
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
14
14
|
tslib_1.__exportStar(require("./refusal/index.js"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./row-ties/index.js"), exports);
|
|
15
16
|
tslib_1.__exportStar(require("./reversibility/index.js"), exports);
|
|
16
17
|
tslib_1.__exportStar(require("./typeorm/long-text-column.js"), exports);
|
|
17
18
|
tslib_1.__exportStar(require("./typeorm/encrypt-transform.js"), exports);
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,gEAAqC;AACrC,4EAAiD;AACjD,qEAA0C;AAC1C,2DAAgC;AAChC,sDAA2B;AAC3B,oEAAyC;AACzC,iEAAsC;AACtC,+DAAoC;AACpC,oEAAyC;AACzC,6DAAkC;AAClC,6DAAkC;AAClC,mEAAwC;AAExC,wEAA6C;AAC7C,yEAA8C;AAC9C,8EAAmD;AACnD,uEAA4C;AAC5C,uEAA4C;AAC5C,oEAAyC;AACzC,kEAAuC","sourcesContent":["export * from './migrations/index.js'\nexport * from './initializers/naming-strategy.js'\nexport * from './initializers/database.js'\nexport * from './utils/index.js'\nexport * from './pubsub.js'\nexport * from './pubsub-log-transport.js'\nexport * from './middlewares/index.js'\nexport * from './bootstrap-hooks.js'\nexport * from './graphql-local-client.js'\nexport * from './service/index.js'\nexport * from './refusal/index.js'\nexport * from './reversibility/index.js'\n\nexport * from './typeorm/long-text-column.js'\nexport * from './typeorm/encrypt-transform.js'\nexport * from './typeorm/encrypt-json-transform.js'\nexport * from './typeorm/json5-transform.js'\nexport * from './typeorm/round-transform.js'\nexport * from './typeorm/money-column.js'\nexport * from './typeorm/write-lock.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,gEAAqC;AACrC,4EAAiD;AACjD,qEAA0C;AAC1C,2DAAgC;AAChC,sDAA2B;AAC3B,oEAAyC;AACzC,iEAAsC;AACtC,+DAAoC;AACpC,oEAAyC;AACzC,6DAAkC;AAClC,6DAAkC;AAClC,8DAAmC;AACnC,mEAAwC;AAExC,wEAA6C;AAC7C,yEAA8C;AAC9C,8EAAmD;AACnD,uEAA4C;AAC5C,uEAA4C;AAC5C,oEAAyC;AACzC,kEAAuC","sourcesContent":["export * from './migrations/index.js'\nexport * from './initializers/naming-strategy.js'\nexport * from './initializers/database.js'\nexport * from './utils/index.js'\nexport * from './pubsub.js'\nexport * from './pubsub-log-transport.js'\nexport * from './middlewares/index.js'\nexport * from './bootstrap-hooks.js'\nexport * from './graphql-local-client.js'\nexport * from './service/index.js'\nexport * from './refusal/index.js'\nexport * from './row-ties/index.js'\nexport * from './reversibility/index.js'\n\nexport * from './typeorm/long-text-column.js'\nexport * from './typeorm/encrypt-transform.js'\nexport * from './typeorm/encrypt-json-transform.js'\nexport * from './typeorm/json5-transform.js'\nexport * from './typeorm/round-transform.js'\nexport * from './typeorm/money-column.js'\nexport * from './typeorm/write-lock.js'\n"]}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* **worklist 가 아닌 까닭.** 거기에는 이미 `Reversibility` 라는 이름이 있고 그것은 다른 타입이다
|
|
18
18
|
* (`{ reversible: boolean, caveat? }` — 결재 슬롯이 읽는 모양). 「승인을 되돌릴 수 있나」와 「이 문의
|
|
19
|
-
* 효과를 되돌릴 수 있나」는 다른 사실이고, 한
|
|
19
|
+
* 효과를 되돌릴 수 있나」는 다른 사실이고, 한 패키지에서 한 이름을 두고 다투게 두지 않는다.
|
|
20
20
|
*
|
|
21
21
|
* **화면은 이 상수를 import 하지 않는다.** 화면이 읽는 값은 질의로 오는 문자열이고, shell 의
|
|
22
22
|
* 클라이언트 진입점은 부수 효과를 들고 있어 타입 하나를 위해 지날 자리가 아니다.
|
|
@@ -19,7 +19,7 @@ exports.REVERSIBILITIES = void 0;
|
|
|
19
19
|
*
|
|
20
20
|
* **worklist 가 아닌 까닭.** 거기에는 이미 `Reversibility` 라는 이름이 있고 그것은 다른 타입이다
|
|
21
21
|
* (`{ reversible: boolean, caveat? }` — 결재 슬롯이 읽는 모양). 「승인을 되돌릴 수 있나」와 「이 문의
|
|
22
|
-
* 효과를 되돌릴 수 있나」는 다른 사실이고, 한
|
|
22
|
+
* 효과를 되돌릴 수 있나」는 다른 사실이고, 한 패키지에서 한 이름을 두고 다투게 두지 않는다.
|
|
23
23
|
*
|
|
24
24
|
* **화면은 이 상수를 import 하지 않는다.** 화면이 읽는 값은 질의로 오는 문자열이고, shell 의
|
|
25
25
|
* 클라이언트 진입점은 부수 효과를 들고 있어 타입 하나를 위해 지날 자리가 아니다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reversibility.js","sourceRoot":"","sources":["../../server/reversibility/reversibility.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACU,QAAA,eAAe,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,CAAU,CAAA","sourcesContent":["/**\n * Whether an operation's effect can be undone (ADR-0064 decision 1).\n *\n * - `irreversible` the effect happens there and then, and there is no way back.\n * - `compensable` it cannot be undone, but the original record stays and a new record corrects it.\n * - `reversible` a paired operation returns to the same state, and no other record holds the id of a\n * removed row (a row made again has a new id).\n * - `unknown` not yet established. A value of its own, so the classifier never guesses a neighbour.\n *\n * Screens ask a destructive confirmation only for `irreversible`.\n *\n * ── 왜 shell 인가 (ADR-0064 보탬, 2026-09-20) ────────────────────────────────\n * operato-plant 가 먼저 적었고 `@things-factory/station` 이 둘째 소비자로 서면서 올라왔다 —\n * `refusal` 이 지난 길과 같다. 자리가 그 옆인 것은 같은 종류의 선언이기 때문이다: **문이 하는 일에\n * 대해 서버가 아는 사실.** 소비처 넷이 이미 shell 을 들고 있다.\n *\n * **worklist 가 아닌 까닭.** 거기에는 이미 `Reversibility` 라는 이름이 있고 그것은 다른 타입이다\n * (`{ reversible: boolean, caveat? }` — 결재 슬롯이 읽는 모양). 「승인을 되돌릴 수 있나」와 「이 문의\n * 효과를 되돌릴 수 있나」는 다른 사실이고, 한
|
|
1
|
+
{"version":3,"file":"reversibility.js","sourceRoot":"","sources":["../../server/reversibility/reversibility.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACU,QAAA,eAAe,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,CAAU,CAAA","sourcesContent":["/**\n * Whether an operation's effect can be undone (ADR-0064 decision 1).\n *\n * - `irreversible` the effect happens there and then, and there is no way back.\n * - `compensable` it cannot be undone, but the original record stays and a new record corrects it.\n * - `reversible` a paired operation returns to the same state, and no other record holds the id of a\n * removed row (a row made again has a new id).\n * - `unknown` not yet established. A value of its own, so the classifier never guesses a neighbour.\n *\n * Screens ask a destructive confirmation only for `irreversible`.\n *\n * ── 왜 shell 인가 (ADR-0064 보탬, 2026-09-20) ────────────────────────────────\n * operato-plant 가 먼저 적었고 `@things-factory/station` 이 둘째 소비자로 서면서 올라왔다 —\n * `refusal` 이 지난 길과 같다. 자리가 그 옆인 것은 같은 종류의 선언이기 때문이다: **문이 하는 일에\n * 대해 서버가 아는 사실.** 소비처 넷이 이미 shell 을 들고 있다.\n *\n * **worklist 가 아닌 까닭.** 거기에는 이미 `Reversibility` 라는 이름이 있고 그것은 다른 타입이다\n * (`{ reversible: boolean, caveat? }` — 결재 슬롯이 읽는 모양). 「승인을 되돌릴 수 있나」와 「이 문의\n * 효과를 되돌릴 수 있나」는 다른 사실이고, 한 패키지에서 한 이름을 두고 다투게 두지 않는다.\n *\n * **화면은 이 상수를 import 하지 않는다.** 화면이 읽는 값은 질의로 오는 문자열이고, shell 의\n * 클라이언트 진입점은 부수 효과를 들고 있어 타입 하나를 위해 지날 자리가 아니다.\n */\nexport const REVERSIBILITIES = ['irreversible', 'compensable', 'reversible', 'unknown'] as const\n\nexport type Reversibility = (typeof REVERSIBILITIES)[number]\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './row-ties.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/row-ties/index.ts"],"names":[],"mappings":";;;AAAA,wDAA6B","sourcesContent":["export * from './row-ties.js'\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* **A ledger row is not deleted while another row is tied to it** (2026-10-23, user: 「원장 삭제의 경우에는 기존에
|
|
4
|
+
* 연결되어있던 관계를 모두 클리어시켜야 한다. 혹은 관계가 이어진 상태에서는 삭제할 수 없어야 한다」).
|
|
5
|
+
*
|
|
6
|
+
* Tied is a live row of any ledger that names it in a field - a repayment its loan, a contract its counterparty, a
|
|
7
|
+
* forecast line the recurring item it was made from - or names it by kind and id (§`registerKindTie`). The person
|
|
8
|
+
* unlinks or deletes that first, and the refusal says what it is and how many. Nothing is unlinked unseen: clearing the
|
|
9
|
+
* link would change a collected amount or a contract's party where nobody looked.
|
|
10
|
+
*
|
|
11
|
+
* Read from the entities as they are declared, so a ledger added later - in any module - is held by the same rule.
|
|
12
|
+
* Not counted: the versions a history table keeps of a row (its past, not a row naming it), the rows being deleted
|
|
13
|
+
* together, and rows already deleted. A row's own parts go with it: the caller deletes them first, in the same
|
|
14
|
+
* transaction.
|
|
15
|
+
*
|
|
16
|
+
* ── Why it lives in the shell ─────────────────────────────────────────────
|
|
17
|
+
* operato-office wrote it first for its own ledgers; the ledgers it shows from biz-base and accounting (counterparties,
|
|
18
|
+
* accounts, bank accounts, transactions) are deleted by their own packages, and a counterparty is named by rows of
|
|
19
|
+
* modules biz-base does not know. Everything it needs is TypeORM's metadata, which every module's entities are in.
|
|
20
|
+
*
|
|
21
|
+
* The sentence is `error.row-tied` here; what is tied reads `label.row-tie-<Entity>`, declared by the module that owns
|
|
22
|
+
* that entity. An entity without one reads its class name.
|
|
23
|
+
*/
|
|
24
|
+
export declare function refuseTiedRows(tx: EntityManager, entity: Function, ids: string[]): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Declare a ledger that names rows by kind and id - accounting's payment (`targetType`, `targetId`) - so a row it
|
|
27
|
+
* names is held as one named by a key is. Its owner declares it once, where the entity is.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerKindTie(entity: Function, fields: {
|
|
30
|
+
kind: string;
|
|
31
|
+
id: string;
|
|
32
|
+
}): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refuseTiedRows = refuseTiedRows;
|
|
4
|
+
exports.registerKindTie = registerKindTie;
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
/* The mark `HistoryOriginalIdColumn` leaves on a history entity; the package does not re-export it. */
|
|
7
|
+
const constants_1 = require("@operato/typeorm-history/dist/constants");
|
|
8
|
+
const refusal_js_1 = require("../refusal/refusal.js");
|
|
9
|
+
/**
|
|
10
|
+
* **A ledger row is not deleted while another row is tied to it** (2026-10-23, user: 「원장 삭제의 경우에는 기존에
|
|
11
|
+
* 연결되어있던 관계를 모두 클리어시켜야 한다. 혹은 관계가 이어진 상태에서는 삭제할 수 없어야 한다」).
|
|
12
|
+
*
|
|
13
|
+
* Tied is a live row of any ledger that names it in a field - a repayment its loan, a contract its counterparty, a
|
|
14
|
+
* forecast line the recurring item it was made from - or names it by kind and id (§`registerKindTie`). The person
|
|
15
|
+
* unlinks or deletes that first, and the refusal says what it is and how many. Nothing is unlinked unseen: clearing the
|
|
16
|
+
* link would change a collected amount or a contract's party where nobody looked.
|
|
17
|
+
*
|
|
18
|
+
* Read from the entities as they are declared, so a ledger added later - in any module - is held by the same rule.
|
|
19
|
+
* Not counted: the versions a history table keeps of a row (its past, not a row naming it), the rows being deleted
|
|
20
|
+
* together, and rows already deleted. A row's own parts go with it: the caller deletes them first, in the same
|
|
21
|
+
* transaction.
|
|
22
|
+
*
|
|
23
|
+
* ── Why it lives in the shell ─────────────────────────────────────────────
|
|
24
|
+
* operato-office wrote it first for its own ledgers; the ledgers it shows from biz-base and accounting (counterparties,
|
|
25
|
+
* accounts, bank accounts, transactions) are deleted by their own packages, and a counterparty is named by rows of
|
|
26
|
+
* modules biz-base does not know. Everything it needs is TypeORM's metadata, which every module's entities are in.
|
|
27
|
+
*
|
|
28
|
+
* The sentence is `error.row-tied` here; what is tied reads `label.row-tie-<Entity>`, declared by the module that owns
|
|
29
|
+
* that entity. An entity without one reads its class name.
|
|
30
|
+
*/
|
|
31
|
+
async function refuseTiedRows(tx, entity, ids) {
|
|
32
|
+
if (!ids.length)
|
|
33
|
+
return;
|
|
34
|
+
for (const tie of kindTies) {
|
|
35
|
+
const count = await tx.getRepository(tie.entity).count({
|
|
36
|
+
where: { [tie.kind]: entity.name, [tie.id]: (0, typeorm_1.In)(ids), ...(tie.entity === entity ? { id: (0, typeorm_1.Not)((0, typeorm_1.In)(ids)) } : {}) }
|
|
37
|
+
});
|
|
38
|
+
if (count)
|
|
39
|
+
throw tied(tie.entity.name, count);
|
|
40
|
+
}
|
|
41
|
+
for (const meta of tx.connection.entityMetadatas) {
|
|
42
|
+
if (typeof meta.target !== 'function' || Reflect.hasMetadata(constants_1.TYPEORM_HELPER_HISTORY_ORIGINAL_ID, meta.target.prototype))
|
|
43
|
+
continue;
|
|
44
|
+
for (const relation of meta.relations) {
|
|
45
|
+
if (!(relation.isManyToOne || relation.isOneToOneOwner) || relation.inverseEntityMetadata.target !== entity)
|
|
46
|
+
continue;
|
|
47
|
+
const count = await tx.getRepository(meta.target).count({
|
|
48
|
+
where: { [relation.propertyName]: { id: (0, typeorm_1.In)(ids) }, ...(meta.target === entity ? { id: (0, typeorm_1.Not)((0, typeorm_1.In)(ids)) } : {}) }
|
|
49
|
+
});
|
|
50
|
+
if (count)
|
|
51
|
+
throw tied(meta.name, count);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const kindTies = [];
|
|
56
|
+
/**
|
|
57
|
+
* Declare a ledger that names rows by kind and id - accounting's payment (`targetType`, `targetId`) - so a row it
|
|
58
|
+
* names is held as one named by a key is. Its owner declares it once, where the entity is.
|
|
59
|
+
*/
|
|
60
|
+
function registerKindTie(entity, fields) {
|
|
61
|
+
if (kindTies.some(one => one.entity === entity))
|
|
62
|
+
throw new Error(`kind tie '${entity.name}' is registered twice`);
|
|
63
|
+
kindTies.push({ entity, ...fields });
|
|
64
|
+
}
|
|
65
|
+
const tied = (kind, count) => new refusal_js_1.Refusal('row-tied', { kind, count }, `${count} ${kind} row(s) are tied to it - unlink or delete them first`);
|
|
66
|
+
//# sourceMappingURL=row-ties.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-ties.js","sourceRoot":"","sources":["../../server/row-ties/row-ties.ts"],"names":[],"mappings":";;AA6BA,wCAoBC;AAeD,0CAGC;AAnED,qCAAgD;AAEhD,uGAAuG;AACvG,uEAA4F;AAE5F,sDAA+C;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,cAAc,CAAC,EAAiB,EAAE,MAAgB,EAAE,GAAa;IACrF,IAAI,CAAC,GAAG,CAAC,MAAM;QAAE,OAAM;IAEvB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;YACrD,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAA,YAAE,EAAC,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAA,aAAG,EAAC,IAAA,YAAE,EAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAS;SACrH,CAAC,CAAA;QACF,IAAI,KAAK;YAAE,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC/C,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC;QACjD,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,8CAAkC,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAAE,SAAQ;QACjI,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,eAAe,CAAC,IAAI,QAAQ,CAAC,qBAAqB,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAQ;YACrH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;gBACtD,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAA,aAAG,EAAC,IAAA,YAAE,EAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAS;aACpH,CAAC,CAAA;YACF,IAAI,KAAK;gBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;AACH,CAAC;AASD,MAAM,QAAQ,GAAc,EAAE,CAAA;AAE9B;;;GAGG;AACH,SAAgB,eAAe,CAAC,MAAgB,EAAE,MAAoC;IACpF,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,CAAC,IAAI,uBAAuB,CAAC,CAAA;IACjH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE,CAC3C,IAAI,oBAAO,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,IAAI,IAAI,sDAAsD,CAAC,CAAA","sourcesContent":["import { EntityManager, In, Not } from 'typeorm'\n\n/* The mark `HistoryOriginalIdColumn` leaves on a history entity; the package does not re-export it. */\nimport { TYPEORM_HELPER_HISTORY_ORIGINAL_ID } from '@operato/typeorm-history/dist/constants'\n\nimport { Refusal } from '../refusal/refusal.js'\n\n/**\n * **A ledger row is not deleted while another row is tied to it** (2026-10-23, user: 「원장 삭제의 경우에는 기존에\n * 연결되어있던 관계를 모두 클리어시켜야 한다. 혹은 관계가 이어진 상태에서는 삭제할 수 없어야 한다」).\n *\n * Tied is a live row of any ledger that names it in a field - a repayment its loan, a contract its counterparty, a\n * forecast line the recurring item it was made from - or names it by kind and id (§`registerKindTie`). The person\n * unlinks or deletes that first, and the refusal says what it is and how many. Nothing is unlinked unseen: clearing the\n * link would change a collected amount or a contract's party where nobody looked.\n *\n * Read from the entities as they are declared, so a ledger added later - in any module - is held by the same rule.\n * Not counted: the versions a history table keeps of a row (its past, not a row naming it), the rows being deleted\n * together, and rows already deleted. A row's own parts go with it: the caller deletes them first, in the same\n * transaction.\n *\n * ── Why it lives in the shell ─────────────────────────────────────────────\n * operato-office wrote it first for its own ledgers; the ledgers it shows from biz-base and accounting (counterparties,\n * accounts, bank accounts, transactions) are deleted by their own packages, and a counterparty is named by rows of\n * modules biz-base does not know. Everything it needs is TypeORM's metadata, which every module's entities are in.\n *\n * The sentence is `error.row-tied` here; what is tied reads `label.row-tie-<Entity>`, declared by the module that owns\n * that entity. An entity without one reads its class name.\n */\nexport async function refuseTiedRows(tx: EntityManager, entity: Function, ids: string[]): Promise<void> {\n if (!ids.length) return\n\n for (const tie of kindTies) {\n const count = await tx.getRepository(tie.entity).count({\n where: { [tie.kind]: entity.name, [tie.id]: In(ids), ...(tie.entity === entity ? { id: Not(In(ids)) } : {}) } as any\n })\n if (count) throw tied(tie.entity.name, count)\n }\n\n for (const meta of tx.connection.entityMetadatas) {\n if (typeof meta.target !== 'function' || Reflect.hasMetadata(TYPEORM_HELPER_HISTORY_ORIGINAL_ID, meta.target.prototype)) continue\n for (const relation of meta.relations) {\n if (!(relation.isManyToOne || relation.isOneToOneOwner) || relation.inverseEntityMetadata.target !== entity) continue\n const count = await tx.getRepository(meta.target).count({\n where: { [relation.propertyName]: { id: In(ids) }, ...(meta.target === entity ? { id: Not(In(ids)) } : {}) } as any\n })\n if (count) throw tied(meta.name, count)\n }\n }\n}\n\n/** A ledger that names a row by its kind (the entity's class name) and id, not by a key. */\ninterface KindTie {\n entity: Function\n kind: string\n id: string\n}\n\nconst kindTies: KindTie[] = []\n\n/**\n * Declare a ledger that names rows by kind and id - accounting's payment (`targetType`, `targetId`) - so a row it\n * names is held as one named by a key is. Its owner declares it once, where the entity is.\n */\nexport function registerKindTie(entity: Function, fields: { kind: string; id: string }): void {\n if (kindTies.some(one => one.entity === entity)) throw new Error(`kind tie '${entity.name}' is registered twice`)\n kindTies.push({ entity, ...fields })\n}\n\nconst tied = (kind: string, count: number) =>\n new Refusal('row-tied', { kind, count }, `${count} ${kind} row(s) are tied to it - unlink or delete them first`)\n"]}
|