@things-factory/worklist 10.1.249 → 10.1.253

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 (51) hide show
  1. package/dist-client/approval-progress.d.ts +11 -2
  2. package/dist-client/approval-progress.js +19 -8
  3. package/dist-client/approval-progress.js.map +1 -1
  4. package/dist-client/pages/activity-approval/activity-approval-page.js +1 -0
  5. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  6. package/dist-client/pages/activity-instance/activity-instance-view.js +1 -0
  7. package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
  8. package/dist-client/pages/activity-thread/activity-thread-page.js +1 -0
  9. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  10. package/dist-client/pages/activity-thread/activity-thread-view-page.js +1 -0
  11. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  12. package/dist-client/pages/approval-progress/approval-progress-page.d.ts +46 -0
  13. package/dist-client/pages/approval-progress/approval-progress-page.js +294 -0
  14. package/dist-client/pages/approval-progress/approval-progress-page.js.map +1 -0
  15. package/dist-client/route.d.ts +1 -1
  16. package/dist-client/route.js +3 -0
  17. package/dist-client/route.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/controllers/activity-approval/approval-line.d.ts +2 -2
  20. package/dist-server/controllers/activity-approval/approval-line.js +5 -6
  21. package/dist-server/controllers/activity-approval/approval-line.js.map +1 -1
  22. package/dist-server/service/approval-progress/approval-progress-query.d.ts +14 -0
  23. package/dist-server/service/approval-progress/approval-progress-query.js +142 -0
  24. package/dist-server/service/approval-progress/approval-progress-query.js.map +1 -0
  25. package/dist-server/service/approval-progress/approval-progress-rule.d.ts +67 -0
  26. package/dist-server/service/approval-progress/approval-progress-rule.js +102 -0
  27. package/dist-server/service/approval-progress/approval-progress-rule.js.map +1 -0
  28. package/dist-server/service/approval-progress/approval-progress-type.d.ts +44 -0
  29. package/dist-server/service/approval-progress/approval-progress-type.js +172 -0
  30. package/dist-server/service/approval-progress/approval-progress-type.js.map +1 -0
  31. package/dist-server/service/approval-progress/index.d.ts +2 -0
  32. package/dist-server/service/approval-progress/index.js +6 -0
  33. package/dist-server/service/approval-progress/index.js.map +1 -0
  34. package/dist-server/service/approval-progress/waiting-on.d.ts +15 -0
  35. package/dist-server/service/approval-progress/waiting-on.js +40 -0
  36. package/dist-server/service/approval-progress/waiting-on.js.map +1 -0
  37. package/dist-server/service/index.d.ts +2 -1
  38. package/dist-server/service/index.js +7 -2
  39. package/dist-server/service/index.js.map +1 -1
  40. package/dist-server/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +10 -10
  42. package/shared/approval-step.d.ts +21 -0
  43. package/shared/approval-step.js +55 -0
  44. package/tests/approval-progress-db.test.ts +143 -0
  45. package/things-factory.config.js +1 -0
  46. package/translations/en.json +20 -1
  47. package/translations/es.json +20 -1
  48. package/translations/ja.json +20 -1
  49. package/translations/ko.json +20 -1
  50. package/translations/ms.json +20 -1
  51. package/translations/zh.json +20 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/worklist",
3
- "version": "10.1.249",
3
+ "version": "10.1.253",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "exports": {
@@ -55,16 +55,16 @@
55
55
  "@operato/moment-timezone-es": "^10.0.0",
56
56
  "@operato/p13n": "^10.29.43",
57
57
  "@operato/popup": "^10.29.43",
58
- "@things-factory/attachment-base": "^10.1.247",
59
- "@things-factory/auth-base": "^10.1.247",
60
- "@things-factory/board-service": "^10.1.247",
58
+ "@things-factory/attachment-base": "^10.1.250",
59
+ "@things-factory/auth-base": "^10.1.250",
60
+ "@things-factory/board-service": "^10.1.253",
61
61
  "@things-factory/context-ui": "^10.1.247",
62
- "@things-factory/i18n-base": "^10.1.247",
63
- "@things-factory/notification": "^10.1.248",
64
- "@things-factory/organization": "^10.1.247",
65
- "@things-factory/personalization": "^10.1.247",
66
- "@things-factory/scheduler-client": "^10.1.247",
62
+ "@things-factory/i18n-base": "^10.1.250",
63
+ "@things-factory/notification": "^10.1.250",
64
+ "@things-factory/organization": "^10.1.250",
65
+ "@things-factory/personalization": "^10.1.250",
66
+ "@things-factory/scheduler-client": "^10.1.250",
67
67
  "@things-factory/shell": "^10.1.247"
68
68
  },
