@things-factory/worklist 10.1.29 → 10.1.31

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 (64) hide show
  1. package/dist-client/pages/work-door/door-words.d.ts +41 -0
  2. package/dist-client/pages/work-door/door-words.js +54 -0
  3. package/dist-client/pages/work-door/door-words.js.map +1 -0
  4. package/dist-client/pages/work-door/work-doors-page.d.ts +37 -0
  5. package/dist-client/pages/work-door/work-doors-page.js +369 -0
  6. package/dist-client/pages/work-door/work-doors-page.js.map +1 -0
  7. package/dist-client/route.d.ts +1 -1
  8. package/dist-client/route.js +3 -0
  9. package/dist-client/route.js.map +1 -1
  10. package/dist-client/tsconfig.tsbuildinfo +1 -1
  11. package/dist-server/service/activity/index.d.ts +1 -1
  12. package/dist-server/service/activity-instance/index.d.ts +1 -1
  13. package/dist-server/service/activity-template/index.d.ts +1 -1
  14. package/dist-server/service/activity-thread/index.d.ts +1 -1
  15. package/dist-server/service/index.d.ts +3 -3
  16. package/dist-server/service/index.js +5 -2
  17. package/dist-server/service/index.js.map +1 -1
  18. package/dist-server/service/work-door/activity-lookup.d.ts +70 -0
  19. package/dist-server/service/work-door/activity-lookup.js +110 -0
  20. package/dist-server/service/work-door/activity-lookup.js.map +1 -0
  21. package/dist-server/service/work-door/approval-line-standing.d.ts +92 -0
  22. package/dist-server/service/work-door/approval-line-standing.js +47 -0
  23. package/dist-server/service/work-door/approval-line-standing.js.map +1 -0
  24. package/dist-server/service/work-door/index.d.ts +15 -0
  25. package/dist-server/service/work-door/index.js +20 -0
  26. package/dist-server/service/work-door/index.js.map +1 -0
  27. package/dist-server/service/work-door/pass-work-door.d.ts +74 -0
  28. package/dist-server/service/work-door/pass-work-door.js +45 -0
  29. package/dist-server/service/work-door/pass-work-door.js.map +1 -0
  30. package/dist-server/service/work-door/work-door-declaration.d.ts +114 -0
  31. package/dist-server/service/work-door/work-door-declaration.js +67 -0
  32. package/dist-server/service/work-door/work-door-declaration.js.map +1 -0
  33. package/dist-server/service/work-door/work-door-event.d.ts +20 -0
  34. package/dist-server/service/work-door/work-door-event.js +25 -0
  35. package/dist-server/service/work-door/work-door-event.js.map +1 -0
  36. package/dist-server/service/work-door/work-door-journal-service.d.ts +47 -0
  37. package/dist-server/service/work-door/work-door-journal-service.js +75 -0
  38. package/dist-server/service/work-door/work-door-journal-service.js.map +1 -0
  39. package/dist-server/service/work-door/work-door-journal.d.ts +30 -0
  40. package/dist-server/service/work-door/work-door-journal.js +94 -0
  41. package/dist-server/service/work-door/work-door-journal.js.map +1 -0
  42. package/dist-server/service/work-door/work-door-not-used.d.ts +39 -0
  43. package/dist-server/service/work-door/work-door-not-used.js +86 -0
  44. package/dist-server/service/work-door/work-door-not-used.js.map +1 -0
  45. package/dist-server/service/work-door/work-door-resolver.d.ts +29 -0
  46. package/dist-server/service/work-door/work-door-resolver.js +127 -0
  47. package/dist-server/service/work-door/work-door-resolver.js.map +1 -0
  48. package/dist-server/service/work-door/work-door-service.d.ts +29 -0
  49. package/dist-server/service/work-door/work-door-service.js +126 -0
  50. package/dist-server/service/work-door/work-door-service.js.map +1 -0
  51. package/dist-server/service/work-door/work-door-type.d.ts +49 -0
  52. package/dist-server/service/work-door/work-door-type.js +122 -0
  53. package/dist-server/service/work-door/work-door-type.js.map +1 -0
  54. package/dist-server/service/work-door/work-door.d.ts +138 -0
  55. package/dist-server/service/work-door/work-door.js +156 -0
  56. package/dist-server/service/work-door/work-door.js.map +1 -0
  57. package/dist-server/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +10 -10
  59. package/tests/pass-work-door.test.ts +133 -0
  60. package/translations/en.json +12 -1
  61. package/translations/ja.json +12 -1
  62. package/translations/ko.json +12 -1
  63. package/translations/ms.json +12 -1
  64. package/translations/zh.json +12 -1
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkDoorsType = exports.WorkDoorType = exports.DoorKindValue = exports.DoorStandingValue = void 0;
4
+ exports.toDoorType = toDoorType;
5
+ const tslib_1 = require("tslib");
6
+ const type_graphql_1 = require("type-graphql");
7
+ /**
8
+ * 「이 공장에서 어느 결재 문이 서 있나」를 화면에 낼 모양.
9
+ *
10
+ * 문자열 하나로 안 낸다 — 화면이 상태별로 다르게 그려야 하고, 그러려면 상태가 값이어야 한다.
11
+ * 문장만 주면 화면이 그 문장을 다시 뜯어 읽는다.
12
+ */
13
+ var DoorStandingValue;
14
+ (function (DoorStandingValue) {
15
+ /** 채워졌다. */
16
+ DoorStandingValue["On"] = "on";
17
+ /** 양식은 섰는데 받을 사람이 없다. 조치 대상. */
18
+ DoorStandingValue["Off"] = "off";
19
+ /** 양식이 아예 안 섰다 — 설정이 아니라 결함이다. 조치 대상. */
20
+ DoorStandingValue["Missing"] = "missing";
21
+ /** 이 공장이 안 쓰기로 했다. 조치 대상이 아니다. */
22
+ DoorStandingValue["NotUsed"] = "not-used";
23
+ })(DoorStandingValue || (exports.DoorStandingValue = DoorStandingValue = {}));
24
+ /** 결재냐 배정이냐 — 비었을 때 결과가 반대라 화면이 갈라 그려야 한다. */
25
+ var DoorKindValue;
26
+ (function (DoorKindValue) {
27
+ DoorKindValue["Approval"] = "approval";
28
+ DoorKindValue["Assignment"] = "assignment";
29
+ })(DoorKindValue || (exports.DoorKindValue = DoorKindValue = {}));
30
+ (0, type_graphql_1.registerEnumType)(DoorKindValue, {
31
+ name: 'DoorKind',
32
+ description: 'Whether the plant fills an approval line or an assignee role on this door'
33
+ });
34
+ (0, type_graphql_1.registerEnumType)(DoorStandingValue, {
35
+ name: 'DoorStanding',
36
+ description: 'Whether this plant has an approval step standing for that form'
37
+ });
38
+ let WorkDoorType = class WorkDoorType {
39
+ };
40
+ exports.WorkDoorType = WorkDoorType;
41
+ tslib_1.__decorate([
42
+ (0, type_graphql_1.Field)({ description: 'the form name — the key the callback is found by' }),
43
+ tslib_1.__metadata("design:type", String)
44
+ ], WorkDoorType.prototype, "name", void 0);
45
+ tslib_1.__decorate([
46
+ (0, type_graphql_1.Field)({ description: 'what to show a person' }),
47
+ tslib_1.__metadata("design:type", String)
48
+ ], WorkDoorType.prototype, "label", void 0);
49
+ tslib_1.__decorate([
50
+ (0, type_graphql_1.Field)(type => DoorKindValue),
51
+ tslib_1.__metadata("design:type", String)
52
+ ], WorkDoorType.prototype, "kind", void 0);
53
+ tslib_1.__decorate([
54
+ (0, type_graphql_1.Field)(type => DoorStandingValue),
55
+ tslib_1.__metadata("design:type", String)
56
+ ], WorkDoorType.prototype, "standing", void 0);
57
+ tslib_1.__decorate([
58
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many approvers — zero for an assignment door or an empty line' }),
59
+ tslib_1.__metadata("design:type", Number)
60
+ ], WorkDoorType.prototype, "approvers", void 0);
61
+ tslib_1.__decorate([
62
+ (0, type_graphql_1.Field)({ nullable: true, description: 'which domain the approval line came from — a plant may use its parent\'s' }),
63
+ tslib_1.__metadata("design:type", String)
64
+ ], WorkDoorType.prototype, "from", void 0);
65
+ tslib_1.__decorate([
66
+ (0, type_graphql_1.Field)({ description: 'what this standing means, in the words the screen shows' }),
67
+ tslib_1.__metadata("design:type", String)
68
+ ], WorkDoorType.prototype, "because", void 0);
69
+ exports.WorkDoorType = WorkDoorType = tslib_1.__decorate([
70
+ (0, type_graphql_1.ObjectType)({ description: 'One approval form and whether this plant has approvers on it' })
71
+ ], WorkDoorType);
72
+ let WorkDoorsType = class WorkDoorsType {
73
+ };
74
+ exports.WorkDoorsType = WorkDoorsType;
75
+ tslib_1.__decorate([
76
+ (0, type_graphql_1.Field)(type => [WorkDoorType]),
77
+ tslib_1.__metadata("design:type", Array)
78
+ ], WorkDoorsType.prototype, "doors", void 0);
79
+ tslib_1.__decorate([
80
+ (0, type_graphql_1.Field)({ description: 'the whole plant in one sentence — a list alone makes the reader count rows' }),
81
+ tslib_1.__metadata("design:type", String)
82
+ ], WorkDoorsType.prototype, "summary", void 0);
83
+ tslib_1.__decorate([
84
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many still need filling — not-used doors are never counted' }),
85
+ tslib_1.__metadata("design:type", Number)
86
+ ], WorkDoorsType.prototype, "todo", void 0);
87
+ tslib_1.__decorate([
88
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many of the role seats this product offers actually exist here' }),
89
+ tslib_1.__metadata("design:type", Number)
90
+ ], WorkDoorsType.prototype, "seatsStanding", void 0);
91
+ tslib_1.__decorate([
92
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many role seats this product offers' }),
93
+ tslib_1.__metadata("design:type", Number)
94
+ ], WorkDoorsType.prototype, "seatsOffered", void 0);
95
+ exports.WorkDoorsType = WorkDoorsType = tslib_1.__decorate([
96
+ (0, type_graphql_1.ObjectType)({ description: 'Every approval form this product installs, and where this plant stands on each' })
97
+ ], WorkDoorsType);
98
+ /** 판단의 모양을 화면의 모양으로. 열거형 철자는 `@Field` 가 정하므로 값으로 옮긴다. */
99
+ function toDoorType(door) {
100
+ return {
101
+ name: door.name,
102
+ label: door.label,
103
+ kind: door.kind === 'assignment' ? DoorKindValue.Assignment : DoorKindValue.Approval,
104
+ standing: standingValue(door.standing),
105
+ approvers: door.approvers,
106
+ from: door.from,
107
+ because: door.because
108
+ };
109
+ }
110
+ function standingValue(standing) {
111
+ switch (standing) {
112
+ case 'on':
113
+ return DoorStandingValue.On;
114
+ case 'off':
115
+ return DoorStandingValue.Off;
116
+ case 'missing':
117
+ return DoorStandingValue.Missing;
118
+ default:
119
+ return DoorStandingValue.NotUsed;
120
+ }
121
+ }
122
+ //# sourceMappingURL=work-door-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-door-type.js","sourceRoot":"","sources":["../../../server/service/work-door/work-door-type.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, Int, ObjectType, registerEnumType } from 'type-graphql'\n\nimport type { WorkDoor, DoorStanding } from './work-door.js'\n\n/**\n * 「이 공장에서 어느 결재 문이 서 있나」를 화면에 낼 모양.\n *\n * 문자열 하나로 안 낸다 — 화면이 상태별로 다르게 그려야 하고, 그러려면 상태가 값이어야 한다.\n * 문장만 주면 화면이 그 문장을 다시 뜯어 읽는다.\n */\n\nexport enum DoorStandingValue {\n /** 채워졌다. */\n On = 'on',\n /** 양식은 섰는데 받을 사람이 없다. 조치 대상. */\n Off = 'off',\n /** 양식이 아예 안 섰다 — 설정이 아니라 결함이다. 조치 대상. */\n Missing = 'missing',\n /** 이 공장이 안 쓰기로 했다. 조치 대상이 아니다. */\n NotUsed = 'not-used'\n}\n\n/** 결재냐 배정이냐 — 비었을 때 결과가 반대라 화면이 갈라 그려야 한다. */\nexport enum DoorKindValue {\n Approval = 'approval',\n Assignment = 'assignment'\n}\n\nregisterEnumType(DoorKindValue, {\n name: 'DoorKind',\n description: 'Whether the plant fills an approval line or an assignee role on this door'\n})\n\nregisterEnumType(DoorStandingValue, {\n name: 'DoorStanding',\n description: 'Whether this plant has an approval step standing for that form'\n})\n\n@ObjectType({ description: 'One approval form and whether this plant has approvers on it' })\nexport class WorkDoorType {\n @Field({ description: 'the form name — the key the callback is found by' })\n name: string\n\n @Field({ description: 'what to show a person' })\n label: string\n\n @Field(type => DoorKindValue)\n kind: DoorKindValue\n\n @Field(type => DoorStandingValue)\n standing: DoorStandingValue\n\n @Field(type => Int, { description: 'how many approvers — zero for an assignment door or an empty line' })\n approvers: number\n\n @Field({ nullable: true, description: 'which domain the approval line came from — a plant may use its parent\\'s' })\n from?: string\n\n @Field({ description: 'what this standing means, in the words the screen shows' })\n because: string\n}\n\n@ObjectType({ description: 'Every approval form this product installs, and where this plant stands on each' })\nexport class WorkDoorsType {\n @Field(type => [WorkDoorType])\n doors: WorkDoorType[]\n\n @Field({ description: 'the whole plant in one sentence — a list alone makes the reader count rows' })\n summary: string\n\n @Field(type => Int, { description: 'how many still need filling — not-used doors are never counted' })\n todo: number\n\n /**\n * 이 제품이 내놓는 역할 자리 중 **이 공장에 실제로 선 것.**\n *\n * ── 왜 문 목록에 자리 수가 붙나 ────────────────────────────────────────\n * 문이 비었다는 말만 하면 사람은 양식 화면으로 간다. 그런데 **고를 역할이 없으면 그 화면의\n * 결재선 칸이 빈 목록**이다. 헛걸음하고 돌아와서, 무엇이 잘못됐는지 모른 채 이 화면을 다시 본다.\n *\n * 두 수를 함께 내면 화면이 순서를 말할 수 있다 — 자리부터 세우고, 그다음에 결재선을 건다.\n */\n @Field(type => Int, { description: 'how many of the role seats this product offers actually exist here' })\n seatsStanding: number\n\n @Field(type => Int, { description: 'how many role seats this product offers' })\n seatsOffered: number\n}\n\n/** 판단의 모양을 화면의 모양으로. 열거형 철자는 `@Field` 가 정하므로 값으로 옮긴다. */\nexport function toDoorType(door: WorkDoor): WorkDoorType {\n return {\n name: door.name,\n label: door.label,\n kind: door.kind === 'assignment' ? DoorKindValue.Assignment : DoorKindValue.Approval,\n standing: standingValue(door.standing),\n approvers: door.approvers,\n from: door.from,\n because: door.because\n }\n}\n\nfunction standingValue(standing: DoorStanding): DoorStandingValue {\n switch (standing) {\n case 'on':\n return DoorStandingValue.On\n case 'off':\n return DoorStandingValue.Off\n case 'missing':\n return DoorStandingValue.Missing\n default:\n return DoorStandingValue.NotUsed\n }\n}\n"]}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 이 공장에서 **어느 결재 문이 서 있나** — 순수 판단, DB 를 모른다.
3
+ *
4
+ * 결재선이 찼나를 세는 규칙은 상신하는 쪽과 **같은 함수**를 쓴다(§`approval-line-standing`). 여기서
5
+ * 따로 세면 화면이 「섰다」로 그리고 상신이 거절하는 날이 온다 — 그 자리가 실제로 있었다.
6
+ *
7
+ * ═══════════════════════════════════════════════════════════════════════════
8
+ * ── 왜 이것을 볼 자리가 필요한가 ───────────────────────────────────────────
9
+ * 양식은 제품이 기동할 때 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는
10
+ * 선언이다. 그래서 결재선이 비어 있는 것은 결함이 아니다 — 그 공장의 선택이다.
11
+ *
12
+ * 문제는 **그 선택이 어디에도 안 보인다**는 것이다. 업무 카탈로그는 「제품이 이 문을 만들었나」를
13
+ * 보여 주지만 「이 공장에서 이 문이 서 있나」는 말하지 않는다. 두 질문이 다른데 하나만 보인다.
14
+ *
15
+ * 실측(2026-09-11): 미라텍 도메인에 양식 11개가 다 설치돼 있고 **11개 전부 결재선이 비어 있다.**
16
+ * 그 공장에서는 실적 정정도 폐기도 승인 없이 바로 반영되는데, 화면에는 문이 있는 것처럼 보인다.
17
+ *
18
+ * ── 비어 있으면 그냥 통과가 아니다 ─────────────────────────────────────────
19
+ * 워크리스트의 거동이 그렇다 — **결재선이 비면 상신이 곧 완료가 되어, 올린 사람이 자기 건을 승인한
20
+ * 것이 된다**(`activity-lookup.ts` § `approvalLineStanding`). 「승인을 안 받는다」가 아니라 「자기가
21
+ * 승인한다」다. 그 차이를 문장에 담는다.
22
+ *
23
+ * ── 기동 로그는 사실의 집이 아니다 ─────────────────────────────────────────
24
+ * `install-boot.ts` 가 기동할 때 한 줄 찍는다. 그 줄은 한 번 지나가고, 그 뒤로 어느 화면도 이것을
25
+ * 말하지 않는다. 사람이 조치해야 하는 사실은 물어볼 수 있는 자리에 있어야 한다.
26
+ * ═══════════════════════════════════════════════════════════════════════════
27
+ */
28
+ /**
29
+ * 문이 두 종류다 — **축이 다르고, 비었을 때 결과도 다르다.**
30
+ *
31
+ * ```
32
+ * approval 누가 승인하나 결재선이 비면 올린 사람이 곧 승인한 것이 된다 → 통과해 버린다
33
+ * assignment 누가 받나 역할이 안 묶이면 배정 업무가 아무에게도 안 간다 → 아무도 안 받는다
34
+ * ```
35
+ *
36
+ * 같은 `off` 인데 결과가 반대다. 한 벌 문장으로 뭉개면 관리자가 무엇을 해야 하는지 알 수 없다.
37
+ */
38
+ import type { ApprovalLineStanding } from './approval-line-standing.js';
39
+ export type DoorKind = 'approval' | 'assignment';
40
+ /** 문 하나가 어떤 상태인가. */
41
+ export type DoorStanding =
42
+ /** 채워졌다 — 이 공장은 이 문을 쓴다. */
43
+ 'on'
44
+ /** 양식은 섰는데 비었다. **조치 대상.** */
45
+ | 'off'
46
+ /** 양식이 아예 안 섰다 — 기동이 못 세웠다. 공장의 선택이 아니라 결함이다. **조치 대상.** */
47
+ | 'missing'
48
+ /**
49
+ * **이 공장은 이 문을 안 쓴다** — 공장이 그렇게 말했다.
50
+ *
51
+ * 조치 대상이 아니다. 이것이 없으면 이 조사는 영원히 잔소리하는 화면이 되고, 그러면 사람들이
52
+ * 안 본다 — 그때 진짜 빈 문 하나가 그 잔소리에 섞여 묻힌다(아키텍트 판정 2026-09-11).
53
+ *
54
+ * **자동으로 추론하지 않는다.** 「석 달 안 썼으니 안 쓰는 것」은 조용한 한 철에 참이 된다.
55
+ */
56
+ | 'not-used';
57
+ /** 이 문 하나에 대해 아는 것. */
58
+ export interface WorkDoor {
59
+ /** 양식 이름. 콜백을 찾는 키다. */
60
+ name: string;
61
+ /** 화면에 낼 이름. */
62
+ label: string;
63
+ kind: DoorKind;
64
+ standing: DoorStanding;
65
+ /** 결재자 수. 안 섰거나 비었으면 0. */
66
+ approvers: number;
67
+ /**
68
+ * **남의 도메인에서 빌려 온 양식일 때만** 그 도메인의 이름.
69
+ *
70
+ * 본사에서 정하고 공장이 쓰는 것이 정상이라, 자기 것이 없으면 부모 것을 쓴다
71
+ * (`activityNamedIn`). 그 사실을 안 말하면 「우리가 정한 적 없는데 왜 서 있나」가 된다.
72
+ *
73
+ * ⚠ 자기 도메인이면 **없다.** 자기 이름을 채우면 화면이 모든 줄에 「miratek 에서 정했습니다」를
74
+ * 달고, 그 말은 아무것도 알려 주지 않는다 — 실제로 그랬다(2026-09-12, 화면으로 보고 알았다).
75
+ */
76
+ from?: string;
77
+ /** 이 상태가 무엇을 뜻하는지 — 화면에 그대로 나가는 문장. */
78
+ because: string;
79
+ }
80
+ /** 한 문에 대해 읽은 것. 엔티티를 통째로 받지 않는다 — 그러면 이 판단이 DB 를 끌고 온다. */
81
+ export interface DoorReading {
82
+ name: string;
83
+ label: string;
84
+ kind: DoorKind;
85
+ /**
86
+ * 그 도메인(또는 부모)에 선 양식. 못 찾았으면 없다.
87
+ *
88
+ * 채워졌나를 보는 칸이 종류마다 다르다 — 결재는 `approvalLine`, 배정은 `assigneeRole` 이다.
89
+ */
90
+ /**
91
+ * `borrowedFrom` 은 **다른 도메인의 양식일 때만** 채운다. 자기 것이면 비운다 — 읽는 쪽이
92
+ * 「빌려 왔나」를 이 칸 하나로 판단한다.
93
+ */
94
+ installed?: {
95
+ approvalLine?: unknown[];
96
+ assigneeRole?: string;
97
+ borrowedFrom?: string;
98
+ };
99
+ /** 공장이 「안 쓴다」고 말했으면 그 사유. 말한 적 없으면 없다. */
100
+ notUsedBecause?: string;
101
+ /**
102
+ * **다른 문의 결재선을 따르면** 그 문의 이름(화면에 나가는 것). 그러면 `approvalStanding` 도 그 문의
103
+ * 것이다 — 이 문에서 결재선을 고치러 가면 안 되고 그 문으로 가야 한다는 것을 문장이 말한다.
104
+ */
105
+ follows?: string;
106
+ /**
107
+ * 결재선이 찼나 — **상신하는 쪽과 같은 함수**(`judgeApprovalLine`)가 낸 결과를 그대로 받는다.
108
+ *
109
+ * 여기서 다시 세지 않는다. 역할에 사람이 있는지는 DB 를 읽어야 알고, 이 판단은 DB 를 모른다.
110
+ * 없으면 「못 읽었다」로 읽는다 — 「안 읽었다」가 「다 찼다」로 새면 도로 줄 수 세기가 된다.
111
+ */
112
+ approvalStanding?: ApprovalLineStanding;
113
+ }
114
+ export declare function judgeDoor(reading: DoorReading): WorkDoor;
115
+ /**
116
+ * 아직 채울 것이 몇 개인가 — **안 쓰기로 한 것은 안 센다.**
117
+ *
118
+ * 그래야 0 이 「다 됐다」를 뜻한다. 안 쓰는 것을 남은 수에 넣으면 다 채운 공장에서도 수가 안 줄고,
119
+ * 그 화면은 잔소리가 된다(아키텍트 판정 2026-09-11).
120
+ */
121
+ export declare function doorsToFill(doors: readonly WorkDoor[]): number;
122
+ /** 이 공장 전체를 한 문장으로 — 목록만 내면 읽는 사람이 11줄을 세어야 한다. */
123
+ export declare function summarizeDoors(doors: readonly WorkDoor[]): string;
124
+ /** The not-used declaration's content columns: which door and why. */
125
+ export declare const WORK_DOOR_NOT_USED_CONTENT: readonly ["doorName", "because"];
126
+ /** The declaration as it was — its content, who declared it, and when — written when it is withdrawn. */
127
+ export declare function workDoorNotUsedBefore(declaration: {
128
+ doorName: string;
129
+ because: string;
130
+ declaredBy?: {
131
+ email?: string;
132
+ name?: string;
133
+ } | null;
134
+ createdAt?: Date | null;
135
+ }): {
136
+ declaredBy: string;
137
+ declaredAt: Date;
138
+ };
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * 이 공장에서 **어느 결재 문이 서 있나** — 순수 판단, DB 를 모른다.
4
+ *
5
+ * 결재선이 찼나를 세는 규칙은 상신하는 쪽과 **같은 함수**를 쓴다(§`approval-line-standing`). 여기서
6
+ * 따로 세면 화면이 「섰다」로 그리고 상신이 거절하는 날이 온다 — 그 자리가 실제로 있었다.
7
+ *
8
+ * ═══════════════════════════════════════════════════════════════════════════
9
+ * ── 왜 이것을 볼 자리가 필요한가 ───────────────────────────────────────────
10
+ * 양식은 제품이 기동할 때 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는
11
+ * 선언이다. 그래서 결재선이 비어 있는 것은 결함이 아니다 — 그 공장의 선택이다.
12
+ *
13
+ * 문제는 **그 선택이 어디에도 안 보인다**는 것이다. 업무 카탈로그는 「제품이 이 문을 만들었나」를
14
+ * 보여 주지만 「이 공장에서 이 문이 서 있나」는 말하지 않는다. 두 질문이 다른데 하나만 보인다.
15
+ *
16
+ * 실측(2026-09-11): 미라텍 도메인에 양식 11개가 다 설치돼 있고 **11개 전부 결재선이 비어 있다.**
17
+ * 그 공장에서는 실적 정정도 폐기도 승인 없이 바로 반영되는데, 화면에는 문이 있는 것처럼 보인다.
18
+ *
19
+ * ── 비어 있으면 그냥 통과가 아니다 ─────────────────────────────────────────
20
+ * 워크리스트의 거동이 그렇다 — **결재선이 비면 상신이 곧 완료가 되어, 올린 사람이 자기 건을 승인한
21
+ * 것이 된다**(`activity-lookup.ts` § `approvalLineStanding`). 「승인을 안 받는다」가 아니라 「자기가
22
+ * 승인한다」다. 그 차이를 문장에 담는다.
23
+ *
24
+ * ── 기동 로그는 사실의 집이 아니다 ─────────────────────────────────────────
25
+ * `install-boot.ts` 가 기동할 때 한 줄 찍는다. 그 줄은 한 번 지나가고, 그 뒤로 어느 화면도 이것을
26
+ * 말하지 않는다. 사람이 조치해야 하는 사실은 물어볼 수 있는 자리에 있어야 한다.
27
+ * ═══════════════════════════════════════════════════════════════════════════
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.WORK_DOOR_NOT_USED_CONTENT = void 0;
31
+ exports.judgeDoor = judgeDoor;
32
+ exports.doorsToFill = doorsToFill;
33
+ exports.summarizeDoors = summarizeDoors;
34
+ exports.workDoorNotUsedBefore = workDoorNotUsedBefore;
35
+ function judgeDoor(reading) {
36
+ const { name, label, kind, installed, notUsedBecause, follows } = reading;
37
+ /* 따르는 문이 있으면 결재선을 고치는 자리가 그 문이다. 문장 끝에 그 사실을 붙인다. */
38
+ const following = follows ? ` — 「${follows}」 의 결재선을 따르므로, 결재선은 그 문에서 정합니다.` : '';
39
+ /*
40
+ * **공장이 안 쓴다고 말한 것이 먼저다.** 뒤에 두면 양식이 안 선 문을 「결함」으로 먼저 그리고,
41
+ * 안 쓰기로 한 공장이 고치라는 말을 계속 듣는다.
42
+ */
43
+ if (notUsedBecause) {
44
+ return {
45
+ name,
46
+ label,
47
+ kind,
48
+ standing: 'not-used',
49
+ approvers: 0,
50
+ because: `「${label}」 은 이 공장이 쓰지 않기로 한 업무입니다 — ${notUsedBecause}`
51
+ };
52
+ }
53
+ if (!installed) {
54
+ return {
55
+ name,
56
+ label,
57
+ kind,
58
+ standing: 'missing',
59
+ approvers: 0,
60
+ because: `「${label}」 양식이 이 공장에 서지 않았습니다. 공장이 안 쓰기로 한 것이 아니라 세우지 못한 것이므로 기동 기록을 보십시오.`
61
+ };
62
+ }
63
+ /*
64
+ * 결재 문은 **줄 수가 아니라 그 줄에 사람이 있는지**로 센다. 역할을 지목해 놓고 그 역할이 빈
65
+ * 결재선은 화면에 「섰다」로 그려지고 상신하는 순간 터진다(ADR-0047 ③).
66
+ */
67
+ const line = kind === 'approval' ? reading.approvalStanding : undefined;
68
+ const filled = kind === 'approval' ? (line && !line.refusal ? line.approvers : 0) : installed.assigneeRole ? 1 : 0;
69
+ const roleIsEmpty = line?.refusal?.code === 'approval-role-empty';
70
+ const personIsGone = line?.refusal?.code === 'approval-person-absent';
71
+ if (!filled) {
72
+ return {
73
+ name,
74
+ label,
75
+ kind,
76
+ standing: 'off',
77
+ approvers: 0,
78
+ from: installed.borrowedFrom,
79
+ /* 종류마다 결과가 반대다(§`DoorKind`). 그래서 문장도 다르다. */
80
+ because: (kind !== 'approval'
81
+ ? `「${label}」 을 받을 역할이 묶이지 않았습니다. 이 업무는 만들어져도 아무에게도 가지 않습니다.`
82
+ : roleIsEmpty
83
+ ? /*
84
+ * **관리자가 할 일이 다르다.** 결재선을 정하는 것과 그 역할에 사람을 넣는 것은 다른
85
+ * 일이고, 뒤엣것을 앞엣것으로 말하면 이미 정해 둔 결재선을 다시 들여다보게 만든다.
86
+ */
87
+ `「${label}」 의 결재선이 지목한 역할에 사람이 없습니다. 결재선은 정해져 있으니 그 역할에 사람을 넣어야 이 단계가 섭니다.`
88
+ : personIsGone
89
+ ? `「${label}」 의 결재선이 지목한 사람이 이 공장에 없습니다. 다른 사람을 지목하거나 그 사람을 넣어야 이 단계가 섭니다.`
90
+ : `「${label}」 에 결재자가 없습니다. 이 단계는 서지 않고, 올린 사람이 올리는 순간 자기 건을 승인한 것이 됩니다.`) +
91
+ following
92
+ };
93
+ }
94
+ return {
95
+ name,
96
+ label,
97
+ kind,
98
+ standing: 'on',
99
+ approvers: filled,
100
+ from: installed.borrowedFrom,
101
+ because: kind === 'approval'
102
+ ? `「${label}」 은 결재자 ${filled}명을 지납니다.${following}`
103
+ : `「${label}」 은 ${installed.assigneeRole} 가 받습니다.`
104
+ };
105
+ }
106
+ /**
107
+ * 아직 채울 것이 몇 개인가 — **안 쓰기로 한 것은 안 센다.**
108
+ *
109
+ * 그래야 0 이 「다 됐다」를 뜻한다. 안 쓰는 것을 남은 수에 넣으면 다 채운 공장에서도 수가 안 줄고,
110
+ * 그 화면은 잔소리가 된다(아키텍트 판정 2026-09-11).
111
+ */
112
+ function doorsToFill(doors) {
113
+ return doors.filter(one => one.standing === 'off' || one.standing === 'missing').length;
114
+ }
115
+ /** 이 공장 전체를 한 문장으로 — 목록만 내면 읽는 사람이 11줄을 세어야 한다. */
116
+ function summarizeDoors(doors) {
117
+ if (!doors.length) {
118
+ return '이 제품이 세우는 업무 양식이 없습니다.';
119
+ }
120
+ const on = doors.filter(one => one.standing === 'on').length;
121
+ const off = doors.filter(one => one.standing === 'off').length;
122
+ const missing = doors.filter(one => one.standing === 'missing').length;
123
+ const notUsed = doors.filter(one => one.standing === 'not-used').length;
124
+ /*
125
+ * **셀 것은 `off + missing` 뿐이다.** 그래야 0 이 「다 됐다」를 뜻한다. 안 쓰기로 한 것을 남은 수에
126
+ * 넣으면 다 채운 공장에서도 수가 안 줄고, 그 화면은 잔소리가 된다.
127
+ */
128
+ const todo = off + missing;
129
+ if (!todo) {
130
+ return notUsed
131
+ ? `채울 것이 남지 않았습니다. 업무 문 ${on}개가 서 있고 ${notUsed}개는 이 공장이 쓰지 않기로 한 것입니다.`
132
+ : `업무 문 ${doors.length}개가 다 서 있습니다.`;
133
+ }
134
+ const said = [`채울 것이 ${todo}개 남았습니다.`];
135
+ if (off) {
136
+ said.push(`${off}개는 양식은 섰는데 받을 사람이 없습니다 — 누가 할지 이 공장이 아직 정하지 않은 것입니다.`);
137
+ }
138
+ if (missing) {
139
+ said.push(`${missing}개는 양식이 아예 서지 않았습니다. 이것은 설정이 아니라 결함입니다.`);
140
+ }
141
+ if (notUsed) {
142
+ said.push(`${notUsed}개는 이 공장이 쓰지 않기로 한 것이라 세지 않았습니다.`);
143
+ }
144
+ return said.join(' ');
145
+ }
146
+ /** The not-used declaration's content columns: which door and why. */
147
+ exports.WORK_DOOR_NOT_USED_CONTENT = ['doorName', 'because'];
148
+ /** The declaration as it was — its content, who declared it, and when — written when it is withdrawn. */
149
+ function workDoorNotUsedBefore(declaration) {
150
+ return {
151
+ ...Object.fromEntries(exports.WORK_DOOR_NOT_USED_CONTENT.map(column => [column, declaration[column] ?? null])),
152
+ declaredBy: declaration.declaredBy?.email ?? declaration.declaredBy?.name ?? null,
153
+ declaredAt: declaration.createdAt ?? null
154
+ };
155
+ }
156
+ //# sourceMappingURL=work-door.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-door.js","sourceRoot":"","sources":["../../../server/service/work-door/work-door.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 이 공장에서 **어느 결재 문이 서 있나** — 순수 판단, DB 를 모른다.\n *\n * 결재선이 찼나를 세는 규칙은 상신하는 쪽과 **같은 함수**를 쓴다(§`approval-line-standing`). 여기서\n * 따로 세면 화면이 「섰다」로 그리고 상신이 거절하는 날이 온다 — 그 자리가 실제로 있었다.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 왜 이것을 볼 자리가 필요한가 ───────────────────────────────────────────\n * 양식은 제품이 기동할 때 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는\n * 선언이다. 그래서 결재선이 비어 있는 것은 결함이 아니다 — 그 공장의 선택이다.\n *\n * 문제는 **그 선택이 어디에도 안 보인다**는 것이다. 업무 카탈로그는 「제품이 이 문을 만들었나」를\n * 보여 주지만 「이 공장에서 이 문이 서 있나」는 말하지 않는다. 두 질문이 다른데 하나만 보인다.\n *\n * 실측(2026-09-11): 미라텍 도메인에 양식 11개가 다 설치돼 있고 **11개 전부 결재선이 비어 있다.**\n * 그 공장에서는 실적 정정도 폐기도 승인 없이 바로 반영되는데, 화면에는 문이 있는 것처럼 보인다.\n *\n * ── 비어 있으면 그냥 통과가 아니다 ─────────────────────────────────────────\n * 워크리스트의 거동이 그렇다 — **결재선이 비면 상신이 곧 완료가 되어, 올린 사람이 자기 건을 승인한\n * 것이 된다**(`activity-lookup.ts` § `approvalLineStanding`). 「승인을 안 받는다」가 아니라 「자기가\n * 승인한다」다. 그 차이를 문장에 담는다.\n *\n * ── 기동 로그는 사실의 집이 아니다 ─────────────────────────────────────────\n * `install-boot.ts` 가 기동할 때 한 줄 찍는다. 그 줄은 한 번 지나가고, 그 뒤로 어느 화면도 이것을\n * 말하지 않는다. 사람이 조치해야 하는 사실은 물어볼 수 있는 자리에 있어야 한다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/**\n * 문이 두 종류다 — **축이 다르고, 비었을 때 결과도 다르다.**\n *\n * ```\n * approval 누가 승인하나 결재선이 비면 올린 사람이 곧 승인한 것이 된다 → 통과해 버린다\n * assignment 누가 받나 역할이 안 묶이면 배정 업무가 아무에게도 안 간다 → 아무도 안 받는다\n * ```\n *\n * 같은 `off` 인데 결과가 반대다. 한 벌 문장으로 뭉개면 관리자가 무엇을 해야 하는지 알 수 없다.\n */\nimport type { ApprovalLineStanding } from './approval-line-standing.js'\n\nexport type DoorKind = 'approval' | 'assignment'\n\n/** 문 하나가 어떤 상태인가. */\nexport type DoorStanding =\n /** 채워졌다 — 이 공장은 이 문을 쓴다. */\n | 'on'\n /** 양식은 섰는데 비었다. **조치 대상.** */\n | 'off'\n /** 양식이 아예 안 섰다 — 기동이 못 세웠다. 공장의 선택이 아니라 결함이다. **조치 대상.** */\n | 'missing'\n /**\n * **이 공장은 이 문을 안 쓴다** — 공장이 그렇게 말했다.\n *\n * 조치 대상이 아니다. 이것이 없으면 이 조사는 영원히 잔소리하는 화면이 되고, 그러면 사람들이\n * 안 본다 — 그때 진짜 빈 문 하나가 그 잔소리에 섞여 묻힌다(아키텍트 판정 2026-09-11).\n *\n * **자동으로 추론하지 않는다.** 「석 달 안 썼으니 안 쓰는 것」은 조용한 한 철에 참이 된다.\n */\n | 'not-used'\n\n/** 이 문 하나에 대해 아는 것. */\nexport interface WorkDoor {\n /** 양식 이름. 콜백을 찾는 키다. */\n name: string\n /** 화면에 낼 이름. */\n label: string\n kind: DoorKind\n standing: DoorStanding\n /** 결재자 수. 안 섰거나 비었으면 0. */\n approvers: number\n /**\n * **남의 도메인에서 빌려 온 양식일 때만** 그 도메인의 이름.\n *\n * 본사에서 정하고 공장이 쓰는 것이 정상이라, 자기 것이 없으면 부모 것을 쓴다\n * (`activityNamedIn`). 그 사실을 안 말하면 「우리가 정한 적 없는데 왜 서 있나」가 된다.\n *\n * ⚠ 자기 도메인이면 **없다.** 자기 이름을 채우면 화면이 모든 줄에 「miratek 에서 정했습니다」를\n * 달고, 그 말은 아무것도 알려 주지 않는다 — 실제로 그랬다(2026-09-12, 화면으로 보고 알았다).\n */\n from?: string\n /** 이 상태가 무엇을 뜻하는지 — 화면에 그대로 나가는 문장. */\n because: string\n}\n\n/** 한 문에 대해 읽은 것. 엔티티를 통째로 받지 않는다 — 그러면 이 판단이 DB 를 끌고 온다. */\nexport interface DoorReading {\n name: string\n label: string\n kind: DoorKind\n /**\n * 그 도메인(또는 부모)에 선 양식. 못 찾았으면 없다.\n *\n * 채워졌나를 보는 칸이 종류마다 다르다 — 결재는 `approvalLine`, 배정은 `assigneeRole` 이다.\n */\n /**\n * `borrowedFrom` 은 **다른 도메인의 양식일 때만** 채운다. 자기 것이면 비운다 — 읽는 쪽이\n * 「빌려 왔나」를 이 칸 하나로 판단한다.\n */\n installed?: { approvalLine?: unknown[]; assigneeRole?: string; borrowedFrom?: string }\n /** 공장이 「안 쓴다」고 말했으면 그 사유. 말한 적 없으면 없다. */\n notUsedBecause?: string\n /**\n * **다른 문의 결재선을 따르면** 그 문의 이름(화면에 나가는 것). 그러면 `approvalStanding` 도 그 문의\n * 것이다 — 이 문에서 결재선을 고치러 가면 안 되고 그 문으로 가야 한다는 것을 문장이 말한다.\n */\n follows?: string\n /**\n * 결재선이 찼나 — **상신하는 쪽과 같은 함수**(`judgeApprovalLine`)가 낸 결과를 그대로 받는다.\n *\n * 여기서 다시 세지 않는다. 역할에 사람이 있는지는 DB 를 읽어야 알고, 이 판단은 DB 를 모른다.\n * 없으면 「못 읽었다」로 읽는다 — 「안 읽었다」가 「다 찼다」로 새면 도로 줄 수 세기가 된다.\n */\n approvalStanding?: ApprovalLineStanding\n}\n\nexport function judgeDoor(reading: DoorReading): WorkDoor {\n const { name, label, kind, installed, notUsedBecause, follows } = reading\n /* 따르는 문이 있으면 결재선을 고치는 자리가 그 문이다. 문장 끝에 그 사실을 붙인다. */\n const following = follows ? ` — 「${follows}」 의 결재선을 따르므로, 결재선은 그 문에서 정합니다.` : ''\n\n /*\n * **공장이 안 쓴다고 말한 것이 먼저다.** 뒤에 두면 양식이 안 선 문을 「결함」으로 먼저 그리고,\n * 안 쓰기로 한 공장이 고치라는 말을 계속 듣는다.\n */\n if (notUsedBecause) {\n return {\n name,\n label,\n kind,\n standing: 'not-used',\n approvers: 0,\n because: `「${label}」 은 이 공장이 쓰지 않기로 한 업무입니다 — ${notUsedBecause}`\n }\n }\n\n if (!installed) {\n return {\n name,\n label,\n kind,\n standing: 'missing',\n approvers: 0,\n because: `「${label}」 양식이 이 공장에 서지 않았습니다. 공장이 안 쓰기로 한 것이 아니라 세우지 못한 것이므로 기동 기록을 보십시오.`\n }\n }\n\n /*\n * 결재 문은 **줄 수가 아니라 그 줄에 사람이 있는지**로 센다. 역할을 지목해 놓고 그 역할이 빈\n * 결재선은 화면에 「섰다」로 그려지고 상신하는 순간 터진다(ADR-0047 ③).\n */\n const line = kind === 'approval' ? reading.approvalStanding : undefined\n\n const filled = kind === 'approval' ? (line && !line.refusal ? line.approvers : 0) : installed.assigneeRole ? 1 : 0\n const roleIsEmpty = line?.refusal?.code === 'approval-role-empty'\n const personIsGone = line?.refusal?.code === 'approval-person-absent'\n\n if (!filled) {\n return {\n name,\n label,\n kind,\n standing: 'off',\n approvers: 0,\n from: installed.borrowedFrom,\n /* 종류마다 결과가 반대다(§`DoorKind`). 그래서 문장도 다르다. */\n because:\n (kind !== 'approval'\n ? `「${label}」 을 받을 역할이 묶이지 않았습니다. 이 업무는 만들어져도 아무에게도 가지 않습니다.`\n : roleIsEmpty\n ? /*\n * **관리자가 할 일이 다르다.** 결재선을 정하는 것과 그 역할에 사람을 넣는 것은 다른\n * 일이고, 뒤엣것을 앞엣것으로 말하면 이미 정해 둔 결재선을 다시 들여다보게 만든다.\n */\n `「${label}」 의 결재선이 지목한 역할에 사람이 없습니다. 결재선은 정해져 있으니 그 역할에 사람을 넣어야 이 단계가 섭니다.`\n : personIsGone\n ? `「${label}」 의 결재선이 지목한 사람이 이 공장에 없습니다. 다른 사람을 지목하거나 그 사람을 넣어야 이 단계가 섭니다.`\n : `「${label}」 에 결재자가 없습니다. 이 단계는 서지 않고, 올린 사람이 올리는 순간 자기 건을 승인한 것이 됩니다.`) +\n following\n }\n }\n\n return {\n name,\n label,\n kind,\n standing: 'on',\n approvers: filled,\n from: installed.borrowedFrom,\n because:\n kind === 'approval'\n ? `「${label}」 은 결재자 ${filled}명을 지납니다.${following}`\n : `「${label}」 은 ${installed.assigneeRole} 가 받습니다.`\n }\n}\n\n/**\n * 아직 채울 것이 몇 개인가 — **안 쓰기로 한 것은 안 센다.**\n *\n * 그래야 0 이 「다 됐다」를 뜻한다. 안 쓰는 것을 남은 수에 넣으면 다 채운 공장에서도 수가 안 줄고,\n * 그 화면은 잔소리가 된다(아키텍트 판정 2026-09-11).\n */\nexport function doorsToFill(doors: readonly WorkDoor[]): number {\n return doors.filter(one => one.standing === 'off' || one.standing === 'missing').length\n}\n\n/** 이 공장 전체를 한 문장으로 — 목록만 내면 읽는 사람이 11줄을 세어야 한다. */\nexport function summarizeDoors(doors: readonly WorkDoor[]): string {\n if (!doors.length) {\n return '이 제품이 세우는 업무 양식이 없습니다.'\n }\n\n const on = doors.filter(one => one.standing === 'on').length\n const off = doors.filter(one => one.standing === 'off').length\n const missing = doors.filter(one => one.standing === 'missing').length\n const notUsed = doors.filter(one => one.standing === 'not-used').length\n\n /*\n * **셀 것은 `off + missing` 뿐이다.** 그래야 0 이 「다 됐다」를 뜻한다. 안 쓰기로 한 것을 남은 수에\n * 넣으면 다 채운 공장에서도 수가 안 줄고, 그 화면은 잔소리가 된다.\n */\n const todo = off + missing\n\n if (!todo) {\n return notUsed\n ? `채울 것이 남지 않았습니다. 업무 문 ${on}개가 서 있고 ${notUsed}개는 이 공장이 쓰지 않기로 한 것입니다.`\n : `업무 문 ${doors.length}개가 다 서 있습니다.`\n }\n\n const said = [`채울 것이 ${todo}개 남았습니다.`]\n\n if (off) {\n said.push(`${off}개는 양식은 섰는데 받을 사람이 없습니다 — 누가 할지 이 공장이 아직 정하지 않은 것입니다.`)\n }\n\n if (missing) {\n said.push(`${missing}개는 양식이 아예 서지 않았습니다. 이것은 설정이 아니라 결함입니다.`)\n }\n\n if (notUsed) {\n said.push(`${notUsed}개는 이 공장이 쓰지 않기로 한 것이라 세지 않았습니다.`)\n }\n\n return said.join(' ')\n}\n\n/** The not-used declaration's content columns: which door and why. */\nexport const WORK_DOOR_NOT_USED_CONTENT = ['doorName', 'because'] as const\n\n/** The declaration as it was — its content, who declared it, and when — written when it is withdrawn. */\nexport function workDoorNotUsedBefore(declaration: {\n doorName: string\n because: string\n declaredBy?: { email?: string; name?: string } | null\n createdAt?: Date | null\n}) {\n return {\n ...Object.fromEntries(WORK_DOOR_NOT_USED_CONTENT.map(column => [column, declaration[column] ?? null])),\n declaredBy: declaration.declaredBy?.email ?? declaration.declaredBy?.name ?? null,\n declaredAt: declaration.createdAt ?? null\n }\n}\n"]}