@things-factory/worklist 10.0.18 → 10.1.0

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 (215) hide show
  1. package/dist-client/activity-cells.d.ts +21 -0
  2. package/dist-client/activity-cells.js +117 -0
  3. package/dist-client/activity-cells.js.map +1 -0
  4. package/dist-client/activity-summary-generator.js +57 -11
  5. package/dist-client/activity-summary-generator.js.map +1 -1
  6. package/dist-client/approval-progress.d.ts +18 -0
  7. package/dist-client/approval-progress.js +31 -0
  8. package/dist-client/approval-progress.js.map +1 -0
  9. package/dist-client/bootstrap.js +6 -11
  10. package/dist-client/bootstrap.js.map +1 -1
  11. package/dist-client/chip-filters.d.ts +53 -0
  12. package/dist-client/chip-filters.js +60 -0
  13. package/dist-client/chip-filters.js.map +1 -0
  14. package/dist-client/components/activity-approval-ribon.d.ts +19 -0
  15. package/dist-client/components/activity-approval-ribon.js +50 -126
  16. package/dist-client/components/activity-approval-ribon.js.map +1 -1
  17. package/dist-client/components/activity-fields.d.ts +50 -0
  18. package/dist-client/components/activity-fields.js +393 -0
  19. package/dist-client/components/activity-fields.js.map +1 -0
  20. package/dist-client/components/activity-instance-ribon.d.ts +19 -0
  21. package/dist-client/components/activity-instance-ribon.js +55 -122
  22. package/dist-client/components/activity-instance-ribon.js.map +1 -1
  23. package/dist-client/components/activity-thread-ribon.d.ts +25 -0
  24. package/dist-client/components/activity-thread-ribon.js +64 -121
  25. package/dist-client/components/activity-thread-ribon.js.map +1 -1
  26. package/dist-client/components/activity-thread-timeline.d.ts +14 -2
  27. package/dist-client/components/activity-thread-timeline.js +234 -104
  28. package/dist-client/components/activity-thread-timeline.js.map +1 -1
  29. package/dist-client/components/state-ribon.d.ts +40 -0
  30. package/dist-client/components/state-ribon.js +200 -0
  31. package/dist-client/components/state-ribon.js.map +1 -0
  32. package/dist-client/due.d.ts +16 -0
  33. package/dist-client/due.js +78 -0
  34. package/dist-client/due.js.map +1 -0
  35. package/dist-client/pages/activity/activity-list-page.js +4 -6
  36. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  37. package/dist-client/pages/activity-approval/activity-approval-list-page.js +2 -1
  38. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  39. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +52 -5
  40. package/dist-client/pages/activity-approval/activity-approval-page.js +265 -118
  41. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  42. package/dist-client/pages/activity-instance/activity-instance-list-page.d.ts +0 -1
  43. package/dist-client/pages/activity-instance/activity-instance-list-page.js +8 -24
  44. package/dist-client/pages/activity-instance/activity-instance-list-page.js.map +1 -1
  45. package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +1 -0
  46. package/dist-client/pages/activity-instance/activity-instance-start-page.js +7 -0
  47. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  48. package/dist-client/pages/activity-instance/activity-instance-view.js +1 -1
  49. package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
  50. package/dist-client/pages/activity-stats/activity-stats-list-page.js +2 -1
  51. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  52. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +30 -4
  53. package/dist-client/pages/activity-thread/activity-thread-page.js +142 -66
  54. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  55. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +17 -6
  56. package/dist-client/pages/activity-thread/activity-thread-view-page.js +118 -65
  57. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  58. package/dist-client/pages/activity-thread/activity-thread-view.d.ts +24 -7
  59. package/dist-client/pages/activity-thread/activity-thread-view.js +84 -308
  60. package/dist-client/pages/activity-thread/activity-thread-view.js.map +1 -1
  61. package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +1 -1
  62. package/dist-client/pages/installable-activity/installable-activity-list-page.js +13 -9
  63. package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
  64. package/dist-client/pages/todo/approval-done-list-page.d.ts +1 -0
  65. package/dist-client/pages/todo/approval-done-list-page.js +111 -58
  66. package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
  67. package/dist-client/pages/todo/approval-pending-list-page.d.ts +32 -1
  68. package/dist-client/pages/todo/approval-pending-list-page.js +485 -130
  69. package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
  70. package/dist-client/pages/todo/done-list-calendar-page.d.ts +2 -0
  71. package/dist-client/pages/todo/done-list-calendar-page.js +213 -29
  72. package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
  73. package/dist-client/pages/todo/done-list-page.d.ts +1 -0
  74. package/dist-client/pages/todo/done-list-page.js +126 -62
  75. package/dist-client/pages/todo/done-list-page.js.map +1 -1
  76. package/dist-client/pages/todo/draft-list-page.d.ts +1 -0
  77. package/dist-client/pages/todo/draft-list-page.js +82 -34
  78. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  79. package/dist-client/pages/todo/pickable-list-page.js +6 -10
  80. package/dist-client/pages/todo/pickable-list-page.js.map +1 -1
  81. package/dist-client/pages/todo/todo-list-page.d.ts +1 -1
  82. package/dist-client/pages/todo/todo-list-page.js +102 -78
  83. package/dist-client/pages/todo/todo-list-page.js.map +1 -1
  84. package/dist-client/route.d.ts +1 -1
  85. package/dist-client/route.js +0 -6
  86. package/dist-client/route.js.map +1 -1
  87. package/dist-client/templates/activity-approval-context-template.js +60 -138
  88. package/dist-client/templates/activity-approval-context-template.js.map +1 -1
  89. package/dist-client/templates/activity-instance-context-template.js +13 -172
  90. package/dist-client/templates/activity-instance-context-template.js.map +1 -1
  91. package/dist-client/templates/activity-thread-context-template.js +39 -131
  92. package/dist-client/templates/activity-thread-context-template.js.map +1 -1
  93. package/dist-client/templates/context-panel.d.ts +26 -0
  94. package/dist-client/templates/context-panel.js +182 -0
  95. package/dist-client/templates/context-panel.js.map +1 -0
  96. package/dist-client/tsconfig.tsbuildinfo +1 -1
  97. package/dist-client/when.d.ts +4 -0
  98. package/dist-client/when.js +72 -0
  99. package/dist-client/when.js.map +1 -0
  100. package/dist-server/controllers/activity-approval/_abort.js +2 -1
  101. package/dist-server/controllers/activity-approval/_abort.js.map +1 -1
  102. package/dist-server/controllers/activity-approval/approval-line.d.ts +106 -0
  103. package/dist-server/controllers/activity-approval/approval-line.js +151 -0
  104. package/dist-server/controllers/activity-approval/approval-line.js.map +1 -0
  105. package/dist-server/controllers/activity-approval/approve.js +61 -51
  106. package/dist-server/controllers/activity-approval/approve.js.map +1 -1
  107. package/dist-server/controllers/activity-approval/claim.d.ts +42 -0
  108. package/dist-server/controllers/activity-approval/claim.js +77 -0
  109. package/dist-server/controllers/activity-approval/claim.js.map +1 -0
  110. package/dist-server/controllers/activity-approval/create-approval.d.ts +41 -0
  111. package/dist-server/controllers/activity-approval/create-approval.js +78 -0
  112. package/dist-server/controllers/activity-approval/create-approval.js.map +1 -0
  113. package/dist-server/controllers/activity-approval/delegate.d.ts +16 -0
  114. package/dist-server/controllers/activity-approval/delegate.js +43 -7
  115. package/dist-server/controllers/activity-approval/delegate.js.map +1 -1
  116. package/dist-server/controllers/activity-approval/opinion.d.ts +10 -0
  117. package/dist-server/controllers/activity-approval/opinion.js +43 -0
  118. package/dist-server/controllers/activity-approval/opinion.js.map +1 -0
  119. package/dist-server/controllers/activity-approval/reject.js +21 -1
  120. package/dist-server/controllers/activity-approval/reject.js.map +1 -1
  121. package/dist-server/controllers/activity-approval/save.js +26 -2
  122. package/dist-server/controllers/activity-approval/save.js.map +1 -1
  123. package/dist-server/controllers/activity-installation/activity-installation-controller.d.ts +26 -0
  124. package/dist-server/controllers/activity-installation/activity-installation-controller.js +45 -0
  125. package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
  126. package/dist-server/controllers/activity-instance/approval-expectation.d.ts +34 -0
  127. package/dist-server/controllers/activity-instance/approval-expectation.js +18 -0
  128. package/dist-server/controllers/activity-instance/approval-expectation.js.map +1 -0
  129. package/dist-server/controllers/activity-instance/issue.js +32 -0
  130. package/dist-server/controllers/activity-instance/issue.js.map +1 -1
  131. package/dist-server/controllers/activity-thread/restart.js +17 -1
  132. package/dist-server/controllers/activity-thread/restart.js.map +1 -1
  133. package/dist-server/controllers/activity-thread/submit.js +23 -43
  134. package/dist-server/controllers/activity-thread/submit.js.map +1 -1
  135. package/dist-server/controllers/common.js +46 -0
  136. package/dist-server/controllers/common.js.map +1 -1
  137. package/dist-server/controllers/index.d.ts +1 -0
  138. package/dist-server/controllers/index.js +8 -0
  139. package/dist-server/controllers/index.js.map +1 -1
  140. package/dist-server/service/activity/activity-mutation.js +25 -5
  141. package/dist-server/service/activity/activity-mutation.js.map +1 -1
  142. package/dist-server/service/activity/activity-type.d.ts +14 -0
  143. package/dist-server/service/activity/activity-type.js +8 -0
  144. package/dist-server/service/activity/activity-type.js.map +1 -1
  145. package/dist-server/service/activity/activity.d.ts +18 -0
  146. package/dist-server/service/activity/activity.js +5 -0
  147. package/dist-server/service/activity/activity.js.map +1 -1
  148. package/dist-server/service/activity-approval/activity-approval-mutation.js +3 -2
  149. package/dist-server/service/activity-approval/activity-approval-mutation.js.map +1 -1
  150. package/dist-server/service/activity-approval/activity-approval-query.d.ts +19 -1
  151. package/dist-server/service/activity-approval/activity-approval-query.js +186 -56
  152. package/dist-server/service/activity-approval/activity-approval-query.js.map +1 -1
  153. package/dist-server/service/activity-approval/activity-approval.d.ts +29 -1
  154. package/dist-server/service/activity-approval/activity-approval.js +19 -0
  155. package/dist-server/service/activity-approval/activity-approval.js.map +1 -1
  156. package/dist-server/service/activity-approval/my-pending-approvals.d.ts +21 -0
  157. package/dist-server/service/activity-approval/my-pending-approvals.js +40 -0
  158. package/dist-server/service/activity-approval/my-pending-approvals.js.map +1 -0
  159. package/dist-server/service/activity-instance/activity-instance-type.d.ts +20 -0
  160. package/dist-server/service/activity-instance/activity-instance-type.js +11 -0
  161. package/dist-server/service/activity-instance/activity-instance-type.js.map +1 -1
  162. package/dist-server/service/activity-instance/activity-instance.d.ts +6 -0
  163. package/dist-server/service/activity-instance/activity-instance.js +5 -0
  164. package/dist-server/service/activity-instance/activity-instance.js.map +1 -1
  165. package/dist-server/service/activity-stats/activity-stats-query.d.ts +5 -0
  166. package/dist-server/service/activity-stats/activity-stats-query.js +149 -11
  167. package/dist-server/service/activity-stats/activity-stats-query.js.map +1 -1
  168. package/dist-server/service/activity-stats/activity-stats-type.d.ts +19 -6
  169. package/dist-server/service/activity-stats/activity-stats-type.js +37 -10
  170. package/dist-server/service/activity-stats/activity-stats-type.js.map +1 -1
  171. package/dist-server/service/activity-stats/event-placement.d.ts +55 -0
  172. package/dist-server/service/activity-stats/event-placement.js +82 -0
  173. package/dist-server/service/activity-stats/event-placement.js.map +1 -0
  174. package/dist-server/service/activity-thread/activity-thread-query.js +48 -24
  175. package/dist-server/service/activity-thread/activity-thread-query.js.map +1 -1
  176. package/dist-server/service/activity-thread/my-open-threads.d.ts +15 -0
  177. package/dist-server/service/activity-thread/my-open-threads.js +40 -0
  178. package/dist-server/service/activity-thread/my-open-threads.js.map +1 -0
  179. package/dist-server/service/activity-thread/todo-order.d.ts +49 -0
  180. package/dist-server/service/activity-thread/todo-order.js +70 -0
  181. package/dist-server/service/activity-thread/todo-order.js.map +1 -0
  182. package/dist-server/service/installable-activity/installable-activity-mutation.d.ts +6 -0
  183. package/dist-server/service/installable-activity/installable-activity-mutation.js +7 -24
  184. package/dist-server/service/installable-activity/installable-activity-mutation.js.map +1 -1
  185. package/dist-server/service/nulls-last.d.ts +30 -0
  186. package/dist-server/service/nulls-last.js +37 -0
  187. package/dist-server/service/nulls-last.js.map +1 -0
  188. package/dist-server/tsconfig.tsbuildinfo +1 -1
  189. package/helps/dataset/activity-instance-search-page.ko.md +5 -0
  190. package/helps/dataset/activity-instance-search-page.md +13 -0
  191. package/helps/worklist/activity-approval.ko.md +5 -0
  192. package/helps/worklist/activity-approval.md +14 -0
  193. package/helps/worklist/activity-bank-page.ko.md +5 -0
  194. package/helps/worklist/activity-bank-page.md +13 -0
  195. package/helps/worklist/activity-reporter-list.ko.md +5 -0
  196. package/helps/worklist/activity-reporter-list.md +13 -0
  197. package/helps/worklist/activity-store.ko.md +5 -0
  198. package/helps/worklist/activity-store.md +13 -0
  199. package/helps/worklist/activity-template.ko.md +5 -0
  200. package/helps/worklist/activity-template.md +13 -0
  201. package/helps/worklist/installable-activity.ko.md +5 -0
  202. package/helps/worklist/installable-activity.md +13 -0
  203. package/package.json +13 -12
  204. package/things-factory.config.js +0 -5
  205. package/translations/en.json +129 -51
  206. package/translations/ja.json +130 -52
  207. package/translations/ko.json +82 -4
  208. package/translations/ms.json +129 -51
  209. package/translations/zh.json +132 -54
  210. package/dist-client/pages/dashboard/dashboard-home.d.ts +0 -1
  211. package/dist-client/pages/dashboard/dashboard-home.js +0 -22
  212. package/dist-client/pages/dashboard/dashboard-home.js.map +0 -1
  213. package/dist-client/pages/worklist-home.d.ts +0 -1
  214. package/dist-client/pages/worklist-home.js +0 -17
  215. package/dist-client/pages/worklist-home.js.map +0 -1