69
- "gitHead": "5de27900f2de34064c11c8ee0b7b689d7ac8f9bd"
69
+ "gitHead": "e3d26f853d258ada1a2e77c7f533cc8f5b2528fb"
70
70
  }
@@ -0,0 +1,21 @@
1
+ /** The fields of an approval row the rule reads. */
2
+ export interface ApprovalStepRow {
3
+ round?: number | null
4
+ order?: number | null
5
+ judgment?: string | null
6
+ participation?: string | null
7
+ }
8
+
9
+ /** The judgments after which a row waits on nothing more. */
10
+ export const TERMINATED_JUDGMENTS: readonly ['approved', 'escalated', 'delegated', 'rejected', 'aborted', 'closed']
11
+
12
+ export function isTerminated(judgment: string | null | undefined): boolean
13
+
14
+ /** The latest round — 0 when there are no approvals. */
15
+ export function latestRound(approvals: readonly ApprovalStepRow[] | null | undefined): number
16
+
17
+ /** The step waited on now; undefined when nothing is waited on. */
18
+ export function currentStepOf(approvals: readonly ApprovalStepRow[] | null | undefined): { round: number; order: number } | undefined
19
+
20
+ /** The rows of the step waited on now. */
21
+ export function currentStepRows<T extends ApprovalStepRow>(approvals: readonly T[] | null | undefined): T[]
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Whose turn an approval waits on — read by the server (approval progress, Office's "waiting on") and by the screens'
3
+ * approval-line ribbons. Hand-written .js with a .d.ts beside it, as `task-followup`: a .ts here would be taken into
4
+ * the client's compile and move its rootDir up to the package root.
5
+ *
6
+ * ── Why one file (ADR-0183 decision 2, 2026-11-04) ─────────────────────────
7
+ * The rule lived twice and the two answered differently: Office's `waitingOn` ignored the round, the ribbon's
8
+ * `currentApprovalOrder` counted a reference seat — one who comments and never decides — as the step waited on.
9
+ */
10
+
11
+ /** The judgments after which a row waits on nothing more. The list queries read this very array. */
12
+ const TERMINATED_JUDGMENTS = ['approved', 'escalated', 'delegated', 'rejected', 'aborted', 'closed']
13
+
14
+ function isTerminated(judgment) {
15
+ return TERMINATED_JUDGMENTS.includes(judgment ?? '')
16
+ }
17
+
18
+ /** The latest round — a resubmission raises it, and the judgments of an earlier round are past. 0 when none. */
19
+ function latestRound(approvals) {
20
+ return (approvals || []).reduce((max, one) => Math.max(max, Number(one?.round) || 0), 0)
21
+ }
22
+
23
+ /**
24
+ * The step waited on now: in the latest round, among the deciding (non-reference) rows not yet judged, the lowest
25
+ * order. Undefined when nothing is waited on — no approvals, or every one judged.
26
+ */
27
+ function currentStepOf(approvals) {
28
+ const round = latestRound(approvals)
29
+ const open = (approvals || []).filter(
30
+ one => (Number(one?.round) || 0) === round && one?.participation !== 'reference' && !isTerminated(one?.judgment)
31
+ )
32
+ if (!open.length) return undefined
33
+
34
+ return { round, order: Math.min(...open.map(one => Number(one?.order) || 0)) }
35
+ }
36
+
37
+ /** The rows of the step waited on now — a role's row and a person's row may share one order. */
38
+ function currentStepRows(approvals) {
39
+ const step = currentStepOf(approvals)
40
+ if (!step) return []
41
+
42
+ return (approvals || []).filter(
43
+ one =>
44
+ (Number(one?.round) || 0) === step.round &&
45
+ (Number(one?.order) || 0) === step.order &&
46
+ one?.participation !== 'reference' &&
47
+ !isTerminated(one?.judgment)
48
+ )
49
+ }
50
+
51
+ exports.TERMINATED_JUDGMENTS = TERMINATED_JUDGMENTS
52
+ exports.isTerminated = isTerminated
53
+ exports.latestRound = latestRound
54
+ exports.currentStepOf = currentStepOf
55
+ exports.currentStepRows = currentStepRows
@@ -0,0 +1,143 @@
1
+ import { join } from 'path'
2
+
3
+ /**
4
+ * 결재 현황 through the real resolver on sqlite (ADR-0183): every kind of approval in one list, its requester and
5
+ * department, whom it waits on - a role as its holders - and the counts the chips show.
6
+ *
7
+ * Reads dist-server - run the package's build:server first.
8
+ */
9
+
10
+ const { createSqliteTestDataSource } = require(join(__dirname, '..', '..', 'ops-master', 'test', 'support', 'database.cjs'))
11
+ const DIST = join(__dirname, '..', 'dist-server')
12
+ const { ActivityInstance } = require(join(DIST, 'service', 'activity-instance', 'activity-instance.js'))
13
+ const { ActivityThread } = require(join(DIST, 'service', 'activity-thread', 'activity-thread.js'))
14
+ const { ActivityApproval } = require(join(DIST, 'service', 'activity-approval', 'activity-approval.js'))
15
+ const { Activity } = require(join(DIST, 'service', 'activity', 'activity.js'))
16
+ const { ApprovalProgressQuery } = require(join(DIST, 'service', 'approval-progress', 'approval-progress-query.js'))
17
+ const { waitingOn } = require(join(DIST, 'service', 'approval-progress', 'waiting-on.js'))
18
+ const shell = require('@things-factory/shell')
19
+ const { Role, User } = require('@things-factory/auth-base')
20
+ const { Department, Employee } = require('@things-factory/organization')
21
+
22
+ let db: any
23
+ let here: any
24
+ let elsewhere: any
25
+ let kim: any
26
+ let lee: any
27
+ let ceo: any
28
+ let holder: any
29
+ let heads: any
30
+ let dev: any
31
+ let sales: any
32
+ let weekly: any
33
+ let leave: any
34
+
35
+ const asAdmin = () => ({ state: { domain: here, user: ceo }, t: (key: string) => key }) as any
36
+ const progress = (filter: any = {}, pagination: any = undefined) =>
37
+ new ApprovalProgressQuery().approvalProgress(filter, pagination, asAdmin())
38
+
39
+ /* One clock across the cases - each row is made an hour after the one before, so the waiting order is defined. */
40
+ let at = Date.parse('2026-11-01T00:00:00Z')
41
+
42
+ /** A case of `activity` put up by `requester`, its line's rows given as [order, judgment, seat, participation?]. */
43
+ async function aCase(domain: any, activity: any, name: string, requester: any, rows: any[][], extra: any = {}) {
44
+ const instance = await db.getRepository(ActivityInstance).save({
45
+ domain, activity, name, state: 'started', creator: requester, issuer: requester,
46
+ approvalLine: rows.map(([, , , participation]) => ({ participation: participation ?? 'approval' })), ...extra
47
+ })
48
+ const thread = await db.getRepository(ActivityThread).save({
49
+ domain, name, round: 1, state: 'submitted', assignee: requester, activityInstance: instance
50
+ })
51
+ for (const [order, judgment, seat, participation] of rows) {
52
+ at += 3600_000
53
+ await db.getRepository(ActivityApproval).save({
54
+ domain, activityThread: thread, order, round: 1, judgment, participation: participation ?? 'approval',
55
+ ...(seat === heads ? { approverRole: seat } : { approver: seat }),
56
+ createdAt: new Date(at), terminatedAt: judgment ? new Date(at + 60_000) : null
57
+ })
58
+ }
59
+ return instance
60
+ }
61
+
62
+ beforeAll(async () => {
63
+ db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval, Activity, Employee, Department])
64
+ await db.initialize()
65
+ shell.addDataSource('default', db)
66
+
67
+ here = await db.getRepository(shell.Domain).save({ name: 'here', subdomain: 'here' })
68
+ elsewhere = await db.getRepository(shell.Domain).save({ name: 'elsewhere', subdomain: 'elsewhere' })
69
+
70
+ const person = (name: string) =>
71
+ db.getRepository(User).save({ name, email: `${name}@example.com`, password: 'x', status: 'activated', domains: [here] })
72
+ kim = await person('kim')
73
+ lee = await person('lee')
74
+ ceo = await person('ceo')
75
+ holder = await person('holder')
76
+ heads = await db.getRepository(Role).save({ name: 'heads', domain: here, users: [holder] })
77
+
78
+ dev = await db.getRepository(Department).save({ domain: here, name: '개발', controlNo: 'D1' })
79
+ sales = await db.getRepository(Department).save({ domain: here, name: '영업', controlNo: 'D2' })
80
+ await db.getRepository(Employee).save({ domain: here, controlNo: 'E1', name: '김개발', user: kim, department: dev, active: true })
81
+ await db.getRepository(Employee).save({ domain: here, controlNo: 'E2', name: '이영업', user: lee, department: sales, active: true })
82
+
83
+ weekly = await db.getRepository(Activity).save({ domain: here, name: 'Office Weekly Report', label: '주간업무보고' })
84
+ leave = await db.getRepository(Activity).save({ domain: here, name: 'Office Leave Request', label: '휴가 신청' })
85
+ const other = await db.getRepository(Activity).save({ domain: elsewhere, name: 'Office Leave Request' })
86
+
87
+ /* kim's weekly report: the head (a role) approved, now the CEO's turn - and it is past due. */
88
+ await aCase(here, weekly, '주간보고 11월 1주 김개발', kim, [[1, 'approved', heads], [2, '', ceo]], { dueAt: new Date('2026-11-02T00:00:00Z') })
89
+ /* lee's weekly report waits on the head role, a reference seated before it. */
90
+ await aCase(here, weekly, '주간보고 11월 1주 이영업', lee, [[1, '', ceo, 'reference'], [2, '', heads]])
91
+ /* kim's leave was rejected - an office door closes the case on a rejection. */
92
+ const rejected = await aCase(here, leave, '휴가 11-10 김개발', kim, [[1, 'rejected', ceo]])
93
+ await db.getRepository(ActivityInstance).update(rejected.id, { state: 'aborted' })
94
+ /* Another domain's case never shows here. */
95
+ await aCase(elsewhere, other, '남의 휴가', kim, [[1, '', ceo]])
96
+ })
97
+
98
+ afterAll(async () => db?.destroy())
99
+
100
+ it('★ shows every kind of approval of this domain, the waiting first', async () => {
101
+ const list = await progress()
102
+
103
+ expect(list.total).toBe(3)
104
+ expect(list.items.map((one: any) => one.headline)).toEqual(['주간보고 11월 1주 김개발', '주간보고 11월 1주 이영업', '휴가 11-10 김개발'])
105
+ expect(list.kinds.map((one: any) => one.label)).toEqual(['주간업무보고', '휴가 신청'])
106
+ })
107
+
108
+ it('★ names the requester, their department, the step and whom it waits on', async () => {
109
+ const [first, second, third] = (await progress()).items
110
+
111
+ expect(first).toMatchObject({ requesterName: '김개발', departmentName: '개발', state: 'PENDING', step: 2, steps: 2, waitingOn: ['ceo'], overdue: true })
112
+ /* A role's seat names its holders and the role; the reference before it is not a step. */
113
+ expect(second).toMatchObject({ requesterName: '이영업', departmentName: '영업', step: 1, steps: 1, waitingOn: ['holder'], waitingRole: 'heads', overdue: false })
114
+ expect(third).toMatchObject({ state: 'REJECTED', waitingOn: [] })
115
+ })
116
+
117
+ it('★ counts each state and the overdue under the other conditions', async () => {
118
+ const list = await progress({ states: ['REJECTED'], activityNames: ['Office Weekly Report', 'Office Leave Request'] })
119
+
120
+ expect(list.total).toBe(1)
121
+ expect(list.counts).toEqual([
122
+ { state: 'PENDING', count: 2 },
123
+ { state: 'APPROVED', count: 0 },
124
+ { state: 'REJECTED', count: 1 },
125
+ { state: 'ABORTED', count: 0 }
126
+ ])
127
+ expect((await progress()).overdue).toBe(1)
128
+ })
129
+
130
+ it('filters by kind, department and overdue, and pages', async () => {
131
+ expect((await progress({ activityNames: ['Office Leave Request'] })).total).toBe(1)
132
+ expect((await progress({ departmentIds: [sales.id] })).items.map((one: any) => one.requesterName)).toEqual(['이영업'])
133
+ expect((await progress({ overdueOnly: true })).total).toBe(1)
134
+
135
+ const page = await progress({}, { page: 2, limit: 2 })
136
+ expect(page.total).toBe(3)
137
+ expect(page.items.map((one: any) => one.headline)).toEqual(['휴가 11-10 김개발'])
138
+ })
139
+
140
+ it("names whom a case waits on by its instance - Office's approval doors read it", async () => {
141
+ const [first] = (await progress()).items
142
+ expect(await waitingOn(db.manager, first.id)).toEqual(['ceo'])
143
+ })
@@ -18,6 +18,7 @@ export default {
18
18
  { tagname: 'activity-list-page', page: 'activity-list' },
19
19
  { tagname: 'activity-page', page: 'activity' },
20
20
  { tagname: 'activity-instance-search-page', page: 'activity-instance-search' },
21
+ { tagname: 'approval-progress-page', page: 'approval-progress' },
21
22
  { tagname: 'activity-instance-list-page', page: 'activity-instance-list' },
22
23
  { tagname: 'activity-instance-start-page', page: 'activity-instance-start' },
23
24
  { tagname: 'activity-thread-page', page: 'activity-thread' },
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "\"{activity}\" has no approval line. It was submitted to be approved, so it is not sent on - left as it is, it would be confirmed without approval.",
403
403
  "error.activityInstance not found": "This task could not be found. Reload the list.",
404
404
  "error.activityInstance state should be draft": "Only a draft can be changed. This task has already been submitted.",
405
- "error.no user holds the assignee roles": "Nobody holds the assignee roles ({roles}). Give the role to someone first."
405
+ "error.no user holds the assignee roles": "Nobody holds the assignee roles ({roles}). Give the role to someone first.",
406
+ "title.approval progress": "Approval progress",
407
+ "field.approval-kind": "Kind",
408
+ "field.approval-headline": "Subject",
409
+ "field.approval-requester": "Requester",
410
+ "field.approval-department": "Department",
411
+ "field.approval-step": "Step",
412
+ "field.approval-waiting-on": "Waiting on",
413
+ "field.approval-waited": "Waited",
414
+ "field.approval-due-at": "Due",
415
+ "field.approval-raised-at": "Raised",
416
+ "field.approval-ended-at": "Ended",
417
+ "label.approval-progress-pending": "In progress",
418
+ "label.approval-progress-approved": "Approved",
419
+ "label.approval-progress-rejected": "Rejected",
420
+ "label.approval-progress-aborted": "Stopped",
421
+ "label.approval-progress-overdue": "Overdue",
422
+ "text.waited-days": "{days}d {hours}h",
423
+ "text.waited-hours": "{hours}h {minutes}m",
424
+ "text.waited-minutes": "{minutes}m"
406
425
  }
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "\"{activity}\" no tiene línea de aprobación. Se envió para ser aprobado, así que no se envía: tal como está se confirmaría sin aprobación.",
403
403
  "error.activityInstance not found": "No se encontró esta tarea. Vuelva a cargar la lista.",
