@things-factory/ops-master 10.1.36 → 10.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist-client/bootstrap.d.ts +1 -0
  2. package/dist-client/bootstrap.js +12 -0
  3. package/dist-client/bootstrap.js.map +1 -1
  4. package/dist-client/master-change-policy-axes.d.ts +43 -0
  5. package/dist-client/master-change-policy-axes.js +35 -0
  6. package/dist-client/master-change-policy-axes.js.map +1 -0
  7. package/dist-client/master-change-policy-panel.d.ts +59 -0
  8. package/dist-client/master-change-policy-panel.js +452 -0
  9. package/dist-client/master-change-policy-panel.js.map +1 -0
  10. package/dist-client/pages/master/applied-when-dialog.d.ts +2 -2
  11. package/dist-client/pages/master/applied-when-dialog.js +2 -2
  12. package/dist-client/pages/master/applied-when-dialog.js.map +1 -1
  13. package/dist-client/pages/master/assignment-fields.d.ts +8 -9
  14. package/dist-client/pages/master/assignment-fields.js +25 -18
  15. package/dist-client/pages/master/assignment-fields.js.map +1 -1
  16. package/dist-client/pages/master/master-page-base.d.ts +0 -2
  17. package/dist-client/pages/master/master-page-base.js +1 -3
  18. package/dist-client/pages/master/master-page-base.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-server/index.js +10 -0
  21. package/dist-server/index.js.map +1 -1
  22. package/dist-server/service/approval-route.d.ts +37 -0
  23. package/dist-server/service/approval-route.js +34 -0
  24. package/dist-server/service/approval-route.js.map +1 -0
  25. package/dist-server/service/assignment/assignment-history.d.ts +21 -16
  26. package/dist-server/service/assignment/assignment-history.js +91 -52
  27. package/dist-server/service/assignment/assignment-history.js.map +1 -1
  28. package/dist-server/service/assignment/assignment-write.d.ts +21 -4
  29. package/dist-server/service/assignment/assignment-write.js +41 -9
  30. package/dist-server/service/assignment/assignment-write.js.map +1 -1
  31. package/dist-server/service/assignment/{assignment-claim.d.ts → master-value-claim.d.ts} +31 -7
  32. package/dist-server/service/assignment/{assignment-claim.js → master-value-claim.js} +45 -31
  33. package/dist-server/service/assignment/master-value-claim.js.map +1 -0
  34. package/dist-server/service/index.d.ts +5 -2
  35. package/dist-server/service/index.js +9 -1
  36. package/dist-server/service/index.js.map +1 -1
  37. package/dist-server/service/master-axis.d.ts +14 -3
  38. package/dist-server/service/master-axis.js +16 -15
  39. package/dist-server/service/master-axis.js.map +1 -1
  40. package/dist-server/service/master-change-policy.d.ts +74 -2
  41. package/dist-server/service/master-change-policy.js +187 -21
  42. package/dist-server/service/master-change-policy.js.map +1 -1
  43. package/dist-server/service/material/material-revision-resolver.d.ts +7 -2
  44. package/dist-server/service/material/material-revision-resolver.js +18 -0
  45. package/dist-server/service/material/material-revision-resolver.js.map +1 -1
  46. package/dist-server/service/material/material-revision-service.d.ts +32 -2
  47. package/dist-server/service/material/material-revision-service.js +129 -0
  48. package/dist-server/service/material/material-revision-service.js.map +1 -1
  49. package/dist-server/service/material/material-revision.d.ts +7 -5
  50. package/dist-server/service/material/material-revision.js +2 -2
  51. package/dist-server/service/material/material-revision.js.map +1 -1
  52. package/dist-server/service/recipe/index.d.ts +1 -1
  53. package/dist-server/service/recipe/recipe-revision-resolver.d.ts +7 -2
  54. package/dist-server/service/recipe/recipe-revision-resolver.js +18 -0
  55. package/dist-server/service/recipe/recipe-revision-resolver.js.map +1 -1
  56. package/dist-server/service/recipe/recipe-revision-service.d.ts +33 -2
  57. package/dist-server/service/recipe/recipe-revision-service.js +139 -0
  58. package/dist-server/service/recipe/recipe-revision-service.js.map +1 -1
  59. package/dist-server/service/recipe/recipe-revision.d.ts +8 -6
  60. package/dist-server/service/recipe/recipe-revision.js +2 -2
  61. package/dist-server/service/recipe/recipe-revision.js.map +1 -1
  62. package/dist-server/service/revision-baseline.d.ts +63 -0
  63. package/dist-server/service/revision-baseline.js +72 -0
  64. package/dist-server/service/revision-baseline.js.map +1 -0
  65. package/dist-server/service/test-spec/test-spec-revision-resolver.d.ts +7 -2
  66. package/dist-server/service/test-spec/test-spec-revision-resolver.js +18 -0
  67. package/dist-server/service/test-spec/test-spec-revision-resolver.js.map +1 -1
  68. package/dist-server/service/test-spec/test-spec-revision-service.d.ts +37 -2
  69. package/dist-server/service/test-spec/test-spec-revision-service.js +122 -0
  70. package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -1
  71. package/dist-server/service/test-spec/test-spec-revision.d.ts +7 -5
  72. package/dist-server/service/test-spec/test-spec-revision.js +2 -2
  73. package/dist-server/service/test-spec/test-spec-revision.js.map +1 -1
  74. package/dist-server/tsconfig.tsbuildinfo +1 -1
  75. package/package.json +5 -4
  76. package/translations/en.json +63 -2
  77. package/translations/ja.json +62 -1
  78. package/translations/ko.json +63 -2
  79. package/translations/ms.json +62 -1
  80. package/translations/zh.json +64 -1
  81. package/dist-server/service/assignment/assignment-claim.js.map +0 -1
@@ -1,5 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const shell_1 = require("@things-factory/shell");
5
+ const revision_baseline_1 = require("./service/revision-baseline");
4
6
  tslib_1.__exportStar(require("./service"), exports);