@@ -0,0 +1,4 @@
1
+ /** 날짜와 시각 — 이력처럼 날이 넘어갈 수 있는 자리. */
2
+ export declare function whenText(value?: Date | string | number): string;
3
+ /** 목록의 곁줄에 붙는 짧은 시각 — 「9. 2. 오후 12:48」. */
4
+ export declare function whenShortText(value?: Date | string | number): string;
@@ -0,0 +1,72 @@
1
+ import { i18next } from '@operato/i18n';
2
+ /**
3
+ * 시각을 화면의 말로 — **worklist 화면의 모든 시각이 여기를 지난다.**
4
+ *
5
+ * ── 무엇이 잘못되고 있었나 (2026-09-06) ────────────────────────────────────
6
+ * 같은 화면 안에서 시각 표기가 두 벌이었다.
7
+ *
8
+ * ```
9
+ * 목록(activity-cells.ts) 9. 2. 오후 12:48 toLocaleString(i18next.language, {...})
10
+ * 상세의 타임라인 Tuesday, September 1, 2026 at 11:51 AM
11
+ * ```
12
+ *
13
+ * 타임라인은 `Intl.DateTimeFormat(navigator.language, { dateStyle: 'full' })` 이었다. 두 가지가
14
+ * 어긋난다 — 언어를 **화면이 아니라 브라우저 설정**에서 가져오고, `full` 은 요일까지 적는다.
15
+ * 한국어로 쓰는 사용자가 목록에서 건을 고르고 상세로 들어가면 같은 종류의 값이 다른 말로 나온다.
16
+ *
17
+ * operato-plant 가 `client/when.ts` 로 같은 문제를 정리했다. worklist 는 framework 패키지라
18
+ * 앱을 참조할 수 없으므로(역방향 의존) 같은 규칙을 여기에 둔다.
19
+ *
20
+ * ── 시간대는 다루지 않는다 ─────────────────────────────────────────────────
21
+ * 도메인 시간대를 읽고 표시마다 고르게 하는 것은 공용 모듈이 할 일이고 operato-twin 의
22
+ * `when-core.ts` 가 이미 갖고 있다. 여기서 흉내 내면 같은 사실을 화면마다 다른 시각으로 말하게
23
+ * 된다. 그때까지 언어만 맞춘다.
24
+ */
25
+ /**
26
+ * 시 · 분까지만.
27
+ *
28
+ * 기본값은 초까지 낸다. 결재 이력의 시각은 「언제쯤이었나」를 말하는 값이라 초가 뜻을 더하지 않고,
29
+ * 자릿수만 늘려 옆 칸을 밀어낸다.
30
+ */
31
+ const CLOCK = { hour: 'numeric', minute: '2-digit' };
32
+ /**
33
+ * 날짜와 시 · 분.
34
+ *
35
+ * `Intl` 은 **지정한 성분만** 낸다. 시 · 분만 주면 날짜가 사라지므로 날짜 성분을 함께 적는다 —
36
+ * 결재 이력은 날을 넘기는 값이라 날짜가 빠지면 어제 것과 구별되지 않는다.
37
+ */
38
+ const DAY_AND_CLOCK = {
39
+ year: 'numeric',
40
+ month: 'numeric',
41
+ day: 'numeric',
42
+ ...CLOCK
43
+ };
44
+ /** 목록의 좁은 칸 — 해를 적지 않는다. 대부분의 줄이 올해다. */
45
+ const SHORT_DAY_AND_CLOCK = {
46
+ month: 'numeric',
47
+ day: 'numeric',
48
+ ...CLOCK
49
+ };
50
+ /** 날짜와 시각 — 이력처럼 날이 넘어갈 수 있는 자리. */
51
+ export function whenText(value) {
52
+ const date = toDate(value);
53
+ return date ? date.toLocaleString(lang(), DAY_AND_CLOCK) : '';
54
+ }
55
+ /** 목록의 곁줄에 붙는 짧은 시각 — 「9. 2. 오후 12:48」. */
56
+ export function whenShortText(value) {
57
+ const date = toDate(value);
58
+ return date ? date.toLocaleString(lang(), SHORT_DAY_AND_CLOCK) : '';
59
+ }
60
+ /** 없는 값은 빈 글자다. `Invalid Date` 를 화면에 내보내지 않는다. */
61
+ function toDate(value) {
62
+ if (value === undefined || value === null || value === '') {
63
+ return undefined;
64
+ }
65
+ const date = value instanceof Date ? value : new Date(value);
66
+ return Number.isNaN(date.getTime()) ? undefined : date;
67
+ }
68
+ /** `undefined` 면 브라우저 설정을 따른다 — i18next 가 아직 뜨지 않았을 때뿐이다. */
69
+ function lang() {
70
+ return i18next.language || undefined;
71
+ }
72
+ //# sourceMappingURL=when.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"when.js","sourceRoot":"","sources":["../client/when.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;GAKG;AACH,MAAM,KAAK,GAA+B,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;AAEhF;;;;;GAKG;AACH,MAAM,aAAa,GAA+B;IAChD,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,GAAG,KAAK;CACT,CAAA;AAED,yCAAyC;AACzC,MAAM,mBAAmB,GAA+B;IACtD,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,GAAG,KAAK;CACT,CAAA;AAED,oCAAoC;AACpC,MAAM,UAAU,QAAQ,CAAC,KAA8B;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAE1B,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC/D,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAE1B,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACrE,CAAC;AAED,kDAAkD;AAClD,SAAS,MAAM,CAAC,KAA8B;IAC5C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;IAE5D,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;AACxD,CAAC;AAED,6DAA6D;AAC7D,SAAS,IAAI;IACX,OAAO,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAA;AACtC,CAAC","sourcesContent":["import { i18next } from '@operato/i18n'\n\n/**\n * 시각을 화면의 말로 — **worklist 화면의 모든 시각이 여기를 지난다.**\n *\n * ── 무엇이 잘못되고 있었나 (2026-09-06) ────────────────────────────────────\n * 같은 화면 안에서 시각 표기가 두 벌이었다.\n *\n * ```\n * 목록(activity-cells.ts) 9. 2. 오후 12:48 toLocaleString(i18next.language, {...})\n * 상세의 타임라인 Tuesday, September 1, 2026 at 11:51 AM\n * ```\n *\n * 타임라인은 `Intl.DateTimeFormat(navigator.language, { dateStyle: 'full' })` 이었다. 두 가지가\n * 어긋난다 — 언어를 **화면이 아니라 브라우저 설정**에서 가져오고, `full` 은 요일까지 적는다.\n * 한국어로 쓰는 사용자가 목록에서 건을 고르고 상세로 들어가면 같은 종류의 값이 다른 말로 나온다.\n *\n * operato-plant 가 `client/when.ts` 로 같은 문제를 정리했다. worklist 는 framework 패키지라\n * 앱을 참조할 수 없으므로(역방향 의존) 같은 규칙을 여기에 둔다.\n *\n * ── 시간대는 다루지 않는다 ─────────────────────────────────────────────────\n * 도메인 시간대를 읽고 표시마다 고르게 하는 것은 공용 모듈이 할 일이고 operato-twin 의\n * `when-core.ts` 가 이미 갖고 있다. 여기서 흉내 내면 같은 사실을 화면마다 다른 시각으로 말하게\n * 된다. 그때까지 언어만 맞춘다.\n */\n\n/**\n * 시 · 분까지만.\n *\n * 기본값은 초까지 낸다. 결재 이력의 시각은 「언제쯤이었나」를 말하는 값이라 초가 뜻을 더하지 않고,\n * 자릿수만 늘려 옆 칸을 밀어낸다.\n */\nconst CLOCK: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit' }\n\n/**\n * 날짜와 시 · 분.\n *\n * `Intl` 은 **지정한 성분만** 낸다. 시 · 분만 주면 날짜가 사라지므로 날짜 성분을 함께 적는다 —\n * 결재 이력은 날을 넘기는 값이라 날짜가 빠지면 어제 것과 구별되지 않는다.\n */\nconst DAY_AND_CLOCK: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n ...CLOCK\n}\n\n/** 목록의 좁은 칸 — 해를 적지 않는다. 대부분의 줄이 올해다. */\nconst SHORT_DAY_AND_CLOCK: Intl.DateTimeFormatOptions = {\n month: 'numeric',\n day: 'numeric',\n ...CLOCK\n}\n\n/** 날짜와 시각 — 이력처럼 날이 넘어갈 수 있는 자리. */\nexport function whenText(value?: Date | string | number): string {\n const date = toDate(value)\n\n return date ? date.toLocaleString(lang(), DAY_AND_CLOCK) : ''\n}\n\n/** 목록의 곁줄에 붙는 짧은 시각 — 「9. 2. 오후 12:48」. */\nexport function whenShortText(value?: Date | string | number): string {\n const date = toDate(value)\n\n return date ? date.toLocaleString(lang(), SHORT_DAY_AND_CLOCK) : ''\n}\n\n/** 없는 값은 빈 글자다. `Invalid Date` 를 화면에 내보내지 않는다. */\nfunction toDate(value?: Date | string | number): Date | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n\n const date = value instanceof Date ? value : new Date(value)\n\n return Number.isNaN(date.getTime()) ? undefined : date\n}\n\n/** `undefined` 면 브라우저 설정을 따른다 — i18next 가 아직 뜨지 않았을 때뿐이다. */\nfunction lang(): string | undefined {\n return i18next.language || undefined\n}\n"]}
@@ -2,13 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports._abort = _abort;
4
4
  const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