404
404
  "error.activityInstance state should be draft": "Solo se puede cambiar un borrador. Esta tarea ya se envió.",
405
- "error.no user holds the assignee roles": "Nadie tiene los roles asignados ({roles}). Asigne primero el rol a alguien."
405
+ "error.no user holds the assignee roles": "Nadie tiene los roles asignados ({roles}). Asigne primero el rol a alguien.",
406
+ "title.approval progress": "Estado de aprobaciones",
407
+ "field.approval-kind": "Tipo",
408
+ "field.approval-headline": "Asunto",
409
+ "field.approval-requester": "Solicitante",
410
+ "field.approval-department": "Departamento",
411
+ "field.approval-step": "Paso",
412
+ "field.approval-waiting-on": "Espera a",
413
+ "field.approval-waited": "Tiempo de espera",
414
+ "field.approval-due-at": "Vencimiento",
415
+ "field.approval-raised-at": "Enviado",
416
+ "field.approval-ended-at": "Finalizado",
417
+ "label.approval-progress-pending": "En curso",
418
+ "label.approval-progress-approved": "Aprobado",
419
+ "label.approval-progress-rejected": "Rechazado",
420
+ "label.approval-progress-aborted": "Detenido",
421
+ "label.approval-progress-overdue": "Vencido",
422
+ "text.waited-days": "{days} d {hours} h",
423
+ "text.waited-hours": "{hours} h {minutes} min",
424
+ "text.waited-minutes": "{minutes} min"
406
425
  }
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "「{activity}」に承認ラインがありません。承認を受ける前提で提出されたため送りません。このままでは承認なしで確定します。",
403
403
  "error.activityInstance not found": "この作業が見つかりません。一覧を再読み込みしてください。",