7
+ /*
8
+ * 판을 올린 설치본의 기준정보에 첫 개정본을 세운다(2026-09-24). 개정본이 이미 있는 것은 손대지 않으므로
9
+ * 기동마다 불러도 된다. 무엇이 실패해도 던지지 않는다 — 서버를 반쯤 뜬 채로 두지 않는다
10
+ * (§`revision-baseline`).
11
+ */
12
+ process.on('bootstrap-module-start', () => {
13
+ (0, revision_baseline_1.standMasterBaselinesAtBoot)((0, shell_1.getDataSource)()).catch(error => console.error(`[ops-master] 기준선을 세우지 못했습니다 — ${error?.message ?? error}`));
14
+ });
5
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,oDAAyB","sourcesContent":["export * from './service'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,iDAAqD;AAErD,mEAAwE;AAExE,oDAAyB;AAEzB;;;;GAIG;AACH,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,GAAG,EAAE;IAC/C,IAAA,8CAA0B,EAAC,IAAA,qBAAa,GAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CACxD,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,EAAE,OAAO,IAAI,KAAK,EAAE,CAAC,CAC1E,CAAA;AACH,CAAC,CAAC,CAAA","sourcesContent":["import { getDataSource } from '@things-factory/shell'\n\nimport { standMasterBaselinesAtBoot } from './service/revision-baseline'\n\nexport * from './service'\n\n/*\n * 판을 올린 설치본의 기준정보에 첫 개정본을 세운다(2026-09-24). 개정본이 이미 있는 것은 손대지 않으므로\n * 기동마다 불러도 된다. 무엇이 실패해도 던지지 않는다 — 서버를 반쯤 뜬 채로 두지 않는다\n * (§`revision-baseline`).\n */\nprocess.on('bootstrap-module-start' as any, () => {\n standMasterBaselinesAtBoot(getDataSource()).catch(error =>\n console.error(`[ops-master] 기준선을 세우지 못했습니다 — ${error?.message ?? error}`)\n )\n})\n"]}
@@ -0,0 +1,37 @@
1
+ import { EntityManager } from 'typeorm';
2
+ /**
3
+ * **규제를 걸려면 누가 결재할지가 정해져 있어야 한다.**
4
+ *
5
+ * ── 왜 이 길이 필요한가 ────────────────────────────────────────────────────
6
+ * 소유자가 어느 축을 「승인 후 변경」으로 바꾸면, 그 축의 기준정보는 그때부터 결재를 지나야 바뀐다.
7
+ * 그런데 결재선은 양식이 아니라 **그 현장이 정하는 것**이라, 비어 있을 수 있다 — 기동 로그가 매번
8
+ * 「결재선 비어 있음 miratek, system」이라고 말하고 있었다. 비어 있는 채로 규제를 걸면 상신은 되고
9
+ * 내려 줄 사람이 없어서, 그 축의 기준정보가 통째로 멈춘다.
10
+ *
11
+ * ── 왜 여기서 직접 안 보나 ─────────────────────────────────────────────────
12
+ * 결재 단계 수는 `worklist` 가 안다. 그런데 **어느 축이 어느 결재 양식을 쓰는가**는 앱이 안다 —
13
+ * plant 의 `activity-recipe-change`·`activity-work-shift-change` 가 그것이다. 이 묶음은 둘 다
14
+ * 모르므로, 아는 쪽이 자기 짝을 등록한다.
15
+ *
16
+ * ⚠ **아무도 등록 안 했으면 지나간다.** 등록이 없다는 것은 「결재선이 없다」가 아니라 「이 앱은 이
17
+ * 물음에 답할 길을 아직 안 냈다」이다. 모른다고 막으면 아직 등록하지 않은 앱이 전부 멈춘다.
18
+ */
19
+ export interface ApprovalRouteReport {
20
+ /** 이 축을 규제로 걸어도 되는가 — 결재할 사람이 정해져 있는가. */
21
+ ready: boolean;
22
+ /** 어느 결재 양식을 말하는가. 사람이 어디를 고쳐야 하는지 알아야 한다. */
23
+ activity?: string;
24
+ }
25
+ export type ApprovalRouteInspector = (manager: EntityManager, domainId: string, axis: string) => Promise<ApprovalRouteReport | undefined>;
26
+ /** 앱이 자기 결재 양식을 아는 길을 낸다. 나중 것이 앞의 것을 대신한다. */
27
+ export declare function registerApprovalRouteInspector(one: ApprovalRouteInspector): void;
28
+ /** 시험이 자기 자리를 되돌린다. */
29
+ export declare function clearApprovalRouteInspector(): void;
30
+ /**
31
+ * 이 축의 결재 길이 준비됐나 — **묻기만 한다.**
32
+ *
33
+ * 화면이 누르기 전에 먼저 본다. 눌러서 거절당하고 나서야 「결재선을 정하십시오」를 듣는 것은, 고를 수
34
+ * 없는 것을 골라 보게 한 것이다.
35
+ */
36
+ export declare function inspectApprovalRoute(manager: EntityManager, domainId: string, axis: string): Promise<ApprovalRouteReport>;
37
+ export declare function assertApprovalRouteReady(manager: EntityManager, domainId: string, axis: string): Promise<void>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerApprovalRouteInspector = registerApprovalRouteInspector;
4
+ exports.clearApprovalRouteInspector = clearApprovalRouteInspector;
5
+ exports.inspectApprovalRoute = inspectApprovalRoute;
6
+ exports.assertApprovalRouteReady = assertApprovalRouteReady;
7
+ const shell_1 = require("@things-factory/shell");
8
+ let inspector;
9
+ /** 앱이 자기 결재 양식을 아는 길을 낸다. 나중 것이 앞의 것을 대신한다. */
10
+ function registerApprovalRouteInspector(one) {
11
+ inspector = one;
12
+ }
13
+ /** 시험이 자기 자리를 되돌린다. */
14
+ function clearApprovalRouteInspector() {
15
+ inspector = undefined;
16
+ }
17
+ /**
18
+ * 이 축의 결재 길이 준비됐나 — **묻기만 한다.**
19
+ *
20
+ * 화면이 누르기 전에 먼저 본다. 눌러서 거절당하고 나서야 「결재선을 정하십시오」를 듣는 것은, 고를 수
21
+ * 없는 것을 골라 보게 한 것이다.
22
+ */
23
+ function inspectApprovalRoute(manager, domainId, axis) {
24
+ return inspector?.(manager, domainId, axis);
25
+ }
26
+ async function assertApprovalRouteReady(manager, domainId, axis) {
27
+ const report = await inspector?.(manager, domainId, axis);
28
+ /* 답이 없으면 이 앱은 아직 이 물음에 답할 길이 없다 — 막지 않는다. */
29
+ if (!report || report.ready) {
30
+ return;
31
+ }
32
+ throw new shell_1.Refusal('MASTER_APPROVAL_LINE_REQUIRED', { axis, activity: report.activity }, 'Set the approval line for this activity before requiring approval on this master axis.');
33
+ }
34
+ //# sourceMappingURL=approval-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-route.js","sourceRoot":"","sources":["../../server/service/approval-route.ts"],"names":[],"mappings":";;AAoCA,wEAEC;AAGD,kEAEC;AAQD,oDAEC;AAED,4DAaC;AAnED,iDAA+C;AAgC/C,IAAI,SAA6C,CAAA;AAEjD,+CAA+C;AAC/C,SAAgB,8BAA8B,CAAC,GAA2B;IACxE,SAAS,GAAG,GAAG,CAAA;AACjB,CAAC;AAED,uBAAuB;AACvB,SAAgB,2BAA2B;IACzC,SAAS,GAAG,SAAS,CAAA;AACvB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAC,OAAsB,EAAE,QAAgB,EAAE,IAAY;IACzF,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;AAC7C,CAAC;AAEM,KAAK,UAAU,wBAAwB,CAAC,OAAsB,EAAE,QAAgB,EAAE,IAAY;IACnG,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;IAEzD,6CAA6C;IAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,OAAM;IACR,CAAC;IAED,MAAM,IAAI,eAAO,CACf,+BAA+B,EAC/B,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EACnC,wFAAwF,CACzF,CAAA;AACH,CAAC","sourcesContent":["import { EntityManager } from 'typeorm'\nimport { Refusal } from '@things-factory/shell'\n\n/**\n * **규제를 걸려면 누가 결재할지가 정해져 있어야 한다.**\n *\n * ── 왜 이 길이 필요한가 ────────────────────────────────────────────────────\n * 소유자가 어느 축을 「승인 후 변경」으로 바꾸면, 그 축의 기준정보는 그때부터 결재를 지나야 바뀐다.\n * 그런데 결재선은 양식이 아니라 **그 현장이 정하는 것**이라, 비어 있을 수 있다 — 기동 로그가 매번\n * 「결재선 비어 있음 miratek, system」이라고 말하고 있었다. 비어 있는 채로 규제를 걸면 상신은 되고\n * 내려 줄 사람이 없어서, 그 축의 기준정보가 통째로 멈춘다.\n *\n * ── 왜 여기서 직접 안 보나 ─────────────────────────────────────────────────\n * 결재 단계 수는 `worklist` 가 안다. 그런데 **어느 축이 어느 결재 양식을 쓰는가**는 앱이 안다 —\n * plant 의 `activity-recipe-change`·`activity-work-shift-change` 가 그것이다. 이 묶음은 둘 다\n * 모르므로, 아는 쪽이 자기 짝을 등록한다.\n *\n * ⚠ **아무도 등록 안 했으면 지나간다.** 등록이 없다는 것은 「결재선이 없다」가 아니라 「이 앱은 이\n * 물음에 답할 길을 아직 안 냈다」이다. 모른다고 막으면 아직 등록하지 않은 앱이 전부 멈춘다.\n */\nexport interface ApprovalRouteReport {\n /** 이 축을 규제로 걸어도 되는가 — 결재할 사람이 정해져 있는가. */\n ready: boolean\n /** 어느 결재 양식을 말하는가. 사람이 어디를 고쳐야 하는지 알아야 한다. */\n activity?: string\n}\n\nexport type ApprovalRouteInspector = (\n manager: EntityManager,\n domainId: string,\n axis: string\n) => Promise<ApprovalRouteReport | undefined>\n\nlet inspector: ApprovalRouteInspector | undefined\n\n/** 앱이 자기 결재 양식을 아는 길을 낸다. 나중 것이 앞의 것을 대신한다. */\nexport function registerApprovalRouteInspector(one: ApprovalRouteInspector) {\n inspector = one\n}\n\n/** 시험이 자기 자리를 되돌린다. */\nexport function clearApprovalRouteInspector() {\n inspector = undefined\n}\n\n/**\n * 이 축의 결재 길이 준비됐나 — **묻기만 한다.**\n *\n * 화면이 누르기 전에 먼저 본다. 눌러서 거절당하고 나서야 「결재선을 정하십시오」를 듣는 것은, 고를 수\n * 없는 것을 골라 보게 한 것이다.\n */\nexport function inspectApprovalRoute(manager: EntityManager, domainId: string, axis: string) {\n return inspector?.(manager, domainId, axis)\n}\n\nexport async function assertApprovalRouteReady(manager: EntityManager, domainId: string, axis: string) {\n const report = await inspector?.(manager, domainId, axis)\n\n /* 답이 없으면 이 앱은 아직 이 물음에 답할 길이 없다 — 막지 않는다. */\n if (!report || report.ready) {\n return\n }\n\n throw new Refusal(\n 'MASTER_APPROVAL_LINE_REQUIRED',\n { axis, activity: report.activity },\n 'Set the approval line for this activity before requiring approval on this master axis.'\n )\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { EntityManager } from 'typeorm';
2
2
  import { Domain } from '@things-factory/shell';
3
- import { AssignmentClaim } from './assignment-claim';
3
+ import { MasterValueClaim } from './master-value-claim';
4
4
  /**
5
5
  * 주장을 남기고 읽는 규칙 — **덮어쓰지 않는다.**
6
6
  *
@@ -30,12 +30,19 @@ export interface ClaimTarget {
30
30
  * **없으면 `undefined` 다 — 「선언 없음」과 다르다.** 선언 없음은 `value` 가 `null` 인 주장이 있는
31
31
  * 것이고, 여기서 `undefined` 는 **그때 무엇이었는지 모른다**는 뜻이다.
32
32
  */