5
+ const opinion_js_1 = require("./opinion.js");
5
6
  const activity_approval_js_1 = require("../../service/activity-approval/activity-approval.js");
6
7
  async function _abort({ activityApproval, comment }, context) {
7
8
  const { user, tx } = context.state;
8
9
  var result = await tx.getRepository(activity_approval_js_1.ActivityApproval).save({
9
10
  ...activityApproval,
10
11
  judgment: activity_approval_js_1.ActivityApprovalJudgment.Aborted,
11
- comment,
12
+ ...(0, opinion_js_1.stampedComment)(comment),
12
13
  transaction: 'abort',
13
14
  updater: user,
14
15
  terminatedAt: new Date(),
@@ -1 +1 @@
1
- {"version":3,"file":"_abort.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/_abort.ts"],"names":[],"mappings":";;AAGA,wBA2BC;AA9BD,yFAAuG;AACvG,+FAAiH;AAE1G,KAAK,UAAU,MAAM,CAC1B,EAAE,gBAAgB,EAAE,OAAO,EAA2D,EACtF,OAAwB;IAExB,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAElC,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,IAAI,CAAC;QACzD,GAAG,gBAAgB;QACnB,QAAQ,EAAE,+CAAwB,CAAC,OAAO;QAC1C,OAAO;QACP,WAAW,EAAE,OAAO;QACpB,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,IAAI,IAAI,EAAE;QACxB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAA;IAEF,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,IAAI,CAAC;QAC1C,GAAG,gBAAgB,CAAC,cAAc;QAClC,WAAW,EAAE,OAAO;QACpB,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,yCAAoB,CAAC,OAAO;QACnC,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,IAAI,IAAI,EAAE;QACxB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../../service/activity-approval/activity-approval.js'\n\nexport async function _abort(\n { activityApproval, comment }: { activityApproval: ActivityApproval; comment: string },\n context: ResolverContext\n): Promise<ActivityApproval> {\n const { user, tx } = context.state\n\n var result = await tx.getRepository(ActivityApproval).save({\n ...activityApproval,\n judgment: ActivityApprovalJudgment.Aborted,\n comment,\n transaction: 'abort',\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n await tx.getRepository(ActivityThread).save({\n ...activityApproval.activityThread,\n transaction: 'abort',\n reason: comment,\n state: ActivityThreadStatus.Aborted,\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n return result\n}\n"]}
1
+ {"version":3,"file":"_abort.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/_abort.ts"],"names":[],"mappings":";;AAIA,wBA2BC;AA/BD,yFAAuG;AACvG,6CAA6C;AAC7C,+FAAiH;AAE1G,KAAK,UAAU,MAAM,CAC1B,EAAE,gBAAgB,EAAE,OAAO,EAA2D,EACtF,OAAwB;IAExB,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAElC,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,IAAI,CAAC;QACzD,GAAG,gBAAgB;QACnB,QAAQ,EAAE,+CAAwB,CAAC,OAAO;QAC1C,GAAG,IAAA,2BAAc,EAAC,OAAO,CAAC;QAC1B,WAAW,EAAE,OAAO;QACpB,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,IAAI,IAAI,EAAE;QACxB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAA;IAEF,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,IAAI,CAAC;QAC1C,GAAG,gBAAgB,CAAC,cAAc;QAClC,WAAW,EAAE,OAAO;QACpB,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,yCAAoB,CAAC,OAAO;QACnC,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,IAAI,IAAI,EAAE;QACxB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { stampedComment } from './opinion.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../../service/activity-approval/activity-approval.js'\n\nexport async function _abort(\n { activityApproval, comment }: { activityApproval: ActivityApproval; comment: string },\n context: ResolverContext\n): Promise<ActivityApproval> {\n const { user, tx } = context.state\n\n var result = await tx.getRepository(ActivityApproval).save({\n ...activityApproval,\n judgment: ActivityApprovalJudgment.Aborted,\n ...stampedComment(comment),\n transaction: 'abort',\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n await tx.getRepository(ActivityThread).save({\n ...activityApproval.activityThread,\n transaction: 'abort',\n reason: comment,\n state: ActivityThreadStatus.Aborted,\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n return result\n}\n"]}
@@ -0,0 +1,106 @@
1
+ import type { ApprovalLineItem } from '@things-factory/organization';
2
+ /**
3
+ * 결재선에서 다음 결재자를 찾는다.
4
+ *
5
+ * ── 왜 따로 뺐나 ───────────────────────────────────────────────────────────
6
+ * 같은 규칙이 두 파일에 나뉘어 있었다.
7
+ *
8
+ * submit.ts 첫 결재를 `order: 1` 로 만들고 `approvalLine[0]` 을 넣는다
9
+ * approve.ts `order` 를 승인하면 `approvalLine[order]` 를 다음 결재자로 삼는다
10
+ *
11
+ * `order` 와 배열 자리가 1만큼 다르다. 지금은 맞게 짝이 맞는데, 한쪽만 고치면 결재자가 한 명 빠지거나
12
+ * 같은 사람이 두 번 들어간다. 그리고 **오류가 안 난다** — 결재가 다 끝나고 나서야 안다.
13
+ *
14
+ * 그래서 한 자리에 적고 양쪽이 이것을 부른다.
15
+ *
16
+ * `order` 는 사람이 세는 수다 — 1차 결재, 2차 결재. 배열은 0부터이므로 그 차이를 여기서만 다룬다.
17
+ */
18
+ /**
19
+ * 이 항목이 **결재**인가 — 비어 있으면 결재다.
20
+ *
21
+ * 지금 도는 결재선에는 이 칸이 없다(gangsters·dataset·quotation·accounting). 그 항목들이 전부 결재자로
22
+ * 읽혀야 거동이 안 바뀐다. 그래서 「참조라고 적힌 것만 참조」다.
23
+ */
24
+ export declare function isApprovalStep(item: ApprovalLineItem | undefined): boolean;
25
+ /** 이 항목이 참조인가 — 의견만 내고 결재를 막지 않는다. */
26
+ export declare function isReferenceStep(item: ApprovalLineItem | undefined): boolean;
27
+ /**
28
+ * 이 차례의 결재자는 결재선의 어느 줄인가. `order` 는 1부터.
29
+ *
30
+ * **참조 항목도 자리를 차지한다.** `order` 는 배열 자리 그대로다 — 다시 매기면 이미 쌓인 행의 뜻이
31
+ * 바뀐다. 그래서 결재자의 차례가 1·3·4 처럼 띄엄띄엄해질 수 있고, 그것이 맞다.
32
+ *
33
+ * 이 함수는 그 자리에 무엇이 있든 돌려준다. 결재인지 아닌지는 부르는 쪽이 본다(§`nextApproval`).
34
+ */
35
+ export declare function lineItemFor(approvalLine: ApprovalLineItem[] | undefined, order: number): ApprovalLineItem | undefined;
36
+ /**
37
+ * 첫 **결재자**는 몇 번째 자리인가 — 앞에 참조가 있으면 건너뛴다.
38
+ *
39
+ * 결재선이 참조로만 이루어져 있으면 `undefined` 다. 그때는 결재 없이 끝난다(§`needsApproval`).
40
+ */
41
+ export declare function firstApprovalOrder(approvalLine: ApprovalLineItem[] | undefined): number | undefined;
42
+ /**
43
+ * 이 결재선의 참조들 — **언제 알릴지와 함께.**
44
+ *
45
+ * `notifyAfter` 가 없으면 `0` 으로 답한다. 0 은 「상신 즉시」다 — 아직 아무 결재도 끝나지 않은 시점.
46
+ */
47
+ export declare function referenceSteps(approvalLine: ApprovalLineItem[] | undefined): {
48
+ item: ApprovalLineItem;
49
+ order: number;
50
+ notifyAfter: number;
51
+ }[];
52
+ /** 이번에 알려야 할 참조들 — `afterOrder` 번째 결재가 막 끝났을 때. 상신 직후는 `0`. */
53
+ export declare function referencesToNotify(approvalLine: ApprovalLineItem[] | undefined, afterOrder: number): {
54
+ item: ApprovalLineItem;
55
+ order: number;
56
+ }[];
57
+ /** 첫 결재의 차례. */
58
+ export declare const FIRST_ORDER = 1;
59
+ /**
60
+ * 이 차례를 승인하면 다음 결재자가 있나. 없으면 이것이 마지막이다.
61
+ *
62
+ * **참조는 건너뛴다.** 참조자가 아직 안 읽었어도 결재는 다음 결재자로 넘어간다 — 막게 만들면 그 사람이
63
+ * 휴가일 때 결재가 선다. 막아야 하는 자격은 합의이고, 그것은 아직 없다.
64
+ */
65
+ export declare function nextApproval(approvalLine: ApprovalLineItem[] | undefined, order: number): {
66
+ item: ApprovalLineItem;
67
+ order: number;
68
+ } | undefined;
69
+ /** 이 차례가 마지막인가. */
70
+ export declare function isLastApproval(approvalLine: ApprovalLineItem[] | undefined, order: number): boolean;
71
+ /**
72
+ * 결재가 필요한가 — **결재자가 하나라도 있어야 한다.**
73
+ *
74
+ * 비어 있으면 결재 없이 끝난다. 그리고 **참조만 있는 결재선도 결재 없이 끝난다** — 참조는 막지 않으므로
75
+ * 기다릴 것이 없다. 참조가 있다고 결재를 기다리게 하면, 그 사람이 승인 권한도 없이 결재를 세운다.
76
+ */
77
+ export declare function needsApproval(approvalLine: ApprovalLineItem[] | undefined): boolean;
78
+ /**
79
+ * 이 판정이 「승인했다」인가.
80
+ *
81
+ * ── 옛 표기를 여기서 흡수한다 ──────────────────────────────────────────────
82
+ * 2026-08-31 전에는 다음 결재자가 있으면 승인을 `escalated` 로 적었다. **그 행은 고치지 않는다** —
83
+ * 승인 기록을 뒤에서 고쳐 쓰는 것은 규제 산업이 금지하는 바로 그 행위다.
84
+ *
85
+ * 대신 읽는 자리 하나에서 옛 표기를 새 뜻으로 읽는다. 이 규율이 두 곳으로 늘어나면 한쪽만 고쳐지는
86
+ * 날이 온다. 「승인했나」를 묻는 코드는 전부 이것을 부른다.
87
+ */
88
+ export declare function isApproved(judgment: string | undefined): boolean;
89
+ /**
90
+ * 더 기다릴 것이 없는 판정들 — **목록 질의와 이 파일이 같은 값을 본다.**
91
+ *
92
+ * 목록 질의가 이 배열을 그대로 쓴다. 두 벌로 두면 값이 늘 때 한쪽만 늘고, 그 판정을 받은 행이 대기
93
+ * 목록에 영원히 남는다 — 실제로 그렇게 됐다(아래).
94
+ */
95
+ export declare const TERMINATED_JUDGMENTS: readonly ["approved", "escalated", "delegated", "rejected", "aborted", "closed"];
96
+ /**
97
+ * 이 판정이 끝난 것인가 — 더 기다릴 것이 없나.
98
+ *
99
+ * ── `aborted` 가 빠져 있었다 (2026-09-01) ─────────────────────────────────
100
+ * 스레드를 중단하면 그 스레드의 대기 결재가 `aborted` 로 닫힌다(§`activity-thread/_abort.ts`).
101
+ * 그런데 **목록 질의도 이 함수도 그 값을 「끝난 것」으로 세지 않았다.**
102
+ *
103
+ * 그래서 중단된 건의 결재가 그 사람의 **승인 대기 목록에 영원히 남았다.** 눌러 봐야 이미 끝난 건이고,
104
+ * 목록에서 사라지지도 않는다. 닫는 쪽은 제대로 닫았는데 읽는 쪽이 그것을 못 읽었다.
105
+ */
106
+ export declare function isTerminated(judgment: string | undefined): boolean;
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TERMINATED_JUDGMENTS = exports.FIRST_ORDER = void 0;
4
+ exports.isApprovalStep = isApprovalStep;
5
+ exports.isReferenceStep = isReferenceStep;
6
+ exports.lineItemFor = lineItemFor;
7
+ exports.firstApprovalOrder = firstApprovalOrder;
8
+ exports.referenceSteps = referenceSteps;
9
+ exports.referencesToNotify = referencesToNotify;
10
+ exports.nextApproval = nextApproval;
11
+ exports.isLastApproval = isLastApproval;
12
+ exports.needsApproval = needsApproval;
13
+ exports.isApproved = isApproved;
14
+ exports.isTerminated = isTerminated;
15
+ /**
16
+ * 결재선에서 다음 결재자를 찾는다.
17
+ *
18
+ * ── 왜 따로 뺐나 ───────────────────────────────────────────────────────────
19
+ * 같은 규칙이 두 파일에 나뉘어 있었다.
20
+ *
21
+ * submit.ts 첫 결재를 `order: 1` 로 만들고 `approvalLine[0]` 을 넣는다
22
+ * approve.ts `order` 를 승인하면 `approvalLine[order]` 를 다음 결재자로 삼는다
23
+ *
24
+ * `order` 와 배열 자리가 1만큼 다르다. 지금은 맞게 짝이 맞는데, 한쪽만 고치면 결재자가 한 명 빠지거나
25
+ * 같은 사람이 두 번 들어간다. 그리고 **오류가 안 난다** — 결재가 다 끝나고 나서야 안다.
26
+ *
27
+ * 그래서 한 자리에 적고 양쪽이 이것을 부른다.
28
+ *
29
+ * `order` 는 사람이 세는 수다 — 1차 결재, 2차 결재. 배열은 0부터이므로 그 차이를 여기서만 다룬다.
30
+ */
31
+ /**
32
+ * 이 항목이 **결재**인가 — 비어 있으면 결재다.
33
+ *
34
+ * 지금 도는 결재선에는 이 칸이 없다(gangsters·dataset·quotation·accounting). 그 항목들이 전부 결재자로
35
+ * 읽혀야 거동이 안 바뀐다. 그래서 「참조라고 적힌 것만 참조」다.
36
+ */
37
+ function isApprovalStep(item) {
38
+ return !!item && item.participation !== 'reference';
39
+ }
40
+ /** 이 항목이 참조인가 — 의견만 내고 결재를 막지 않는다. */
41
+ function isReferenceStep(item) {
42
+ return !!item && item.participation === 'reference';
43
+ }
44
+ /**
45
+ * 이 차례의 결재자는 결재선의 어느 줄인가. `order` 는 1부터.
46
+ *
47
+ * **참조 항목도 자리를 차지한다.** `order` 는 배열 자리 그대로다 — 다시 매기면 이미 쌓인 행의 뜻이
48
+ * 바뀐다. 그래서 결재자의 차례가 1·3·4 처럼 띄엄띄엄해질 수 있고, 그것이 맞다.
49
+ *
50
+ * 이 함수는 그 자리에 무엇이 있든 돌려준다. 결재인지 아닌지는 부르는 쪽이 본다(§`nextApproval`).
51
+ */
52
+ function lineItemFor(approvalLine, order) {
53
+ if (!approvalLine?.length || order < 1) {
54
+ return undefined;
55
+ }
56
+ return approvalLine[order - 1];
57
+ }
58
+ /**
59
+ * 첫 **결재자**는 몇 번째 자리인가 — 앞에 참조가 있으면 건너뛴다.
60
+ *
61
+ * 결재선이 참조로만 이루어져 있으면 `undefined` 다. 그때는 결재 없이 끝난다(§`needsApproval`).
62
+ */
63
+ function firstApprovalOrder(approvalLine) {
64
+ if (!approvalLine?.length)
65
+ return undefined;
66
+ const index = approvalLine.findIndex(isApprovalStep);
67
+ return index < 0 ? undefined : index + 1;
68
+ }
69
+ /**
70
+ * 이 결재선의 참조들 — **언제 알릴지와 함께.**
71
+ *
72
+ * `notifyAfter` 가 없으면 `0` 으로 답한다. 0 은 「상신 즉시」다 — 아직 아무 결재도 끝나지 않은 시점.
73
+ */
74
+ function referenceSteps(approvalLine) {
75
+ if (!approvalLine?.length)
76
+ return [];
77
+ return approvalLine
78
+ .map((item, index) => ({ item, order: index + 1, notifyAfter: Number(item.notifyAfter ?? 0) || 0 }))
79
+ .filter(x => isReferenceStep(x.item));
80
+ }
81
+ /** 이번에 알려야 할 참조들 — `afterOrder` 번째 결재가 막 끝났을 때. 상신 직후는 `0`. */
82
+ function referencesToNotify(approvalLine, afterOrder) {
83
+ return referenceSteps(approvalLine)
84
+ .filter(x => x.notifyAfter === afterOrder)
85
+ .map(({ item, order }) => ({ item, order }));
86
+ }
87
+ /** 첫 결재의 차례. */
88
+ exports.FIRST_ORDER = 1;
89
+ /**
90
+ * 이 차례를 승인하면 다음 결재자가 있나. 없으면 이것이 마지막이다.
91
+ *
92
+ * **참조는 건너뛴다.** 참조자가 아직 안 읽었어도 결재는 다음 결재자로 넘어간다 — 막게 만들면 그 사람이
93
+ * 휴가일 때 결재가 선다. 막아야 하는 자격은 합의이고, 그것은 아직 없다.
94
+ */
95
+ function nextApproval(approvalLine, order) {
96
+ if (!approvalLine?.length)
97
+ return undefined;
98
+ for (let next = order + 1; next <= approvalLine.length; next++) {
99
+ const item = lineItemFor(approvalLine, next);
100
+ if (isApprovalStep(item))
101
+ return { item: item, order: next };
102
+ }
103
+ return undefined;
104
+ }
105
+ /** 이 차례가 마지막인가. */
106
+ function isLastApproval(approvalLine, order) {
107
+ return !nextApproval(approvalLine, order);
108
+ }
109
+ /**
110
+ * 결재가 필요한가 — **결재자가 하나라도 있어야 한다.**
111
+ *
112
+ * 비어 있으면 결재 없이 끝난다. 그리고 **참조만 있는 결재선도 결재 없이 끝난다** — 참조는 막지 않으므로
113
+ * 기다릴 것이 없다. 참조가 있다고 결재를 기다리게 하면, 그 사람이 승인 권한도 없이 결재를 세운다.
114
+ */
115
+ function needsApproval(approvalLine) {
116
+ return firstApprovalOrder(approvalLine) !== undefined;
117
+ }
118
+ /**
119
+ * 이 판정이 「승인했다」인가.
120
+ *
121
+ * ── 옛 표기를 여기서 흡수한다 ──────────────────────────────────────────────
122
+ * 2026-08-31 전에는 다음 결재자가 있으면 승인을 `escalated` 로 적었다. **그 행은 고치지 않는다** —
123
+ * 승인 기록을 뒤에서 고쳐 쓰는 것은 규제 산업이 금지하는 바로 그 행위다.
124
+ *
125
+ * 대신 읽는 자리 하나에서 옛 표기를 새 뜻으로 읽는다. 이 규율이 두 곳으로 늘어나면 한쪽만 고쳐지는
126
+ * 날이 온다. 「승인했나」를 묻는 코드는 전부 이것을 부른다.
127
+ */
128
+ function isApproved(judgment) {
129
+ return judgment === 'approved' || judgment === 'escalated';
130
+ }
131
+ /**
132
+ * 더 기다릴 것이 없는 판정들 — **목록 질의와 이 파일이 같은 값을 본다.**
133
+ *
134
+ * 목록 질의가 이 배열을 그대로 쓴다. 두 벌로 두면 값이 늘 때 한쪽만 늘고, 그 판정을 받은 행이 대기
135
+ * 목록에 영원히 남는다 — 실제로 그렇게 됐다(아래).
136
+ */
137
+ exports.TERMINATED_JUDGMENTS = ['approved', 'escalated', 'delegated', 'rejected', 'aborted', 'closed'];
138
+ /**
139
+ * 이 판정이 끝난 것인가 — 더 기다릴 것이 없나.
140
+ *
141
+ * ── `aborted` 가 빠져 있었다 (2026-09-01) ─────────────────────────────────
142
+ * 스레드를 중단하면 그 스레드의 대기 결재가 `aborted` 로 닫힌다(§`activity-thread/_abort.ts`).
143
+ * 그런데 **목록 질의도 이 함수도 그 값을 「끝난 것」으로 세지 않았다.**
144
+ *
145
+ * 그래서 중단된 건의 결재가 그 사람의 **승인 대기 목록에 영원히 남았다.** 눌러 봐야 이미 끝난 건이고,
146
+ * 목록에서 사라지지도 않는다. 닫는 쪽은 제대로 닫았는데 읽는 쪽이 그것을 못 읽었다.
147
+ */
148
+ function isTerminated(judgment) {
149
+ return exports.TERMINATED_JUDGMENTS.includes(judgment ?? '');
150
+ }
151
+ //# sourceMappingURL=approval-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-line.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/approval-line.ts"],"names":[],"mappings":";;;AA6BA,wCAEC;AAGD,0CAEC;AAUD,kCAMC;AAOD,gDAKC;AAOD,wCAQC;AAGD,gDAOC;AAWD,oCAYC;AAGD,wCAEC;AAQD,sCAEC;AAYD,gCAEC;AAoBD,oCAEC;AA7JD;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,IAAkC;IAC/D,OAAO,CAAC,CAAC,IAAI,IAAK,IAAY,CAAC,aAAa,KAAK,WAAW,CAAA;AAC9D,CAAC;AAED,sCAAsC;AACtC,SAAgB,eAAe,CAAC,IAAkC;IAChE,OAAO,CAAC,CAAC,IAAI,IAAK,IAAY,CAAC,aAAa,KAAK,WAAW,CAAA;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,YAA4C,EAAE,KAAa;IACrF,IAAI,CAAC,YAAY,EAAE,MAAM,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,YAAY,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,YAA4C;IAC7E,IAAI,CAAC,YAAY,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAE3C,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;IACpD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAA;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAC5B,YAA4C;IAE5C,IAAI,CAAC,YAAY,EAAE,MAAM;QAAE,OAAO,EAAE,CAAA;IAEpC,OAAO,YAAY;SAChB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,WAAW,EAAE,MAAM,CAAE,IAAY,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAC5G,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AACzC,CAAC;AAED,+DAA+D;AAC/D,SAAgB,kBAAkB,CAChC,YAA4C,EAC5C,UAAkB;IAElB,OAAO,cAAc,CAAC,YAAY,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC;SACzC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAChD,CAAC;AAED,gBAAgB;AACH,QAAA,WAAW,GAAG,CAAC,CAAA;AAE5B;;;;;GAKG;AACH,SAAgB,YAAY,CAC1B,YAA4C,EAC5C,KAAa;IAEb,IAAI,CAAC,YAAY,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAE3C,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,CAAC,EAAE,IAAI,IAAI,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAG,WAAW,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;QAC5C,IAAI,cAAc,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,IAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAC/D,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,mBAAmB;AACnB,SAAgB,cAAc,CAAC,YAA4C,EAAE,KAAa;IACxF,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;AAC3C,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,YAA4C;IACxE,OAAO,kBAAkB,CAAC,YAAY,CAAC,KAAK,SAAS,CAAA;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,QAA4B;IACrD,OAAO,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,WAAW,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACU,QAAA,oBAAoB,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA;AAEpH;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAAC,QAA4B;IACvD,OAAQ,4BAA0C,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["/*\n * **타입만 가져온다.** 값으로 가져오면 `@things-factory/organization` 이 실려 오고, 그것이 auth-base 의\n * 라우터까지 끌고 온다. 순수 규칙 하나를 시험하려는데 서버가 통째로 뜬다.\n */\nimport type { ApprovalLineItem } from '@things-factory/organization'\n\n/**\n * 결재선에서 다음 결재자를 찾는다.\n *\n * ── 왜 따로 뺐나 ───────────────────────────────────────────────────────────\n * 같은 규칙이 두 파일에 나뉘어 있었다.\n *\n * submit.ts 첫 결재를 `order: 1` 로 만들고 `approvalLine[0]` 을 넣는다\n * approve.ts `order` 를 승인하면 `approvalLine[order]` 를 다음 결재자로 삼는다\n *\n * `order` 와 배열 자리가 1만큼 다르다. 지금은 맞게 짝이 맞는데, 한쪽만 고치면 결재자가 한 명 빠지거나\n * 같은 사람이 두 번 들어간다. 그리고 **오류가 안 난다** — 결재가 다 끝나고 나서야 안다.\n *\n * 그래서 한 자리에 적고 양쪽이 이것을 부른다.\n *\n * `order` 는 사람이 세는 수다 — 1차 결재, 2차 결재. 배열은 0부터이므로 그 차이를 여기서만 다룬다.\n */\n\n/**\n * 이 항목이 **결재**인가 — 비어 있으면 결재다.\n *\n * 지금 도는 결재선에는 이 칸이 없다(gangsters·dataset·quotation·accounting). 그 항목들이 전부 결재자로\n * 읽혀야 거동이 안 바뀐다. 그래서 「참조라고 적힌 것만 참조」다.\n */\nexport function isApprovalStep(item: ApprovalLineItem | undefined): boolean {\n return !!item && (item as any).participation !== 'reference'\n}\n\n/** 이 항목이 참조인가 — 의견만 내고 결재를 막지 않는다. */\nexport function isReferenceStep(item: ApprovalLineItem | undefined): boolean {\n return !!item && (item as any).participation === 'reference'\n}\n\n/**\n * 이 차례의 결재자는 결재선의 어느 줄인가. `order` 는 1부터.\n *\n * **참조 항목도 자리를 차지한다.** `order` 는 배열 자리 그대로다 — 다시 매기면 이미 쌓인 행의 뜻이\n * 바뀐다. 그래서 결재자의 차례가 1·3·4 처럼 띄엄띄엄해질 수 있고, 그것이 맞다.\n *\n * 이 함수는 그 자리에 무엇이 있든 돌려준다. 결재인지 아닌지는 부르는 쪽이 본다(§`nextApproval`).\n */\nexport function lineItemFor(approvalLine: ApprovalLineItem[] | undefined, order: number): ApprovalLineItem | undefined {\n if (!approvalLine?.length || order < 1) {\n return undefined\n }\n\n return approvalLine[order - 1]\n}\n\n/**\n * 첫 **결재자**는 몇 번째 자리인가 — 앞에 참조가 있으면 건너뛴다.\n *\n * 결재선이 참조로만 이루어져 있으면 `undefined` 다. 그때는 결재 없이 끝난다(§`needsApproval`).\n */\nexport function firstApprovalOrder(approvalLine: ApprovalLineItem[] | undefined): number | undefined {\n if (!approvalLine?.length) return undefined\n\n const index = approvalLine.findIndex(isApprovalStep)\n return index < 0 ? undefined : index + 1\n}\n\n/**\n * 이 결재선의 참조들 — **언제 알릴지와 함께.**\n *\n * `notifyAfter` 가 없으면 `0` 으로 답한다. 0 은 「상신 즉시」다 — 아직 아무 결재도 끝나지 않은 시점.\n */\nexport function referenceSteps(\n approvalLine: ApprovalLineItem[] | undefined\n): { item: ApprovalLineItem; order: number; notifyAfter: number }[] {\n if (!approvalLine?.length) return []\n\n return approvalLine\n .map((item, index) => ({ item, order: index + 1, notifyAfter: Number((item as any).notifyAfter ?? 0) || 0 }))\n .filter(x => isReferenceStep(x.item))\n}\n\n/** 이번에 알려야 할 참조들 — `afterOrder` 번째 결재가 막 끝났을 때. 상신 직후는 `0`. */\nexport function referencesToNotify(\n approvalLine: ApprovalLineItem[] | undefined,\n afterOrder: number\n): { item: ApprovalLineItem; order: number }[] {\n return referenceSteps(approvalLine)\n .filter(x => x.notifyAfter === afterOrder)\n .map(({ item, order }) => ({ item, order }))\n}\n\n/** 첫 결재의 차례. */\nexport const FIRST_ORDER = 1\n\n/**\n * 이 차례를 승인하면 다음 결재자가 있나. 없으면 이것이 마지막이다.\n *\n * **참조는 건너뛴다.** 참조자가 아직 안 읽었어도 결재는 다음 결재자로 넘어간다 — 막게 만들면 그 사람이\n * 휴가일 때 결재가 선다. 막아야 하는 자격은 합의이고, 그것은 아직 없다.\n */\nexport function nextApproval(\n approvalLine: ApprovalLineItem[] | undefined,\n order: number\n): { item: ApprovalLineItem; order: number } | undefined {\n if (!approvalLine?.length) return undefined\n\n for (let next = order + 1; next <= approvalLine.length; next++) {\n const item = lineItemFor(approvalLine, next)\n if (isApprovalStep(item)) return { item: item!, order: next }\n }\n\n return undefined\n}\n\n/** 이 차례가 마지막인가. */\nexport function isLastApproval(approvalLine: ApprovalLineItem[] | undefined, order: number): boolean {\n return !nextApproval(approvalLine, order)\n}\n\n/**\n * 결재가 필요한가 — **결재자가 하나라도 있어야 한다.**\n *\n * 비어 있으면 결재 없이 끝난다. 그리고 **참조만 있는 결재선도 결재 없이 끝난다** — 참조는 막지 않으므로\n * 기다릴 것이 없다. 참조가 있다고 결재를 기다리게 하면, 그 사람이 승인 권한도 없이 결재를 세운다.\n */\nexport function needsApproval(approvalLine: ApprovalLineItem[] | undefined): boolean {\n return firstApprovalOrder(approvalLine) !== undefined\n}\n\n/**\n * 이 판정이 「승인했다」인가.\n *\n * ── 옛 표기를 여기서 흡수한다 ──────────────────────────────────────────────\n * 2026-08-31 전에는 다음 결재자가 있으면 승인을 `escalated` 로 적었다. **그 행은 고치지 않는다** —\n * 승인 기록을 뒤에서 고쳐 쓰는 것은 규제 산업이 금지하는 바로 그 행위다.\n *\n * 대신 읽는 자리 하나에서 옛 표기를 새 뜻으로 읽는다. 이 규율이 두 곳으로 늘어나면 한쪽만 고쳐지는\n * 날이 온다. 「승인했나」를 묻는 코드는 전부 이것을 부른다.\n */\nexport function isApproved(judgment: string | undefined): boolean {\n return judgment === 'approved' || judgment === 'escalated'\n}\n\n/**\n * 더 기다릴 것이 없는 판정들 — **목록 질의와 이 파일이 같은 값을 본다.**\n *\n * 목록 질의가 이 배열을 그대로 쓴다. 두 벌로 두면 값이 늘 때 한쪽만 늘고, 그 판정을 받은 행이 대기\n * 목록에 영원히 남는다 — 실제로 그렇게 됐다(아래).\n */\nexport const TERMINATED_JUDGMENTS = ['approved', 'escalated', 'delegated', 'rejected', 'aborted', 'closed'] as const\n\n/**\n * 이 판정이 끝난 것인가 — 더 기다릴 것이 없나.\n *\n * ── `aborted` 가 빠져 있었다 (2026-09-01) ─────────────────────────────────\n * 스레드를 중단하면 그 스레드의 대기 결재가 `aborted` 로 닫힌다(§`activity-thread/_abort.ts`).\n * 그런데 **목록 질의도 이 함수도 그 값을 「끝난 것」으로 세지 않았다.**\n *\n * 그래서 중단된 건의 결재가 그 사람의 **승인 대기 목록에 영원히 남았다.** 눌러 봐야 이미 끝난 건이고,\n * 목록에서 사라지지도 않는다. 닫는 쪽은 제대로 닫았는데 읽는 쪽이 그것을 못 읽었다.\n */\nexport function isTerminated(judgment: string | undefined): boolean {\n return (TERMINATED_JUDGMENTS as readonly string[]).includes(judgment ?? '')\n}\n"]}
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.approve = approve;
4
- const organization_1 = require("@things-factory/organization");
5
- const auth_base_1 = require("@things-factory/auth-base");
6
- const common_js_1 = require("../../controllers/common.js");
4
+ const opinion_js_1 = require("./opinion.js");
7
5
  const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
