@things-factory/worklist 10.1.184 → 10.1.186

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 (46) hide show
  1. package/dist-client/my-work-menus.d.ts +0 -5
  2. package/dist-client/my-work-menus.js +1 -2
  3. package/dist-client/my-work-menus.js.map +1 -1
  4. package/dist-client/pages/activity-catalog/activity-catalog-page.d.ts +18 -74
  5. package/dist-client/pages/activity-catalog/activity-catalog-page.js +292 -527
  6. package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -1
  7. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -1
  8. package/dist-client/pages/work-door/door-links.d.ts +1 -9
  9. package/dist-client/pages/work-door/door-links.js +1 -11
  10. package/dist-client/pages/work-door/door-links.js.map +1 -1
  11. package/dist-client/pages/work-door/door-sentence.d.ts +26 -14
  12. package/dist-client/pages/work-door/door-sentence.js +58 -60
  13. package/dist-client/pages/work-door/door-sentence.js.map +1 -1
  14. package/dist-client/route.d.ts +1 -1
  15. package/dist-client/route.js +0 -3
  16. package/dist-client/route.js.map +1 -1
  17. package/dist-client/tsconfig.tsbuildinfo +1 -1
  18. package/dist-server/service/activity/activity-query.js +2 -31
  19. package/dist-server/service/activity/activity-query.js.map +1 -1
  20. package/dist-server/service/activity/approval-line-seats.d.ts +12 -0
  21. package/dist-server/service/activity/approval-line-seats.js +68 -0
  22. package/dist-server/service/activity/approval-line-seats.js.map +1 -0
  23. package/dist-server/service/activity/index.d.ts +1 -1
  24. package/dist-server/service/activity-instance/activity-instance-query.js +2 -54
  25. package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
  26. package/dist-server/service/activity-instance/index.d.ts +1 -1
  27. package/dist-server/service/index.d.ts +3 -3
  28. package/dist-server/service/work-door/work-door-resolver.js +7 -2
  29. package/dist-server/service/work-door/work-door-resolver.js.map +1 -1
  30. package/dist-server/service/work-door/work-door-service.d.ts +8 -1
  31. package/dist-server/service/work-door/work-door-service.js +11 -2
  32. package/dist-server/service/work-door/work-door-service.js.map +1 -1
  33. package/dist-server/service/work-door/work-door-type.d.ts +2 -0
  34. package/dist-server/service/work-door/work-door-type.js +8 -0
  35. package/dist-server/service/work-door/work-door-type.js.map +1 -1
  36. package/dist-server/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +3 -3
  38. package/tests/work-door-door-words.test.ts +1 -1
  39. package/things-factory.config.js +1 -6
  40. package/translations/en.json +11 -45
  41. package/translations/es.json +11 -45
  42. package/translations/ja.json +11 -45
  43. package/translations/ko.json +11 -45
  44. package/translations/ms.json +11 -45
  45. package/translations/zh.json +11 -45
  46. package/tests/work-door-sentence.test.ts +0 -199
