@things-factory/ops-master 10.1.35 → 10.1.37
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-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +26 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/master-change-policy-axes.d.ts +26 -0
- package/dist-client/master-change-policy-axes.js +18 -0
- package/dist-client/master-change-policy-axes.js.map +1 -0
- package/dist-client/master-change-policy-panel.d.ts +49 -0
- package/dist-client/master-change-policy-panel.js +393 -0
- package/dist-client/master-change-policy-panel.js.map +1 -0
- package/dist-client/pages/master/applied-when-dialog.d.ts +45 -0
- package/dist-client/pages/master/applied-when-dialog.js +139 -0
- package/dist-client/pages/master/applied-when-dialog.js.map +1 -0
- package/dist-client/pages/master/assignment-fields.d.ts +27 -0
- package/dist-client/pages/master/assignment-fields.js +62 -0
- package/dist-client/pages/master/assignment-fields.js.map +1 -0
- package/dist-client/pages/master/equipment.js +1 -0
- package/dist-client/pages/master/equipment.js.map +1 -1
- package/dist-client/pages/master/master-page-base.d.ts +12 -0
- package/dist-client/pages/master/master-page-base.js +38 -4
- package/dist-client/pages/master/master-page-base.js.map +1 -1
- package/dist-client/pages/master/operational-location.js +1 -0
- package/dist-client/pages/master/operational-location.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/approval-route.d.ts +37 -0
- package/dist-server/service/approval-route.js +34 -0
- package/dist-server/service/approval-route.js.map +1 -0
- package/dist-server/service/assignment/applied-when.d.ts +24 -0
- package/dist-server/service/assignment/applied-when.js +16 -0
- package/dist-server/service/assignment/applied-when.js.map +1 -0
- package/dist-server/service/assignment/assignment-history.d.ts +92 -0
- package/dist-server/service/assignment/assignment-history.js +289 -0
- package/dist-server/service/assignment/assignment-history.js.map +1 -0
- package/dist-server/service/assignment/assignment-write.d.ts +42 -0
- package/dist-server/service/assignment/assignment-write.js +101 -0
- package/dist-server/service/assignment/assignment-write.js.map +1 -0
- package/dist-server/service/assignment/master-value-claim.d.ts +135 -0
- package/dist-server/service/assignment/master-value-claim.js +174 -0
- package/dist-server/service/assignment/master-value-claim.js.map +1 -0
- package/dist-server/service/equipment/equipment-master-resolver.d.ts +9 -1
- package/dist-server/service/equipment/equipment-master-resolver.js +6 -3
- package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +6 -1
- package/dist-server/service/index.js +10 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/location/location-master-resolver.d.ts +4 -1
- package/dist-server/service/location/location-master-resolver.js +6 -3
- package/dist-server/service/location/location-master-resolver.js.map +1 -1
- package/dist-server/service/master-change-policy.d.ts +65 -2
- package/dist-server/service/master-change-policy.js +166 -21
- package/dist-server/service/master-change-policy.js.map +1 -1
- package/dist-server/service/master-crud.d.ts +11 -0
- package/dist-server/service/master-crud.js +12 -1
- package/dist-server/service/master-crud.js.map +1 -1
- package/dist-server/service/material/material-revision-service.js +13 -0
- package/dist-server/service/material/material-revision-service.js.map +1 -1
- package/dist-server/service/recipe/recipe-revision-service.js +13 -0
- package/dist-server/service/recipe/recipe-revision-service.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-revision-service.js +14 -0
- package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/translations/en.json +59 -2
- package/translations/ja.json +58 -1
- package/translations/ko.json +59 -2
- package/translations/ms.json +58 -1
- package/translations/zh.json +60 -1
|
@@ -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"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 화면이 「언제부터」를 말하는 모양.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────
|
|
5
|
+
* 과거 시각을 받으면 그것이 **지연 기록**인지 **이미 적은 것의 정정**인지 알 수 없다. 둘은 다른
|
|
6
|
+
* 일이다 — 앞은 「그때 옮겼는데 이제 적는다」이고 뒤는 「그때 적은 것이 틀렸다」다. 이력이 그
|
|
7
|
+
* 구분을 들어야 나중에 「무엇이 왜 바뀌었나」에 답한다.
|
|
8
|
+
*
|
|
9
|
+
* **기록 시각은 여기 없다.** 사람이 넣지 않고 서버가 붙인다.
|
|
10
|
+
*/
|
|
11
|
+
export interface AppliedWhen {
|
|
12
|
+
/** 언제부터. 없으면 지금부터다. */
|
|
13
|
+
at?: string;
|
|
14
|
+
/** 과거일 때 — 지연 기록인가 정정인가. 없으면 지연 기록으로 본다. */
|
|
15
|
+
kind?: 'change' | 'correction';
|
|
16
|
+
/** 정정이면 어느 구간까지인가. */
|
|
17
|
+
until?: string;
|
|
18
|
+
}
|
|
19
|
+
/** 화면이 준 것을 `saveMasterPatches` 의 선택지로. */
|
|
20
|
+
export declare function appliedOptions(applied?: AppliedWhen): {
|
|
21
|
+
appliedUntil?: Date;
|
|
22
|
+
appliedKind?: "change" | "correction";
|
|
23
|
+
appliedAt?: Date;
|
|
24
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.appliedOptions = appliedOptions;
|
|
4
|
+
const when = (said) => (said ? new Date(said) : undefined);
|
|
5
|
+
/** 화면이 준 것을 `saveMasterPatches` 의 선택지로. */
|
|
6
|
+
function appliedOptions(applied) {
|
|
7
|
+
if (!applied) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
...(applied.at ? { appliedAt: when(applied.at) } : {}),
|
|
12
|
+
...(applied.kind ? { appliedKind: applied.kind } : {}),
|
|
13
|
+
...(applied.until ? { appliedUntil: when(applied.until) } : {})
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=applied-when.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-when.js","sourceRoot":"","sources":["../../../server/service/assignment/applied-when.ts"],"names":[],"mappings":";;AAsBA,wCAUC;AAbD,MAAM,IAAI,GAAG,CAAC,IAAa,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAEnE,2CAA2C;AAC3C,SAAgB,cAAc,CAAC,OAAqB;IAClD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO;QACL,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAA;AACH,CAAC","sourcesContent":["/**\n * 화면이 「언제부터」를 말하는 모양.\n *\n * ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────\n * 과거 시각을 받으면 그것이 **지연 기록**인지 **이미 적은 것의 정정**인지 알 수 없다. 둘은 다른\n * 일이다 — 앞은 「그때 옮겼는데 이제 적는다」이고 뒤는 「그때 적은 것이 틀렸다」다. 이력이 그\n * 구분을 들어야 나중에 「무엇이 왜 바뀌었나」에 답한다.\n *\n * **기록 시각은 여기 없다.** 사람이 넣지 않고 서버가 붙인다.\n */\nexport interface AppliedWhen {\n /** 언제부터. 없으면 지금부터다. */\n at?: string\n /** 과거일 때 — 지연 기록인가 정정인가. 없으면 지연 기록으로 본다. */\n kind?: 'change' | 'correction'\n /** 정정이면 어느 구간까지인가. */\n until?: string\n}\n\nconst when = (said?: string) => (said ? new Date(said) : undefined)\n\n/** 화면이 준 것을 `saveMasterPatches` 의 선택지로. */\nexport function appliedOptions(applied?: AppliedWhen) {\n if (!applied) {\n return {}\n }\n\n return {\n ...(applied.at ? { appliedAt: when(applied.at) } : {}),\n ...(applied.kind ? { appliedKind: applied.kind } : {}),\n ...(applied.until ? { appliedUntil: when(applied.until) } : {})\n }\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { MasterValueClaim } from './master-value-claim';
|
|
4
|
+
/**
|
|
5
|
+
* 주장을 남기고 읽는 규칙 — **덮어쓰지 않는다.**
|
|
6
|
+
*
|
|
7
|
+
* ── 정정이 양옆을 남긴다 ───────────────────────────────────────────────────
|
|
8
|
+
* 겹치는 주장을 물러나게 하는 것만으로는 모자란다. `A [1일, ∞)` 에 「10~15는 B」를 정정하면 A 의
|
|
9
|
+
* **앞뒤 조각을 같은 값으로 다시 주장**해야 최신 답이 `A·B·A` 가 된다. 조각도 새 주장이므로 정정 전
|
|
10
|
+
* 기록시점으로 읽으면 원래 A 하나가 그대로 나온다.
|
|
11
|
+
*
|
|
12
|
+
* ── 동시 정정은 거절한다 ───────────────────────────────────────────────────
|
|
13
|
+
* 물러나게 하려던 주장이 이미 물러나 있으면 충돌이다. **자동으로 다시 시도하지 않는다** — 덮으면
|
|
14
|
+
* 한쪽의 판단이 사라진다. 다시 읽고 다시 정하는 것은 사람의 일이다.
|
|
15
|
+
*/
|
|
16
|
+
export interface ClaimAt {
|
|
17
|
+
/** 어느 업무시각의 이야기인가. */
|
|
18
|
+
asOfBusiness: Date;
|
|
19
|
+
/** 어느 기록시점까지 알려진 것만 볼 것인가. 안 주면 지금까지 아는 전부다. */
|
|
20
|
+
asOfRecord?: Date;
|
|
21
|
+
}
|
|
22
|
+
export interface ClaimTarget {
|
|
23
|
+
axis: string;
|
|
24
|
+
rowId: string;
|
|
25
|
+
field: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 그 업무시각에 유효했던 주장.
|
|
29
|
+
*
|
|
30
|
+
* **없으면 `undefined` 다 — 「선언 없음」과 다르다.** 선언 없음은 `value` 가 `null` 인 주장이 있는
|
|
31
|
+
* 것이고, 여기서 `undefined` 는 **그때 무엇이었는지 모른다**는 뜻이다.
|
|
32
|
+
*/
|
|
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>;
|
|
41
|
+
/** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */
|
|
42
|
+
export declare function claimsDuring(manager: EntityManager, domain: Domain, target: ClaimTarget, window: {
|
|
43
|
+
from: Date;
|
|
44
|
+
to: Date;
|
|
45
|
+
}, asOfRecord?: Date): Promise<MasterValueClaim[]>;
|
|
46
|
+
export interface RecordClaim {
|
|
47
|
+
target: ClaimTarget;
|
|
48
|
+
value: Record<string, unknown> | null;
|
|
49
|
+
/** 언제부터 그러한가. 안 주면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */
|
|
50
|
+
validFrom?: Date;
|
|
51
|
+
/** 언제까지. 정정에서만 쓴다. */
|
|
52
|
+
validTo?: Date;
|
|
53
|
+
reason: 'initial' | 'change' | 'correction';
|
|
54
|
+
recorder?: unknown;
|
|
55
|
+
/**
|
|
56
|
+
* 바뀌기 전 값. **첫 주장일 때만 쓴다** — 그 앞을 덮는 줄을 같이 세운다.
|
|
57
|
+
*
|
|
58
|
+
* 부르는 쪽이 같은 트랜잭션에서 이미 알고 있는 값이다(`claimAssignmentChanges`).
|
|
59
|
+
*/
|
|
60
|
+
previous?: Record<string, unknown> | null;
|
|
61
|
+
/**
|
|
62
|
+
* 내가 읽었을 때 서 있던 주장들의 id.
|
|
63
|
+
*
|
|
64
|
+
* 주면 **그 사이에 누가 바꿨는지** 본다 — 화면에서 읽고 고치는 동안 다른 사람이 같은 구간을
|
|
65
|
+
* 정정했으면 내 정정이 그것을 덮는다. 덮지 않고 거절한다.
|
|
66
|
+
*/
|
|
67
|
+
expecting?: readonly string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 주장을 남긴다. **정정이면 겹치는 것을 물러나게 하고 앞뒤 조각을 다시 주장한다.**
|
|
71
|
+
*
|
|
72
|
+
* 부르는 쪽이 마스터 변경과 **같은 트랜잭션**으로 부른다 — 갈라지면 값은 바뀌고 이력은 없는 상태가
|
|
73
|
+
* 생긴다.
|
|
74
|
+
*/
|
|
75
|
+
export declare function recordClaim(manager: EntityManager, domain: Domain, said: RecordClaim, now?: Date): Promise<MasterValueClaim>;
|
|
76
|
+
/**
|
|
77
|
+
* 도래한 미래 주장을 마스터 행에 옮긴다 — **앱의 주기 작업이 부른다.**
|
|
78
|
+
*
|
|
79
|
+
* ops-master 는 라이브러리라 스스로 돌지 않는다. 그 사이 본행은 낡아 있고, **정확해야 하는 소비처는
|
|
80
|
+
* 본행이 아니라 주장을 읽는다.**
|
|
81
|
+
*/
|
|
82
|
+
export declare function projectDueClaims(manager: EntityManager, domain: Domain, entities: {
|
|
83
|
+
axis: string;
|
|
84
|
+
entity: Function;
|
|
85
|
+
}[], now?: Date): Promise<number>;
|
|
86
|
+
/**
|
|
87
|
+
* 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다.
|
|
88
|
+
*
|
|
89
|
+
* ⚠ **관계 칸에는 이름을 돌려준다** — 그 칸이 이름을 담기 때문이다. 식별자는 주장 안에만 있고,
|
|
90
|
+
* 그것이 이름이 바뀌어도 과거가 안 끊기는 까닭이다.
|
|
91
|
+
*/
|
|
92
|
+
export declare function valueOnRow(claim: MasterValueClaim): unknown;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claimAt = claimAt;
|
|
4
|
+
exports.hasAssignmentHistory = hasAssignmentHistory;
|
|
5
|
+
exports.claimsDuring = claimsDuring;
|
|
6
|
+
exports.recordClaim = recordClaim;
|
|
7
|
+
exports.projectDueClaims = projectDueClaims;
|
|
8
|
+
exports.valueOnRow = valueOnRow;
|
|
9
|
+
const typeorm_1 = require("typeorm");
|
|
10
|
+
const shell_1 = require("@things-factory/shell");
|
|
11
|
+
const master_axis_1 = require("../master-axis");
|
|
12
|
+
const master_value_claim_1 = require("./master-value-claim");
|
|
13
|
+
const assignment_write_1 = require("./assignment-write");
|
|
14
|
+
const ms = (at) => at.getTime();
|
|
15
|
+
/** 그 기록시점에 서 있던 주장들 — 물러난 것은 뺀다. */
|
|
16
|
+
async function standingClaims(manager, domain, target, asOfRecord) {
|
|
17
|
+
const rows = await manager.find(master_value_claim_1.MasterValueClaim, {
|
|
18
|
+
where: { domain: { id: domain.id }, axis: target.axis, rowId: target.rowId, field: target.field }
|
|
19
|
+
});
|
|
20
|
+
const at = asOfRecord ? ms(asOfRecord) : undefined;
|
|
21
|
+
return rows.filter(one => {
|
|
22
|
+
/* 그 시점 뒤에 적힌 주장은 그때 몰랐다. */
|
|
23
|
+
if (at !== undefined && ms(one.recordedAt) > at)
|
|
24
|
+
return false;
|
|
25
|
+
/* 그 시점에 이미 물러났으면 안 본다. 그 뒤에 물러난 것은 그때는 서 있었다. */
|
|
26
|
+
return !(one.supersededAt && (at === undefined || ms(one.supersededAt) <= at));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 그 업무시각에 유효했던 주장.
|
|
31
|
+
*
|
|
32
|
+
* **없으면 `undefined` 다 — 「선언 없음」과 다르다.** 선언 없음은 `value` 가 `null` 인 주장이 있는
|
|
33
|
+
* 것이고, 여기서 `undefined` 는 **그때 무엇이었는지 모른다**는 뜻이다.
|
|
34
|
+
*/
|
|
35
|
+
async function claimAt(manager, domain, target, at) {
|
|
36
|
+
const standing = await standingClaims(manager, domain, target, at.asOfRecord);
|
|
37
|
+
const when = ms(at.asOfBusiness);
|
|
38
|
+
const covering = standing.filter(one => (!one.validFrom || ms(one.validFrom) <= when) && (!one.validTo || when < ms(one.validTo)));
|
|
39
|
+
if (covering.length > 1) {
|
|
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
|
+
}
|
|
42
|
+
return covering[0];
|
|
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
|
+
}
|
|
56
|
+
/** 한 창을 덮는 주장들 — **계산 구간을 나눌 경계**가 여기서 나온다. 시작 시각 순이다. */
|
|
57
|
+
async function claimsDuring(manager, domain, target, window, asOfRecord) {
|
|
58
|
+
const standing = await standingClaims(manager, domain, target, asOfRecord);
|
|
59
|
+
const from = ms(window.from);
|
|
60
|
+
const to = ms(window.to);
|
|
61
|
+
return standing
|
|
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));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 주장을 남긴다. **정정이면 겹치는 것을 물러나게 하고 앞뒤 조각을 다시 주장한다.**
|
|
67
|
+
*
|
|
68
|
+
* 부르는 쪽이 마스터 변경과 **같은 트랜잭션**으로 부른다 — 갈라지면 값은 바뀌고 이력은 없는 상태가
|
|
69
|
+
* 생긴다.
|
|
70
|
+
*/
|
|
71
|
+
async function recordClaim(manager, domain, said, now = new Date()) {
|
|
72
|
+
const declared = (0, master_value_claim_1.claimedField)(said.target.axis, said.target.field);
|
|
73
|
+
if (!declared) {
|
|
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.');
|
|
75
|
+
}
|
|
76
|
+
await assertUsableValue(manager, domain, declared, said, now);
|
|
77
|
+
/*
|
|
78
|
+
* **주장은 늘 식별자로 가리킨다.** 부르는 쪽이 이름을 줘도 여기서 푼다 — 한 자리에서 풀지 않으면
|
|
79
|
+
* 어떤 길로는 이름이 그대로 들어가고, 그 줄은 이름을 바꾸는 날 끊긴다.
|
|
80
|
+
*/
|
|
81
|
+
const value = await normalisedValue(manager, domain, declared, said.value);
|
|
82
|
+
const validFrom = said.validFrom ?? now;
|
|
83
|
+
const validTo = said.validTo;
|
|
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
|
+
}
|
|
109
|
+
const from = ms(validFrom);
|
|
110
|
+
const to = validTo ? ms(validTo) : undefined;
|
|
111
|
+
const overlapping = standing.filter(one => {
|
|
112
|
+
const start = one.validFrom ? ms(one.validFrom) : Number.NEGATIVE_INFINITY;
|
|
113
|
+
const end = one.validTo ? ms(one.validTo) : undefined;
|
|
114
|
+
return (to === undefined || start < to) && (end === undefined || from < end);
|
|
115
|
+
});
|
|
116
|
+
/*
|
|
117
|
+
* **읽은 뒤에 바뀌었으면 거절한다.** 부르는 쪽이 읽은 것을 주면 지금 서 있는 것과 견준다 —
|
|
118
|
+
* 다르면 그 사이에 누가 고친 것이고, 내 정정이 그 판단을 덮는다.
|
|
119
|
+
*/
|
|
120
|
+
if (said.expecting) {
|
|
121
|
+
const now = new Set(overlapping.map(one => one.id));
|
|
122
|
+
const read = new Set(said.expecting);
|
|
123
|
+
const changed = now.size !== read.size || [...now].some(id => !read.has(id));
|
|
124
|
+
if (changed) {
|
|
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.');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const one of overlapping) {
|
|
129
|
+
/*
|
|
130
|
+
* 이미 물러난 것을 다시 물러나게 하려는 것은 **동시 정정**이다. 덮지 않고 거절한다.
|
|
131
|
+
*/
|
|
132
|
+
if (one.supersededAt) {
|
|
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.');
|
|
134
|
+
}
|
|
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, {
|
|
139
|
+
domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
|
|
140
|
+
value: one.value ?? null, validFrom: one.validFrom, validTo: validFrom,
|
|
141
|
+
recordedAt: now, reason: 'remainder', recorder: said.recorder
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/* 뒤 조각 — 정정이 끝난 뒤에도 그 주장이 이어졌으면 그만큼도 그대로였다. */
|
|
145
|
+
const end = one.validTo ? ms(one.validTo) : undefined;
|
|
146
|
+
if (to !== undefined && (end === undefined || end > to)) {
|
|
147
|
+
await manager.save(master_value_claim_1.MasterValueClaim, {
|
|
148
|
+
domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
|
|
149
|
+
value: one.value ?? null, validFrom: validTo, validTo: one.validTo,
|
|
150
|
+
recordedAt: now, reason: 'remainder', recorder: said.recorder
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return manager.save(master_value_claim_1.MasterValueClaim, {
|
|
155
|
+
domain, axis: said.target.axis, rowId: said.target.rowId, field: said.target.field,
|
|
156
|
+
value, validFrom, validTo, recordedAt: now, reason: said.reason, recorder: said.recorder
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 도래한 미래 주장을 마스터 행에 옮긴다 — **앱의 주기 작업이 부른다.**
|
|
161
|
+
*
|
|
162
|
+
* ops-master 는 라이브러리라 스스로 돌지 않는다. 그 사이 본행은 낡아 있고, **정확해야 하는 소비처는
|
|
163
|
+
* 본행이 아니라 주장을 읽는다.**
|
|
164
|
+
*/
|
|
165
|
+
async function projectDueClaims(manager, domain, entities, now = new Date()) {
|
|
166
|
+
let moved = 0;
|
|
167
|
+
for (const { axis, entity } of entities) {
|
|
168
|
+
const fields = [...new Set(claimedFieldsOfAxis(axis))];
|
|
169
|
+
for (const field of fields) {
|
|
170
|
+
const due = await manager.find(master_value_claim_1.MasterValueClaim, {
|
|
171
|
+
where: {
|
|
172
|
+
domain: { id: domain.id }, axis, field,
|
|
173
|
+
supersededAt: (0, typeorm_1.IsNull)(), validFrom: (0, typeorm_1.LessThanOrEqual)(now), reason: (0, typeorm_1.Not)('remainder')
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
for (const claim of due) {
|
|
177
|
+
const current = await claimAt(manager, domain, { axis, rowId: claim.rowId, field }, { asOfBusiness: now });
|
|
178
|
+
if (!current || current.id !== claim.id) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const row = await manager.findOne(entity, { where: { id: claim.rowId, domain: { id: domain.id } } });
|
|
182
|
+
if (!row) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const next = valueOnRow(claim);
|
|
186
|
+
if (row[field] !== next) {
|
|
187
|
+
await manager.save(entity, { ...row, [field]: next });
|
|
188
|
+
moved++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return moved;
|
|
194
|
+
}
|
|
195
|
+
function claimedFieldsOfAxis(axis) {
|
|
196
|
+
return master_value_claim_1.CLAIMED_FIELDS.filter(one => one.axis === axis).map(one => one.field);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 주장의 값을 마스터 칸이 드는 모양으로. 「선언 없음」은 `null` 이다.
|
|
200
|
+
*
|
|
201
|
+
* ⚠ **관계 칸에는 이름을 돌려준다** — 그 칸이 이름을 담기 때문이다. 식별자는 주장 안에만 있고,
|
|
202
|
+
* 그것이 이름이 바뀌어도 과거가 안 끊기는 까닭이다.
|
|
203
|
+
*/
|
|
204
|
+
function valueOnRow(claim) {
|
|
205
|
+
const value = claim.value;
|
|
206
|
+
if (value === null || value === undefined) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (Array.isArray(value.names)) {
|
|
210
|
+
return value.names;
|
|
211
|
+
}
|
|
212
|
+
const said = value;
|
|
213
|
+
return said.label ?? said.ref ?? null;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 값이 이 칸의 모양인가, 그리고 **우리 도메인의 것인가.**
|
|
217
|
+
*
|
|
218
|
+
* 다른 도메인의 행을 가리키면 그 도메인이 자기 자료를 고쳐도 이쪽 과거가 흔들린다. 그리고 부모
|
|
219
|
+
* 관계는 **그 시점에** 도는지 본다 — 지금 안 돌아도 그때는 돌 수 있다.
|
|
220
|
+
*/
|
|
221
|
+
async function assertUsableValue(manager, domain, declared, said, now) {
|
|
222
|
+
const value = said.value;
|
|
223
|
+
if (value === null || value === undefined) {
|
|
224
|
+
return;
|
|
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
|
+
}
|
|
232
|
+
if (declared.kind === 'names') {
|
|
233
|
+
if (!Array.isArray(value.names)) {
|
|
234
|
+
throw new shell_1.Refusal('assignment-value-shape', { field: declared.field }, 'this field takes a list of names.');
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const ref = value.ref;
|
|
239
|
+
if (typeof ref !== 'string' || !ref) {
|
|
240
|
+
throw new shell_1.Refusal('assignment-value-shape', { field: declared.field }, 'this field points at a row, so it needs that row identifier — not its name.');
|
|
241
|
+
}
|
|
242
|
+
const axis = master_axis_1.MASTER_AXES.find(one => one.key === declared.target);
|
|
243
|
+
if (!axis) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
/* 이름으로 적힌 칸도 있으므로 둘 다 찾아 본다(§`rowOf`). */
|
|
247
|
+
const pointed = await (0, assignment_write_1.rowOf)(manager, domain, declared.target, ref);
|
|
248
|
+
if (!pointed) {
|
|
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.');
|
|
250
|
+
}
|
|
251
|
+
if (declared.field === 'parentId') {
|
|
252
|
+
/*
|
|
253
|
+
* **푼 식별자로 걷는다.** 안 풀고 걸으면 이름으로 온 값은 아무 주장도 못 찾아 고리를 그냥
|
|
254
|
+
* 지나간다 — 실제로 그랬다(2026-09-22).
|
|
255
|
+
*/
|
|
256
|
+
await assertNoCycleAt(manager, domain, said.target.rowId, pointed.id, said.validFrom ?? now);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** 그 시점의 부모 사슬을 걸어 본다. 자기 자신에 닿으면 고리다. */
|
|
260
|
+
async function assertNoCycleAt(manager, domain, rowId, parentRef, at) {
|
|
261
|
+
const walked = new Set([rowId]);
|
|
262
|
+
let cursor = parentRef;
|
|
263
|
+
while (cursor) {
|
|
264
|
+
if (walked.has(cursor)) {
|
|
265
|
+
throw new shell_1.Refusal('assignment-parent-cycle', { at: at.toISOString() }, 'this parent makes a loop at that moment. choose another parent.');
|
|
266
|
+
}
|
|
267
|
+
walked.add(cursor);
|
|
268
|
+
const claim = await claimAt(manager, domain, { axis: 'operationalLocation', rowId: cursor, field: 'parentId' }, { asOfBusiness: at });
|
|
269
|
+
cursor = claim ? (claim.value?.ref ?? undefined) : undefined;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 도입할 때 — **지금 값을 초기 상태로 적는다.**
|
|
274
|
+
*
|
|
275
|
+
* 과거를 소급해 만드는 것과 다르다. 「도입 시각에 이 값이었다는 것을 확인했다」는 주장이고, **그
|
|
276
|
+
* 전은 결측 그대로** 둔다. 이것이 없으면 **한 번도 안 옮긴 설비가 영영 이력이 빈다** — 그런 설비가
|
|
277
|
+
* 가장 많다.
|
|
278
|
+
*
|
|
279
|
+
* 이미 주장이 있는 행은 건너뛴다. 두 번 불러도 같다.
|
|
280
|
+
*/
|
|
281
|
+
/** 이름으로 왔든 식별자로 왔든 **식별자와 그때의 이름**으로 만든다. */
|
|
282
|
+
async function normalisedValue(manager, domain, declared, value) {
|
|
283
|
+
if (!value || declared.kind !== 'relation') {
|
|
284
|
+
return value ?? null;
|
|
285
|
+
}
|
|
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;
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=assignment-history.js.map
|
|
@@ -0,0 +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 { 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"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EntityManager, ObjectLiteral } from 'typeorm';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { claimedField } from './master-value-claim';
|
|
4
|
+
/**
|
|
5
|
+
* 그리드가 보낸 변경에서 **배정·선언 칸을 골라 주장으로 남긴다.**
|
|
6
|
+
*
|
|
7
|
+
* ── 왜 본행을 되돌려 놓나 ──────────────────────────────────────────────────
|
|
8
|
+
* 적용 시각이 미래면 **본행은 그대로여야 한다.** 그리드는 값을 보냈지만 그것은 「다음 달부터」라는
|
|
9
|
+
* 뜻이고, 오늘의 값이 아니다. 그래서 `keepOnRow` 로 옛 값을 되돌려 저장에 얹는다 — 도래하면
|
|
10
|
+
* `projectDueClaims` 가 옮긴다.
|
|
11
|
+
*
|
|
12
|
+
* ── 마스터 변경과 한 트랜잭션이다 ──────────────────────────────────────────
|
|
13
|
+
* 부르는 쪽이 `saveMasterPatches` 안이므로 같은 `manager` 다. 갈라지면 값은 바뀌고 이력은 없는
|
|
14
|
+
* 상태가 생긴다.
|
|
15
|
+
*/
|
|
16
|
+
export declare function claimAssignmentChanges(manager: EntityManager, domain: Domain, axis: string, found: ObjectLiteral, values: ObjectLiteral, user: unknown, options: {
|
|
17
|
+
appliedAt?: Date;
|
|
18
|
+
appliedKind?: 'change' | 'correction';
|
|
19
|
+
appliedUntil?: Date;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
keepOnRow: ObjectLiteral;
|
|
22
|
+
}>;
|
|
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 };
|