404
404
  "error.activityInstance state should be draft": "下書きのときだけ変更できます。この作業はすでに提出されています。",
405
- "error.no user holds the assignee roles": "担当ロール({roles})を持つユーザーがいません。先にロールにユーザーを割り当ててください。"
405
+ "error.no user holds the assignee roles": "担当ロール({roles})を持つユーザーがいません。先にロールにユーザーを割り当ててください。",
406
+ "title.approval progress": "決裁状況",
407
+ "field.approval-kind": "種類",
408
+ "field.approval-headline": "件名",
409
+ "field.approval-requester": "申請者",
410
+ "field.approval-department": "部署",
411
+ "field.approval-step": "段階",
412
+ "field.approval-waiting-on": "待っている人",
413
+ "field.approval-waited": "待ち時間",
414
+ "field.approval-due-at": "期限",
415
+ "field.approval-raised-at": "申請日時",
416
+ "field.approval-ended-at": "終了日時",
417
+ "label.approval-progress-pending": "進行中",
418
+ "label.approval-progress-approved": "承認",
419
+ "label.approval-progress-rejected": "差戻し",
420
+ "label.approval-progress-aborted": "中止",
421
+ "label.approval-progress-overdue": "期限超過",
422
+ "text.waited-days": "{days}日 {hours}時間",
423
+ "text.waited-hours": "{hours}時間 {minutes}分",
424
+ "text.waited-minutes": "{minutes}分"
406
425
  }
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "\"{activity}\" 에 결재선이 없습니다. 이 건은 결재를 받기로 하고 낸 것이라 올리지 않습니다 — 그대로 두면 결재 없이 확정됩니다.",
403
403
  "error.activityInstance not found": "이 작업을 찾을 수 없습니다. 목록을 새로 불러오세요.",