@@ -1,199 +0,0 @@
1
- import { strict as assert } from 'node:assert'
2
- import { readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
4
-
5
- import {
6
- DoorFacts,
7
- doorLabel,
8
- doorSentence,
9
- doorsSummary,
10
- whenNoLineTag
11
- } from '../client/pages/work-door/door-sentence.js'
12
- import { DOOR_REASON_WORD, WHEN_NO_LINE_BECAUSE_WORD, WHEN_NO_LINE_WORD } from '../client/pages/work-door/door-words.js'
13
-
14
- /*
15
- * ═══════════════════════════════════════════════════════════════════════════
16
- * The work-doors screen writes its own sentences from the facts the server sends. These read them
17
- * through the real translation files, so a missing key or a sentence that says the wrong
18
- * consequence shows here and not on a customer's screen.
19
- *
20
- * The consequence of an empty line is the door's declaration. On :4000 (2026-09-24) four doors that
21
- * refuse an empty line - spec, recipe, material definition and work shift change approvals - read
22
- * "the raiser approves their own the moment they raise it". That was the `run` sentence on every door.
23
- * ═══════════════════════════════════════════════════════════════════════════
24
- */
25
-
26
- const LANGS = ['ko', 'en', 'ja', 'zh', 'ms'] as const
27
-
28
- const said = Object.fromEntries(
29
- LANGS.map(lang => [
30
- lang,
31
- JSON.parse(readFileSync(join(__dirname, '..', 'translations', `${lang}.json`), 'utf-8')) as Record<string, string>
32
- ])
33
- ) as Record<(typeof LANGS)[number], Record<string, string>>
34
-
35
- /** A small `t` over one language: key lookup, `{name}` interpolation, `defaultValue`. Missing keys print themselves, as i18next does. */
36
- function tIn(lang: (typeof LANGS)[number], extra: Record<string, string> = {}) {
37
- const table = { ...said[lang], ...extra }
38
-
39
- return (key: string, options: Record<string, any> = {}) => {
40
- const found = table[key] ?? options.defaultValue ?? key
41
-
42
- return String(found).replace(/\{(\w+)\}/g, (_, name) => (name in options ? String(options[name]) : `{${name}}`))
43
- }
44
- }
45
-
46
- const ko = tIn('ko', {
47
- 'activity.material-definition-change-approval.label': '자재 정의 변경 승인',
48
- 'activity.work-order-priority-change.label': '작업지시 우선순위 변경',
49
- 'activity.station-screen-release.label': '화면 발행 승인'
50
- })
51
-
52
- const door = (over: Partial<DoorFacts>): DoorFacts => ({
53
- name: 'MaterialDefinitionChangeApproval',
54
- labelKey: 'activity.material-definition-change-approval.label',
55
- kind: 'approval',
56
- standing: 'off',
57
- reason: 'line-empty',
58
- approvers: 0,
59
- ...over
60
- })
61
-
62
- describe('work door sentences', () => {
63
- it('tells a refusing door it cannot be raised, and why - never that the raiser approves their own', () => {
64
- const sentence = doorSentence(ko, door({ whenNoLine: 'refuse', whenNoLineBecause: 'approval-is-the-act' }))
65
-
66
- assert.ok(sentence.includes('「자재 정의 변경 승인」 에 결재자가 없습니다'), sentence)
67
- assert.ok(sentence.includes('상신할 수 없습니다'), sentence)
68
- assert.ok(sentence.includes('승인 자체가 이 업무의 일'), sentence)
69
- assert.ok(!sentence.includes('상신자의 승인'), sentence)
70
- })
71
-
72
- it('keeps the self-approval sentence for a door that runs on an empty line', () => {
73
- const sentence = doorSentence(
74
- ko,
75
- door({ name: 'WorkOrderPriorityChange', labelKey: 'activity.work-order-priority-change.label', whenNoLine: 'run' })
76
- )
77
-
78
- assert.equal(
79
- sentence,
80
- '「작업지시 우선순위 변경」에 결재자가 지정되지 않았습니다. 결재 단계를 거치지 않으며, 상신 즉시 상신자의 승인으로 처리됩니다.'
81
- )
82
- })
83
-
84
- it('says a door that follows its history depends on how the record stood', () => {
85
- const sentence = doorSentence(ko, door({ whenNoLine: 'as-established', whenNoLineBecause: 'approval-is-the-act' }))
86
-
87
- assert.ok(sentence.includes('결재를 통해 생성된 건은'), sentence)
88
- assert.ok(!sentence.includes('상신자의 승인'), sentence)
89
- })
90
-
91
- /** A door with no declaration says only what is known - it does not guess a consequence. */
92
- it('names no consequence for an undeclared door', () => {
93
- assert.equal(doorSentence(ko, door({})), '「자재 정의 변경 승인」 에 결재자가 없습니다.')
94
- })
95
-
96
- it('says where the line is set when a door follows another door', () => {
97
- const sentence = doorSentence(ko, door({ followsLabelKey: 'activity.station-screen-release.label', whenNoLine: 'run' }))
98
-
99
- assert.ok(sentence.includes('「화면 발행 승인」의 결재선을 사용합니다'), sentence)
100
- })
101
-
102
- it('shows the empty-line declaration on a filled row, with the reason for a refusing door', () => {
103
- const tag = whenNoLineTag(
104
- ko,
105
- door({ standing: 'on', reason: 'line-filled', approvers: 1, whenNoLine: 'refuse', whenNoLineBecause: 'irreversible' })
106
- )
107
-
108
- assert.equal(tag, '결재선이 비면 상신할 수 없습니다 (되돌릴 수 없는 일)')
109
- /* An empty row's sentence already says it; saying it twice is noise. */
110
- assert.equal(whenNoLineTag(ko, door({ whenNoLine: 'refuse', whenNoLineBecause: 'irreversible' })), '')
111
- assert.equal(whenNoLineTag(ko, door({ kind: 'assignment', standing: 'on', reason: 'assignee-bound' })), '')
112
- })
113
-
114
- it('keeps the other rows in their Korean wording', () => {
115
- assert.ok(doorSentence(ko, door({ reason: 'role-empty' })).includes('역할을 보유한 사용자가 없습니다'))
116
- assert.ok(doorSentence(ko, door({ reason: 'person-absent' })).includes('지정 결재자가 이 사업장에 등록되어 있지 않습니다'))
117
- assert.ok(doorSentence(ko, door({ kind: 'assignment', reason: 'no-assignee' })).includes('아무에게도 가지 않습니다'))
118
- assert.ok(
119
- doorSentence(ko, door({ kind: 'assignment', standing: 'on', reason: 'assignee-bound', assigneeRole: 'MAINTENANCE-LEAD' })).includes(
120
- 'MAINTENANCE-LEAD'
121
- )
122
- )
123
- assert.ok(doorSentence(ko, door({ standing: 'on', reason: 'line-filled', approvers: 2 })).includes('결재자 2명'))
124
- assert.ok(doorSentence(ko, door({ standing: 'missing', reason: 'missing' })).includes('등록 실패'))
125
- assert.ok(
126
- doorSentence(ko, door({ standing: 'not-used', reason: 'not-used', notUsedBecause: '폐기는 본사가 처리합니다' })).includes(
127
- '폐기는 본사가 처리합니다'
128
- )
129
- )
130
- })
131
-
132
- /** A label with no translation shows the form name, which a person can search for - not the key. */
133
- it('falls back to the form name, never the key', () => {
134
- assert.equal(doorLabel(tIn('ko'), { name: 'SomeDoor', labelKey: 'activity.some-door.label' }), 'SomeDoor')
135
- })
136
-
137
- /*
138
- * Every sentence this screen can build, in every language shipped. A missing one prints its key on
139
- * the screen and nothing throws.
140
- */
141
- it('has every sentence it can build, in every language', () => {
142
- const keys = [
143
- ...Object.values(DOOR_REASON_WORD)
144
- .filter(reason => reason !== 'line-empty')
145
- .map(reason => `text.door-because-${reason}`),
146
- 'text.door-because-line-empty',
147
- ...Object.values(WHEN_NO_LINE_WORD).map(when => `text.door-because-line-empty-${when}`),
148
- ...Object.values(WHEN_NO_LINE_WORD).map(when => `text.door-when-no-line-${when}`),
149
- ...Object.values(WHEN_NO_LINE_BECAUSE_WORD).map(why => `text.door-refuses-because-${why}`),
150
- 'text.door-because-follows',
151
- ...['none', 'all-on', 'done-not-used', 'todo', 'off', 'missing', 'not-used'].map(
152
- one => `text.work-doors-summary-${one}`
153
- )
154
- ]
155
- const missing = LANGS.flatMap(lang => keys.filter(key => !said[lang][key]).map(key => `${lang} · ${key}`))
156
-
157
- assert.deepEqual(missing, [], `\n없는 문구는 화면에 키 글자로 뜹니다:\n${missing.join('\n')}`)
158
- })
159
-
160
- it('reads English to an English reader', () => {
161
- const sentence = doorSentence(tIn('en'), door({ whenNoLine: 'refuse', whenNoLineBecause: 'irreversible' }))
162
-
163
- assert.ok(sentence.includes('cannot be raised until its approval line is filled'), sentence)
164
- assert.ok(!/[가-힣]/.test(sentence), sentence)
165
- })
166
- })
167
-
168
- describe('work door summary', () => {
169
- const counts = (over: object) => ({ on: 0, off: 0, missing: 0, notUsed: 0, todo: 0, ...over })
170
-
171
- it('says everything stands when it does', () => {
172
- const summary = doorsSummary(ko, counts({ on: 2 }))
173
-
174
- assert.equal(summary, '업무 문 2개가 다 서 있습니다.')
175
- })
176
-
177
- it('reads as done when the only unfilled ones were opted out', () => {
178
- const summary = doorsSummary(ko, counts({ on: 1, notUsed: 1 }))
179
-
180
- assert.ok(summary.includes('채울 것이 남지 않았습니다'))
181
- assert.ok(summary.includes('1개는 이 사업장이 쓰지 않기로'))
182
- })
183
-
184
- it('leads with how many are left to fill', () => {
185
- const summary = doorsSummary(ko, counts({ on: 1, off: 2, notUsed: 1, todo: 2 }))
186
-
187
- assert.ok(summary.startsWith('채울 것이 2개 남았습니다.'))
188
- assert.ok(summary.includes('1개는 이 사업장이 쓰지 않기로 한 것이라 세지 않았습니다'))
189
- })
190
-
191
- it('names a door that never installed as a defect', () => {
192
- assert.ok(doorsSummary(ko, counts({ on: 1, missing: 1, todo: 1 })).includes('시스템 오류를 확인해야 합니다'))
193
- })
194
-
195
- /** Zero doors is a different fact from "none of them stand" and must not be reported as it. */
196
- it('does not report an empty product as a plant with nothing standing', () => {
197
- assert.equal(doorsSummary(ko, counts({})), '이 제품이 세우는 업무 양식이 없습니다.')
198
- })
199
- })