8
6
  const activity_approval_js_1 = require("../../service/activity-approval/activity-approval.js");
9
- const common_js_2 = require("../common.js");
7
+ const common_js_1 = require("../common.js");
10
8
  const check_authority_js_1 = require("./check-authority.js");
9
+ const approval_line_js_1 = require("./approval-line.js");
10
+ const create_approval_js_1 = require("./create-approval.js");
11
+ const claim_js_1 = require("./claim.js");
11
12
  async function approve({ id, comment }, context) {
12
13
  const { domain, user, tx } = context.state;
13
14
  var repository = tx.getRepository(activity_approval_js_1.ActivityApproval);
@@ -34,63 +35,72 @@ async function approve({ id, comment }, context) {
34
35
  where: { domain: { id: domain.id }, id: activityApproval.activityThreadId },
35
36
  relations: ['activityInstance']
36
37
  });
38
+ /*
39
+ * 여기까지 오면 스레드가 있어야 한다. 없는 채로 아래를 읽으면 `TypeError` 가 나고, 그 오류로는
40
+ * 무엇이 없는지 알 수 없다.
41
+ */
42
+ if (!activityThread) {
43
+ throw new Error(context.t('error.activity-thread not found', { activityThread: activityApproval.activityThreadId }));
44
+ }
45
+ if (!activityThread.activityInstance) {
46
+ throw new Error(context.t('error.activity-instance not found', { activityThread: activityThread.id }));
47
+ }
37
48
  const approvalLine = activityThread.activityInstance.approvalLine;