404
404
  "error.activityInstance state should be draft": "초안일 때만 고칠 수 있습니다. 이 작업은 이미 올라갔습니다.",
405
- "error.no user holds the assignee roles": "담당 역할({roles})을 가진 사용자가 없습니다. 역할에 사용자를 먼저 지정해 주세요."
405
+ "error.no user holds the assignee roles": "담당 역할({roles})을 가진 사용자가 없습니다. 역할에 사용자를 먼저 지정해 주세요.",
406
+ "title.approval progress": "결재 현황",
407
+ "field.approval-kind": "종류",
408
+ "field.approval-headline": "제목",
409
+ "field.approval-requester": "올린 사람",
410
+ "field.approval-department": "부서",
411
+ "field.approval-step": "단계",
412
+ "field.approval-waiting-on": "기다리는 사람",
413
+ "field.approval-waited": "기다린 시간",
414
+ "field.approval-due-at": "기한",
415
+ "field.approval-raised-at": "올린 때",
416
+ "field.approval-ended-at": "끝난 때",
417
+ "label.approval-progress-pending": "진행 중",
418
+ "label.approval-progress-approved": "승인",
419
+ "label.approval-progress-rejected": "반려",
420
+ "label.approval-progress-aborted": "중단",
421
+ "label.approval-progress-overdue": "기한 넘김",
422
+ "text.waited-days": "{days}일 {hours}시간",
423
+ "text.waited-hours": "{hours}시간 {minutes}분",
424
+ "text.waited-minutes": "{minutes}분"
406
425
  }
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "\"{activity}\" tiada barisan kelulusan. Ia dihantar untuk diluluskan, jadi ia tidak dihantar - jika dibiarkan ia akan disahkan tanpa kelulusan.",
403
403
  "error.activityInstance not found": "Tugas ini tidak dapat ditemui. Muat semula senarai.",
