@things-factory/ops-master 10.1.36 → 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.
Files changed (45) hide show
  1. package/dist-client/bootstrap.d.ts +1 -0
  2. package/dist-client/bootstrap.js +26 -0
  3. package/dist-client/bootstrap.js.map +1 -1
  4. package/dist-client/master-change-policy-axes.d.ts +26 -0
  5. package/dist-client/master-change-policy-axes.js +18 -0
  6. package/dist-client/master-change-policy-axes.js.map +1 -0
  7. package/dist-client/master-change-policy-panel.d.ts +49 -0
  8. package/dist-client/master-change-policy-panel.js +393 -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/service/approval-route.d.ts +37 -0
  21. package/dist-server/service/approval-route.js +34 -0
  22. package/dist-server/service/approval-route.js.map +1 -0
  23. package/dist-server/service/assignment/assignment-history.d.ts +21 -16
  24. package/dist-server/service/assignment/assignment-history.js +91 -52
  25. package/dist-server/service/assignment/assignment-history.js.map +1 -1
  26. package/dist-server/service/assignment/assignment-write.d.ts +21 -4
  27. package/dist-server/service/assignment/assignment-write.js +41 -9
  28. package/dist-server/service/assignment/assignment-write.js.map +1 -1
  29. package/dist-server/service/assignment/{assignment-claim.d.ts → master-value-claim.d.ts} +31 -7
  30. package/dist-server/service/assignment/{assignment-claim.js → master-value-claim.js} +45 -31
  31. package/dist-server/service/assignment/master-value-claim.js.map +1 -0
  32. package/dist-server/service/index.d.ts +4 -2
  33. package/dist-server/service/index.js +8 -1
  34. package/dist-server/service/index.js.map +1 -1
  35. package/dist-server/service/master-change-policy.d.ts +65 -2
  36. package/dist-server/service/master-change-policy.js +166 -21
  37. package/dist-server/service/master-change-policy.js.map +1 -1
  38. package/dist-server/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +6 -4
  40. package/translations/en.json +59 -2
  41. package/translations/ja.json +58 -1
  42. package/translations/ko.json +59 -2
  43. package/translations/ms.json +58 -1
  44. package/translations/zh.json +60 -1
  45. package/dist-server/service/assignment/assignment-claim.js.map +0 -1
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assignmentField = void 0;
3
+ exports.claimedField = void 0;
4
4
  exports.claimAssignmentChanges = claimAssignmentChanges;
5
5
  exports.toClaimValue = toClaimValue;
6
- const assignment_claim_1 = require("./assignment-claim");
7
- Object.defineProperty(exports, "assignmentField", { enumerable: true, get: function () { return assignment_claim_1.assignmentField; } });
6
+ exports.rowOf = rowOf;
7
+ const master_value_claim_1 = require("./master-value-claim");
8
+ Object.defineProperty(exports, "claimedField", { enumerable: true, get: function () { return master_value_claim_1.claimedField; } });
9
+ const master_axis_1 = require("../master-axis");
8
10
  const assignment_history_1 = require("./assignment-history");
9
11
  /**
10
12
  * 그리드가 보낸 변경에서 **배정·선언 칸을 골라 주장으로 남긴다.**
@@ -20,7 +22,7 @@ const assignment_history_1 = require("./assignment-history");
20
22
  */