38
49
  const order = activityApproval.order;
50
+ /* 다음 결재자가 누구인지는 한 자리가 정한다 — `approval-line.ts`. */
51
+ const next = (0, approval_line_js_1.nextApproval)(approvalLine, order);
52
+ /*
53
+ * **여기서 잡는다.** 위의 검사는 읽은 시점의 것이라 그 사이에 남이 처리할 수 있다. 잡기가 첫 쓰기여야
54
+ * 진 쪽이 다음 결재자를 만들기 전에 물러난다.
55
+ */
56
+ if (!(await (0, claim_js_1.claimPendingApproval)(tx, activityApproval, activity_approval_js_1.ActivityApprovalJudgment.Approved))) {
57
+ /* 「이미 끝났다」보다 **그 사람이 할 일**을 말한다 — 목록을 새로 읽으면 지금 상태가 보인다. */
58
+ throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: id }));
59
+ }
60
+ /*
61
+ * **승인한 사람은 승인한 것이다** (2026-08-31, 아키텍트 확인).
62
+ *
63
+ * 앞서는 다음 결재자가 있으면 `Escalated` 로 적었다. 그러면 「누가 승인했나」를 물을 때 중간 결재자가
64
+ * 전부 「상신함」으로 나오고, **승인한 것과 그냥 넘긴 것이 구별되지 않는다.** 규제 산업에서는 결재
65
+ * 이력이 곧 근거라 이것이 결함이다.
66
+ *
67
+ * 다음으로 넘어간 사실은 `transaction` 이 그대로 들고 있으므로 잃는 것이 없다.
68
+ */
39
69
  const result = await tx.getRepository(activity_approval_js_1.ActivityApproval).save({
40
70
  ...activityApproval,
41
- judgment: approvalLine[order] ? activity_approval_js_1.ActivityApprovalJudgment.Escalated : activity_approval_js_1.ActivityApprovalJudgment.Approved,
42
- comment,
43
- transaction: approvalLine[order] ? 'escalate' : 'approve',
71
+ judgment: activity_approval_js_1.ActivityApprovalJudgment.Approved,
72
+ ...(0, opinion_js_1.stampedComment)(comment),
73
+ transaction: next ? 'escalate' : 'approve',
74
+ /*
75
+ * **누가 처리했는지 남긴다.**
76
+ *
77
+ * 역할로 걸린 결재는 `approver` 가 비어 있고 `approverRole` 만 있다. 처리해도 그 자리가 비어 있으면
78
+ * 두 가지가 함께 무너진다 — 그 역할을 가진 사람들의 **대기 목록에 영원히 남고**(`approver IS NULL`
79
+ * 로 걸리므로), 정작 처리한 사람의 **완료 목록에는 안 나온다**(`approver = 나` 로 거르므로).
80
+ *
81
+ * 그리고 결재 이력에서 「누가 승인했나」는 역할이 아니라 사람이어야 한다.
82
+ */
83
+ approver: activityApproval.approver ?? user,
44
84
  updater: user,
45
85
  terminatedAt: new Date(),
46
86
  terminator: user
47
87
  });