33
- export declare function claimAt(manager: EntityManager, domain: Domain, target: ClaimTarget, at: ClaimAt): Promise<AssignmentClaim | undefined>;
33
+ export declare function claimAt(manager: EntityManager, domain: Domain, target: ClaimTarget, at: ClaimAt): Promise<MasterValueClaim | undefined>;
34
+ /**
35
+ * 이 대상에 **주장이 한 건이라도 있나** — 창 밖의 것까지 센다.
36
+ *
37
+ * 창 안에 없는 것과 아예 없는 것은 다른 사실이다. 아예 없으면 이 설치본이 아직 이력을 안 쓰는
38
+ * 것이고, 있는데 그 창을 안 덮으면 **구멍**이다. 부르는 쪽이 둘을 다르게 다뤄야 해서 따로 묻는다.
39
+ */
40
+ export declare function hasAssignmentHistory(manager: EntityManager, domain: Domain, target: ClaimTarget): Promise<boolean>;
34
41
  /** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */
35
42
  export declare function claimsDuring(manager: EntityManager, domain: Domain, target: ClaimTarget, window: {
36
43
  from: Date;
37
44
  to: Date;
38
- }, asOfRecord?: Date): Promise<AssignmentClaim[]>;
45
+ }, asOfRecord?: Date): Promise<MasterValueClaim[]>;
39
46
  export interface RecordClaim {
40
47
  target: ClaimTarget;
41
48
  value: Record<string, unknown> | null;
@@ -45,6 +52,12 @@ export interface RecordClaim {
45
52
  validTo?: Date;
46
53
  reason: 'initial' | 'change' | 'correction';
47
54
  recorder?: unknown;
55
+ /**
56
+ * 바뀌기 전 값. **첫 주장일 때만 쓴다** — 그 앞을 덮는 줄을 같이 세운다.
57
+ *
58
+ * 부르는 쪽이 같은 트랜잭션에서 이미 알고 있는 값이다(`claimAssignmentChanges`).
59
+ */
60
+ previous?: Record<string, unknown> | null;
48
61
  /**
49
62
  * 내가 읽었을 때 서 있던 주장들의 id.
50
63
  *
@@ -59,7 +72,7 @@ export interface RecordClaim {
59
72
  * 부르는 쪽이 마스터 변경과 **같은 트랜잭션**으로 부른다 — 갈라지면 값은 바뀌고 이력은 없는 상태가
60
73
  * 생긴다.
61
74
  */
62
- export declare function recordClaim(manager: EntityManager, domain: Domain, said: RecordClaim, now?: Date): Promise<AssignmentClaim>;
75
+ export declare function recordClaim(manager: EntityManager, domain: Domain, said: RecordClaim, now?: Date): Promise<MasterValueClaim>;
63
76
  /**
64
77
  * 도래한 미래 주장을 마스터 행에 옮긴다 — **앱의 주기 작업이 부른다.**
65
78
  *
@@ -70,18 +83,10 @@ export declare function projectDueClaims(manager: EntityManager, domain: Domain,
70
83
  axis: string;
71
84
  entity: Function;
72
85
  }[], now?: Date): Promise<number>;
73
- /** 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다. */
74
- export declare function valueOnRow(claim: AssignmentClaim): unknown;
75
86
  /**
76
- * 도입할 때 — **지금 값을 초기 상태로 적는다.**
77
- *
78
- * 과거를 소급해 만드는 것과 다르다. 「도입 시각에 이 값이었다는 것을 확인했다」는 주장이고, **그
79
- * 전은 결측 그대로** 둔다. 이것이 없으면 **한 번도 안 옮긴 설비가 영영 이력이 빈다** — 그런 설비가
80
- * 가장 많다.
87
+ * 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다.
81
88
  *
82
- * 이미 주장이 있는 행은 건너뛴다. 두 번 불러도 같다.
89
+ * ⚠ **관계 칸에는 이름을 돌려준다** — 그 칸이 이름을 담기 때문이다. 식별자는 주장 안에만 있고,
90
+ * 그것이 이름이 바뀌어도 과거가 안 끊기는 까닭이다.
83
91
  */
84
- export declare function recordInitialAssignments(manager: EntityManager, domain: Domain, entities: {
85
- axis: string;
86
- entity: Function;
87
- }[], at?: Date, recorder?: unknown): Promise<number>;
92
+ export declare function valueOnRow(claim: MasterValueClaim): unknown;
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.claimAt = claimAt;
4
+ exports.hasAssignmentHistory = hasAssignmentHistory;
4
5
  exports.claimsDuring = claimsDuring;
5
6
  exports.recordClaim = recordClaim;
6
7
  exports.projectDueClaims = projectDueClaims;
7
8
  exports.valueOnRow = valueOnRow;
8
- exports.recordInitialAssignments = recordInitialAssignments;
9
9
  const typeorm_1 = require("typeorm");
10
10
  const shell_1 = require("@things-factory/shell");
11
11
  const master_axis_1 = require("../master-axis");
12
- const assignment_claim_1 = require("./assignment-claim");
12
+ const master_value_claim_1 = require("./master-value-claim");
13
13
  const assignment_write_1 = require("./assignment-write");
14
14
  const ms = (at) => at.getTime();
15
15
  /** 그 기록시점에 서 있던 주장들 — 물러난 것은 뺀다. */
16
16
  async function standingClaims(manager, domain, target, asOfRecord) {
17
- const rows = await manager.find(assignment_claim_1.AssignmentClaim, {
17
+ const rows = await manager.find(master_value_claim_1.MasterValueClaim, {
18
18
  where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }
19
19
  });
20
20
  const at = asOfRecord ? ms(asOfRecord) : undefined;
@@ -35,20 +35,32 @@ async function standingClaims(manager, domain, target, asOfRecord) {
35
35
  async function claimAt(manager, domain, target, at) {
36
36
  const standing = await standingClaims(manager, domain, target, at.asOfRecord);
37
37
  const when = ms(at.asOfBusiness);
38
- const covering = standing.filter(one => ms(one.validFrom) <= when && (!one.validTo || when < ms(one.validTo)));
38
+ const covering = standing.filter(one => (!one.validFrom || ms(one.validFrom) <= when) && (!one.validTo || when < ms(one.validTo)));
39
39
  if (covering.length > 1) {
40
- throw new shell_1.Refusal('assignment-claim-conflict', { axis: target.axis, field: target.field, at: at.asOfBusiness.toISOString() }, 'two assignment claims cover the same moment. fix the history before asking.');
40
+ throw new shell_1.Refusal('master-value-claim-conflict', { axis: target.axis, field: target.field, at: at.asOfBusiness.toISOString() }, 'two assignment claims cover the same moment. fix the history before asking.');
41
41
  }
42
42
  return covering[0];
43
43
  }
44
+ /**
45
+ * 이 대상에 **주장이 한 건이라도 있나** — 창 밖의 것까지 센다.
46
+ *
47
+ * 창 안에 없는 것과 아예 없는 것은 다른 사실이다. 아예 없으면 이 설치본이 아직 이력을 안 쓰는
48
+ * 것이고, 있는데 그 창을 안 덮으면 **구멍**이다. 부르는 쪽이 둘을 다르게 다뤄야 해서 따로 묻는다.
49
+ */
50
+ async function hasAssignmentHistory(manager, domain, target) {
51
+ const count = await manager.count(master_value_claim_1.MasterValueClaim, {
52
+ where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }
53
+ });
54
+ return count > 0;
55
+ }
44
56
  /** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */
45
57
  async function claimsDuring(manager, domain, target, window, asOfRecord) {
46
58
  const standing = await standingClaims(manager, domain, target, asOfRecord);
47
59
  const from = ms(window.from);
48
60
  const to = ms(window.to);
49
61
  return standing
50
- .filter(one => ms(one.validFrom) < to && (!one.validTo || from < ms(one.validTo)))
51
- .sort((a, b) => ms(a.validFrom) - ms(b.validFrom));
62
+ .filter(one => (!one.validFrom || ms(one.validFrom) < to) && (!one.validTo || from < ms(one.validTo)))
63
+ .sort((a, b) => (a.validFrom ? ms(a.validFrom) : -Infinity) - (b.validFrom ? ms(b.validFrom) : -Infinity));
52
64
  }
53
65
  /**
54
66
  * 주장을 남긴다. **정정이면 겹치는 것을 물러나게 하고 앞뒤 조각을 다시 주장한다.**
@@ -57,18 +69,47 @@ async function claimsDuring(manager, domain, target, window, asOfRecord) {
57
69
  * 생긴다.
58
70
  */
59
71
  async function recordClaim(manager, domain, said, now = new Date()) {
60
- const declared = (0, assignment_claim_1.assignmentField)(said.target.axis, said.target.field);
72
+ const declared = (0, master_value_claim_1.claimedField)(said.target.axis, said.target.field);
61
73
  if (!declared) {
62
74
  throw new shell_1.Refusal('assignment-field-not-declared', { axis: said.target.axis, field: said.target.field }, 'this field does not keep an assignment history. declare it first.');
63
75
  }
64
76
  await assertUsableValue(manager, domain, declared, said, now);
77
+ /*
78
+ * **주장은 늘 식별자로 가리킨다.** 부르는 쪽이 이름을 줘도 여기서 푼다 — 한 자리에서 풀지 않으면
79
+ * 어떤 길로는 이름이 그대로 들어가고, 그 줄은 이름을 바꾸는 날 끊긴다.
80
+ */
81
+ const value = await normalisedValue(manager, domain, declared, said.value);
65
82
  const validFrom = said.validFrom ?? now;
66
83
  const validTo = said.validTo;
67
- const standing = await standingClaims(manager, domain, said.target, undefined);
84
+ let standing = await standingClaims(manager, domain, said.target, undefined);
85
+ /*
86
+ * ── 첫 주장이면 **바뀌기 전 값도 같이 적는다** (2026-09-23) ───────────────
87
+ * 새 값만 적으면 그 앞이 통째로 구멍이 된다 — 3월에 옮긴 설비의 2월을 아무 주장도 안 덮는다.
88
+ * 그래서 그 칸이 처음 바뀔 때 옛 값을 `[?, 이 시각)` 으로 같이 세운다. 앞끝은 비운다: 언제부터
89
+ * 그랬는지는 아무도 안 적었고, 시각을 지어내면 적은 적 없는 것이 적힌 것이 된다.
90
+ *
91
+ * 이것 덕분에 **아무것도 안 눌러도 된다.** 주장이 하나도 없다는 것은 「이 칸은 한 번도 안 바뀌었다」
92
+ * 는 뜻이 되고, 그때는 오늘 값이 과거에도 그 값이었다. 채택이라는 절차가 필요 없어진다.
93
+ */
94
+ if (!standing.length && said.previous !== undefined && said.reason !== 'initial') {
95
+ await manager.save(master_value_claim_1.MasterValueClaim, {
96
+ domain,
97
+ axis: said.target.axis,
98
+ rowId: said.target.rowId,
99
+ field: said.target.field,
100
+ value: said.previous,
101
+ validFrom: null,
102
+ validTo: validFrom,
103
+ recordedAt: now,
104
+ reason: 'initial',
105
+ recorder: said.recorder
106
+ });
107
+ standing = await standingClaims(manager, domain, said.target, undefined);
108
+ }
68
109
  const from = ms(validFrom);
69
110
  const to = validTo ? ms(validTo) : undefined;
70
111
  const overlapping = standing.filter(one => {
71
- const start = ms(one.validFrom);
112
+ const start = one.validFrom ? ms(one.validFrom) : Number.NEGATIVE_INFINITY;
72
113
  const end = one.validTo ? ms(one.validTo) : undefined;
73
114
  return (to === undefined || start < to) && (end === undefined || from < end);
74
115
  });
@@ -81,7 +122,7 @@ async function recordClaim(manager, domain, said, now = new Date()) {
81
122
  const read = new Set(said.expecting);
82
123
  const changed = now.size !== read.size || [...now].some(id => !read.has(id));
83
124
  if (changed) {
84
- throw new shell_1.Refusal('assignment-claim-superseded', { axis: said.target.axis, field: said.target.field }, 'this history changed while you were editing. read it again and decide again.');
125
+ throw new shell_1.Refusal('master-value-claim-superseded', { axis: said.target.axis, field: said.target.field }, 'this history changed while you were editing. read it again and decide again.');
85
126
  }
86
127
  }
87
128
  for (const one of overlapping) {
@@ -89,12 +130,12 @@ async function recordClaim(manager, domain, said, now = new Date()) {
89
130
  * 이미 물러난 것을 다시 물러나게 하려는 것은 **동시 정정**이다. 덮지 않고 거절한다.
90
131
  */
91
132
  if (one.supersededAt) {
92
- throw new shell_1.Refusal('assignment-claim-superseded', { axis: said.target.axis, field: said.target.field }, 'this history changed while you were editing. read it again and decide again.');
133
+ throw new shell_1.Refusal('master-value-claim-superseded', { axis: said.target.axis, field: said.target.field }, 'this history changed while you were editing. read it again and decide again.');
93
134
  }
94
- await manager.save(assignment_claim_1.AssignmentClaim, { ...one, supersededAt: now });
95
- /* 앞 조각 — 물러난 주장이 이 정정보다 먼저 시작했으면 그만큼은 그대로였다. */
96
- if (ms(one.validFrom) < from) {
97
- await manager.save(assignment_claim_1.AssignmentClaim, {
135
+ await manager.save(master_value_claim_1.MasterValueClaim, { ...one, supersededAt: now });
136
+ /* 앞 조각 — 물러난 주장이 이 정정보다 먼저 시작했으면 그만큼은 그대로였다. 앞끝이 비면 「쭉」이다. */
137
+ if (!one.validFrom || ms(one.validFrom) < from) {
138
+ await manager.save(master_value_claim_1.MasterValueClaim, {
98
139
  domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
99
140
  value: one.value ?? null, validFrom: one.validFrom, validTo: validFrom,
100
141
  recordedAt: now, reason: 'remainder', recorder: said.recorder
@@ -103,16 +144,16 @@ async function recordClaim(manager, domain, said, now = new Date()) {
103
144
  /* 뒤 조각 — 정정이 끝난 뒤에도 그 주장이 이어졌으면 그만큼도 그대로였다. */
104
145
  const end = one.validTo ? ms(one.validTo) : undefined;
105
146
  if (to !== undefined && (end === undefined || end > to)) {
106
- await manager.save(assignment_claim_1.AssignmentClaim, {
147
+ await manager.save(master_value_claim_1.MasterValueClaim, {
107
148
  domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
108
149
  value: one.value ?? null, validFrom: validTo, validTo: one.validTo,
109
150
  recordedAt: now, reason: 'remainder', recorder: said.recorder
110
151
  });
111
152
  }
112
153
  }
113
- return manager.save(assignment_claim_1.AssignmentClaim, {
154
+ return manager.save(master_value_claim_1.MasterValueClaim, {
114
155
  domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
115
- value: said.value, validFrom, validTo, recordedAt: now, reason: said.reason, recorder: said.recorder
156
+ value, validFrom, validTo, recordedAt: now, reason: said.reason, recorder: said.recorder
116
157
  });
117
158
  }
118
159
  /**
@@ -124,9 +165,9 @@ async function recordClaim(manager, domain, said, now = new Date()) {
124
165
  async function projectDueClaims(manager, domain, entities, now = new Date()) {
125
166
  let moved = 0;
126
167
  for (const { axis, entity } of entities) {
127
- const fields = [...new Set(ASSIGNMENT_FIELDS_OF(axis))];
168
+ const fields = [...new Set(claimedFieldsOfAxis(axis))];
128
169
  for (const field of fields) {
129
- const due = await manager.find(assignment_claim_1.AssignmentClaim, {
170
+ const due = await manager.find(master_value_claim_1.MasterValueClaim, {
130
171
  where: {
131
172
  domain: { id: domain.id }, axis, field,
132
173
  supersededAt: (0, typeorm_1.IsNull)(), validFrom: (0, typeorm_1.LessThanOrEqual)(now), reason: (0, typeorm_1.Not)('remainder')
@@ -151,10 +192,15 @@ async function projectDueClaims(manager, domain, entities, now = new Date()) {
151
192
  }
152
193
  return moved;
153
194
  }
154
- function ASSIGNMENT_FIELDS_OF(axis) {
155
- return assignment_claim_1.ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field);
195
+ function claimedFieldsOfAxis(axis) {
196
+ return master_value_claim_1.CLAIMED_FIELDS.filter(one => one.axis === axis).map(one => one.field);
156
197
  }
157
- /** 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다. */
198
+ /**
199
+ * 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다.
200
+ *
201
+ * ⚠ **관계 칸에는 이름을 돌려준다** — 그 칸이 이름을 담기 때문이다. 식별자는 주장 안에만 있고,
202
+ * 그것이 이름이 바뀌어도 과거가 안 끊기는 까닭이다.
203
+ */
158
204
  function valueOnRow(claim) {
159
205
  const value = claim.value;
160
206
  if (value === null || value === undefined) {
@@ -163,7 +209,8 @@ function valueOnRow(claim) {
163
209
  if (Array.isArray(value.names)) {
164
210
  return value.names;
165
211
  }
166
- return value.ref ?? null;
212
+ const said = value;
213
+ return said.label ?? said.ref ?? null;
167
214
  }
168
215
  /**
169
216
  * 값이 이 칸의 모양인가, 그리고 **우리 도메인의 것인가.**
@@ -176,6 +223,12 @@ async function assertUsableValue(manager, domain, declared, said, now) {
176
223
  if (value === null || value === undefined) {
177
224
  return;
178
225
  }
226
+ if (declared.kind === 'literal') {
227
+ if (typeof value.literal !== 'string') {
228
+ throw new shell_1.Refusal('claim-value-shape', { field: declared.field }, 'this field takes the value itself, as written.');
229
+ }
230
+ return;
231
+ }
179
232
  if (declared.kind === 'names') {
180
233
  if (!Array.isArray(value.names)) {
181
234
  throw new shell_1.Refusal('assignment-value-shape', { field: declared.field }, 'this field takes a list of names.');
@@ -190,12 +243,17 @@ async function assertUsableValue(manager, domain, declared, said, now) {
190
243
  if (!axis) {
191
244
  return;
192
245
  }
193
- const pointed = await manager.findOne(axis.entity, { where: { id: ref, domain: { id: domain.id } } });
246
+ /* 이름으로 적힌 칸도 있으므로 둘 다 찾아 본다(§`rowOf`). */
247
+ const pointed = await (0, assignment_write_1.rowOf)(manager, domain, declared.target, ref);
194
248
  if (!pointed) {
195
249
  throw new shell_1.Refusal('assignment-value-unknown', { field: declared.field, ref }, 'that row is not in this site. point at a row that is.');
196
250
  }
197
251
  if (declared.field === 'parentId') {
198
- await assertNoCycleAt(manager, domain, said.target.rowId, ref, said.validFrom ?? now);
252
+ /*
253
+ * **푼 식별자로 걷는다.** 안 풀고 걸으면 이름으로 온 값은 아무 주장도 못 찾아 고리를 그냥
254
+ * 지나간다 — 실제로 그랬다(2026-09-22).
255
+ */
256
+ await assertNoCycleAt(manager, domain, said.target.rowId, pointed.id, said.validFrom ?? now);
199
257
  }
200
258
  }
201
259
  /** 그 시점의 부모 사슬을 걸어 본다. 자기 자신에 닿으면 고리다. */
@@ -220,31 +278,12 @@ async function assertNoCycleAt(manager, domain, rowId, parentRef, at) {
220
278
  *
221
279
  * 이미 주장이 있는 행은 건너뛴다. 두 번 불러도 같다.
222
280
  */
223
- async function recordInitialAssignments(manager, domain, entities, at = new Date(), recorder) {
224
- let written = 0;
225
- for (const { axis, entity } of entities) {
226
- const fields = assignment_claim_1.ASSIGNMENT_FIELDS.filter(one => one.axis === axis);
227
- if (!fields.length) {
228
- continue;
229
- }
230
- const rows = await manager.find(entity, { where: { domain: { id: domain.id } } });
231
- for (const row of rows) {
232
- for (const declared of fields) {
233
- const already = await manager.findOne(assignment_claim_1.AssignmentClaim, {
234
- where: { domain: { id: domain.id }, axis, rowId: String(row.id), field: declared.field }
235
- });
236
- if (already) {
237
- continue;
238
- }
239
- await manager.save(assignment_claim_1.AssignmentClaim, {
240
- domain, axis, rowId: String(row.id), field: declared.field,
241
- value: (0, assignment_write_1.toClaimValue)(declared.kind, row[declared.field]),
242
- validFrom: at, recordedAt: at, reason: 'initial', recorder
243
- });
244
- written++;
245
- }
246
- }
281
+ /** 이름으로 왔든 식별자로 왔든 **식별자와 그때의 이름**으로 만든다. */
282
+ async function normalisedValue(manager, domain, declared, value) {
283
+ if (!value || declared.kind !== 'relation') {
284
+ return value ?? null;
247
285
  }
248
- return written;
286
+ const found = await (0, assignment_write_1.rowOf)(manager, domain, declared.target, String(value.ref ?? ''));
287
+ return found ? { ref: found.id, label: found.name } : value;
249
288
  }
250
289
  //# sourceMappingURL=assignment-history.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assignment-history.js","sourceRoot":"","sources":["../../../server/service/assignment/assignment-history.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, IsNull, LessThanOrEqual, Not, ObjectLiteral } from 'typeorm'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { MASTER_AXES } from '../master-axis'\n\nimport { ASSIGNMENT_FIELDS, AssignmentClaim, assignmentField } from './assignment-claim'\nimport { toClaimValue } from './assignment-write'\n\n/**\n * 주장을 남기고 읽는 규칙 — **덮어쓰지 않는다.**\n *\n * ── 정정이 양옆을 남긴다 ───────────────────────────────────────────────────\n * 겹치는 주장을 물러나게 하는 것만으로는 모자란다. `A [1일, ∞)` 에 「10~15는 B」를 정정하면 A 의\n * **앞뒤 조각을 같은 값으로 다시 주장**해야 최신 답이 `A·B·A` 가 된다. 조각도 새 주장이므로 정정 전\n * 기록시점으로 읽으면 원래 A 하나가 그대로 나온다.\n *\n * ── 동시 정정은 거절한다 ───────────────────────────────────────────────────\n * 물러나게 하려던 주장이 이미 물러나 있으면 충돌이다. **자동으로 다시 시도하지 않는다** — 덮으면\n * 한쪽의 판단이 사라진다. 다시 읽고 다시 정하는 것은 사람의 일이다.\n */\n\nexport interface ClaimAt {\n /** 어느 업무시각의 이야기인가. */\n asOfBusiness: Date\n /** 어느 기록시점까지 알려진 것만 볼 것인가. 안 주면 지금까지 아는 전부다. */\n asOfRecord?: Date\n}\n\nexport interface ClaimTarget {\n axis: string\n rowId: string\n field: string\n}\n\nconst ms = (at: Date) => at.getTime()\n\n/** 그 기록시점에 서 있던 주장들 — 물러난 것은 뺀다. */\nasync function standingClaims(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n asOfRecord?: Date\n): Promise<AssignmentClaim[]> {\n const rows = await manager.find(AssignmentClaim, {\n where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }\n })\n\n const at = asOfRecord ? ms(asOfRecord) : undefined\n\n return rows.filter(one => {\n /* 그 시점 뒤에 적힌 주장은 그때 몰랐다. */\n if (at !== undefined && ms(one.recordedAt) > at) return false\n\n /* 그 시점에 이미 물러났으면 안 본다. 그 뒤에 물러난 것은 그때는 서 있었다. */\n return !(one.supersededAt && (at === undefined || ms(one.supersededAt) <= at))\n })\n}\n\n/**\n * 그 업무시각에 유효했던 주장.\n *\n * **없으면 `undefined` 다 — 「선언 없음」과 다르다.** 선언 없음은 `value` 가 `null` 인 주장이 있는\n * 것이고, 여기서 `undefined` 는 **그때 무엇이었는지 모른다**는 뜻이다.\n */\nexport async function claimAt(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n at: ClaimAt\n): Promise<AssignmentClaim | undefined> {\n const standing = await standingClaims(manager, domain, target, at.asOfRecord)\n const when = ms(at.asOfBusiness)\n\n const covering = standing.filter(one => ms(one.validFrom) <= when && (!one.validTo || when < ms(one.validTo)))\n\n if (covering.length > 1) {\n throw new Refusal(\n 'assignment-claim-conflict',\n { axis: target.axis, field: target.field, at: at.asOfBusiness.toISOString() },\n 'two assignment claims cover the same moment. fix the history before asking.'\n )\n }\n\n return covering[0]\n}\n\n/** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */\nexport async function claimsDuring(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n window: { from: Date; to: Date },\n asOfRecord?: Date\n): Promise<AssignmentClaim[]> {\n const standing = await standingClaims(manager, domain, target, asOfRecord)\n const from = ms(window.from)\n const to = ms(window.to)\n\n return standing\n .filter(one => ms(one.validFrom) < to && (!one.validTo || from < ms(one.validTo)))\n .sort((a, b) => ms(a.validFrom) - ms(b.validFrom))\n}\n\nexport interface RecordClaim {\n target: ClaimTarget\n value: Record<string, unknown> | null\n /** 언제부터 그러한가. 안 주면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */\n validFrom?: Date\n /** 언제까지. 정정에서만 쓴다. */\n validTo?: Date\n reason: 'initial' | 'change' | 'correction'\n recorder?: unknown\n /**\n * 내가 읽었을 때 서 있던 주장들의 id.\n *\n * 주면 **그 사이에 누가 바꿨는지** 본다 — 화면에서 읽고 고치는 동안 다른 사람이 같은 구간을\n * 정정했으면 내 정정이 그것을 덮는다. 덮지 않고 거절한다.\n */\n expecting?: readonly string[]\n}\n\n/**\n * 주장을 남긴다. **정정이면 겹치는 것을 물러나게 하고 앞뒤 조각을 다시 주장한다.**\n *\n * 부르는 쪽이 마스터 변경과 **같은 트랜잭션**으로 부른다 — 갈라지면 값은 바뀌고 이력은 없는 상태가\n * 생긴다.\n */\nexport async function recordClaim(\n manager: EntityManager,\n domain: Domain,\n said: RecordClaim,\n now = new Date()\n): Promise<AssignmentClaim> {\n const declared = assignmentField(said.target.axis, said.target.field)\n\n if (!declared) {\n throw new Refusal(\n 'assignment-field-not-declared',\n { axis: said.target.axis, field: said.target.field },\n 'this field does not keep an assignment history. declare it first.'\n )\n }\n\n await assertUsableValue(manager, domain, declared, said, now)\n\n const validFrom = said.validFrom ?? now\n const validTo = said.validTo\n const standing = await standingClaims(manager, domain, said.target, undefined)\n const from = ms(validFrom)\n const to = validTo ? ms(validTo) : undefined\n\n const overlapping = standing.filter(one => {\n const start = ms(one.validFrom)\n const end = one.validTo ? ms(one.validTo) : undefined\n\n return (to === undefined || start < to) && (end === undefined || from < end)\n })\n\n /*\n * **읽은 뒤에 바뀌었으면 거절한다.** 부르는 쪽이 읽은 것을 주면 지금 서 있는 것과 견준다 —\n * 다르면 그 사이에 누가 고친 것이고, 내 정정이 그 판단을 덮는다.\n */\n if (said.expecting) {\n const now = new Set(overlapping.map(one => one.id))\n const read = new Set(said.expecting)\n const changed = now.size !== read.size || [...now].some(id => !read.has(id))\n\n if (changed) {\n throw new Refusal(\n 'assignment-claim-superseded',\n { axis: said.target.axis, field: said.target.field },\n 'this history changed while you were editing. read it again and decide again.'\n )\n }\n }\n\n for (const one of overlapping) {\n /*\n * 이미 물러난 것을 다시 물러나게 하려는 것은 **동시 정정**이다. 덮지 않고 거절한다.\n */\n if (one.supersededAt) {\n throw new Refusal(\n 'assignment-claim-superseded',\n { axis: said.target.axis, field: said.target.field },\n 'this history changed while you were editing. read it again and decide again.'\n )\n }\n\n await manager.save(AssignmentClaim, { ...one, supersededAt: now })\n\n /* 앞 조각 — 물러난 주장이 이 정정보다 먼저 시작했으면 그만큼은 그대로였다. */\n if (ms(one.validFrom) < from) {\n await manager.save(AssignmentClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value: one.value ?? null, validFrom: one.validFrom, validTo: validFrom,\n recordedAt: now, reason: 'remainder', recorder: said.recorder\n } as Partial<AssignmentClaim>)\n }\n\n /* 뒤 조각 — 정정이 끝난 뒤에도 그 주장이 이어졌으면 그만큼도 그대로였다. */\n const end = one.validTo ? ms(one.validTo) : undefined\n\n if (to !== undefined && (end === undefined || end > to)) {\n await manager.save(AssignmentClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value: one.value ?? null, validFrom: validTo as Date, validTo: one.validTo,\n recordedAt: now, reason: 'remainder', recorder: said.recorder\n } as Partial<AssignmentClaim>)\n }\n }\n\n return manager.save(AssignmentClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value: said.value, validFrom, validTo, recordedAt: now, reason: said.reason, recorder: said.recorder\n } as Partial<AssignmentClaim>)\n}\n\n/**\n * 도래한 미래 주장을 마스터 행에 옮긴다 — **앱의 주기 작업이 부른다.**\n *\n * ops-master 는 라이브러리라 스스로 돌지 않는다. 그 사이 본행은 낡아 있고, **정확해야 하는 소비처는\n * 본행이 아니라 주장을 읽는다.**\n */\nexport async function projectDueClaims(\n manager: EntityManager,\n domain: Domain,\n entities: { axis: string; entity: Function }[],\n now = new Date()\n): Promise<number> {\n let moved = 0\n\n for (const { axis, entity } of entities) {\n const fields = [...new Set(ASSIGNMENT_FIELDS_OF(axis))]\n\n for (const field of fields) {\n const due = await manager.find(AssignmentClaim, {\n where: {\n domain: { id: domain.id }, axis, field,\n supersededAt: IsNull(), validFrom: LessThanOrEqual(now), reason: Not('remainder')\n }\n })\n\n for (const claim of due) {\n const current = await claimAt(manager, domain, { axis, rowId: claim.rowId, field }, { asOfBusiness: now })\n\n if (!current || current.id !== claim.id) {\n continue\n }\n\n const row = await manager.findOne(entity as never, { where: { id: claim.rowId, domain: { id: domain.id } } } as never)\n\n if (!row) {\n continue\n }\n\n const next = valueOnRow(claim)\n\n if ((row as Record<string, unknown>)[field] !== next) {\n await manager.save(entity as never, { ...(row as object), [field]: next } as never)\n moved++\n }\n }\n }\n }\n\n return moved\n}\n\nfunction ASSIGNMENT_FIELDS_OF(axis: string): string[] {\n return ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field)\n}\n\n/** 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다. */\nexport function valueOnRow(claim: AssignmentClaim): unknown {\n const value = claim.value\n\n if (value === null || value === undefined) {\n return null\n }\n\n if (Array.isArray((value as { names?: unknown }).names)) {\n return (value as { names: unknown[] }).names\n }\n\n return (value as { ref?: unknown }).ref ?? null\n}\n\n/**\n * 값이 이 칸의 모양인가, 그리고 **우리 도메인의 것인가.**\n *\n * 다른 도메인의 행을 가리키면 그 도메인이 자기 자료를 고쳐도 이쪽 과거가 흔들린다. 그리고 부모\n * 관계는 **그 시점에** 도는지 본다 — 지금 안 돌아도 그때는 돌 수 있다.\n */\nasync function assertUsableValue(\n manager: EntityManager,\n domain: Domain,\n declared: { axis: string; field: string; kind: 'relation' | 'names'; target?: string },\n said: RecordClaim,\n now: Date\n): Promise<void> {\n const value = said.value\n\n if (value === null || value === undefined) {\n return\n }\n\n if (declared.kind === 'names') {\n if (!Array.isArray((value as { names?: unknown }).names)) {\n throw new Refusal('assignment-value-shape', { field: declared.field }, 'this field takes a list of names.')\n }\n\n return\n }\n\n const ref = (value as { ref?: unknown }).ref\n\n if (typeof ref !== 'string' || !ref) {\n throw new Refusal(\n 'assignment-value-shape',\n { field: declared.field },\n 'this field points at a row, so it needs that row identifier — not its name.'\n )\n }\n\n const axis = MASTER_AXES.find(one => one.key === declared.target)\n\n if (!axis) {\n return\n }\n\n const pointed = await manager.findOne(axis.entity as never, { where: { id: ref, domain: { id: domain.id } } } as never)\n\n if (!pointed) {\n throw new Refusal(\n 'assignment-value-unknown',\n { field: declared.field, ref },\n 'that row is not in this site. point at a row that is.'\n )\n }\n\n if (declared.field === 'parentId') {\n await assertNoCycleAt(manager, domain, said.target.rowId, ref, said.validFrom ?? now)\n }\n}\n\n/** 그 시점의 부모 사슬을 걸어 본다. 자기 자신에 닿으면 고리다. */\nasync function assertNoCycleAt(\n manager: EntityManager,\n domain: Domain,\n rowId: string,\n parentRef: string,\n at: Date\n): Promise<void> {\n const walked = new Set<string>([rowId])\n let cursor: string | undefined = parentRef\n\n while (cursor) {\n if (walked.has(cursor)) {\n throw new Refusal(\n 'assignment-parent-cycle',\n { at: at.toISOString() },\n 'this parent makes a loop at that moment. choose another parent.'\n )\n }\n\n walked.add(cursor)\n\n const claim = await claimAt(manager, domain, { axis: 'operationalLocation', rowId: cursor, field: 'parentId' }, { asOfBusiness: at })\n\n cursor = claim ? ((claim.value as { ref?: string } | null)?.ref ?? undefined) : undefined\n }\n}\n\n/**\n * 도입할 때 — **지금 값을 초기 상태로 적는다.**\n *\n * 과거를 소급해 만드는 것과 다르다. 「도입 시각에 이 값이었다는 것을 확인했다」는 주장이고, **그\n * 전은 결측 그대로** 둔다. 이것이 없으면 **한 번도 안 옮긴 설비가 영영 이력이 빈다** — 그런 설비가\n * 가장 많다.\n *\n * 이미 주장이 있는 행은 건너뛴다. 두 번 불러도 같다.\n */\nexport async function recordInitialAssignments(\n manager: EntityManager,\n domain: Domain,\n entities: { axis: string; entity: Function }[],\n at = new Date(),\n recorder?: unknown\n): Promise<number> {\n let written = 0\n\n for (const { axis, entity } of entities) {\n const fields = ASSIGNMENT_FIELDS.filter(one => one.axis === axis)\n\n if (!fields.length) {\n continue\n }\n\n const rows: ObjectLiteral[] = await manager.find(entity as never, { where: { domain: { id: domain.id } } } as never)\n\n for (const row of rows) {\n for (const declared of fields) {\n const already = await manager.findOne(AssignmentClaim, {\n where: { domain: { id: domain.id }, axis, rowId: String(row.id), field: declared.field }\n })\n\n if (already) {\n continue\n }\n\n await manager.save(AssignmentClaim, {\n domain, axis, rowId: String(row.id), field: declared.field,\n value: toClaimValue(declared.kind, row[declared.field]),\n validFrom: at, recordedAt: at, reason: 'initial', recorder\n } as Partial<AssignmentClaim>)\n written++\n }\n }\n }\n\n return written\n}\n"]}
1
+ {"version":3,"file":"assignment-history.js","sourceRoot":"","sources":["../../../server/service/assignment/assignment-history.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, IsNull, LessThanOrEqual, Not, ObjectLiteral } from 'typeorm'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { MASTER_AXES } from '../master-axis'\n\nimport { CLAIMED_FIELDS, MasterValueClaim, claimedField } from './master-value-claim'\nimport { rowOf, toClaimValue } from './assignment-write'\n\n/**\n * 주장을 남기고 읽는 규칙 — **덮어쓰지 않는다.**\n *\n * ── 정정이 양옆을 남긴다 ───────────────────────────────────────────────────\n * 겹치는 주장을 물러나게 하는 것만으로는 모자란다. `A [1일, ∞)` 에 「10~15는 B」를 정정하면 A 의\n * **앞뒤 조각을 같은 값으로 다시 주장**해야 최신 답이 `A·B·A` 가 된다. 조각도 새 주장이므로 정정 전\n * 기록시점으로 읽으면 원래 A 하나가 그대로 나온다.\n *\n * ── 동시 정정은 거절한다 ───────────────────────────────────────────────────\n * 물러나게 하려던 주장이 이미 물러나 있으면 충돌이다. **자동으로 다시 시도하지 않는다** — 덮으면\n * 한쪽의 판단이 사라진다. 다시 읽고 다시 정하는 것은 사람의 일이다.\n */\n\nexport interface ClaimAt {\n /** 어느 업무시각의 이야기인가. */\n asOfBusiness: Date\n /** 어느 기록시점까지 알려진 것만 볼 것인가. 안 주면 지금까지 아는 전부다. */\n asOfRecord?: Date\n}\n\nexport interface ClaimTarget {\n axis: string\n rowId: string\n field: string\n}\n\nconst ms = (at: Date) => at.getTime()\n\n/** 그 기록시점에 서 있던 주장들 — 물러난 것은 뺀다. */\nasync function standingClaims(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n asOfRecord?: Date\n): Promise<MasterValueClaim[]> {\n const rows = await manager.find(MasterValueClaim, {\n where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }\n })\n\n const at = asOfRecord ? ms(asOfRecord) : undefined\n\n return rows.filter(one => {\n /* 그 시점 뒤에 적힌 주장은 그때 몰랐다. */\n if (at !== undefined && ms(one.recordedAt) > at) return false\n\n /* 그 시점에 이미 물러났으면 안 본다. 그 뒤에 물러난 것은 그때는 서 있었다. */\n return !(one.supersededAt && (at === undefined || ms(one.supersededAt) <= at))\n })\n}\n\n/**\n * 그 업무시각에 유효했던 주장.\n *\n * **없으면 `undefined` 다 — 「선언 없음」과 다르다.** 선언 없음은 `value` 가 `null` 인 주장이 있는\n * 것이고, 여기서 `undefined` 는 **그때 무엇이었는지 모른다**는 뜻이다.\n */\nexport async function claimAt(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n at: ClaimAt\n): Promise<MasterValueClaim | undefined> {\n const standing = await standingClaims(manager, domain, target, at.asOfRecord)\n const when = ms(at.asOfBusiness)\n\n const covering = standing.filter(one => (!one.validFrom || ms(one.validFrom) <= when) && (!one.validTo || when < ms(one.validTo)))\n\n if (covering.length > 1) {\n throw new Refusal(\n 'master-value-claim-conflict',\n { axis: target.axis, field: target.field, at: at.asOfBusiness.toISOString() },\n 'two assignment claims cover the same moment. fix the history before asking.'\n )\n }\n\n return covering[0]\n}\n\n/**\n * 이 대상에 **주장이 한 건이라도 있나** — 창 밖의 것까지 센다.\n *\n * 창 안에 없는 것과 아예 없는 것은 다른 사실이다. 아예 없으면 이 설치본이 아직 이력을 안 쓰는\n * 것이고, 있는데 그 창을 안 덮으면 **구멍**이다. 부르는 쪽이 둘을 다르게 다뤄야 해서 따로 묻는다.\n */\nexport async function hasAssignmentHistory(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget\n): Promise<boolean> {\n const count = await manager.count(MasterValueClaim, {\n where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }\n })\n\n return count > 0\n}\n\n/** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */\nexport async function claimsDuring(\n manager: EntityManager,\n domain: Domain,\n target: ClaimTarget,\n window: { from: Date; to: Date },\n asOfRecord?: Date\n): Promise<MasterValueClaim[]> {\n const standing = await standingClaims(manager, domain, target, asOfRecord)\n const from = ms(window.from)\n const to = ms(window.to)\n\n return standing\n .filter(one => (!one.validFrom || ms(one.validFrom) < to) && (!one.validTo || from < ms(one.validTo)))\n .sort((a, b) => (a.validFrom ? ms(a.validFrom) : -Infinity) - (b.validFrom ? ms(b.validFrom) : -Infinity))\n}\n\nexport interface RecordClaim {\n target: ClaimTarget\n value: Record<string, unknown> | null\n /** 언제부터 그러한가. 안 주면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */\n validFrom?: Date\n /** 언제까지. 정정에서만 쓴다. */\n validTo?: Date\n reason: 'initial' | 'change' | 'correction'\n recorder?: unknown\n /**\n * 바뀌기 전 값. **첫 주장일 때만 쓴다** — 그 앞을 덮는 줄을 같이 세운다.\n *\n * 부르는 쪽이 같은 트랜잭션에서 이미 알고 있는 값이다(`claimAssignmentChanges`).\n */\n previous?: Record<string, unknown> | null\n /**\n * 내가 읽었을 때 서 있던 주장들의 id.\n *\n * 주면 **그 사이에 누가 바꿨는지** 본다 — 화면에서 읽고 고치는 동안 다른 사람이 같은 구간을\n * 정정했으면 내 정정이 그것을 덮는다. 덮지 않고 거절한다.\n */\n expecting?: readonly string[]\n}\n\n/**\n * 주장을 남긴다. **정정이면 겹치는 것을 물러나게 하고 앞뒤 조각을 다시 주장한다.**\n *\n * 부르는 쪽이 마스터 변경과 **같은 트랜잭션**으로 부른다 — 갈라지면 값은 바뀌고 이력은 없는 상태가\n * 생긴다.\n */\nexport async function recordClaim(\n manager: EntityManager,\n domain: Domain,\n said: RecordClaim,\n now = new Date()\n): Promise<MasterValueClaim> {\n const declared = claimedField(said.target.axis, said.target.field)\n\n if (!declared) {\n throw new Refusal(\n 'assignment-field-not-declared',\n { axis: said.target.axis, field: said.target.field },\n 'this field does not keep an assignment history. declare it first.'\n )\n }\n\n await assertUsableValue(manager, domain, declared, said, now)\n\n /*\n * **주장은 늘 식별자로 가리킨다.** 부르는 쪽이 이름을 줘도 여기서 푼다 — 한 자리에서 풀지 않으면\n * 어떤 길로는 이름이 그대로 들어가고, 그 줄은 이름을 바꾸는 날 끊긴다.\n */\n const value = await normalisedValue(manager, domain, declared, said.value)\n\n const validFrom = said.validFrom ?? now\n const validTo = said.validTo\n let standing = await standingClaims(manager, domain, said.target, undefined)\n\n /*\n * ── 첫 주장이면 **바뀌기 전 값도 같이 적는다** (2026-09-23) ───────────────\n * 새 값만 적으면 그 앞이 통째로 구멍이 된다 — 3월에 옮긴 설비의 2월을 아무 주장도 안 덮는다.\n * 그래서 그 칸이 처음 바뀔 때 옛 값을 `[?, 이 시각)` 으로 같이 세운다. 앞끝은 비운다: 언제부터\n * 그랬는지는 아무도 안 적었고, 시각을 지어내면 적은 적 없는 것이 적힌 것이 된다.\n *\n * 이것 덕분에 **아무것도 안 눌러도 된다.** 주장이 하나도 없다는 것은 「이 칸은 한 번도 안 바뀌었다」\n * 는 뜻이 되고, 그때는 오늘 값이 과거에도 그 값이었다. 채택이라는 절차가 필요 없어진다.\n */\n if (!standing.length && said.previous !== undefined && said.reason !== 'initial') {\n await manager.save(MasterValueClaim, {\n domain,\n axis: said.target.axis,\n rowId: said.target.rowId,\n field: said.target.field,\n value: said.previous,\n validFrom: null,\n validTo: validFrom,\n recordedAt: now,\n reason: 'initial',\n recorder: said.recorder\n } as Partial<MasterValueClaim>)\n\n standing = await standingClaims(manager, domain, said.target, undefined)\n }\n const from = ms(validFrom)\n const to = validTo ? ms(validTo) : undefined\n\n const overlapping = standing.filter(one => {\n const start = one.validFrom ? ms(one.validFrom) : Number.NEGATIVE_INFINITY\n const end = one.validTo ? ms(one.validTo) : undefined\n\n return (to === undefined || start < to) && (end === undefined || from < end)\n })\n\n /*\n * **읽은 뒤에 바뀌었으면 거절한다.** 부르는 쪽이 읽은 것을 주면 지금 서 있는 것과 견준다 —\n * 다르면 그 사이에 누가 고친 것이고, 내 정정이 그 판단을 덮는다.\n */\n if (said.expecting) {\n const now = new Set(overlapping.map(one => one.id))\n const read = new Set(said.expecting)\n const changed = now.size !== read.size || [...now].some(id => !read.has(id))\n\n if (changed) {\n throw new Refusal(\n 'master-value-claim-superseded',\n { axis: said.target.axis, field: said.target.field },\n 'this history changed while you were editing. read it again and decide again.'\n )\n }\n }\n\n for (const one of overlapping) {\n /*\n * 이미 물러난 것을 다시 물러나게 하려는 것은 **동시 정정**이다. 덮지 않고 거절한다.\n */\n if (one.supersededAt) {\n throw new Refusal(\n 'master-value-claim-superseded',\n { axis: said.target.axis, field: said.target.field },\n 'this history changed while you were editing. read it again and decide again.'\n )\n }\n\n await manager.save(MasterValueClaim, { ...one, supersededAt: now })\n\n /* 앞 조각 — 물러난 주장이 이 정정보다 먼저 시작했으면 그만큼은 그대로였다. 앞끝이 비면 「쭉」이다. */\n if (!one.validFrom || ms(one.validFrom) < from) {\n await manager.save(MasterValueClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value: one.value ?? null, validFrom: one.validFrom, validTo: validFrom,\n recordedAt: now, reason: 'remainder', recorder: said.recorder\n } as Partial<MasterValueClaim>)\n }\n\n /* 뒤 조각 — 정정이 끝난 뒤에도 그 주장이 이어졌으면 그만큼도 그대로였다. */\n const end = one.validTo ? ms(one.validTo) : undefined\n\n if (to !== undefined && (end === undefined || end > to)) {\n await manager.save(MasterValueClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value: one.value ?? null, validFrom: validTo as Date, validTo: one.validTo,\n recordedAt: now, reason: 'remainder', recorder: said.recorder\n } as Partial<MasterValueClaim>)\n }\n }\n\n return manager.save(MasterValueClaim, {\n domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,\n value, validFrom, validTo, recordedAt: now, reason: said.reason, recorder: said.recorder\n } as Partial<MasterValueClaim>)\n}\n\n/**\n * 도래한 미래 주장을 마스터 행에 옮긴다 — **앱의 주기 작업이 부른다.**\n *\n * ops-master 는 라이브러리라 스스로 돌지 않는다. 그 사이 본행은 낡아 있고, **정확해야 하는 소비처는\n * 본행이 아니라 주장을 읽는다.**\n */\nexport async function projectDueClaims(\n manager: EntityManager,\n domain: Domain,\n entities: { axis: string; entity: Function }[],\n now = new Date()\n): Promise<number> {\n let moved = 0\n\n for (const { axis, entity } of entities) {\n const fields = [...new Set(claimedFieldsOfAxis(axis))]\n\n for (const field of fields) {\n const due = await manager.find(MasterValueClaim, {\n where: {\n domain: { id: domain.id }, axis, field,\n supersededAt: IsNull(), validFrom: LessThanOrEqual(now), reason: Not('remainder')\n }\n })\n\n for (const claim of due) {\n const current = await claimAt(manager, domain, { axis, rowId: claim.rowId, field }, { asOfBusiness: now })\n\n if (!current || current.id !== claim.id) {\n continue\n }\n\n const row = await manager.findOne(entity as never, { where: { id: claim.rowId, domain: { id: domain.id } } } as never)\n\n if (!row) {\n continue\n }\n\n const next = valueOnRow(claim)\n\n if ((row as Record<string, unknown>)[field] !== next) {\n await manager.save(entity as never, { ...(row as object), [field]: next } as never)\n moved++\n }\n }\n }\n }\n\n return moved\n}\n\nfunction claimedFieldsOfAxis(axis: string): string[] {\n return CLAIMED_FIELDS.filter(one => one.axis === axis).map(one => one.field)\n}\n\n/**\n * 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다.\n *\n * ⚠ **관계 칸에는 이름을 돌려준다** — 그 칸이 이름을 담기 때문이다. 식별자는 주장 안에만 있고,\n * 그것이 이름이 바뀌어도 과거가 안 끊기는 까닭이다.\n */\nexport function valueOnRow(claim: MasterValueClaim): unknown {\n const value = claim.value\n\n if (value === null || value === undefined) {\n return null\n }\n\n if (Array.isArray((value as { names?: unknown }).names)) {\n return (value as { names: unknown[] }).names\n }\n\n const said = value as { ref?: unknown; label?: unknown }\n\n return said.label ?? said.ref ?? null\n}\n\n/**\n * 값이 이 칸의 모양인가, 그리고 **우리 도메인의 것인가.**\n *\n * 다른 도메인의 행을 가리키면 그 도메인이 자기 자료를 고쳐도 이쪽 과거가 흔들린다. 그리고 부모\n * 관계는 **그 시점에** 도는지 본다 — 지금 안 돌아도 그때는 돌 수 있다.\n */\nasync function assertUsableValue(\n manager: EntityManager,\n domain: Domain,\n declared: { axis: string; field: string; kind: 'relation' | 'names' | 'literal'; target?: string },\n said: RecordClaim,\n now: Date\n): Promise<void> {\n const value = said.value\n\n if (value === null || value === undefined) {\n return\n }\n\n if (declared.kind === 'literal') {\n if (typeof (value as { literal?: unknown }).literal !== 'string') {\n throw new Refusal('claim-value-shape', { field: declared.field }, 'this field takes the value itself, as written.')\n }\n\n return\n }\n\n if (declared.kind === 'names') {\n if (!Array.isArray((value as { names?: unknown }).names)) {\n throw new Refusal('assignment-value-shape', { field: declared.field }, 'this field takes a list of names.')\n }\n\n return\n }\n\n const ref = (value as { ref?: unknown }).ref\n\n if (typeof ref !== 'string' || !ref) {\n throw new Refusal(\n 'assignment-value-shape',\n { field: declared.field },\n 'this field points at a row, so it needs that row identifier — not its name.'\n )\n }\n\n const axis = MASTER_AXES.find(one => one.key === declared.target)\n\n if (!axis) {\n return\n }\n\n /* 이름으로 적힌 칸도 있으므로 둘 다 찾아 본다(§`rowOf`). */\n const pointed = await rowOf(manager, domain, declared.target, ref)\n\n if (!pointed) {\n throw new Refusal(\n 'assignment-value-unknown',\n { field: declared.field, ref },\n 'that row is not in this site. point at a row that is.'\n )\n }\n\n if (declared.field === 'parentId') {\n /*\n * **푼 식별자로 걷는다.** 안 풀고 걸으면 이름으로 온 값은 아무 주장도 못 찾아 고리를 그냥\n * 지나간다 — 실제로 그랬다(2026-09-22).\n */\n await assertNoCycleAt(manager, domain, said.target.rowId, pointed.id, said.validFrom ?? now)\n }\n}\n\n/** 그 시점의 부모 사슬을 걸어 본다. 자기 자신에 닿으면 고리다. */\nasync function assertNoCycleAt(\n manager: EntityManager,\n domain: Domain,\n rowId: string,\n parentRef: string,\n at: Date\n): Promise<void> {\n const walked = new Set<string>([rowId])\n let cursor: string | undefined = parentRef\n\n while (cursor) {\n if (walked.has(cursor)) {\n throw new Refusal(\n 'assignment-parent-cycle',\n { at: at.toISOString() },\n 'this parent makes a loop at that moment. choose another parent.'\n )\n }\n\n walked.add(cursor)\n\n const claim = await claimAt(manager, domain, { axis: 'operationalLocation', rowId: cursor, field: 'parentId' }, { asOfBusiness: at })\n\n cursor = claim ? ((claim.value as { ref?: string } | null)?.ref ?? undefined) : undefined\n }\n}\n\n/**\n * 도입할 때 — **지금 값을 초기 상태로 적는다.**\n *\n * 과거를 소급해 만드는 것과 다르다. 「도입 시각에 이 값이었다는 것을 확인했다」는 주장이고, **그\n * 전은 결측 그대로** 둔다. 이것이 없으면 **한 번도 안 옮긴 설비가 영영 이력이 빈다** — 그런 설비가\n * 가장 많다.\n *\n * 이미 주장이 있는 행은 건너뛴다. 두 번 불러도 같다.\n */\n\n/** 이름으로 왔든 식별자로 왔든 **식별자와 그때의 이름**으로 만든다. */\nasync function normalisedValue(\n manager: EntityManager,\n domain: Domain,\n declared: { kind: 'relation' | 'names' | 'literal'; target?: string },\n value: Record<string, unknown> | null | undefined\n): Promise<Record<string, unknown> | null> {\n if (!value || declared.kind !== 'relation') {\n return value ?? null\n }\n\n const found = await rowOf(manager, domain, declared.target, String((value as { ref?: unknown }).ref ?? ''))\n\n return found ? { ref: found.id, label: found.name } : value\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { EntityManager, ObjectLiteral } from 'typeorm';
2
2
  import { Domain } from '@things-factory/shell';
3
- import { assignmentField } from './assignment-claim';
3
+ import { claimedField } from './master-value-claim';
4
4
  /**
5
5
  * 그리드가 보낸 변경에서 **배정·선언 칸을 골라 주장으로 남긴다.**
6
6
  *
@@ -20,6 +20,23 @@ export declare function claimAssignmentChanges(manager: EntityManager, domain: D
20
20
  }): Promise<{
21
21
  keepOnRow: ObjectLiteral;
22
22
  }>;
23
- /** 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다. */
24
- export declare function toClaimValue(kind: 'relation' | 'names', value: unknown): Record<string, unknown> | null;
25
- export { assignmentField };
23
+ /**
24
+ * 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다.
25
+ *
26
+ * ⚠ **관계 칸은 UUID 가 아니라 이름을 담는다.** `ops_equipment.home_location` 에 `UNCOIL-01` 이
27
+ * 들어 있고, `parentId` 는 id 일 수도 이름일 수도 있다(이 제품의 규약). 처음에 칸 값을 그대로
28
+ * 식별자로 썼다가 **정상 편집이 전부 거절됐다**(2026-09-22, 화면에서 드러남).
29
+ *
30
+ * 그래서 여기서 **가리키는 행을 찾아 식별자와 그때의 이름을 함께 담는다.** 마스터 칸은 그대로
31
+ * 이름을 들고, 주장은 이름이 바뀌어도 안 끊긴다.
32
+ */
33
+ export declare function toClaimValue(manager: EntityManager, domain: Domain, declared: {
34
+ kind: 'relation' | 'names' | 'literal';
35
+ target?: string;
36
+ }, value: unknown): Promise<Record<string, unknown> | null>;
37
+ /** 이름으로도 id 로도 찾는다 — 이 제품의 관계 칸이 둘 다 담는다. */
38
+ export declare function rowOf(manager: EntityManager, domain: Domain, targetAxis: string | undefined, given: string): Promise<{
39
+ id: string;
40
+ name: string;
41
+ } | undefined>;
42
+ export { claimedField };