21
23
  async function claimAssignmentChanges(manager, domain, axis, found, values, user, options) {
22
24
  const keepOnRow = {};
23
- const fields = assignment_claim_1.ASSIGNMENT_FIELDS.filter(one => one.axis === axis);
25
+ const fields = master_value_claim_1.CLAIMED_FIELDS.filter(one => one.axis === axis);
24
26
  if (!fields.length) {
25
27
  return { keepOnRow };
26
28
  }
@@ -38,7 +40,9 @@ async function claimAssignmentChanges(manager, domain, axis, found, values, user
38
40
  }
39
41
  await (0, assignment_history_1.recordClaim)(manager, domain, {
40
42
  target: { axis, rowId: String(found.id), field: declared.field },
41
- value: toClaimValue(declared.kind, next),
43
+ value: await toClaimValue(manager, domain, declared, next),
44
+ /* 바뀌기 전 값 — 첫 주장이면 그 앞을 덮는 줄이 같이 선다(§`recordClaim`). */
45
+ previous: await toClaimValue(manager, domain, declared, found[declared.field]),
42
46
  validFrom: appliedAt,
43
47
  validTo: options.appliedUntil,
44
48
  reason: options.appliedKind === 'correction' ? 'correction' : 'change',
@@ -50,16 +54,44 @@ async function claimAssignmentChanges(manager, domain, axis, found, values, user
50
54
  }
51
55
  return { keepOnRow };
52
56
  }
53
- /** 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다. */
54
- function toClaimValue(kind, value) {
57
+ /**
58
+ * 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다.
59
+ *
60
+ * ⚠ **관계 칸은 UUID 가 아니라 이름을 담는다.** `ops_equipment.home_location` 에 `UNCOIL-01` 이
61
+ * 들어 있고, `parentId` 는 id 일 수도 이름일 수도 있다(이 제품의 규약). 처음에 칸 값을 그대로
62
+ * 식별자로 썼다가 **정상 편집이 전부 거절됐다**(2026-09-22, 화면에서 드러남).
63
+ *
64
+ * 그래서 여기서 **가리키는 행을 찾아 식별자와 그때의 이름을 함께 담는다.** 마스터 칸은 그대로
65
+ * 이름을 들고, 주장은 이름이 바뀌어도 안 끊긴다.
66
+ */
67
+ async function toClaimValue(manager, domain, declared, value) {
55
68
  if (value === null || value === undefined || value === '') {
56
69
  return null;
57
70
  }
58
- if (kind === 'names') {
71
+ /* 값 그 자체인 칸 — 가리키는 것이 없으므로 푸는 일도 없다. */
72
+ if (declared.kind === 'literal') {
73
+ return { literal: String(value) };
74
+ }
75
+ if (declared.kind === 'names') {
59
76
  return { names: Array.isArray(value) ? value.map(String) : [String(value)] };
60
77
  }
61
- return { ref: String(value) };
78
+ const found = await rowOf(manager, domain, declared.target, String(value));
79
+ /* 못 찾으면 그대로 담는다 — 검증이 그 다음에 거절한다. 여기서 지어내지 않는다. */
80
+ return found ? { ref: found.id, label: found.name } : { ref: String(value) };
81
+ }
82
+ /** 이름으로도 id 로도 찾는다 — 이 제품의 관계 칸이 둘 다 담는다. */
83
+ async function rowOf(manager, domain, targetAxis, given) {
84
+ const axis = master_axis_1.MASTER_AXES.find(one => one.key === targetAxis);
85
+ if (!axis) {
86
+ return undefined;
87
+ }
88
+ const byId = UUID.test(given)
89
+ ? await manager.findOne(axis.entity, { where: { id: given, domain: { id: domain.id } } })
90
+ : undefined;
91
+ const found = byId ?? (await manager.findOne(axis.entity, { where: { name: given, domain: { id: domain.id } } }));
92
+ return found;
62
93
  }
94
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
63
95
  function sameValue(before, after) {
64
96
  if (Array.isArray(before) || Array.isArray(after)) {
65
97
  return JSON.stringify(before ?? null) === JSON.stringify(after ?? null);
@@ -1 +1 @@
1
- {"version":3,"file":"assignment-write.js","sourceRoot":"","sources":["../../../server/service/assignment/assignment-write.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, ObjectLiteral } from 'typeorm'\nimport { Domain } from '@things-factory/shell'\n\nimport { ASSIGNMENT_FIELDS, assignmentField } from './assignment-claim'\nimport { recordClaim } from './assignment-history'\n\n/**\n * 그리드가 보낸 변경에서 **배정·선언 칸을 골라 주장으로 남긴다.**\n *\n * ── 왜 본행을 되돌려 놓나 ──────────────────────────────────────────────────\n * 적용 시각이 미래면 **본행은 그대로여야 한다.** 그리드는 값을 보냈지만 그것은 「다음 달부터」라는\n * 뜻이고, 오늘의 값이 아니다. 그래서 `keepOnRow` 로 옛 값을 되돌려 저장에 얹는다 — 도래하면\n * `projectDueClaims` 가 옮긴다.\n *\n * ── 마스터 변경과 한 트랜잭션이다 ──────────────────────────────────────────\n * 부르는 쪽이 `saveMasterPatches` 안이므로 같은 `manager` 다. 갈라지면 값은 바뀌고 이력은 없는\n * 상태가 생긴다.\n */\nexport async function claimAssignmentChanges(\n manager: EntityManager,\n domain: Domain,\n axis: string,\n found: ObjectLiteral,\n values: ObjectLiteral,\n user: unknown,\n options: { appliedAt?: Date; appliedKind?: 'change' | 'correction'; appliedUntil?: Date }\n): Promise<{ keepOnRow: ObjectLiteral }> {\n const keepOnRow: ObjectLiteral = {}\n const fields = ASSIGNMENT_FIELDS.filter(one => one.axis === axis)\n\n if (!fields.length) {\n return { keepOnRow }\n }\n\n const now = new Date()\n const appliedAt = options.appliedAt ?? now\n const future = appliedAt.getTime() > now.getTime()\n\n for (const declared of fields) {\n if (!(declared.field in values)) {\n continue\n }\n\n const next = values[declared.field]\n\n /* 안 바뀐 칸은 주장을 남기지 않는다 — 같은 값을 다시 적으면 이력이 뜻 없이 길어진다. */\n if (sameValue(found[declared.field], next)) {\n continue\n }\n\n await recordClaim(\n manager,\n domain,\n {\n target: { axis, rowId: String(found.id), field: declared.field },\n value: toClaimValue(declared.kind, next),\n validFrom: appliedAt,\n validTo: options.appliedUntil,\n reason: options.appliedKind === 'correction' ? 'correction' : 'change',\n recorder: user\n },\n now\n )\n\n if (future) {\n keepOnRow[declared.field] = found[declared.field] ?? null\n }\n }\n\n return { keepOnRow }\n}\n\n/** 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다. */\nexport function toClaimValue(kind: 'relation' | 'names', value: unknown): Record<string, unknown> | null {\n if (value === null || value === undefined || value === '') {\n return null\n }\n\n if (kind === 'names') {\n return { names: Array.isArray(value) ? value.map(String) : [String(value)] }\n }\n\n return { ref: String(value) }\n}\n\nfunction sameValue(before: unknown, after: unknown): boolean {\n if (Array.isArray(before) || Array.isArray(after)) {\n return JSON.stringify(before ?? null) === JSON.stringify(after ?? null)\n }\n\n return (before ?? null) === (after ?? null)\n}\n\nexport { assignmentField }\n"]}
1
+ {"version":3,"file":"assignment-write.js","sourceRoot":"","sources":["../../../server/service/assignment/assignment-write.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, ObjectLiteral } from 'typeorm'\nimport { Domain } from '@things-factory/shell'\n\nimport { CLAIMED_FIELDS, claimedField } from './master-value-claim'\nimport { MASTER_AXES } from '../master-axis'\nimport { recordClaim } from './assignment-history'\n\n/**\n * 그리드가 보낸 변경에서 **배정·선언 칸을 골라 주장으로 남긴다.**\n *\n * ── 왜 본행을 되돌려 놓나 ──────────────────────────────────────────────────\n * 적용 시각이 미래면 **본행은 그대로여야 한다.** 그리드는 값을 보냈지만 그것은 「다음 달부터」라는\n * 뜻이고, 오늘의 값이 아니다. 그래서 `keepOnRow` 로 옛 값을 되돌려 저장에 얹는다 — 도래하면\n * `projectDueClaims` 가 옮긴다.\n *\n * ── 마스터 변경과 한 트랜잭션이다 ──────────────────────────────────────────\n * 부르는 쪽이 `saveMasterPatches` 안이므로 같은 `manager` 다. 갈라지면 값은 바뀌고 이력은 없는\n * 상태가 생긴다.\n */\nexport async function claimAssignmentChanges(\n manager: EntityManager,\n domain: Domain,\n axis: string,\n found: ObjectLiteral,\n values: ObjectLiteral,\n user: unknown,\n options: { appliedAt?: Date; appliedKind?: 'change' | 'correction'; appliedUntil?: Date }\n): Promise<{ keepOnRow: ObjectLiteral }> {\n const keepOnRow: ObjectLiteral = {}\n const fields = CLAIMED_FIELDS.filter(one => one.axis === axis)\n\n if (!fields.length) {\n return { keepOnRow }\n }\n\n const now = new Date()\n const appliedAt = options.appliedAt ?? now\n const future = appliedAt.getTime() > now.getTime()\n\n for (const declared of fields) {\n if (!(declared.field in values)) {\n continue\n }\n\n const next = values[declared.field]\n\n /* 안 바뀐 칸은 주장을 남기지 않는다 — 같은 값을 다시 적으면 이력이 뜻 없이 길어진다. */\n if (sameValue(found[declared.field], next)) {\n continue\n }\n\n await recordClaim(\n manager,\n domain,\n {\n target: { axis, rowId: String(found.id), field: declared.field },\n value: await toClaimValue(manager, domain, declared, next),\n /* 바뀌기 전 값 — 첫 주장이면 그 앞을 덮는 줄이 같이 선다(§`recordClaim`). */\n previous: await toClaimValue(manager, domain, declared, found[declared.field]),\n validFrom: appliedAt,\n validTo: options.appliedUntil,\n reason: options.appliedKind === 'correction' ? 'correction' : 'change',\n recorder: user\n },\n now\n )\n\n if (future) {\n keepOnRow[declared.field] = found[declared.field] ?? null\n }\n }\n\n return { keepOnRow }\n}\n\n/**\n * 마스터 칸의 값 → 주장의 값. 「선언 없음」은 `null` 이다.\n *\n * ⚠ **관계 칸은 UUID 가 아니라 이름을 담는다.** `ops_equipment.home_location` 에 `UNCOIL-01` 이\n * 들어 있고, `parentId` 는 id 일 수도 이름일 수도 있다(이 제품의 규약). 처음에 칸 값을 그대로\n * 식별자로 썼다가 **정상 편집이 전부 거절됐다**(2026-09-22, 화면에서 드러남).\n *\n * 그래서 여기서 **가리키는 행을 찾아 식별자와 그때의 이름을 함께 담는다.** 마스터 칸은 그대로\n * 이름을 들고, 주장은 이름이 바뀌어도 안 끊긴다.\n */\nexport async function toClaimValue(\n manager: EntityManager,\n domain: Domain,\n declared: { kind: 'relation' | 'names' | 'literal'; target?: string },\n value: unknown\n): Promise<Record<string, unknown> | null> {\n if (value === null || value === undefined || value === '') {\n return null\n }\n\n /* 값 그 자체인 칸 — 가리키는 것이 없으므로 푸는 일도 없다. */\n if (declared.kind === 'literal') {\n return { literal: String(value) }\n }\n\n if (declared.kind === 'names') {\n return { names: Array.isArray(value) ? value.map(String) : [String(value)] }\n }\n\n const found = await rowOf(manager, domain, declared.target, String(value))\n\n /* 못 찾으면 그대로 담는다 — 검증이 그 다음에 거절한다. 여기서 지어내지 않는다. */\n return found ? { ref: found.id, label: found.name } : { ref: String(value) }\n}\n\n/** 이름으로도 id 로도 찾는다 — 이 제품의 관계 칸이 둘 다 담는다. */\nexport async function rowOf(\n manager: EntityManager,\n domain: Domain,\n targetAxis: string | undefined,\n given: string\n): Promise<{ id: string; name: string } | undefined> {\n const axis = MASTER_AXES.find(one => one.key === targetAxis)\n\n if (!axis) {\n return undefined\n }\n\n const byId = UUID.test(given)\n ? await manager.findOne(axis.entity as never, { where: { id: given, domain: { id: domain.id } } } as never)\n : undefined\n\n const found = byId ?? (await manager.findOne(axis.entity as never, { where: { name: given, domain: { id: domain.id } } } as never))\n\n return found as unknown as { id: string; name: string } | undefined\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nfunction sameValue(before: unknown, after: unknown): boolean {\n if (Array.isArray(before) || Array.isArray(after)) {\n return JSON.stringify(before ?? null) === JSON.stringify(after ?? null)\n }\n\n return (before ?? null) === (after ?? null)\n}\n\nexport { claimedField }\n"]}
@@ -38,7 +38,7 @@ import { User } from '@things-factory/auth-base';
38
38
  * 바꾼 날 과거 관계가 끊긴다(적용 가이드 §0-1 의 그 부류).
39
39
  * ═══════════════════════════════════════════════════════════════════════════
40
40
  */
41
- export declare class AssignmentClaim {
41
+ export declare class MasterValueClaim {
42
42
  readonly id: string;
43
43
  domain?: Domain;
44
44
  domainId?: string;
@@ -46,7 +46,7 @@ export declare class AssignmentClaim {
46
46
  axis: string;
47
47
  /** 그 축의 어느 행인가. UUID 다. */
48
48
  rowId: string;
49
- /** 어느 칸인가 — 선언한 셋만 온다(§`ASSIGNMENT_FIELDS`). */
49
+ /** 어느 칸인가 — 선언한 셋만 온다(§`CLAIMED_FIELDS`). */
50
50
  field: string;
51
51
  /**
52
52
  * 그때의 값.
@@ -58,8 +58,14 @@ export declare class AssignmentClaim {
58
58
  * ```
59
59
  */
60
60
  value?: Record<string, unknown> | null;
61
- /** 언제부터 그러했나 — 업무의 시간. */
62
- validFrom: Date;
61
+ /**
62
+ * 언제부터 그러했나 — 업무의 시간.
63
+ *
64
+ * **비어 있으면 「그때까지 쭉」이다.** 어느 칸이 처음 바뀔 때, 바뀌기 전 값도 같이 적는데 그 값이
65
+ * 언제부터였는지는 아무도 안 적었다. 1970 년 같은 시각을 넣으면 적은 적 없는 것을 적은 것이 되므로
66
+ * 비워 둔다(2026-09-23).
67
+ */
68
+ validFrom?: Date;
63
69
  /** 언제까지. 비면 아직 끝나지 않았다. */
64
70
  validTo?: Date;
65
71
  /** 언제 그렇게 적었나. 서버가 붙인다 — 사람이 넣지 않는다. */
@@ -82,12 +88,20 @@ export declare class AssignmentClaim {
82
88
  createdAt?: Date;
83
89
  }
84
90
  /**
85
- * **이력을 받는 칸은 셋뿐이다.**
91
+ * **이력을 받는 칸은 여기 적힌 것뿐이다.**
86
92
  *
87
93
  * 임의의 축·칸을 받는 범용 쓰기 문을 열지 않는다(설계 점검 2026-09-22). 칸마다 값의 모양과 검증이
88
94
  * 다르고, 아무 칸이나 받는 문은 그 검증을 못 한다.
95
+ *
96
+ * ── 셋에서 넷이 됐다 (2026-09-23) ─────────────────────────────────────────
97
+ * 앞의 셋은 **배정**이다 — 무엇이 어디에 속하나. 넷째는 아니다. 공정의 표준시간은 값 자체이고,
98
+ * 실적의 성능률이 그것을 나눈다. 지금은 **오늘 마스터를 다시 읽어서 지난달 성능이 움직인다**
99
+ * (`operation-effectivity-db.test.cjs` 가 그 거동을 적어 두고 있다).
100
+ *
101
+ * 그래서 이 표의 이름이 「배정」이 아니라 「마스터 값의 주장」이다. 기전은 같다 — 두 시각, 정정은
102
+ * 물리고 앞뒤를 다시 주장, 주장이 없으면 한 번도 안 바뀐 것.
89
103
  */
90
- export declare const ASSIGNMENT_FIELDS: ({
104
+ export declare const CLAIMED_FIELDS: ({
91
105
  axis: string;
92
106
  field: string;
93
107
  kind: "relation";
@@ -97,8 +111,13 @@ export declare const ASSIGNMENT_FIELDS: ({
97
111
  field: string;
98
112
  kind: "names";
99
113
  target?: undefined;
114
+ } | {
115
+ axis: string;
116
+ field: string;
117
+ kind: "literal";
118
+ target?: undefined;
100
119
  })[];
101
- export declare function assignmentField(axis: string, field: string): {
120
+ export declare function claimedField(axis: string, field: string): {
102
121
  axis: string;
103
122
  field: string;
104
123
  kind: "relation";
@@ -108,4 +127,9 @@ export declare function assignmentField(axis: string, field: string): {
108
127
  field: string;
109
128
  kind: "names";
110
129
  target?: undefined;
130
+ } | {
131
+ axis: string;
132
+ field: string;
133
+ kind: "literal";
134
+ target?: undefined;
111
135
  };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ASSIGNMENT_FIELDS = exports.AssignmentClaim = void 0;
4
- exports.assignmentField = assignmentField;
3
+ exports.CLAIMED_FIELDS = exports.MasterValueClaim = void 0;
4
+ exports.claimedField = claimedField;
5
5
  const tslib_1 = require("tslib");
6
6
  const typeorm_1 = require("typeorm");
7
7
  const type_graphql_1 = require("type-graphql");
@@ -45,64 +45,64 @@ const auth_base_1 = require("@things-factory/auth-base");
45
45
  * 바꾼 날 과거 관계가 끊긴다(적용 가이드 §0-1 의 그 부류).
46
46
  * ═══════════════════════════════════════════════════════════════════════════
47
47
  */
48
- let AssignmentClaim = class AssignmentClaim {
48
+ let MasterValueClaim = class MasterValueClaim {
49
49
  };
50
- exports.AssignmentClaim = AssignmentClaim;
50
+ exports.MasterValueClaim = MasterValueClaim;
51
51
  tslib_1.__decorate([
52
52
  (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
53
53
  (0, type_graphql_1.Field)(type => type_graphql_1.ID),
54
54
  tslib_1.__metadata("design:type", String)
55
- ], AssignmentClaim.prototype, "id", void 0);
55
+ ], MasterValueClaim.prototype, "id", void 0);
56
56
  tslib_1.__decorate([
57
57
  (0, typeorm_1.ManyToOne)(type => shell_1.Domain),
58
58
  (0, type_graphql_1.Field)(type => shell_1.Domain),
59
59
  tslib_1.__metadata("design:type", shell_1.Domain)
60
- ], AssignmentClaim.prototype, "domain", void 0);
60
+ ], MasterValueClaim.prototype, "domain", void 0);
61
61
  tslib_1.__decorate([
62
62
  (0, typeorm_1.RelationId)((claim) => claim.domain),
63
63
  tslib_1.__metadata("design:type", String)
64
- ], AssignmentClaim.prototype, "domainId", void 0);
64
+ ], MasterValueClaim.prototype, "domainId", void 0);
65
65
  tslib_1.__decorate([
66
66
  (0, typeorm_1.Column)(),
67
67
  (0, type_graphql_1.Field)(),
68
68
  tslib_1.__metadata("design:type", String)
69
- ], AssignmentClaim.prototype, "axis", void 0);
69
+ ], MasterValueClaim.prototype, "axis", void 0);
70
70
  tslib_1.__decorate([
71
71
  (0, typeorm_1.Column)(),
72
72
  (0, type_graphql_1.Field)(),
73
73
  tslib_1.__metadata("design:type", String)
74
- ], AssignmentClaim.prototype, "rowId", void 0);
74
+ ], MasterValueClaim.prototype, "rowId", void 0);
75
75
  tslib_1.__decorate([
76
76
  (0, typeorm_1.Column)(),
77
77
  (0, type_graphql_1.Field)(),
78
78
  tslib_1.__metadata("design:type", String)
79
- ], AssignmentClaim.prototype, "field", void 0);
79
+ ], MasterValueClaim.prototype, "field", void 0);
80
80
  tslib_1.__decorate([
81
81
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
82
82
  (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true }),
83
83
  tslib_1.__metadata("design:type", Object)
84
- ], AssignmentClaim.prototype, "value", void 0);
84
+ ], MasterValueClaim.prototype, "value", void 0);
85
85
  tslib_1.__decorate([
86
- (0, typeorm_1.Column)(),
87
- (0, type_graphql_1.Field)(),
86
+ (0, typeorm_1.Column)({ nullable: true }),
87
+ (0, type_graphql_1.Field)({ nullable: true }),
88
88
  tslib_1.__metadata("design:type", Date
89
89
  /** 언제까지. 비면 아직 끝나지 않았다. */
90
90
  )
91
- ], AssignmentClaim.prototype, "validFrom", void 0);
91
+ ], MasterValueClaim.prototype, "validFrom", void 0);
92
92
  tslib_1.__decorate([
93
93
  (0, typeorm_1.Column)({ nullable: true }),
94
94
  (0, type_graphql_1.Field)({ nullable: true }),
95
95
  tslib_1.__metadata("design:type", Date
96
96
  /** 언제 그렇게 적었나. 서버가 붙인다 — 사람이 넣지 않는다. */
97
97
  )
98
- ], AssignmentClaim.prototype, "validTo", void 0);
98
+ ], MasterValueClaim.prototype, "validTo", void 0);
99
99
  tslib_1.__decorate([
100
100
  (0, typeorm_1.Column)(),
101
101
  (0, type_graphql_1.Field)(),
102
102
  tslib_1.__metadata("design:type", Date
103
103
  /** 언제 물러났나. 비면 지금도 서 있는 주장이다. */
104
104
  )
105
- ], AssignmentClaim.prototype, "recordedAt", void 0);
105
+ ], MasterValueClaim.prototype, "recordedAt", void 0);
106
106
  tslib_1.__decorate([
107
107
  (0, typeorm_1.Column)({ nullable: true }),
108
108
  (0, type_graphql_1.Field)({ nullable: true }),
@@ -118,43 +118,57 @@ tslib_1.__decorate([
118
118
  * ```
119
119
  */
120
120
  )
121
- ], AssignmentClaim.prototype, "supersededAt", void 0);
121
+ ], MasterValueClaim.prototype, "supersededAt", void 0);
122
122
  tslib_1.__decorate([
123
123
  (0, typeorm_1.Column)(),
124
124
  (0, type_graphql_1.Field)(),
125
125
  tslib_1.__metadata("design:type", String)
126
- ], AssignmentClaim.prototype, "reason", void 0);
126
+ ], MasterValueClaim.prototype, "reason", void 0);
127
127
  tslib_1.__decorate([
128
128
  (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
129
129
  (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
130
130
  tslib_1.__metadata("design:type", auth_base_1.User)
131
- ], AssignmentClaim.prototype, "recorder", void 0);
131
+ ], MasterValueClaim.prototype, "recorder", void 0);
132
132
  tslib_1.__decorate([
133
133
  (0, typeorm_1.RelationId)((claim) => claim.recorder),
134
134
  tslib_1.__metadata("design:type", String)
135
- ], AssignmentClaim.prototype, "recorderId", void 0);
135
+ ], MasterValueClaim.prototype, "recorderId", void 0);
136
136
  tslib_1.__decorate([
137
137
  (0, typeorm_1.CreateDateColumn)(),
138
138
  (0, type_graphql_1.Field)({ nullable: true }),
139
139
  tslib_1.__metadata("design:type", Date)
140
- ], AssignmentClaim.prototype, "createdAt", void 0);
141
- exports.AssignmentClaim = AssignmentClaim = tslib_1.__decorate([
142
- (0, typeorm_1.Entity)({ name: 'ops_assignment_claim' }),
143
- (0, typeorm_1.Index)('ix_ops_assignment_claim_0', (claim) => [claim.domain, claim.axis, claim.rowId, claim.field]),
140
+ ], MasterValueClaim.prototype, "createdAt", void 0);
141
+ exports.MasterValueClaim = MasterValueClaim = tslib_1.__decorate([
142
+ (0, typeorm_1.Entity)({ name: 'ops_master_value_claim' }),
143
+ (0, typeorm_1.Index)('ix_ops_master_value_claim_0', (claim) => [claim.domain, claim.axis, claim.rowId, claim.field]),
144
144
  (0, type_graphql_1.ObjectType)({ description: 'what an assignment or declaration was, from when, as recorded when' })
145
- ], AssignmentClaim);
145
+ ], MasterValueClaim);
146
146
  /**
147
- * **이력을 받는 칸은 셋뿐이다.**
147
+ * **이력을 받는 칸은 여기 적힌 것뿐이다.**
148
148
  *
149
149
  * 임의의 축·칸을 받는 범용 쓰기 문을 열지 않는다(설계 점검 2026-09-22). 칸마다 값의 모양과 검증이
150
150
  * 다르고, 아무 칸이나 받는 문은 그 검증을 못 한다.
151
+ *
152
+ * ── 셋에서 넷이 됐다 (2026-09-23) ─────────────────────────────────────────
153
+ * 앞의 셋은 **배정**이다 — 무엇이 어디에 속하나. 넷째는 아니다. 공정의 표준시간은 값 자체이고,
154
+ * 실적의 성능률이 그것을 나눈다. 지금은 **오늘 마스터를 다시 읽어서 지난달 성능이 움직인다**
155
+ * (`operation-effectivity-db.test.cjs` 가 그 거동을 적어 두고 있다).
156
+ *
157
+ * 그래서 이 표의 이름이 「배정」이 아니라 「마스터 값의 주장」이다. 기전은 같다 — 두 시각, 정정은
158
+ * 물리고 앞뒤를 다시 주장, 주장이 없으면 한 번도 안 바뀐 것.
151
159
  */
152
- exports.ASSIGNMENT_FIELDS = [
160
+ exports.CLAIMED_FIELDS = [
153
161
  { axis: 'equipment', field: 'homeLocation', kind: 'relation', target: 'operationalLocation' },
154
162
  { axis: 'operationalLocation', field: 'workShiftNames', kind: 'names' },
155
- { axis: 'operationalLocation', field: 'parentId', kind: 'relation', target: 'operationalLocation' }
163
+ { axis: 'operationalLocation', field: 'parentId', kind: 'relation', target: 'operationalLocation' },
164
+ /*
165
+ * ISO 8601 기간 문자열 그대로 담는다(`PT1M`). ms 로 바꿔 담으면 사람이 읽을 때 원문이 없고,
166
+ * 원문만 담으면 파싱 규칙이 바뀌는 날 값이 달라진다 — **읽는 쪽이 둘 다 내도록** 원문을 담고
167
+ * 부르는 쪽이 파싱한다(착수 카드 §3-1).
168
+ */
169
+ { axis: 'operationDefinition', field: 'duration', kind: 'literal' }
156
170
  ];
157
- function assignmentField(axis, field) {
158
- return exports.ASSIGNMENT_FIELDS.find(one => one.axis === axis && one.field === field);
171
+ function claimedField(axis, field) {
172
+ return exports.CLAIMED_FIELDS.find(one => one.axis === axis && one.field === field);
159
173
  }
160
- //# sourceMappingURL=assignment-claim.js.map
174
+ //# sourceMappingURL=master-value-claim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"master-value-claim.js","sourceRoot":"","sources":["../../../server/service/assignment/master-value-claim.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * **언제부터 그러했고, 언제 그렇게 적었나** — 배정과 선언의 주장 한 줄.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 무엇이 없었나 ──────────────────────────────────────────────────────────\n * 가동률의 분모는 **설비가 어느 자리에 있었나 · 그 자리가 어느 근무조로 돌았나 · 그 자리가 어디에\n * 속했나**로 정해진다. 셋 다 칸 하나였다. 그래서 설비를 옮기면 **지난달 가동률이 바뀌었고**, 아무도\n * 아무것도 정정하지 않았는데 숫자가 달라진 까닭을 말할 수 없었다.\n *\n * ── 시각이 둘인 까닭 ───────────────────────────────────────────────────────\n * 「언제부터 그렇게 하기로 했나」와 「언제 그렇게 적었나」는 다르다.\n *\n * ```\n * validFrom · validTo 적용 시각 — 업무의 시간. 분모를 나누는 경계가 이것이다\n * recordedAt 기록 시각 — 우리가 그것을 안 시각\n * supersededAt 이 주장이 물러난 시각. 덮어쓰지 않고 물러나게 한다\n * ```\n *\n * 사흘 전에 옮긴 것을 오늘 적으면 둘이 갈라진다. **이틀 전 기록시점으로 물으면 옛 자리가 나와야\n * 한다** — 그때 우리가 알던 것이 그것이기 때문이다(ADR-0088 결정 2 ③).\n *\n * ── 정정은 덮지 않고 조각을 남긴다 ─────────────────────────────────────────\n * ```\n * 지금까지 A [1일, ∞)\n * 오늘 정정 「10~15일은 B 였다」\n * 최신 A [1일, 10일) · B [10일, 15일) · A [15일, ∞)\n * 정정 전 시점 A [1일, ∞) ← 조각은 그때 아직 기록되지 않았다\n * ```\n *\n * ── 이 표가 기준이 되는 원본이고, 마스터 행은 투영이다 ─────────────────────\n * 미래 주장은 마스터 행을 **안 건드린다.** 다음 달 이동을 지금 적었다고 다른 제품이 오늘부터 새\n * 자리를 읽으면 안 된다. 도래하면 투영이 행을 맞춘다(§`projectDueClaims`).\n *\n * ── 이름이 아니라 식별자로 가리킨다 ────────────────────────────────────────\n * `value.ref` 가 안정된 식별자이고 `value.label` 은 **그때 뭐라고 불렀나**다. 이름을 담으면 이름을\n * 바꾼 날 과거 관계가 끊긴다(적용 가이드 §0-1 의 그 부류).\n * ═══════════════════════════════════════════════════════════════════════════\n */\n@Entity({ name: 'ops_master_value_claim' })\n@Index('ix_ops_master_value_claim_0', (claim: MasterValueClaim) => [claim.domain, claim.axis, claim.rowId, claim.field])\n@ObjectType({ description: 'what an assignment or declaration was, from when, as recorded when' })\nexport class MasterValueClaim {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((claim: MasterValueClaim) => claim.domain)\n domainId?: string\n\n /** 어느 축의 무엇인가 — `equipment` · `operationalLocation`. */\n @Column()\n @Field()\n axis: string\n\n /** 그 축의 어느 행인가. UUID 다. */\n @Column()\n @Field()\n rowId: string\n\n /** 어느 칸인가 — 선언한 셋만 온다(§`CLAIMED_FIELDS`). */\n @Column()\n @Field()\n field: string\n\n /**\n * 그때의 값.\n *\n * ```\n * { ref, label } 관계를 가리키는 칸 — 식별자와 그때의 표시값\n * { names: [...] } 이름 목록을 드는 칸 — 근무조 선언\n * null **선언 없음.** 「그때부터 부모가 없다」는 정상 값이고 결측이 아니다\n * ```\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true })\n value?: Record<string, unknown> | null\n\n /**\n * 언제부터 그러했나 — 업무의 시간.\n *\n * **비어 있으면 「그때까지 쭉」이다.** 어느 칸이 처음 바뀔 때, 바뀌기 전 값도 같이 적는데 그 값이\n * 언제부터였는지는 아무도 안 적었다. 1970 년 같은 시각을 넣으면 적은 적 없는 것을 적은 것이 되므로\n * 비워 둔다(2026-09-23).\n */\n @Column({ nullable: true })\n @Field({ nullable: true })\n validFrom?: Date\n\n /** 언제까지. 비면 아직 끝나지 않았다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n validTo?: Date\n\n /** 언제 그렇게 적었나. 서버가 붙인다 — 사람이 넣지 않는다. */\n @Column()\n @Field()\n recordedAt: Date\n\n /** 언제 물러났나. 비면 지금도 서 있는 주장이다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n supersededAt?: Date\n\n /**\n * 왜 이 주장이 섰나 — 읽는 사람이 「지연 기록인가 정정인가」를 알아야 한다.\n *\n * ```\n * initial 도입 시각에 확인된 초기 상태\n * change 바꾼다는 선언\n * correction 이미 적은 것을 고친다\n * remainder 정정이 남긴 앞뒤 조각\n * ```\n */\n @Column()\n @Field()\n reason: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n recorder?: User\n\n @RelationId((claim: MasterValueClaim) => claim.recorder)\n recorderId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n}\n\n/**\n * **이력을 받는 칸은 여기 적힌 것뿐이다.**\n *\n * 임의의 축·칸을 받는 범용 쓰기 문을 열지 않는다(설계 점검 2026-09-22). 칸마다 값의 모양과 검증이\n * 다르고, 아무 칸이나 받는 문은 그 검증을 못 한다.\n *\n * ── 셋에서 넷이 됐다 (2026-09-23) ─────────────────────────────────────────\n * 앞의 셋은 **배정**이다 — 무엇이 어디에 속하나. 넷째는 아니다. 공정의 표준시간은 값 자체이고,\n * 실적의 성능률이 그것을 나눈다. 지금은 **오늘 마스터를 다시 읽어서 지난달 성능이 움직인다**\n * (`operation-effectivity-db.test.cjs` 가 그 거동을 적어 두고 있다).\n *\n * 그래서 이 표의 이름이 「배정」이 아니라 「마스터 값의 주장」이다. 기전은 같다 — 두 시각, 정정은\n * 물리고 앞뒤를 다시 주장, 주장이 없으면 한 번도 안 바뀐 것.\n */\nexport const CLAIMED_FIELDS = [\n { axis: 'equipment', field: 'homeLocation', kind: 'relation' as const, target: 'operationalLocation' },\n { axis: 'operationalLocation', field: 'workShiftNames', kind: 'names' as const },\n { axis: 'operationalLocation', field: 'parentId', kind: 'relation' as const, target: 'operationalLocation' },\n /*\n * ISO 8601 기간 문자열 그대로 담는다(`PT1M`). ms 로 바꿔 담으면 사람이 읽을 때 원문이 없고,\n * 원문만 담으면 파싱 규칙이 바뀌는 날 값이 달라진다 — **읽는 쪽이 둘 다 내도록** 원문을 담고\n * 부르는 쪽이 파싱한다(착수 카드 §3-1).\n */\n { axis: 'operationDefinition', field: 'duration', kind: 'literal' as const }\n]\n\nexport function claimedField(axis: string, field: string) {\n return CLAIMED_FIELDS.find(one => one.axis === axis && one.field === field)\n}\n"]}
@@ -10,6 +10,7 @@ export * from './test-spec/test-spec-master-resolver';
10
10
  export * from './master-crud';
11
11
  export * from './master-axis';
12
12
  export * from './master-change-policy';
13
+ export * from './approval-route';
13
14
  export * from './master-conformance';
14
15
  export * from './master-conformance-resolver';
15
16
  export * from './master-list';
@@ -67,10 +68,11 @@ export * from './status/master-status';
67
68
  export * from './quality/nonconformance-cause';
68
69
  import { MasterConformanceResolver } from './master-conformance-resolver';
69
70
  import { MasterChangePolicy, MasterChangePolicyResolver } from './master-change-policy';
70
- export declare const entities: (typeof import("./equipment/equipment").Equipment | typeof import("./equipment/equipment-class").EquipmentClass | typeof import("./quality/nonconformance-cause").NonconformanceCause | typeof import("./material/material-definition").MaterialDefinition | typeof import("./operation/operation-definition").OperationDefinition | typeof import("./location/operational-location").OperationalLocation | typeof import("./personnel/person").Person | typeof import("./asset/physical-asset").PhysicalAsset | typeof import("./recipe").RecipeDefinition | typeof import("./recipe").RouteDefinition | typeof import("./test-spec/test-specification").TestSpecification | typeof MasterChangePolicy | typeof import("./recipe/recipe-revision-line").RecipeRevisionOutput | typeof import("./recipe/recipe-revision").RecipeRevision | typeof import("./recipe/recipe-revision").RecipePlan | typeof import("./recipe/recipe-revision").RecipeSegment | typeof import("./recipe/recipe-revision").RecipeBasis | typeof import("./material/material-revision").MaterialDefinitionRevision | typeof import("./material/material-revision").MaterialDefinitionPlan | typeof import("./material/material-revision").MaterialDefinitionSegment | typeof import("./material/material-revision").MaterialDefinitionBasis | typeof import("./test-spec/test-spec-revision").TestSpecificationRevision | typeof import("./test-spec/test-spec-revision").TestSpecificationPlan | typeof import("./test-spec/test-spec-revision").TestSpecificationSegment | typeof import("./test-spec/test-spec-revision").TestSpecificationBasis)[];
71
+ import { MasterValueClaim } from './assignment/master-value-claim';
72
+ export declare const entities: (typeof import("./equipment/equipment").Equipment | typeof import("./equipment/equipment-class").EquipmentClass | typeof import("./quality/nonconformance-cause").NonconformanceCause | typeof import("./material/material-definition").MaterialDefinition | typeof import("./operation/operation-definition").OperationDefinition | typeof import("./location/operational-location").OperationalLocation | typeof import("./personnel/person").Person | typeof import("./asset/physical-asset").PhysicalAsset | typeof import("./recipe").RecipeDefinition | typeof import("./recipe").RouteDefinition | typeof import("./test-spec/test-specification").TestSpecification | typeof MasterChangePolicy | typeof MasterValueClaim | typeof import("./recipe/recipe-revision-line").RecipeRevisionOutput | typeof import("./recipe/recipe-revision").RecipeRevision | typeof import("./recipe/recipe-revision").RecipePlan | typeof import("./recipe/recipe-revision").RecipeSegment | typeof import("./recipe/recipe-revision").RecipeBasis | typeof import("./material/material-revision").MaterialDefinitionRevision | typeof import("./material/material-revision").MaterialDefinitionPlan | typeof import("./material/material-revision").MaterialDefinitionSegment | typeof import("./material/material-revision").MaterialDefinitionBasis | typeof import("./test-spec/test-spec-revision").TestSpecificationRevision | typeof import("./test-spec/test-spec-revision").TestSpecificationPlan | typeof import("./test-spec/test-spec-revision").TestSpecificationSegment | typeof import("./test-spec/test-spec-revision").TestSpecificationBasis)[];
71
73
  export declare const schema: {
72
74
  resolverClasses: (typeof MasterChangePolicyResolver | typeof import("./personnel/personnel-master-resolver").PersonnelClassResolver | typeof import("./personnel/personnel-master-resolver").PersonResolver | typeof import("./equipment/equipment-master-resolver").EquipmentClassResolver | typeof import("./equipment/equipment-master-resolver").EquipmentResolver | typeof import("./equipment/equipment-master-resolver").StopReasonResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetClassResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetResolver | typeof import("./material/material-master-resolver").MaterialClassResolver | typeof import("./material/material-master-resolver").MaterialDefinitionResolver | typeof import("./location/location-master-resolver").OperationalLocationResolver | typeof import("./operation/operation-master-resolver").OperationDefinitionResolver | typeof import("./recipe/recipe-master-resolver").RecipeDefinitionResolver | typeof import("./recipe/recipe-master-resolver").RouteDefinitionResolver | typeof import("./test-spec/test-spec-master-resolver").TestSpecificationResolver | typeof MasterConformanceResolver | typeof import("./personnel/personnel-query").PersonnelQuery | typeof import("./equipment/equipment-query").EquipmentQuery | typeof import("./asset/asset-query").AssetQuery | typeof import("./location/location-query").LocationQuery | typeof import("./material/material-revision-resolver").MaterialDefinitionRevisionResolver | typeof import("./material/material-query").MaterialQuery | typeof import("./operation/operation-query").OperationQuery | typeof import("./operation/operation-readiness-resolver").OperationReadinessResolver | typeof import("./recipe/recipe-revision-resolver").RecipeRevisionResolver | typeof import("./recipe/recipe-query").RecipeQuery | typeof import("./test-spec/test-spec-revision-resolver").TestSpecificationRevisionResolver | typeof import("./test-spec/test-spec-query").TestSpecificationQuery | typeof import("./status/master-status").OpsMasterStatusQuery | typeof import("./equipment/stop-reason-query").StopReasonQuery | typeof import("./quality/nonconformance-cause-resolver").NonconformanceCauseResolver)[];
73
75
  };
74
- export * from './assignment/assignment-claim';
76
+ export * from './assignment/master-value-claim';
75
77
  export * from './assignment/assignment-history';
76
78
  export * from './assignment/assignment-write';
@@ -15,6 +15,7 @@ tslib_1.__exportStar(require("./test-spec/test-spec-master-resolver"), exports);
15
15
  tslib_1.__exportStar(require("./master-crud"), exports);
16
16
  tslib_1.__exportStar(require("./master-axis"), exports);
17
17
  tslib_1.__exportStar(require("./master-change-policy"), exports);
18
+ tslib_1.__exportStar(require("./approval-route"), exports);
18
19
  tslib_1.__exportStar(require("./master-conformance"), exports);
19
20
  tslib_1.__exportStar(require("./master-conformance-resolver"), exports);
20
21
  tslib_1.__exportStar(require("./master-list"), exports);
@@ -84,10 +85,16 @@ const status_1 = require("./status");
84
85
  /* 마스터 정합성 — 축을 가로지르므로 어느 축에도 안 속한다. */
85
86
  const master_conformance_resolver_1 = require("./master-conformance-resolver");
86
87
  const master_change_policy_1 = require("./master-change-policy");
88
+ const master_value_claim_1 = require("./assignment/master-value-claim");
87
89
  const test_spec_1 = require("./test-spec");
88
90
  exports.entities = [
89
91
  /* ENTITIES */
90
92
  master_change_policy_1.MasterChangePolicy,
93
+ /*
94
+ * 배정 이력. **등록이 빠져 있었다**(2026-09-23 확인) — 표가 안 서니 이력을 쓰는 길이 실제
95
+ * 설치본에는 없었다. 시험 하네스가 자기 목록에 직접 넣어서 초록이었다.
96
+ */
97
+ master_value_claim_1.MasterValueClaim,
91
98
  ...personnel_1.entities,
92
99
  ...quality_1.entities,
93
100
  ...equipment_1.entities,
@@ -119,7 +126,7 @@ exports.schema = {
119
126
  master_conformance_resolver_1.MasterConformanceResolver
120
127
  ]
121
128
  };
122
- tslib_1.__exportStar(require("./assignment/assignment-claim"), exports);
129
+ tslib_1.__exportStar(require("./assignment/master-value-claim"), exports);
123
130
  tslib_1.__exportStar(require("./assignment/assignment-history"), exports);
124
131
  tslib_1.__exportStar(require("./assignment/assignment-write"), exports);
125
132
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,oEAAyC;AACzC,gFAAqD;AACrD,gFAAqD;AACrD,wEAA6C;AAC7C,8EAAmD;AACnD,8EAAmD;AACnD,gFAAqD;AACrD,0EAA+C;AAC/C,gFAAqD;AACrD,wDAA6B;AAC7B,wDAA6B;AAC7B,iEAAsC;AACtC,+DAAoC;AACpC,wEAA6C;AAC7C,wDAA6B;AAC7B,6DAAkC;AAClC,sEAA2C;AAC3C,uEAA4C;AAC5C,sEAA2C;AAC3C,oEAAyC;AACzC,gEAAqC;AACrC,sEAA2C;AAC3C,yEAA8C;AAC9C,kEAAuC;AACvC,uEAA4C;AAC5C,sEAA2C;AAC3C,kEAAuC;AACvC,iEAAsC;AACtC,uEAA4C;AAC5C,+DAAoC;AACpC,8DAAmC;AACnC,0EAA+C;AAC/C,qEAA0C;AAC1C,oEAAyC;AACzC,oEAAyC;AACzC,yEAA8C;AAC9C,uEAA4C;AAC5C,+EAAoD;AACpD,gFAAqD;AACrD,6EAAkD;AAClD,qEAA0C;AAC1C,oEAAyC;AACzC,2EAAgD;AAChD,uEAA4C;AAC5C,sEAA2C;AAC3C,0EAA+C;AAC/C,mFAAwD;AACxD,qEAA0C;AAC1C,mEAAwC;AACxC,wEAA6C;AAC7C,2EAAgD;AAChD,2EAAgD;AAChD,4EAAiD;AACjD,yEAA8C;AAC9C,oEAAyC;AACzC,iEAAsC;AACtC,gEAAqC;AACrC,yEAA8C;AAC9C,yEAA8C;AAC9C,iFAAsD;AACtD,kFAAuD;AACvD,+EAAoD;AACpD,uEAA4C;AAC5C,sEAA2C;AAC3C,8DAAmC;AACnC,iEAAsC;AACtC,yEAA8C;AAE9C,mCAAmC;AACnC,mCAAgF;AAChF,2CAA4F;AAC5F,yCAAyF;AACzF,yCAAyF;AACzF,2CAA4F;AAC5F,2CAA4F;AAC5F,uCAAsF;AACtF,wEAAwE;AACxE,qCAAmF;AACnF,qCAAuD;AACvD,uCAAuC;AACvC,+EAAyE;AACzE,iEAAuF;AACvF,2CAA0F;AAE7E,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,yCAAkB;IAClB,GAAG,oBAAiB;IACpB,GAAG,kBAAe;IAClB,GAAG,oBAAiB;IACpB,GAAG,gBAAa;IAChB,GAAG,mBAAgB;IACnB,GAAG,mBAAgB;IACnB,GAAG,oBAAiB;IACpB,GAAG,iBAAc;IACjB,GAAG,oBAAgB;CACpB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,iDAA0B;QAC1B,GAAG,qBAAkB;QACrB,GAAG,mBAAgB;QACnB,GAAG,qBAAkB;QACrB,GAAG,iBAAc;QACjB,GAAG,oBAAiB;QACpB,GAAG,oBAAiB;QACpB,GAAG,qBAAkB;QACrB,GAAG,kBAAe;QAClB,GAAG,qBAAiB;QACpB,GAAG,kBAAe;QAElB;;;WAGG;QACH,uDAAyB;KAC1B;CACF,CAAA;AACD,wEAA6C;AAC7C,0EAA+C;AAC/C,wEAA6C","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './resource/resource-class'\nexport * from './personnel/personnel-master-resolver'\nexport * from './equipment/equipment-master-resolver'\nexport * from './asset/asset-master-resolver'\nexport * from './material/material-master-resolver'\nexport * from './location/location-master-resolver'\nexport * from './operation/operation-master-resolver'\nexport * from './recipe/recipe-master-resolver'\nexport * from './test-spec/test-spec-master-resolver'\nexport * from './master-crud'\nexport * from './master-axis'\nexport * from './master-change-policy'\nexport * from './master-conformance'\nexport * from './master-conformance-resolver'\nexport * from './master-list'\nexport * from './personnel/person'\nexport * from './personnel/personnel-class'\nexport * from './personnel/personnel-master'\nexport * from './personnel/personnel-query'\nexport * from './personnel/qualification'\nexport * from './equipment/equipment'\nexport * from './equipment/equipment-class'\nexport * from './equipment/state-period-rules'\nexport * from './equipment/stop-reason'\nexport * from './equipment/equipment-master'\nexport * from './equipment/equipment-query'\nexport * from './asset/asset-lifecycle'\nexport * from './asset/physical-asset'\nexport * from './asset/physical-asset-class'\nexport * from './asset/asset-master'\nexport * from './asset/asset-query'\nexport * from './location/operational-location'\nexport * from './location/location-master'\nexport * from './location/location-query'\nexport * from './material/material-class'\nexport * from './material/material-definition'\nexport * from './material/material-revision'\nexport * from './material/material-revision-service'\nexport * from './material/material-revision-resolver'\nexport * from './material/material-effective-list'\nexport * from './material/material-master'\nexport * from './material/material-query'\nexport * from './operation/operation-definition'\nexport * from './operation/operation-master'\nexport * from './operation/operation-query'\nexport * from './operation/operation-readiness'\nexport * from './operation/operation-readiness-resolver'\nexport * from './recipe/recipe-definition'\nexport * from './recipe/recipe-revision'\nexport * from './recipe/recipe-revision-line'\nexport * from './recipe/recipe-revision-content'\nexport * from './recipe/recipe-revision-service'\nexport * from './recipe/recipe-revision-resolver'\nexport * from './recipe/recipe-effective-list'\nexport * from './recipe/route-definition'\nexport * from './recipe/recipe-master'\nexport * from './recipe/recipe-query'\nexport * from './test-spec/test-specification'\nexport * from './test-spec/test-spec-revision'\nexport * from './test-spec/test-spec-revision-service'\nexport * from './test-spec/test-spec-revision-resolver'\nexport * from './test-spec/test-spec-effective-list'\nexport * from './test-spec/test-spec-master'\nexport * from './test-spec/test-spec-query'\nexport * from './status/axis-stamp'\nexport * from './status/master-status'\nexport * from './quality/nonconformance-cause'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AssetEntities, resolvers as AssetResolvers } from './asset'\nimport { entities as EquipmentEntities, resolvers as EquipmentResolvers } from './equipment'\nimport { entities as LocationEntities, resolvers as LocationResolvers } from './location'\nimport { entities as MaterialEntities, resolvers as MaterialResolvers } from './material'\nimport { entities as OperationEntities, resolvers as OperationResolvers } from './operation'\nimport { entities as PersonnelEntities, resolvers as PersonnelResolvers } from './personnel'\nimport { entities as QualityEntities, resolvers as QualityResolvers } from './quality'\n/* 라우팅·레시피 — 품목→지시서를 이으려면 그 사이에 이 둘이 있어야 한다(§`recipe-definition.ts`). */\nimport { entities as RecipeEntities, resolvers as RecipeResolvers } from './recipe'\nimport { resolvers as StatusResolvers } from './status'\n/* 마스터 정합성 — 축을 가로지르므로 어느 축에도 안 속한다. */\nimport { MasterConformanceResolver } from './master-conformance-resolver'\nimport { MasterChangePolicy, MasterChangePolicyResolver } from './master-change-policy'\nimport { entities as TestSpecEntities, resolvers as TestSpecResolvers } from './test-spec'\n\nexport const entities = [\n /* ENTITIES */\n MasterChangePolicy,\n ...PersonnelEntities,\n ...QualityEntities,\n ...EquipmentEntities,\n ...AssetEntities,\n ...LocationEntities,\n ...MaterialEntities,\n ...OperationEntities,\n ...RecipeEntities,\n ...TestSpecEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n MasterChangePolicyResolver,\n ...PersonnelResolvers,\n ...QualityResolvers,\n ...EquipmentResolvers,\n ...AssetResolvers,\n ...LocationResolvers,\n ...MaterialResolvers,\n ...OperationResolvers,\n ...RecipeResolvers,\n ...TestSpecResolvers,\n ...StatusResolvers,\n\n /*\n * **파일을 export 하는 것만으로는 스키마에 안 올라온다.** 여기 적어야 한다 — 처음에 그것을\n * 빠뜨려 `masterConformance` 가 「Cannot query field」로 나왔다. [확인함] 2026-09-04\n */\n MasterConformanceResolver\n ]\n}\nexport * from './assignment/assignment-claim'\nexport * from './assignment/assignment-history'\nexport * from './assignment/assignment-write'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,oEAAyC;AACzC,gFAAqD;AACrD,gFAAqD;AACrD,wEAA6C;AAC7C,8EAAmD;AACnD,8EAAmD;AACnD,gFAAqD;AACrD,0EAA+C;AAC/C,gFAAqD;AACrD,wDAA6B;AAC7B,wDAA6B;AAC7B,iEAAsC;AACtC,2DAAgC;AAChC,+DAAoC;AACpC,wEAA6C;AAC7C,wDAA6B;AAC7B,6DAAkC;AAClC,sEAA2C;AAC3C,uEAA4C;AAC5C,sEAA2C;AAC3C,oEAAyC;AACzC,gEAAqC;AACrC,sEAA2C;AAC3C,yEAA8C;AAC9C,kEAAuC;AACvC,uEAA4C;AAC5C,sEAA2C;AAC3C,kEAAuC;AACvC,iEAAsC;AACtC,uEAA4C;AAC5C,+DAAoC;AACpC,8DAAmC;AACnC,0EAA+C;AAC/C,qEAA0C;AAC1C,oEAAyC;AACzC,oEAAyC;AACzC,yEAA8C;AAC9C,uEAA4C;AAC5C,+EAAoD;AACpD,gFAAqD;AACrD,6EAAkD;AAClD,qEAA0C;AAC1C,oEAAyC;AACzC,2EAAgD;AAChD,uEAA4C;AAC5C,sEAA2C;AAC3C,0EAA+C;AAC/C,mFAAwD;AACxD,qEAA0C;AAC1C,mEAAwC;AACxC,wEAA6C;AAC7C,2EAAgD;AAChD,2EAAgD;AAChD,4EAAiD;AACjD,yEAA8C;AAC9C,oEAAyC;AACzC,iEAAsC;AACtC,gEAAqC;AACrC,yEAA8C;AAC9C,yEAA8C;AAC9C,iFAAsD;AACtD,kFAAuD;AACvD,+EAAoD;AACpD,uEAA4C;AAC5C,sEAA2C;AAC3C,8DAAmC;AACnC,iEAAsC;AACtC,yEAA8C;AAE9C,mCAAmC;AACnC,mCAAgF;AAChF,2CAA4F;AAC5F,yCAAyF;AACzF,yCAAyF;AACzF,2CAA4F;AAC5F,2CAA4F;AAC5F,uCAAsF;AACtF,wEAAwE;AACxE,qCAAmF;AACnF,qCAAuD;AACvD,uCAAuC;AACvC,+EAAyE;AACzE,iEAAuF;AACvF,wEAAkE;AAClE,2CAA0F;AAE7E,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,yCAAkB;IAClB;;;OAGG;IACH,qCAAgB;IAChB,GAAG,oBAAiB;IACpB,GAAG,kBAAe;IAClB,GAAG,oBAAiB;IACpB,GAAG,gBAAa;IAChB,GAAG,mBAAgB;IACnB,GAAG,mBAAgB;IACnB,GAAG,oBAAiB;IACpB,GAAG,iBAAc;IACjB,GAAG,oBAAgB;CACpB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,iDAA0B;QAC1B,GAAG,qBAAkB;QACrB,GAAG,mBAAgB;QACnB,GAAG,qBAAkB;QACrB,GAAG,iBAAc;QACjB,GAAG,oBAAiB;QACpB,GAAG,oBAAiB;QACpB,GAAG,qBAAkB;QACrB,GAAG,kBAAe;QAClB,GAAG,qBAAiB;QACpB,GAAG,kBAAe;QAElB;;;WAGG;QACH,uDAAyB;KAC1B;CACF,CAAA;AACD,0EAA+C;AAC/C,0EAA+C;AAC/C,wEAA6C","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './resource/resource-class'\nexport * from './personnel/personnel-master-resolver'\nexport * from './equipment/equipment-master-resolver'\nexport * from './asset/asset-master-resolver'\nexport * from './material/material-master-resolver'\nexport * from './location/location-master-resolver'\nexport * from './operation/operation-master-resolver'\nexport * from './recipe/recipe-master-resolver'\nexport * from './test-spec/test-spec-master-resolver'\nexport * from './master-crud'\nexport * from './master-axis'\nexport * from './master-change-policy'\nexport * from './approval-route'\nexport * from './master-conformance'\nexport * from './master-conformance-resolver'\nexport * from './master-list'\nexport * from './personnel/person'\nexport * from './personnel/personnel-class'\nexport * from './personnel/personnel-master'\nexport * from './personnel/personnel-query'\nexport * from './personnel/qualification'\nexport * from './equipment/equipment'\nexport * from './equipment/equipment-class'\nexport * from './equipment/state-period-rules'\nexport * from './equipment/stop-reason'\nexport * from './equipment/equipment-master'\nexport * from './equipment/equipment-query'\nexport * from './asset/asset-lifecycle'\nexport * from './asset/physical-asset'\nexport * from './asset/physical-asset-class'\nexport * from './asset/asset-master'\nexport * from './asset/asset-query'\nexport * from './location/operational-location'\nexport * from './location/location-master'\nexport * from './location/location-query'\nexport * from './material/material-class'\nexport * from './material/material-definition'\nexport * from './material/material-revision'\nexport * from './material/material-revision-service'\nexport * from './material/material-revision-resolver'\nexport * from './material/material-effective-list'\nexport * from './material/material-master'\nexport * from './material/material-query'\nexport * from './operation/operation-definition'\nexport * from './operation/operation-master'\nexport * from './operation/operation-query'\nexport * from './operation/operation-readiness'\nexport * from './operation/operation-readiness-resolver'\nexport * from './recipe/recipe-definition'\nexport * from './recipe/recipe-revision'\nexport * from './recipe/recipe-revision-line'\nexport * from './recipe/recipe-revision-content'\nexport * from './recipe/recipe-revision-service'\nexport * from './recipe/recipe-revision-resolver'\nexport * from './recipe/recipe-effective-list'\nexport * from './recipe/route-definition'\nexport * from './recipe/recipe-master'\nexport * from './recipe/recipe-query'\nexport * from './test-spec/test-specification'\nexport * from './test-spec/test-spec-revision'\nexport * from './test-spec/test-spec-revision-service'\nexport * from './test-spec/test-spec-revision-resolver'\nexport * from './test-spec/test-spec-effective-list'\nexport * from './test-spec/test-spec-master'\nexport * from './test-spec/test-spec-query'\nexport * from './status/axis-stamp'\nexport * from './status/master-status'\nexport * from './quality/nonconformance-cause'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AssetEntities, resolvers as AssetResolvers } from './asset'\nimport { entities as EquipmentEntities, resolvers as EquipmentResolvers } from './equipment'\nimport { entities as LocationEntities, resolvers as LocationResolvers } from './location'\nimport { entities as MaterialEntities, resolvers as MaterialResolvers } from './material'\nimport { entities as OperationEntities, resolvers as OperationResolvers } from './operation'\nimport { entities as PersonnelEntities, resolvers as PersonnelResolvers } from './personnel'\nimport { entities as QualityEntities, resolvers as QualityResolvers } from './quality'\n/* 라우팅·레시피 — 품목→지시서를 이으려면 그 사이에 이 둘이 있어야 한다(§`recipe-definition.ts`). */\nimport { entities as RecipeEntities, resolvers as RecipeResolvers } from './recipe'\nimport { resolvers as StatusResolvers } from './status'\n/* 마스터 정합성 — 축을 가로지르므로 어느 축에도 안 속한다. */\nimport { MasterConformanceResolver } from './master-conformance-resolver'\nimport { MasterChangePolicy, MasterChangePolicyResolver } from './master-change-policy'\nimport { MasterValueClaim } from './assignment/master-value-claim'\nimport { entities as TestSpecEntities, resolvers as TestSpecResolvers } from './test-spec'\n\nexport const entities = [\n /* ENTITIES */\n MasterChangePolicy,\n /*\n * 배정 이력. **등록이 빠져 있었다**(2026-09-23 확인) — 표가 안 서니 이력을 쓰는 길이 실제\n * 설치본에는 없었다. 시험 하네스가 자기 목록에 직접 넣어서 초록이었다.\n */\n MasterValueClaim,\n ...PersonnelEntities,\n ...QualityEntities,\n ...EquipmentEntities,\n ...AssetEntities,\n ...LocationEntities,\n ...MaterialEntities,\n ...OperationEntities,\n ...RecipeEntities,\n ...TestSpecEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n MasterChangePolicyResolver,\n ...PersonnelResolvers,\n ...QualityResolvers,\n ...EquipmentResolvers,\n ...AssetResolvers,\n ...LocationResolvers,\n ...MaterialResolvers,\n ...OperationResolvers,\n ...RecipeResolvers,\n ...TestSpecResolvers,\n ...StatusResolvers,\n\n /*\n * **파일을 export 하는 것만으로는 스키마에 안 올라온다.** 여기 적어야 한다 — 처음에 그것을\n * 빠뜨려 `masterConformance` 가 「Cannot query field」로 나왔다. [확인함] 2026-09-04\n */\n MasterConformanceResolver\n ]\n}\nexport * from './assignment/master-value-claim'\nexport * from './assignment/assignment-history'\nexport * from './assignment/assignment-write'\n"]}
@@ -1,7 +1,13 @@
1
1
  import { EntityManager } from 'typeorm';
2
2
  import { Domain } from '@things-factory/shell';
3
3
  import { User } from '@things-factory/auth-base';
4
- /** Initial domain×axis declaration. Changing an established policy needs its own approved decision. */
4
+ /**
5
+ * 정책을 정할 수 있는 축들 — 마스터 축 열다섯에 `workShift` 를 더한다.
6
+ *
7
+ * `workShift` 는 별도 모듈이라 `MASTER_AXES` 에 없는데 정책은 받는다(§`policyAxis`).
8
+ */
9
+ export declare const POLICY_AXES: string[];
10
+ /** 도메인 × 축의 기준정보 변경 정책 — 정하는 것도 바꾸는 것도 사업장 소유자의 몫이다. */
5
11
  export declare class MasterChangePolicy {
6
12
  id: string;
7
13
  domain: Domain;
@@ -9,15 +15,72 @@ export declare class MasterChangePolicy {
9
15
  axis: string;
10
16
  approvalRequired: boolean;
11
17
  reason: string;
18
+ /**
19
+ * 이 줄이 물러난 시각 — 비어 있는 줄이 **지금 정책**이다.
20
+ *
21
+ * 차례를 `createdAt` 으로 세우지 않는다. sqlite 의 `datetime('now')` 는 초까지라, 한 사람이 같은
22
+ * 초에 두 번 바꾸면 어느 줄이 나중인지 저장소가 못 가린다. 물러난 줄에 표를 하면 그 물음이 사라진다.
23
+ */
24
+ supersededAt: Date;
25
+ /**
26
+ * 누가 정했나 — **비어 있으면 아무도 안 정한 것**이고, 그 줄은 기본값으로 선 줄이다.
27
+ *
28
+ * 사람이 안 정한 줄에 사람을 적으면 그 사람이 정한 것이 된다. 사업장을 만든 사람도 이 축을 보고
29
+ * 고른 적이 없다.
30
+ */
12
31
  creator: User;
13
32
  creatorId: string;
14
33
  createdAt: Date;
15
34
  }
35
+ /** 아무도 안 정한 축이 도는 방식 — 바꾸려면 여기 하나만 고치면 된다. */
36
+ export declare const DEFAULT_APPROVAL_REQUIRED = false;
37
+ /**
38
+ * 이 축이 지금 어떻게 도는가. **아무도 안 정했으면 기본값으로 한 줄을 세우고 그것을 답한다.**
39
+ *
40
+ * 전에는 거절했다(`MASTER_POLICY_REQUIRED`). 그래서 새 사업장은 축 열여섯이 전부 막힌 채로 시작했고,
41
+ * 기준정보를 만들려던 사람이 「정책을 먼저 정하라」는 말만 받았다 — 어디서 정하는지는 안 적혀 있었다.
42
+ *
43
+ * 값을 그때그때 지어내지 않고 **줄을 세우는** 까닭은, 개정본이 자기가 어느 정책 아래 만들어졌는지를
44
+ * 그 줄로 가리키기 때문이다(ADR-0090). 가리킬 줄이 없으면 그 물음에 나중에 답할 수 없다.
45
+ *
46
+ * 그 줄의 `creator` 는 비어 있다. 아무도 고른 적이 없다는 것이 사실이고, 사업장을 만든 사람을 적으면
47
+ * 그 사람이 정한 것이 된다.
48
+ */
16
49
  export declare function masterChangePolicy(manager: EntityManager, domainId: string, axis: string): Promise<MasterChangePolicy>;
50
+ /**
51
+ * 지금 서 있는 줄.
52
+ *
53
+ * 둘 이상이 서 있으면 **가장 늦게 만들어진 것**을 답한다. 그 상태는 이 코드가 못 만들지만, 물러남
54
+ * 표를 두기 전에 쌓인 줄들이 그렇게 남아 있다. 다음 선언이 전부 물러나게 하고 하나로 정리한다.
55
+ */
56
+ export declare function standingPolicy(manager: EntityManager, domainId: string, axis: string): Promise<MasterChangePolicy>;
57
+ /** 이 도메인에서 지금 서 있는 줄 전부 — 축을 열쇠로. */
58
+ export declare function standingPolicies(manager: EntityManager, domainId: string): Promise<Map<string, MasterChangePolicy>>;
17
59
  export declare function declareMasterChangePolicy(manager: EntityManager, domainId: string, actorId: string, axis: string, approvalRequired: boolean, reason: string): Promise<MasterChangePolicy>;
18
- /** The common mutation boundary also protects not-yet-revisioned axes when declared regulated. */
19
60
  export declare function assertDirectMasterChange(manager: EntityManager, domainId: string, axis: string): Promise<void>;
20
61
  export declare class MasterChangePolicyResolver {
21
62
  masterChangePolicy(axis: string, context: ResolverContext): Promise<MasterChangePolicy>;
63
+ /**
64
+ * 축 전부의 정책 상태 — **안 정한 것도 답한다.**
65
+ *
66
+ * `masterChangePolicy` 는 안 정했으면 거절한다. 그것은 「고치기 전에 정해라」를 말하는 자리라 맞지만,
67
+ * **정하러 들어가는 화면까지 막는다.** 그래서 목록은 따로 둔다 — 안 정한 축은 안 정했다고 답한다.
68
+ *
69
+ * `canDeclare` 는 **이 사람이 소유자인가**다. 화면이 스스로 판단하지 않게 서버가 말한다 —
70
+ * 눌러 보고 거절당하는 화면이 되지 않도록.
71
+ */
72
+ masterChangePolicies(context: ResolverContext): Promise<{
73
+ canDeclare: boolean;
74
+ axes: {
75
+ axis: string;
76
+ declared: boolean;
77
+ byDefault: boolean;
78
+ approvalRequired: boolean;
79
+ reason: string;
80
+ declaredAt: Date;
81
+ approvalReady: boolean;
82
+ approvalActivity: string;
83
+ }[];
84
+ }>;
22
85
  declareMasterChangePolicy(axis: string, approvalRequired: boolean, reason: string, context: ResolverContext): Promise<MasterChangePolicy>;
23
86
  }