48
- if (approvalLine && approvalLine.length > order) {
49
- // approvalLine에서 후속 승인자에게 escalating.
50
- if (approvalLine[order].type == organization_1.OrgMemberTargetType.Role) {
51
- const approverRoleId = approvalLine[order].value || approvalLine[order].approver?.id;
52
- if (!approverRoleId) {
53
- throw 'Cannot specify the approver role for this approval line';
54
- }
55
- const approverRole = await tx.getRepository(auth_base_1.Role).findOneBy({
56
- id: approverRoleId
57
- });
58
- if (!approverRole) {
59
- throw 'Cannot find the approver role for this approval line';
60
- }
61
- await tx.getRepository(activity_approval_js_1.ActivityApproval).save({
62
- domain,
63
- activityThread,
64
- round: activityThread.round,
65
- order: order + 1,
66
- judgment: activity_approval_js_1.ActivityApprovalJudgment.Pending,
67
- transaction: 'escalate',
68
- approverRole: approverRole,
69
- creator: user,
70
- updater: user
71
- });
72
- }
73
- else {
74
- var approverUser = await (0, common_js_1.getSystemUserFromOrgMemberItem)(approvalLine[order], context);
75
- if (!approverUser) {
76
- throw 'Cannot specify the approval user for this approval line';
77
- }
78
- await tx.getRepository(activity_approval_js_1.ActivityApproval).save({
79
- domain,
80
- activityThread,
81
- round: activityThread.round,
82
- order: order + 1,
83
- judgment: activity_approval_js_1.ActivityApprovalJudgment.Pending,
84
- transaction: 'escalate',
85
- approver: approverUser,
86
- creator: user,
87
- updater: user
88
- });
89
- }
88
+ /*
89
+ * **이번 결재가 끝나서 알릴 참조들.** `notifyAfter` 가 이 차례를 가리키는 참조가 여기 온다.
90
+ *
91
+ * 다음 결재자를 세우는 것과 **무관하게** 만든다 — 참조는 막지 않으므로, 이것이 실패해도 결재는 간다.
92
+ */
93
+ const refs = await (0, create_approval_js_1.createReferences)({ references: (0, approval_line_js_1.referencesToNotify)(approvalLine, order), activityThread }, context);
94
+ for (const f of refs.failed) {
95
+ console.warn(`[worklist] 참조자를 세우지 못했다 (결재선 ${f.order}번째) — ${f.reason}`);
96
+ }
97
+ if (next) {
98
+ /* 다음 결재자에게 넘긴다 — 행을 만드는 자리는 한 곳이다(§`create-approval.ts`). */
99
+ await (0, create_approval_js_1.createApproval)({ item: next.item, order: next.order, participation: 'approval', activityThread, transaction: 'escalate' }, context);
90
100
  activityThread = await tx.getRepository(activity_thread_js_1.ActivityThread).save({
91
101
  ...activityThread,
92
102
  transaction: 'escalate',
93
- reaseon: comment,
103
+ reason: comment,
94
104
  state: activity_thread_js_1.ActivityThreadStatus.Escalated,
95
105
  updater: user
96
106
  });
@@ -106,7 +116,7 @@ async function approve({ id, comment }, context) {
106
116
  updater: user
107
117
  });
108
118
  }