404
404
  "error.activityInstance state should be draft": "Hanya draf boleh diubah. Tugas ini telah dihantar.",
405
- "error.no user holds the assignee roles": "Tiada pengguna memegang peranan tugasan ({roles}). Berikan peranan kepada seseorang dahulu."
405
+ "error.no user holds the assignee roles": "Tiada pengguna memegang peranan tugasan ({roles}). Berikan peranan kepada seseorang dahulu.",
406
+ "title.approval progress": "Kemajuan kelulusan",
407
+ "field.approval-kind": "Jenis",
408
+ "field.approval-headline": "Perkara",
409
+ "field.approval-requester": "Pemohon",
410
+ "field.approval-department": "Jabatan",
411
+ "field.approval-step": "Langkah",
412
+ "field.approval-waiting-on": "Menunggu",
413
+ "field.approval-waited": "Tempoh menunggu",
414
+ "field.approval-due-at": "Tarikh akhir",
415
+ "field.approval-raised-at": "Dihantar",
416
+ "field.approval-ended-at": "Tamat",
417
+ "label.approval-progress-pending": "Dalam proses",
418
+ "label.approval-progress-approved": "Diluluskan",
419
+ "label.approval-progress-rejected": "Ditolak",
420
+ "label.approval-progress-aborted": "Dihentikan",
421
+ "label.approval-progress-overdue": "Lewat tempoh",
422
+ "text.waited-days": "{days}h {hours}j",
423
+ "text.waited-hours": "{hours}j {minutes}m",
424
+ "text.waited-minutes": "{minutes}m"
406
425
  }