109
- await (0, common_js_2.updateActivityInstanceState)(activityThread.activityInstanceId, { causedBy: [activityThread] }, context);
119
+ await (0, common_js_1.updateActivityInstanceState)(activityThread.activityInstanceId, { causedBy: [activityThread] }, context);
110
120
  return result;
111
121
  }
112
122
  //# sourceMappingURL=approve.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"approve.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/approve.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApprovalLineItem, OrgMemberTargetType } from '@things-factory/organization'\nimport { Role } from '@things-factory/auth-base'\nimport { getSystemUserFromOrgMemberItem } from '../../controllers/common.js'\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../../service/activity-approval/activity-approval.js'\nimport { updateActivityInstanceState } from '../common.js'\nimport { checkAuthority } from './check-authority.js'\n\nexport async function approve(\n { id, comment }: { id: string; comment: string },\n context: ResolverContext\n): Promise<ActivityApproval> {\n const { domain, user, tx } = context.state\n\n var repository = tx.getRepository(ActivityApproval)\n\n var activityApproval = await repository.findOne({\n where: { domain: { id: domain.id }, id }\n })\n\n if (!activityApproval) {\n throw new Error(\n context.t('error.activity-approval not found', {\n activityApproval: id\n })\n )\n }\n\n await checkAuthority(activityApproval, context)\n\n /* \n Prerequisite of Approval\n - Previous judgment should be 'pending'\n */\n if (activityApproval.judgment !== ActivityApprovalJudgment.Pending) {\n throw new Error(\n context.t(`error.activity-approval is already terminated`, {\n activityApproval: id,\n actual: activityApproval.judgment\n })\n )\n }\n\n var activityThread = await tx.getRepository(ActivityThread).findOne({\n where: { domain: { id: domain.id }, id: activityApproval.activityThreadId },\n relations: ['activityInstance']\n })\n\n const approvalLine: ApprovalLineItem[] = activityThread.activityInstance.approvalLine\n const order = activityApproval.order\n\n const result = await tx.getRepository(ActivityApproval).save({\n ...activityApproval,\n judgment: approvalLine[order] ? ActivityApprovalJudgment.Escalated : ActivityApprovalJudgment.Approved,\n comment,\n transaction: approvalLine[order] ? 'escalate' : 'approve',\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n if (approvalLine && approvalLine.length > order) {\n // approvalLine에서 후속 승인자에게 escalating.\n\n if (approvalLine[order].type == OrgMemberTargetType.Role) {\n const approverRoleId = approvalLine[order].value || approvalLine[order].approver?.id\n\n if (!approverRoleId) {\n throw 'Cannot specify the approver role for this approval line'\n }\n\n const approverRole = await tx.getRepository(Role).findOneBy({\n id: approverRoleId\n })\n\n if (!approverRole) {\n throw 'Cannot find the approver role for this approval line'\n }\n\n await tx.getRepository(ActivityApproval).save({\n domain,\n activityThread,\n round: activityThread.round,\n order: order + 1,\n judgment: ActivityApprovalJudgment.Pending,\n transaction: 'escalate',\n approverRole: approverRole,\n creator: user,\n updater: user\n })\n } else {\n var approverUser = await getSystemUserFromOrgMemberItem(approvalLine[order], context)\n\n if (!approverUser) {\n throw 'Cannot specify the approval user for this approval line'\n }\n\n await tx.getRepository(ActivityApproval).save({\n domain,\n activityThread,\n round: activityThread.round,\n order: order + 1,\n judgment: ActivityApprovalJudgment.Pending,\n transaction: 'escalate',\n approver: approverUser,\n creator: user,\n updater: user\n })\n }\n\n activityThread = await tx.getRepository(ActivityThread).save({\n ...activityThread,\n transaction: 'escalate',\n reaseon: comment,\n state: ActivityThreadStatus.Escalated,\n updater: user\n })\n } else {\n activityThread = await tx.getRepository(ActivityThread).save({\n ...activityThread,\n transaction: 'approve',\n reason: comment,\n state: ActivityThreadStatus.Ended,\n terminatedAt: new Date(),\n terminator: user,\n updater: user\n })\n }\n\n await updateActivityInstanceState(activityThread.activityInstanceId, { causedBy: [activityThread] }, context)\n\n return result\n}\n"]}
1
+ {"version":3,"file":"approve.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/approve.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApprovalLineItem } from '@things-factory/organization'\nimport { stampedComment } from './opinion.js'\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../../service/activity-approval/activity-approval.js'\nimport { updateActivityInstanceState } from '../common.js'\nimport { checkAuthority } from './check-authority.js'\nimport { nextApproval, referencesToNotify } from './approval-line.js'\nimport { createApproval, createReferences } from './create-approval.js'\nimport { claimPendingApproval } from './claim.js'\n\nexport async function approve(\n { id, comment }: { id: string; comment: string },\n context: ResolverContext\n): Promise<ActivityApproval> {\n const { domain, user, tx } = context.state\n\n var repository = tx.getRepository(ActivityApproval)\n\n var activityApproval = await repository.findOne({\n where: { domain: { id: domain.id }, id }\n })\n\n if (!activityApproval) {\n throw new Error(\n context.t('error.activity-approval not found', {\n activityApproval: id\n })\n )\n }\n\n await checkAuthority(activityApproval, context)\n\n /* \n Prerequisite of Approval\n - Previous judgment should be 'pending'\n */\n if (activityApproval.judgment !== ActivityApprovalJudgment.Pending) {\n throw new Error(\n context.t(`error.activity-approval is already terminated`, {\n activityApproval: id,\n actual: activityApproval.judgment\n })\n )\n }\n\n var activityThread = await tx.getRepository(ActivityThread).findOne({\n where: { domain: { id: domain.id }, id: activityApproval.activityThreadId },\n relations: ['activityInstance']\n })\n\n /*\n * 여기까지 오면 스레드가 있어야 한다. 없는 채로 아래를 읽으면 `TypeError` 가 나고, 그 오류로는\n * 무엇이 없는지 알 수 없다.\n */\n if (!activityThread) {\n throw new Error(context.t('error.activity-thread not found', { activityThread: activityApproval.activityThreadId }))\n }\n\n if (!activityThread.activityInstance) {\n throw new Error(context.t('error.activity-instance not found', { activityThread: activityThread.id }))\n }\n\n const approvalLine: ApprovalLineItem[] = activityThread.activityInstance.approvalLine\n const order = activityApproval.order\n\n /* 다음 결재자가 누구인지는 한 자리가 정한다 — `approval-line.ts`. */\n const next = nextApproval(approvalLine, order)\n\n /*\n * **여기서 잡는다.** 위의 검사는 읽은 시점의 것이라 그 사이에 남이 처리할 수 있다. 잡기가 첫 쓰기여야\n * 진 쪽이 다음 결재자를 만들기 전에 물러난다.\n */\n if (!(await claimPendingApproval(tx, activityApproval, ActivityApprovalJudgment.Approved))) {\n /* 「이미 끝났다」보다 **그 사람이 일**을 말한다 목록을 새로 읽으면 지금 상태가 보인다. */\n throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: id }))\n }\n\n /*\n * **승인한 사람은 승인한 것이다** (2026-08-31, 아키텍트 확인).\n *\n * 앞서는 다음 결재자가 있으면 `Escalated` 로 적었다. 그러면 「누가 승인했나」를 물을 때 중간 결재자가\n * 전부 「상신함」으로 나오고, **승인한 것과 그냥 넘긴 것이 구별되지 않는다.** 규제 산업에서는 결재\n * 이력이 곧 근거라 이것이 결함이다.\n *\n * 다음으로 넘어간 사실은 `transaction` 이 그대로 들고 있으므로 잃는 것이 없다.\n */\n const result = await tx.getRepository(ActivityApproval).save({\n ...activityApproval,\n judgment: ActivityApprovalJudgment.Approved,\n ...stampedComment(comment),\n transaction: next ? 'escalate' : 'approve',\n /*\n * **누가 처리했는지 남긴다.**\n *\n * 역할로 걸린 결재는 `approver` 가 비어 있고 `approverRole` 만 있다. 처리해도 그 자리가 비어 있으면\n * 두 가지가 함께 무너진다 — 그 역할을 가진 사람들의 **대기 목록에 영원히 남고**(`approver IS NULL`\n * 로 걸리므로), 정작 처리한 사람의 **완료 목록에는 안 나온다**(`approver = 나` 로 거르므로).\n *\n * 그리고 결재 이력에서 「누가 승인했나」는 역할이 아니라 사람이어야 한다.\n */\n approver: activityApproval.approver ?? user,\n updater: user,\n terminatedAt: new Date(),\n terminator: user\n })\n\n /*\n * **이번 결재가 끝나서 알릴 참조들.** `notifyAfter` 가 이 차례를 가리키는 참조가 여기 온다.\n *\n * 다음 결재자를 세우는 것과 **무관하게** 만든다 — 참조는 막지 않으므로, 이것이 실패해도 결재는 간다.\n */\n const refs = await createReferences(\n { references: referencesToNotify(approvalLine, order), activityThread },\n context\n )\n for (const f of refs.failed) {\n console.warn(`[worklist] 참조자를 세우지 못했다 (결재선 ${f.order}번째) ${f.reason}`)\n }\n\n if (next) {\n /* 다음 결재자에게 넘긴다 — 행을 만드는 자리는 한 곳이다(§`create-approval.ts`). */\n await createApproval(\n { item: next.item, order: next.order, participation: 'approval', activityThread, transaction: 'escalate' },\n context\n )\n\n activityThread = await tx.getRepository(ActivityThread).save({\n ...activityThread,\n transaction: 'escalate',\n reason: comment,\n state: ActivityThreadStatus.Escalated,\n updater: user\n })\n } else {\n activityThread = await tx.getRepository(ActivityThread).save({\n ...activityThread,\n transaction: 'approve',\n reason: comment,\n state: ActivityThreadStatus.Ended,\n terminatedAt: new Date(),\n terminator: user,\n updater: user\n })\n }\n\n await updateActivityInstanceState(activityThread.activityInstanceId, { causedBy: [activityThread] }, context)\n\n return result\n}\n"]}