@@ -402,5 +402,24 @@
402
402
  "error.approval line is empty but approval was required": "\"{activity}\" 没有审批线。此件是按需审批提交的,因此不会提交——否则将未经审批即确定。",
403
403
  "error.activityInstance not found": "找不到此任务。请重新加载列表。",
404
404
  "error.activityInstance state should be draft": "只有草稿可以修改。此任务已提交。",
405
- "error.no user holds the assignee roles": "没有用户拥有负责角色({roles})。请先为角色指定用户。"
405
+ "error.no user holds the assignee roles": "没有用户拥有负责角色({roles})。请先为角色指定用户。",
406
+ "title.approval progress": "审批状况",
407
+ "field.approval-kind": "类型",
408
+ "field.approval-headline": "标题",
409
+ "field.approval-requester": "申请人",
410
+ "field.approval-department": "部门",
411
+ "field.approval-step": "步骤",
412
+ "field.approval-waiting-on": "等待审批人",
413
+ "field.approval-waited": "已等待",
414
+ "field.approval-due-at": "期限",
415
+ "field.approval-raised-at": "提交时间",
416
+ "field.approval-ended-at": "结束时间",
417
+ "label.approval-progress-pending": "进行中",
418
+ "label.approval-progress-approved": "已批准",
419
+ "label.approval-progress-rejected": "驳回",
420
+ "label.approval-progress-aborted": "中止",
421
+ "label.approval-progress-overdue": "已逾期",
422
+ "text.waited-days": "{days}天 {hours}小时",
423
+ "text.waited-hours": "{hours}小时 {minutes}分",
424
+ "text.waited-minutes": "{minutes}分"
406
425
  }