@things-factory/worklist 10.1.5 → 10.1.7
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.
- package/dist-client/components/activity-approval-ribon.js +17 -2
- package/dist-client/components/activity-approval-ribon.js.map +1 -1
- package/dist-client/components/activity-instance-preview.js +5 -4
- package/dist-client/components/activity-instance-preview.js.map +1 -1
- package/dist-client/components/activity-starter-form.js +14 -10
- package/dist-client/components/activity-starter-form.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.d.ts +2 -2
- package/dist-client/components/decision-slots.d.ts +118 -0
- package/dist-client/components/decision-slots.js +125 -0
- package/dist-client/components/decision-slots.js.map +1 -0
- package/dist-client/components/mutation-outcome.d.ts +57 -0
- package/dist-client/components/mutation-outcome.js +78 -0
- package/dist-client/components/mutation-outcome.js.map +1 -0
- package/dist-client/components/say-outcome.d.ts +25 -0
- package/dist-client/components/say-outcome.js +51 -0
- package/dist-client/components/say-outcome.js.map +1 -0
- package/dist-client/pages/activity/activity-list-page.js +28 -12
- package/dist-client/pages/activity/activity-list-page.js.map +1 -1
- package/dist-client/pages/activity/activity-model-item-list.js +4 -3
- package/dist-client/pages/activity/activity-model-item-list.js.map +1 -1
- package/dist-client/pages/activity/activity-page.d.ts +2 -2
- package/dist-client/pages/activity/activity-page.js +25 -21
- package/dist-client/pages/activity/activity-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-importer.js +4 -3
- package/dist-client/pages/activity-approval/activity-approval-importer.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-list-page.js +8 -9
- package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.d.ts +75 -1
- package/dist-client/pages/activity-approval/activity-approval-page.js +487 -57
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-catalog/activity-catalog-page.d.ts +11 -0
- package/dist-client/pages/activity-catalog/activity-catalog-page.js +48 -1
- package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-start-page.js +24 -18
- package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-view.js +1 -0
- package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
- package/dist-client/pages/activity-stats/activity-stats-importer.js +4 -3
- package/dist-client/pages/activity-stats/activity-stats-importer.js.map +1 -1
- package/dist-client/pages/activity-stats/activity-stats-list-page.js +8 -9
- package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-importer.js +4 -3
- package/dist-client/pages/activity-template/activity-template-importer.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-list-page.js +8 -9
- package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-model-item-list.js +4 -3
- package/dist-client/pages/activity-template/activity-template-model-item-list.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-importer.js +4 -3
- package/dist-client/pages/activity-thread/activity-thread-importer.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-list-page.js +8 -9
- package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.js +22 -20
- package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.js +1 -0
- package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
- package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +27 -0
- package/dist-client/pages/installable-activity/installable-activity-list-page.js +70 -11
- package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
- package/dist-client/pages/todo/draft-list-page.js +1 -0
- package/dist-client/pages/todo/draft-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-approval/approval-line.d.ts +32 -0
- package/dist-server/controllers/activity-approval/approval-line.js +35 -0
- package/dist-server/controllers/activity-approval/approval-line.js.map +1 -1
- package/dist-server/controllers/activity-approval/approve.js +17 -0
- package/dist-server/controllers/activity-approval/approve.js.map +1 -1
- package/dist-server/controllers/activity-approval/reject.js +21 -1
- package/dist-server/controllers/activity-approval/reject.js.map +1 -1
- package/dist-server/controllers/activity-installation/activity-installation-controller.js +5 -9
- package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
- package/dist-server/controllers/activity-installation/installable-to-activity.d.ts +62 -0
- package/dist-server/controllers/activity-installation/installable-to-activity.js +53 -0
- package/dist-server/controllers/activity-installation/installable-to-activity.js.map +1 -0
- package/dist-server/controllers/activity-installation/registry-conflict.d.ts +32 -0
- package/dist-server/controllers/activity-installation/registry-conflict.js +21 -0
- package/dist-server/controllers/activity-installation/registry-conflict.js.map +1 -0
- package/dist-server/controllers/activity-instance/draft.js +3 -0
- package/dist-server/controllers/activity-instance/draft.js.map +1 -1
- package/dist-server/controllers/activity-instance/entitlement.d.ts +25 -0
- package/dist-server/controllers/activity-instance/entitlement.js +108 -0
- package/dist-server/controllers/activity-instance/entitlement.js.map +1 -0
- package/dist-server/controllers/activity-instance/issue.js +10 -0
- package/dist-server/controllers/activity-instance/issue.js.map +1 -1
- package/dist-server/controllers/activity-instance/pick.js +8 -0
- package/dist-server/controllers/activity-instance/pick.js.map +1 -1
- package/dist-server/service/activity/activity-decision-key-item-type.d.ts +160 -0
- package/dist-server/service/activity/activity-decision-key-item-type.js +176 -0
- package/dist-server/service/activity/activity-decision-key-item-type.js.map +1 -0
- package/dist-server/service/activity/activity-type.d.ts +3 -0
- package/dist-server/service/activity/activity-type.js +8 -0
- package/dist-server/service/activity/activity-type.js.map +1 -1
- package/dist-server/service/activity/activity.d.ts +30 -0
- package/dist-server/service/activity/activity.js +14 -0
- package/dist-server/service/activity/activity.js.map +1 -1
- package/dist-server/service/activity/decision-key-declaration.d.ts +33 -0
- package/dist-server/service/activity/decision-key-declaration.js +67 -0
- package/dist-server/service/activity/decision-key-declaration.js.map +1 -0
- package/dist-server/service/activity/{credential-guard-subscriber.d.ts → declaration-guard-subscriber.d.ts} +6 -1
- package/dist-server/service/activity/declaration-guard-subscriber.js +46 -0
- package/dist-server/service/activity/declaration-guard-subscriber.js.map +1 -0
- package/dist-server/service/activity/index.d.ts +2 -2
- package/dist-server/service/activity/index.js +2 -2
- package/dist-server/service/activity/index.js.map +1 -1
- package/dist-server/service/activity/search-key-declaration.d.ts +38 -0
- package/dist-server/service/activity/search-key-declaration.js +49 -0
- package/dist-server/service/activity/search-key-declaration.js.map +1 -0
- package/dist-server/service/activity-instance/activity-instance-mutation.js +20 -9
- package/dist-server/service/activity-instance/activity-instance-mutation.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-query.js +25 -2
- package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-type.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/installable-activity/installable-activity-mutation.js +24 -0
- package/dist-server/service/installable-activity/installable-activity-mutation.js.map +1 -1
- package/dist-server/service/installable-activity/installable-activity.d.ts +9 -0
- package/dist-server/service/installable-activity/installable-activity.js +5 -0
- package/dist-server/service/installable-activity/installable-activity.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/translations/en.json +33 -6
- package/translations/ja.json +27 -0
- package/translations/ko.json +33 -6
- package/translations/ms.json +27 -0
- package/translations/zh.json +27 -0
- package/dist-server/service/activity/credential-guard-subscriber.js +0 -35
- package/dist-server/service/activity/credential-guard-subscriber.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/draft.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityInstanceDraft } from '../../service/activity-instance/activity-instance-type.js'\nimport { Activity } from '../../service/activity/activity.js'\nimport { fillActivitySearchKeys } from '../common.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\n\nexport async function draft(\n activityInstance: ActivityInstanceDraft,\n context: ResolverContext\n): Promise<ActivityInstance> {\n const { domain, user, tx } = context.state\n const { id, activityId, input, dueAt } = activityInstance\n\n var repository = tx.getRepository(Activity)\n var activity = {} as Activity\n\n if (id) {\n var previousActivityInstance = await tx.getRepository(ActivityInstance).findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['activity']\n })\n\n if (!previousActivityInstance) {\n throw new Error(\n context.t('error.activityInstance not found', {\n activityInstance: id\n })\n )\n }\n\n /* 남의 초안은 못 고친다. 새로 시작하는 것(id 없음)은 이 검사에 안 걸린다. */\n await assertMayWorkOnDraft(previousActivityInstance, context)\n\n if (previousActivityInstance.state !== ActivityInstanceStatus.Draft) {\n throw new Error(\n context.t('error.activityInstance state should be draft', {\n activityInstance: id,\n state: previousActivityInstance.state\n })\n )\n }\n\n activity = previousActivityInstance.activity\n } else {\n activityInstance.adhocType = 'standard'\n activityInstance.refBy = activityId\n }\n\n if (activityId) {\n activity = await repository.findOne({\n where: { domain: { id: domain.id }, id: activityId },\n relations: ['assigneeRole', 'supervisoryRole']\n })\n\n if (!activity) {\n throw new Error(\n context.t('error.activity not found', {\n activity: activityId\n })\n )\n }\n }\n\n if (!activityInstance.assignees) {\n activityInstance.assignees = activity.assignees\n }\n if (!activityInstance.approvalLine) {\n activityInstance.approvalLine = activity.approvalLine\n }\n\n if (!dueAt && activity.standardTime) {\n activityInstance.dueAt = new Date(Date.now() + activity.standardTime * 1000)\n }\n\n const activitySearchKeys = fillActivitySearchKeys(activity?.searchKeys, input)\n\n return await tx.getRepository(ActivityInstance).save({\n ...previousActivityInstance,\n activityType: activity.activityType,\n uiType: activity.uiType,\n uiSource: activity.uiSource,\n viewType: activity.viewType,\n viewSource: activity.viewSource,\n assigneeRole: activity.assigneeRole,\n supervisoryRole: activity.supervisoryRole,\n ...activityInstance,\n transaction: 'draft',\n activity,\n ...activitySearchKeys,\n state: ActivityInstanceStatus.Draft,\n domain,\n creator: user,\n updater: user\n })\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ActivityInstance } from '../../service/activity-instance/activity-instance.js';
|
|
2
|
+
/**
|
|
3
|
+
* May this person edit or issue this draft?
|
|
4
|
+
*
|
|
5
|
+
* The person who wrote it, or someone who manages activity instances. A draft is a private
|
|
6
|
+
* thing until it is issued — it carries an approval line and assignees that become other
|
|
7
|
+
* people's obligations the moment it goes out.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertMayWorkOnDraft(activityInstance: ActivityInstance, context: ResolverContext): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* May this person pick up this task?
|
|
12
|
+
*
|
|
13
|
+
* ── When a role is named, it is the answer ───────────────────────────────────
|
|
14
|
+
* `assigneeRole` is how an issuer says "this is for the quality inspectors". Reading it is
|
|
15
|
+
* the whole point of writing it, and until now it was loaded and ignored.
|
|
16
|
+
*
|
|
17
|
+
* ── When no role is named, anyone in the domain may take it ──────────────────
|
|
18
|
+
* That is the current behaviour and it is deliberate rather than a fallback: an issuer who
|
|
19
|
+
* names nobody is saying "whoever is free". Refusing instead would make every unaddressed
|
|
20
|
+
* task unpickable, which is the opposite of what leaving the field blank means.
|
|
21
|
+
*
|
|
22
|
+
* `reference_absent_value_leans_which_way` asks which way an absent value should lean, and
|
|
23
|
+
* here the lean is the issuer's stated intent — not a guess the code makes to be safe.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertMayPick(activityInstance: ActivityInstance, context: ResolverContext): Promise<void>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertMayWorkOnDraft = assertMayWorkOnDraft;
|
|
4
|
+
exports.assertMayPick = assertMayPick;
|
|
5
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
6
|
+
/**
|
|
7
|
+
* Who may act on **this one** activity instance.
|
|
8
|
+
*
|
|
9
|
+
* ── Why three mutations carry no `@privilege` ────────────────────────────────
|
|
10
|
+
* `draftActivityInstance`, `issueActivityInstance` and `pickActivityInstance` were given
|
|
11
|
+
* `@privilege(category: "activity-instance", privilege: "mutation", domainOwnerGranted: true)`
|
|
12
|
+
* commented out, in the same commit that put it live on the other six
|
|
13
|
+
* (`74acc61f6d`, 2025-04-10). That was a choice, not an omission, and the choice was right:
|
|
14
|
+
*
|
|
15
|
+
* gated acting on someone else's work, or destructive
|
|
16
|
+
* assign · end · abort · delete draft
|
|
17
|
+
* not gated doing your own work
|
|
18
|
+
* draft · issue · pick
|
|
19
|
+
*
|
|
20
|
+
* That privilege is the **management** one. Requiring it to pick up a task addressed to you
|
|
21
|
+
* would mean handing every operator on the floor the right to reassign and abort everyone
|
|
22
|
+
* else's work. So the category gate stays off here.
|
|
23
|
+
*
|
|
24
|
+
* The reason was never written down anywhere, which is why the next person to read the file
|
|
25
|
+
* saw three commented-out lines and could not tell "deliberate" from "forgotten". It is
|
|
26
|
+
* written here now.
|
|
27
|
+
*
|
|
28
|
+
* ── What was actually missing ────────────────────────────────────────────────
|
|
29
|
+
* "Your own work" was the intent and **nothing enforced it.** Each of the three takes an
|
|
30
|
+
* id and loads the record without asking whether this person has anything to do with it:
|
|
31
|
+
*
|
|
32
|
+
* draft(id) edits a draft — never checked who wrote it
|
|
33
|
+
* issue(id) issues a draft, routing approvals — never checked who wrote it
|
|
34
|
+
* pick(id) takes an unassigned task — loaded `assigneeRole` and never read it
|
|
35
|
+
*
|
|
36
|
+
* The category gate cannot fix that. Turning it on locks out the operator whose task it is;
|
|
37
|
+
* leaving it off lets anyone in the domain take, edit or issue anyone else's. Those are two
|
|
38
|
+
* different questions and only the second one is open:
|
|
39
|
+
*
|
|
40
|
+
* category privilege may this person manage activity instances at all
|
|
41
|
+
* entitlement may this person act on **this** record
|
|
42
|
+
*
|
|
43
|
+
* The approval side already has the second one — `reject.ts` refuses a reject from someone
|
|
44
|
+
* who is only a reference on the order. The assignment side had no equivalent.
|
|
45
|
+
*
|
|
46
|
+
* Starting something new is untouched: with no id there is no owner to check, and anyone in
|
|
47
|
+
* the domain may begin a task.
|
|
48
|
+
*/
|
|
49
|
+
/** Whoever holds the management privilege is entitled to every record, as before. */
|
|
50
|
+
async function manages(context) {
|
|
51
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state;
|
|
52
|
+
return await (0, auth_base_1.checkPermission)({ category: 'activity-instance', privilege: 'mutation', owner: true }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* May this person edit or issue this draft?
|
|
56
|
+
*
|
|
57
|
+
* The person who wrote it, or someone who manages activity instances. A draft is a private
|
|
58
|
+
* thing until it is issued — it carries an approval line and assignees that become other
|
|
59
|
+
* people's obligations the moment it goes out.
|
|
60
|
+
*/
|
|
61
|
+
async function assertMayWorkOnDraft(activityInstance, context) {
|
|
62
|
+
const { user } = context.state;
|
|
63
|
+
if (activityInstance.creatorId === user.id) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (await manages(context)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
throw new Error(context.t('error.activity-instance belongs to someone else', {
|
|
70
|
+
id: activityInstance.id
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* May this person pick up this task?
|
|
75
|
+
*
|
|
76
|
+
* ── When a role is named, it is the answer ───────────────────────────────────
|
|
77
|
+
* `assigneeRole` is how an issuer says "this is for the quality inspectors". Reading it is
|
|
78
|
+
* the whole point of writing it, and until now it was loaded and ignored.
|
|
79
|
+
*
|
|
80
|
+
* ── When no role is named, anyone in the domain may take it ──────────────────
|
|
81
|
+
* That is the current behaviour and it is deliberate rather than a fallback: an issuer who
|
|
82
|
+
* names nobody is saying "whoever is free". Refusing instead would make every unaddressed
|
|
83
|
+
* task unpickable, which is the opposite of what leaving the field blank means.
|
|
84
|
+
*
|
|
85
|
+
* `reference_absent_value_leans_which_way` asks which way an absent value should lean, and
|
|
86
|
+
* here the lean is the issuer's stated intent — not a guess the code makes to be safe.
|
|
87
|
+
*/
|
|
88
|
+
async function assertMayPick(activityInstance, context) {
|
|
89
|
+
const { user, tx } = context.state;
|
|
90
|
+
const roleId = activityInstance.assigneeRoleId;
|
|
91
|
+
if (!roleId) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const holder = await tx.getRepository(auth_base_1.User).findOne({
|
|
95
|
+
where: { id: user.id },
|
|
96
|
+
relations: ['roles']
|
|
97
|
+
});
|
|
98
|
+
if ((holder?.roles || []).some((role) => role.id === roleId)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (await manages(context)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
throw new Error(context.t('error.activity-instance is addressed to another role', {
|
|
105
|
+
id: activityInstance.id
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=entitlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlement.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/entitlement.ts"],"names":[],"mappings":";;AAoEA,oDAgBC;AAiBD,sCA0BC;AA/HD,yDAAuE;AAIvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,qFAAqF;AACrF,KAAK,UAAU,OAAO,CAAC,OAAwB;IAC7C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEtE,OAAO,MAAM,IAAA,2BAAe,EAC1B,EAAE,QAAQ,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EACrE,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,oBAAoB,CACrB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,oBAAoB,CAAC,gBAAkC,EAAE,OAAwB;IACrG,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,IAAI,gBAAgB,CAAC,SAAS,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;QAC3C,OAAM;IACR,CAAC;IAED,IAAI,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,iDAAiD,EAAE;QAC3D,EAAE,EAAE,gBAAgB,CAAC,EAAE;KACxB,CAAC,CACH,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACI,KAAK,UAAU,aAAa,CAAC,gBAAkC,EAAE,OAAwB;IAC9F,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAA;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAM;IACR,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,gBAAI,CAAC,CAAC,OAAO,CAAC;QAClD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;QACtB,SAAS,EAAE,CAAC,OAAO,CAAC;KACrB,CAAC,CAAA;IAEF,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAU,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC;QACnE,OAAM;IACR,CAAC;IAED,IAAI,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,sDAAsD,EAAE;QAChE,EAAE,EAAE,gBAAgB,CAAC,EAAE;KACxB,CAAC,CACH,CAAA;AACH,CAAC","sourcesContent":["import { checkPermission, Role, User } from '@things-factory/auth-base'\n\nimport { ActivityInstance } from '../../service/activity-instance/activity-instance.js'\n\n/**\n * Who may act on **this one** activity instance.\n *\n * ── Why three mutations carry no `@privilege` ────────────────────────────────\n * `draftActivityInstance`, `issueActivityInstance` and `pickActivityInstance` were given\n * `@privilege(category: \"activity-instance\", privilege: \"mutation\", domainOwnerGranted: true)`\n * commented out, in the same commit that put it live on the other six\n * (`74acc61f6d`, 2025-04-10). That was a choice, not an omission, and the choice was right:\n *\n * gated acting on someone else's work, or destructive\n * assign · end · abort · delete draft\n * not gated doing your own work\n * draft · issue · pick\n *\n * That privilege is the **management** one. Requiring it to pick up a task addressed to you\n * would mean handing every operator on the floor the right to reassign and abort everyone\n * else's work. So the category gate stays off here.\n *\n * The reason was never written down anywhere, which is why the next person to read the file\n * saw three commented-out lines and could not tell \"deliberate\" from \"forgotten\". It is\n * written here now.\n *\n * ── What was actually missing ────────────────────────────────────────────────\n * \"Your own work\" was the intent and **nothing enforced it.** Each of the three takes an\n * id and loads the record without asking whether this person has anything to do with it:\n *\n * draft(id) edits a draft — never checked who wrote it\n * issue(id) issues a draft, routing approvals — never checked who wrote it\n * pick(id) takes an unassigned task — loaded `assigneeRole` and never read it\n *\n * The category gate cannot fix that. Turning it on locks out the operator whose task it is;\n * leaving it off lets anyone in the domain take, edit or issue anyone else's. Those are two\n * different questions and only the second one is open:\n *\n * category privilege may this person manage activity instances at all\n * entitlement may this person act on **this** record\n *\n * The approval side already has the second one — `reject.ts` refuses a reject from someone\n * who is only a reference on the order. The assignment side had no equivalent.\n *\n * Starting something new is untouched: with no id there is no owner to check, and anyone in\n * the domain may begin a task.\n */\n\n/** Whoever holds the management privilege is entitled to every record, as before. */\nasync function manages(context: ResolverContext): Promise<boolean> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n return await checkPermission(\n { category: 'activity-instance', privilege: 'mutation', owner: true },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n}\n\n/**\n * May this person edit or issue this draft?\n *\n * The person who wrote it, or someone who manages activity instances. A draft is a private\n * thing until it is issued — it carries an approval line and assignees that become other\n * people's obligations the moment it goes out.\n */\nexport async function assertMayWorkOnDraft(activityInstance: ActivityInstance, context: ResolverContext) {\n const { user } = context.state\n\n if (activityInstance.creatorId === user.id) {\n return\n }\n\n if (await manages(context)) {\n return\n }\n\n throw new Error(\n context.t('error.activity-instance belongs to someone else', {\n id: activityInstance.id\n })\n )\n}\n\n/**\n * May this person pick up this task?\n *\n * ── When a role is named, it is the answer ───────────────────────────────────\n * `assigneeRole` is how an issuer says \"this is for the quality inspectors\". Reading it is\n * the whole point of writing it, and until now it was loaded and ignored.\n *\n * ── When no role is named, anyone in the domain may take it ──────────────────\n * That is the current behaviour and it is deliberate rather than a fallback: an issuer who\n * names nobody is saying \"whoever is free\". Refusing instead would make every unaddressed\n * task unpickable, which is the opposite of what leaving the field blank means.\n *\n * `reference_absent_value_leans_which_way` asks which way an absent value should lean, and\n * here the lean is the issuer's stated intent — not a guess the code makes to be safe.\n */\nexport async function assertMayPick(activityInstance: ActivityInstance, context: ResolverContext) {\n const { user, tx } = context.state\n const roleId = activityInstance.assigneeRoleId\n\n if (!roleId) {\n return\n }\n\n const holder = await tx.getRepository(User).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n\n if ((holder?.roles || []).some((role: Role) => role.id === roleId)) {\n return\n }\n\n if (await manages(context)) {\n return\n }\n\n throw new Error(\n context.t('error.activity-instance is addressed to another role', {\n id: activityInstance.id\n })\n )\n}\n"]}
|
|
@@ -6,6 +6,7 @@ const activity_js_1 = require("../../service/activity/activity.js");
|
|
|
6
6
|
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
7
7
|
const common_js_1 = require("../common.js");
|
|
8
8
|
const submit_js_1 = require("../activity-thread/submit.js");
|
|
9
|
+
const entitlement_js_1 = require("./entitlement.js");
|
|
9
10
|
const approval_expectation_js_1 = require("./approval-expectation.js");
|
|
10
11
|
async function issue(activityInstance, context) {
|
|
11
12
|
const { domain, user, tx } = context.state;
|
|
@@ -26,6 +27,15 @@ async function issue(activityInstance, context) {
|
|
|
26
27
|
]
|
|
27
28
|
})
|
|
28
29
|
: null;
|
|
30
|
+
/*
|
|
31
|
+
남의 초안은 못 내보낸다.
|
|
32
|
+
|
|
33
|
+
내보내는 순간 결재선과 담당자가 서고, 그것은 **다른 사람들의 의무**가 된다. 새로 시작하는
|
|
34
|
+
것(id 없음)은 주인이 없으므로 이 검사에 안 걸린다. 자격 규칙은 `entitlement.ts` 에 있다.
|
|
35
|
+
*/
|
|
36
|
+
if (origin) {
|
|
37
|
+
await (0, entitlement_js_1.assertMayWorkOnDraft)(origin, context);
|
|
38
|
+
}
|
|
29
39
|
/*
|
|
30
40
|
Prerequisites for a Task to Be Issued.
|
|
31
41
|
- The previous state of the task should be Draft.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/issue.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Activity } from '../../service/activity/activity.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityInstanceIssue } from '../../service/activity-instance/activity-instance-type.js'\nimport {\n createActivityThreadWithUnassigned,\n createActivityThreadsForAllRoleUsers,\n createActivityThreadsForUsers,\n getSystemUserFromOrgMemberItem,\n createActivityThreadForUser,\n fillActivitySearchKeys\n} from '../common.js'\nimport { submit } from '../activity-thread/submit.js'\nimport { expectationBroken, judgeApprovalExpectation } from './approval-expectation.js'\n\nexport async function issue(\n activityInstance: ActivityInstanceIssue,\n context: ResolverContext\n): Promise<ActivityInstance> {\n const { domain, user, tx } = context.state\n const issuedAt = new Date()\n var {\n id,\n name,\n description,\n assignees,\n uiType,\n uiSource,\n viewType,\n viewSource,\n dueAt,\n priority,\n approvalLine,\n activityId,\n activityType,\n assigneeRole,\n supervisoryRole,\n input\n } = activityInstance\n\n var origin = id\n ? await tx.getRepository(ActivityInstance).findOne({\n where: { domain: { id: domain.id }, id },\n relations: [\n 'domain',\n 'activity',\n 'assigneeRole',\n 'supervisoryRole',\n 'updater',\n 'creator',\n 'starter',\n 'terminator'\n ]\n })\n : null\n\n /* \n Prerequisites for a Task to Be Issued.\n - The previous state of the task should be Draft.\n */\n if (origin && origin.state !== ActivityInstanceStatus.Draft) {\n throw new Error(\n context.t(`error.activity-instance is already issued`, {\n id: id,\n state: origin.state\n })\n )\n }\n\n var repository = tx.getRepository(Activity)\n var activity =\n origin?.activity ||\n (await repository.findOne({\n where: { domain: domain.parentId ? { id: In([domain.id, domain.parentId]) } : { id: domain.id }, id: activityId },\n relations: ['assigneeRole', 'supervisoryRole']\n }))\n\n if (!activity) {\n throw new Error(\n context.t('error.activity not found', {\n activity: activityId\n })\n )\n }\n\n if (!origin) {\n activityInstance.name = name || activity.name\n activityInstance.description = description || activity.description\n activityInstance.activityType = activityType || activity.activityType\n activityInstance.uiType = uiType || activity.uiType\n activityInstance.uiSource = uiSource || activity.uiSource\n activityInstance.viewType = viewType || activity.viewType\n activityInstance.viewSource = viewSource || activity.viewSource\n activityInstance.assigneeRole = assigneeRole || activity.assigneeRole\n activityInstance.supervisoryRole = supervisoryRole || activity.supervisoryRole\n activityInstance.priority = priority ?? activity.priority\n\n activityInstance.adhocType = 'standard'\n activityInstance.refBy = activityId\n\n /*\n * 낸 쪽이 직접 주지 않았으면 액티비티의 선언을 따른다.\n *\n * 위의 `if (!assignees || !approvalLine)` 안에 두지 않는다 — 둘 다 준 건에서는 그 블록에 안 들어가고,\n * 그러면 이 축만 조용히 빠진다.\n */\n if (activityInstance.rejectionIsFinal === undefined || activityInstance.rejectionIsFinal === null) {\n activityInstance.rejectionIsFinal = activity.rejectionIsFinal ?? false\n }\n\n if (!assignees || !approvalLine) {\n if (!assignees) {\n assignees = activityInstance.assignees = activity.assignees\n }\n\n if (!approvalLine) {\n approvalLine = activityInstance.approvalLine = activity.approvalLine\n }\n\n if (!dueAt && activity.standardTime) {\n activityInstance.dueAt = new Date(issuedAt.getTime() + activity.standardTime * 1000)\n }\n }\n }\n\n /*\n * **결재를 받겠다고 낸 건은 결재선이 있어야 한다.**\n *\n * 액티비티의 결재선을 물려받은 뒤에 본다 — 낸 쪽이 직접 주지 않아도 액티비티가 들고 있으면 그것이\n * 이 건의 결재선이다. 물려받기 전에 보면 멀쩡한 건을 막는다.\n *\n * 초안을 다시 내는 경우 결재선은 원본이 들고 있으므로 그것까지 함께 본다.\n *\n * 판단은 `approval-expectation.ts` 가 든다 — 여기서 조건을 적으면 시험할 수 없다.\n */\n const expectation = judgeApprovalExpectation({\n requireApproval: activityInstance.requireApproval,\n approvalLine: activityInstance.approvalLine ?? origin?.approvalLine,\n activityName: activity.name\n })\n\n if (expectationBroken(expectation)) {\n throw new Error(\n context.t('error.approval line is empty but approval was required', {\n activity: expectation.activityName,\n defaultValue:\n `\"${expectation.activityName}\" 에 결재선이 없습니다. 이 건은 결재를 받기로 하고 낸 것이라 올리지 않습니다 — ` +\n '그대로 두면 결재 없이 확정됩니다.'\n })\n )\n }\n\n const activitySearchKeys = fillActivitySearchKeys(activity?.searchKeys, input)\n\n if (activity.startingType == 'post') {\n /* \n startingType이 'post'이면, \n activityInstance 이슈와 동시에 issuer에게 할당된 activityThread가 생성되고 submit까지 모두 완료되게 된다.\n 만일, 결재선이 있다면, 상신되게 된다.\n */\n const posted = await tx.getRepository(ActivityInstance).save({\n version: 1,\n creator: user,\n ...origin,\n ...activityInstance,\n transaction: 'post',\n activity,\n ...activitySearchKeys,\n state: ActivityInstanceStatus.Started,\n domain,\n issuer: user,\n updater: user,\n issuedAt,\n startedAt: issuedAt\n })\n\n const thread = await createActivityThreadForUser('post', posted, user, context)\n\n /* post 방식은 thread가 하나이므로, instance의 output을 그대로 thread의 output이라고 판단할 수 있다. 따라서, 아래 로직은 제거한다. */\n // const data = { ...activityInstance.input, ...activityInstance.output }\n // const output = (activity.model || [])\n // .filter(item => item.inout === 'inout' || item.inout === 'out')\n // .reduce((inout, item) => {\n // inout[item.tag] = data[item.tag]\n // return inout\n // }, {})\n\n await submit({ id: thread.id, output: activityInstance.output }, context)\n\n return await tx.getRepository(ActivityInstance).findOneBy({ id: posted.id })\n } else {\n const issued = await tx.getRepository(ActivityInstance).save({\n version: 1,\n creator: user,\n ...origin,\n ...activityInstance,\n transaction: 'issue',\n activity,\n ...activitySearchKeys,\n state: ActivityInstanceStatus.Issued,\n domain,\n issuer: user,\n updater: user,\n issuedAt\n })\n\n /*\n 1. 하나의 thread가 필요한 경우 (대부분)\n 조건 : activityInstance의 threadsMin이 1 이어야 한다.\n 로직 1 : getSystemUserFromOrgMemberItem 의 결과가 한명이라면 그대로 activityThread.assignee로 직접 할당한다.\n 로직 2 : getSystemUserFromOrgMemberItem 의 결과가 한명이 아니라면, assignee타입이 assigneeRole 인 경우에만 처리가 가능하다.\n 이 경우에는 assignee를 비운다. - createActivityThreadWithUnassigned\n 2. 할당되지 않은 복수개의 thread가 필요한 경우\n 조건 : threadsMin이 1 이상이어야 한다.\n 로직 1 : getSystemUserFromOrgMemberItem 의 결과와 threadsMin 값이 같다면, 그대로 activityThread.assignee로 직접 할당한다. createActivityThreadsForAllRoleUsers\n 로직 2 : createActivityThreadWithUnassigned\n 3. 모든 assignee가 처리를 해야하는 경우\n 조건 : threadsMin이 0 이어야 한다.\n 로직 : getSystemUserFromOrgMemberItem 의 결과로 createActivityThreadsForUsers\n */\n\n // assignees가 복수인 것은 아무 의미가 없다.\n const assignedUsers = await Promise.all(\n (assignees || []).map(assignee => getSystemUserFromOrgMemberItem(assignee, context))\n )\n\n if (assignedUsers.length == 0) {\n if (issued.assigneeRoleId) {\n if (issued.threadsMin === 0) {\n /* assigneeRoleId에 해당하는 모든 assignee 에게 태스크를 할당하는 경우 */\n await createActivityThreadsForAllRoleUsers('issue', issued, context)\n } else {\n /* minimum threads 설정에 따른 pending assignment 상태로 activityThread를 만드는 경우 */\n await createActivityThreadWithUnassigned('issue', issued, context)\n }\n }\n } else {\n await createActivityThreadsForUsers('issue', issued, assignedUsers, context)\n }\n\n return await tx.getRepository(ActivityInstance).findOneBy({ id: issued.id })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/issue.ts"],"names":[],"mappings":";;AAgBA,sBA6OC;AA7PD,qCAA4B;AAC5B,oEAA6D;AAC7D,+FAA+G;AAE/G,4CAOqB;AACrB,4DAAqD;AACrD,qDAAuD;AACvD,uEAAuF;AAEhF,KAAK,UAAU,KAAK,CACzB,gBAAuC,EACvC,OAAwB;IAExB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC1C,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAA;IAC3B,IAAI,EACF,EAAE,EACF,IAAI,EACJ,WAAW,EACX,SAAS,EACT,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,KAAK,EACN,GAAG,gBAAgB,CAAA;IAEpB,IAAI,MAAM,GAAG,EAAE;QACb,CAAC,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,OAAO,CAAC;YAC/C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ;gBACR,UAAU;gBACV,cAAc;gBACd,iBAAiB;gBACjB,SAAS;gBACT,SAAS;gBACT,SAAS;gBACT,YAAY;aACb;SACF,CAAC;QACJ,CAAC,CAAC,IAAI,CAAA;IAER;;;;;MAKE;IACF,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,IAAA,qCAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7C,CAAC;IAED;;;MAGE;IACF,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,6CAAsB,CAAC,KAAK,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,2CAA2C,EAAE;YACrD,EAAE,EAAE,EAAE;YACN,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CACH,CAAA;IACH,CAAC;IAED,IAAI,UAAU,GAAG,EAAE,CAAC,aAAa,CAAC,sBAAQ,CAAC,CAAA;IAC3C,IAAI,QAAQ,GACV,MAAM,EAAE,QAAQ;QAChB,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC;YACxB,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;YACjH,SAAS,EAAE,CAAC,cAAc,EAAE,iBAAiB,CAAC;SAC/C,CAAC,CAAC,CAAA;IAEL,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,0BAA0B,EAAE;YACpC,QAAQ,EAAE,UAAU;SACrB,CAAC,CACH,CAAA;IACH,CAAC;IAED,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,gBAAgB,CAAC,IAAI,GAAG,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAA;QAC7C,gBAAgB,CAAC,WAAW,GAAG,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAA;QAClE,gBAAgB,CAAC,YAAY,GAAG,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAA;QACrE,gBAAgB,CAAC,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAA;QACnD,gBAAgB,CAAC,QAAQ,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAA;QACzD,gBAAgB,CAAC,QAAQ,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAA;QACzD,gBAAgB,CAAC,UAAU,GAAG,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAA;QAC/D,gBAAgB,CAAC,YAAY,GAAG,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAA;QACrE,gBAAgB,CAAC,eAAe,GAAG,eAAe,IAAI,QAAQ,CAAC,eAAe,CAAA;QAC9E,gBAAgB,CAAC,QAAQ,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAA;QAEzD,gBAAgB,CAAC,SAAS,GAAG,UAAU,CAAA;QACvC,gBAAgB,CAAC,KAAK,GAAG,UAAU,CAAA;QAEnC;;;;;WAKG;QACH,IAAI,gBAAgB,CAAC,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;YAClG,gBAAgB,CAAC,gBAAgB,GAAG,QAAQ,CAAC,gBAAgB,IAAI,KAAK,CAAA;QACxE,CAAC;QAED,IAAI,CAAC,SAAS,IAAI,CAAC,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,gBAAgB,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAA;YAC7D,CAAC;YAED,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,YAAY,GAAG,gBAAgB,CAAC,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAA;YACtE,CAAC;YAED,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;gBACpC,gBAAgB,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;YACtF,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,WAAW,GAAG,IAAA,kDAAwB,EAAC;QAC3C,eAAe,EAAE,gBAAgB,CAAC,eAAe;QACjD,YAAY,EAAE,gBAAgB,CAAC,YAAY,IAAI,MAAM,EAAE,YAAY;QACnE,YAAY,EAAE,QAAQ,CAAC,IAAI;KAC5B,CAAC,CAAA;IAEF,IAAI,IAAA,2CAAiB,EAAC,WAAW,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,wDAAwD,EAAE;YAClE,QAAQ,EAAE,WAAW,CAAC,YAAY;YAClC,YAAY,EACV,IAAI,WAAW,CAAC,YAAY,kDAAkD;gBAC9E,qBAAqB;SACxB,CAAC,CACH,CAAA;IACH,CAAC;IAED,MAAM,kBAAkB,GAAG,IAAA,kCAAsB,EAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IAE9E,IAAI,QAAQ,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC;QACpC;;;;UAIE;QACF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,IAAI,CAAC;YAC3D,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,IAAI;YACb,GAAG,MAAM;YACT,GAAG,gBAAgB;YACnB,WAAW,EAAE,MAAM;YACnB,QAAQ;YACR,GAAG,kBAAkB;YACrB,KAAK,EAAE,6CAAsB,CAAC,OAAO;YACrC,MAAM;YACN,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;YACb,QAAQ;YACR,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,MAAM,IAAA,uCAA2B,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QAE/E,iGAAiG;QACjG,yEAAyE;QACzE,wCAAwC;QACxC,oEAAoE;QACpE,+BAA+B;QAC/B,uCAAuC;QACvC,mBAAmB;QACnB,WAAW;QAEX,MAAM,IAAA,kBAAM,EAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAA;QAEzE,OAAO,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAC9E,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,IAAI,CAAC;YAC3D,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,IAAI;YACb,GAAG,MAAM;YACT,GAAG,gBAAgB;YACnB,WAAW,EAAE,OAAO;YACpB,QAAQ;YACR,GAAG,kBAAkB;YACrB,KAAK,EAAE,6CAAsB,CAAC,MAAM;YACpC,MAAM;YACN,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;YACb,QAAQ;SACT,CAAC,CAAA;QAEF;;;;;;;;;;;;;UAaE;QAEF,+BAA+B;QAC/B,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAA,0CAA8B,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CACrF,CAAA;QAED,IAAI,aAAa,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC1B,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;oBAC5B,sDAAsD;oBACtD,MAAM,IAAA,gDAAoC,EAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;gBACtE,CAAC;qBAAM,CAAC;oBACN,0EAA0E;oBAC1E,MAAM,IAAA,8CAAkC,EAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;gBACpE,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAA,yCAA6B,EAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,CAAA;QAC9E,CAAC;QAED,OAAO,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAC9E,CAAC;AACH,CAAC","sourcesContent":["import { In } from 'typeorm'\nimport { Activity } from '../../service/activity/activity.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityInstanceIssue } from '../../service/activity-instance/activity-instance-type.js'\nimport {\n createActivityThreadWithUnassigned,\n createActivityThreadsForAllRoleUsers,\n createActivityThreadsForUsers,\n getSystemUserFromOrgMemberItem,\n createActivityThreadForUser,\n fillActivitySearchKeys\n} from '../common.js'\nimport { submit } from '../activity-thread/submit.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\nimport { expectationBroken, judgeApprovalExpectation } from './approval-expectation.js'\n\nexport async function issue(\n activityInstance: ActivityInstanceIssue,\n context: ResolverContext\n): Promise<ActivityInstance> {\n const { domain, user, tx } = context.state\n const issuedAt = new Date()\n var {\n id,\n name,\n description,\n assignees,\n uiType,\n uiSource,\n viewType,\n viewSource,\n dueAt,\n priority,\n approvalLine,\n activityId,\n activityType,\n assigneeRole,\n supervisoryRole,\n input\n } = activityInstance\n\n var origin = id\n ? await tx.getRepository(ActivityInstance).findOne({\n where: { domain: { id: domain.id }, id },\n relations: [\n 'domain',\n 'activity',\n 'assigneeRole',\n 'supervisoryRole',\n 'updater',\n 'creator',\n 'starter',\n 'terminator'\n ]\n })\n : null\n\n /*\n 남의 초안은 못 내보낸다.\n\n 내보내는 순간 결재선과 담당자가 서고, 그것은 **다른 사람들의 의무**가 된다. 새로 시작하는\n 것(id 없음)은 주인이 없으므로 이 검사에 안 걸린다. 자격 규칙은 `entitlement.ts` 에 있다.\n */\n if (origin) {\n await assertMayWorkOnDraft(origin, context)\n }\n\n /* \n Prerequisites for a Task to Be Issued.\n - The previous state of the task should be Draft.\n */\n if (origin && origin.state !== ActivityInstanceStatus.Draft) {\n throw new Error(\n context.t(`error.activity-instance is already issued`, {\n id: id,\n state: origin.state\n })\n )\n }\n\n var repository = tx.getRepository(Activity)\n var activity =\n origin?.activity ||\n (await repository.findOne({\n where: { domain: domain.parentId ? { id: In([domain.id, domain.parentId]) } : { id: domain.id }, id: activityId },\n relations: ['assigneeRole', 'supervisoryRole']\n }))\n\n if (!activity) {\n throw new Error(\n context.t('error.activity not found', {\n activity: activityId\n })\n )\n }\n\n if (!origin) {\n activityInstance.name = name || activity.name\n activityInstance.description = description || activity.description\n activityInstance.activityType = activityType || activity.activityType\n activityInstance.uiType = uiType || activity.uiType\n activityInstance.uiSource = uiSource || activity.uiSource\n activityInstance.viewType = viewType || activity.viewType\n activityInstance.viewSource = viewSource || activity.viewSource\n activityInstance.assigneeRole = assigneeRole || activity.assigneeRole\n activityInstance.supervisoryRole = supervisoryRole || activity.supervisoryRole\n activityInstance.priority = priority ?? activity.priority\n\n activityInstance.adhocType = 'standard'\n activityInstance.refBy = activityId\n\n /*\n * 낸 쪽이 직접 주지 않았으면 액티비티의 선언을 따른다.\n *\n * 위의 `if (!assignees || !approvalLine)` 안에 두지 않는다 — 둘 다 준 건에서는 그 블록에 안 들어가고,\n * 그러면 이 축만 조용히 빠진다.\n */\n if (activityInstance.rejectionIsFinal === undefined || activityInstance.rejectionIsFinal === null) {\n activityInstance.rejectionIsFinal = activity.rejectionIsFinal ?? false\n }\n\n if (!assignees || !approvalLine) {\n if (!assignees) {\n assignees = activityInstance.assignees = activity.assignees\n }\n\n if (!approvalLine) {\n approvalLine = activityInstance.approvalLine = activity.approvalLine\n }\n\n if (!dueAt && activity.standardTime) {\n activityInstance.dueAt = new Date(issuedAt.getTime() + activity.standardTime * 1000)\n }\n }\n }\n\n /*\n * **결재를 받겠다고 낸 건은 결재선이 있어야 한다.**\n *\n * 액티비티의 결재선을 물려받은 뒤에 본다 — 낸 쪽이 직접 주지 않아도 액티비티가 들고 있으면 그것이\n * 이 건의 결재선이다. 물려받기 전에 보면 멀쩡한 건을 막는다.\n *\n * 초안을 다시 내는 경우 결재선은 원본이 들고 있으므로 그것까지 함께 본다.\n *\n * 판단은 `approval-expectation.ts` 가 든다 — 여기서 조건을 적으면 시험할 수 없다.\n */\n const expectation = judgeApprovalExpectation({\n requireApproval: activityInstance.requireApproval,\n approvalLine: activityInstance.approvalLine ?? origin?.approvalLine,\n activityName: activity.name\n })\n\n if (expectationBroken(expectation)) {\n throw new Error(\n context.t('error.approval line is empty but approval was required', {\n activity: expectation.activityName,\n defaultValue:\n `\"${expectation.activityName}\" 에 결재선이 없습니다. 이 건은 결재를 받기로 하고 낸 것이라 올리지 않습니다 — ` +\n '그대로 두면 결재 없이 확정됩니다.'\n })\n )\n }\n\n const activitySearchKeys = fillActivitySearchKeys(activity?.searchKeys, input)\n\n if (activity.startingType == 'post') {\n /* \n startingType이 'post'이면, \n activityInstance 이슈와 동시에 issuer에게 할당된 activityThread가 생성되고 submit까지 모두 완료되게 된다.\n 만일, 결재선이 있다면, 상신되게 된다.\n */\n const posted = await tx.getRepository(ActivityInstance).save({\n version: 1,\n creator: user,\n ...origin,\n ...activityInstance,\n transaction: 'post',\n activity,\n ...activitySearchKeys,\n state: ActivityInstanceStatus.Started,\n domain,\n issuer: user,\n updater: user,\n issuedAt,\n startedAt: issuedAt\n })\n\n const thread = await createActivityThreadForUser('post', posted, user, context)\n\n /* post 방식은 thread가 하나이므로, instance의 output을 그대로 thread의 output이라고 판단할 수 있다. 따라서, 아래 로직은 제거한다. */\n // const data = { ...activityInstance.input, ...activityInstance.output }\n // const output = (activity.model || [])\n // .filter(item => item.inout === 'inout' || item.inout === 'out')\n // .reduce((inout, item) => {\n // inout[item.tag] = data[item.tag]\n // return inout\n // }, {})\n\n await submit({ id: thread.id, output: activityInstance.output }, context)\n\n return await tx.getRepository(ActivityInstance).findOneBy({ id: posted.id })\n } else {\n const issued = await tx.getRepository(ActivityInstance).save({\n version: 1,\n creator: user,\n ...origin,\n ...activityInstance,\n transaction: 'issue',\n activity,\n ...activitySearchKeys,\n state: ActivityInstanceStatus.Issued,\n domain,\n issuer: user,\n updater: user,\n issuedAt\n })\n\n /*\n 1. 하나의 thread가 필요한 경우 (대부분)\n 조건 : activityInstance의 threadsMin이 1 이어야 한다.\n 로직 1 : getSystemUserFromOrgMemberItem 의 결과가 한명이라면 그대로 activityThread.assignee로 직접 할당한다.\n 로직 2 : getSystemUserFromOrgMemberItem 의 결과가 한명이 아니라면, assignee타입이 assigneeRole 인 경우에만 처리가 가능하다.\n 이 경우에는 assignee를 비운다. - createActivityThreadWithUnassigned\n 2. 할당되지 않은 복수개의 thread가 필요한 경우\n 조건 : threadsMin이 1 이상이어야 한다.\n 로직 1 : getSystemUserFromOrgMemberItem 의 결과와 threadsMin 값이 같다면, 그대로 activityThread.assignee로 직접 할당한다. createActivityThreadsForAllRoleUsers\n 로직 2 : createActivityThreadWithUnassigned\n 3. 모든 assignee가 처리를 해야하는 경우\n 조건 : threadsMin이 0 이어야 한다.\n 로직 : getSystemUserFromOrgMemberItem 의 결과로 createActivityThreadsForUsers\n */\n\n // assignees가 복수인 것은 아무 의미가 없다.\n const assignedUsers = await Promise.all(\n (assignees || []).map(assignee => getSystemUserFromOrgMemberItem(assignee, context))\n )\n\n if (assignedUsers.length == 0) {\n if (issued.assigneeRoleId) {\n if (issued.threadsMin === 0) {\n /* assigneeRoleId에 해당하는 모든 assignee 에게 태스크를 할당하는 경우 */\n await createActivityThreadsForAllRoleUsers('issue', issued, context)\n } else {\n /* minimum threads 설정에 따른 pending assignment 상태로 activityThread를 만드는 경우 */\n await createActivityThreadWithUnassigned('issue', issued, context)\n }\n }\n } else {\n await createActivityThreadsForUsers('issue', issued, assignedUsers, context)\n }\n\n return await tx.getRepository(ActivityInstance).findOneBy({ id: issued.id })\n }\n}\n"]}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.pick = pick;
|
|
4
4
|
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
5
5
|
const common_js_1 = require("../common.js");
|
|
6
|
+
const entitlement_js_1 = require("./entitlement.js");
|
|
6
7
|
async function pick(id, context) {
|
|
7
8
|
const { domain, user, tx } = context.state;
|
|
8
9
|
const repository = tx.getRepository(activity_instance_js_1.ActivityInstance);
|
|
@@ -15,6 +16,13 @@ async function pick(id, context) {
|
|
|
15
16
|
activityInstance: id
|
|
16
17
|
}));
|
|
17
18
|
}
|
|
19
|
+
/*
|
|
20
|
+
누가 집을 수 있나 — 상태만 보면 **아무나 집는다.**
|
|
21
|
+
|
|
22
|
+
`assigneeRole` 을 relations 로 불러오면서 한 번도 안 봤다. 그래서 그 도메인에 로그인한
|
|
23
|
+
사람이면 특정 역할에 걸어 둔 업무도 집을 수 있었다. 자격 규칙은 `entitlement.ts` 에 있다.
|
|
24
|
+
*/
|
|
25
|
+
await (0, entitlement_js_1.assertMayPick)(activityInstance, context);
|
|
18
26
|
/*
|
|
19
27
|
Prerequisites for a Task to Be Picked.
|
|
20
28
|
- The previous state of the task must not be Assigned.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pick.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/pick.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"pick.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/pick.ts"],"names":[],"mappings":";;AAKA,oBAgDC;AArDD,+FAA+G;AAE/G,4CAA4D;AAC5D,qDAAgD;AAEzC,KAAK,UAAU,IAAI,CAAC,EAAU,EAAE,OAAwB;IAC7D,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC1C,MAAM,UAAU,GAAG,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAA;IAErD,IAAI,gBAAgB,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;QAC9C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;QACxC,SAAS,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC;KACpH,CAAC,CAAA;IAEF,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,mCAAmC,EAAE;YAC7C,gBAAgB,EAAE,EAAE;SACrB,CAAC,CACH,CAAA;IACH,CAAC;IAED;;;;;MAKE;IACF,MAAM,IAAA,8BAAa,EAAC,gBAAgB,EAAE,OAAO,CAAC,CAAA;IAE9C;;;MAGE;IACF,IACE,gBAAgB,CAAC,KAAK,KAAK,6CAAsB,CAAC,MAAM;QACxD,gBAAgB,CAAC,KAAK,KAAK,6CAAsB,CAAC,UAAU,EAC5D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,CAAC,CAAC,gDAAgD,EAAE;YAC1D,EAAE;YACF,MAAM,EAAE,gBAAgB,CAAC,KAAK;SAC/B,CAAC,CACH,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC;QACnC,GAAG,gBAAgB;QACnB,WAAW,EAAE,MAAM;QACnB,OAAO,EAAE,IAAI;KACd,CAAC,CAAA;IAEF,OAAO,CAAC,MAAM,IAAA,yCAA6B,EAAC,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAClF,CAAC","sourcesContent":["import { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\nimport { createActivityThreadsForUsers } from '../common.js'\nimport { assertMayPick } from './entitlement.js'\n\nexport async function pick(id: string, context: ResolverContext): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n const repository = tx.getRepository(ActivityInstance)\n\n var activityInstance = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['domain', 'activity', 'assigneeRole', 'supervisoryRole', 'updater', 'creator', 'starter', 'terminator']\n })\n\n if (!activityInstance) {\n throw new Error(\n context.t('error.activity-instance not found', {\n activityInstance: id\n })\n )\n }\n\n /*\n 누가 집을 수 있나 — 상태만 보면 **아무나 집는다.**\n\n `assigneeRole` 을 relations 로 불러오면서 한 번도 안 봤다. 그래서 그 도메인에 로그인한\n 사람이면 특정 역할에 걸어 둔 업무도 집을 수 있었다. 자격 규칙은 `entitlement.ts` 에 있다.\n */\n await assertMayPick(activityInstance, context)\n\n /* \n Prerequisites for a Task to Be Picked.\n - The previous state of the task must not be Assigned.\n */\n if (\n activityInstance.state !== ActivityInstanceStatus.Issued &&\n activityInstance.state !== ActivityInstanceStatus.Unassigned\n ) {\n throw new Error(\n context.t(`error.activity-instance should not be assigned`, {\n id,\n actual: activityInstance.state\n })\n )\n }\n\n const picked = await repository.save({\n ...activityInstance,\n transaction: 'pick',\n updater: user\n })\n\n return (await createActivityThreadsForUsers('pick', picked, [user], context))[0]\n}\n"]}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The part an input value plays on the approval screen.
|
|
3
|
+
*
|
|
4
|
+
* ── The shape of the set: three tenses, and five others ───────────────────
|
|
5
|
+
* Three of the eight say something about the subject at a point in time, and the other five do not:
|
|
6
|
+
*
|
|
7
|
+
* past evidence what the decision was made from (may repeat)
|
|
8
|
+
* present currentState how things stand right now
|
|
9
|
+
* future consequence what approving would change (may repeat)
|
|
10
|
+
*
|
|
11
|
+
* subject what this is about
|
|
12
|
+
* reason what a person wrote
|
|
13
|
+
* policy why it came up for approval at all
|
|
14
|
+
* headline one sentence built from the values
|
|
15
|
+
* reversibility whether approving can be undone
|
|
16
|
+
*
|
|
17
|
+
* Every mis-slotting found so far was the same mistake in that column: currentState under
|
|
18
|
+
* consequence read the present as the future, and evidence under currentState read the past as the
|
|
19
|
+
* present. So when a ninth role is asked for, the first question is whether the value is the
|
|
20
|
+
* subject's past, present or future — if it is, one of those three already holds it. If it is not,
|
|
21
|
+
* whoever asks should be able to say what it is instead. (2026-09-10, architect's ruling)
|
|
22
|
+
*
|
|
23
|
+
* ── Why the keys and the values are spelled the same ──────────────────────
|
|
24
|
+
* type-graphql publishes an enum by its **keys** when the field names the enum type, and by its
|
|
25
|
+
* **values** when the field leaves the type off. One word in a @Field decorator therefore decides
|
|
26
|
+
* what a client receives, and a schema-improving edit far away can flip it. That has already cost
|
|
27
|
+
* this repository twice: ApprovalLineItem.participation arrives as `Reference` and was compared
|
|
28
|
+
* against `reference`, false every time; the catalog page read `NotWorklist` where its selector,
|
|
29
|
+
* i18n key and stage list all said `notworklist`.
|
|
30
|
+
*
|
|
31
|
+
* Writing each key as its own value takes the choice away — either spelling produces the same
|
|
32
|
+
* string. ActivityModelItemInoutType and ActivityModelItemType are written the same way.
|
|
33
|
+
*
|
|
34
|
+
* ── The set is closed, and widening it is the architect's call ────────────
|
|
35
|
+
* Left open as a free string, a convention becomes suffix-appending: every lane invents its own
|
|
36
|
+
* word for the same slot and the screen ends up switching on a dozen spellings, printing the
|
|
37
|
+
* unmatched ones. Adding a role here is an architecture decision, not a lane's. (2026-09-09,
|
|
38
|
+
* architect's ruling)
|
|
39
|
+
*/
|
|
40
|
+
export declare enum ActivityDecisionRole {
|
|
41
|
+
/** One sentence built from the values: 「폐기 30 EA」, 「3에서 1로 올림」. */
|
|
42
|
+
headline = "headline",
|
|
43
|
+
/** What the decision is about — one identifier. */
|
|
44
|
+
subject = "subject",
|
|
45
|
+
/** What the person who raised it wrote. Absent, the screen says so rather than leaving a gap. */
|
|
46
|
+
reason = "reason",
|
|
47
|
+
/**
|
|
48
|
+
* What changes on approval: 「주문 넷이 뒤로 밀린다」. The only role that may appear more than once.
|
|
49
|
+
*
|
|
50
|
+
* Named consequence rather than effect because TwinCommand.effect already means the opposite in
|
|
51
|
+
* time: { status, at, seen }, what the source reported back after the command was dispatched.
|
|
52
|
+
* That one is an observation and past; this one is a prediction and future. Using a real term for
|
|
53
|
+
* the wrong meaning is worse than coining one, and plant already writes .consequence for exactly
|
|
54
|
+
* this sense in its stop-reason dialog. (2026-09-10, architect's ruling)
|
|
55
|
+
*/
|
|
56
|
+
consequence = "consequence",
|
|
57
|
+
/** Why this came up for approval at all: 「폐기는 100 이상이면 결재」. */
|
|
58
|
+
policy = "policy",
|
|
59
|
+
/**
|
|
60
|
+
* What is measured to be true right now — about the world, not about the request.
|
|
61
|
+
*
|
|
62
|
+
* The other roles all describe what is being asked for. This one does not, and putting it under
|
|
63
|
+
* consequence made the screen say the opposite of the truth: the twin's current readings would
|
|
64
|
+
* have read as things the approval causes.
|
|
65
|
+
*
|
|
66
|
+
* Not named observed: ops-contract already has observedBy for which observation backs a declared
|
|
67
|
+
* property, and TwinCommand.effect is an observation too, so observed would sit in the most
|
|
68
|
+
* confusable spot between them. (2026-09-10, architect's ruling) plant never needed it because the subject's present state
|
|
69
|
+
* went into the headline (「폐기 30 EA」); the twin's instruction and its current state are two
|
|
70
|
+
* separate things, and holding them side by side is what this screen is for.
|
|
71
|
+
*/
|
|
72
|
+
currentState = "currentState",
|
|
73
|
+
/**
|
|
74
|
+
* Whether approving can be undone. Drawn in the frame rather than in the body.
|
|
75
|
+
*
|
|
76
|
+
* Not a separate role for want of a place — it fits consequence well enough — but for want of
|
|
77
|
+
* weight. Listed among the consequences it reads with the same force as "four orders get pushed
|
|
78
|
+
* back", and on
|
|
79
|
+
* the twin actuation, where approving moves equipment, it is the heaviest line on the screen.
|
|
80
|
+
*
|
|
81
|
+
* ── Not the same as rejectionIsFinal ──────────────────────────────────
|
|
82
|
+
* Two "cannot be undone" facts attach to opposite actions, and the twin actuation carries both,
|
|
83
|
+
* which is what makes them look like one:
|
|
84
|
+
*
|
|
85
|
+
* reversibility whether **approving** can be undone (this role, read from the input)
|
|
86
|
+
* rejectionIsFinal whether **rejecting** ends the case (a column on Activity)
|
|
87
|
+
*
|
|
88
|
+
* Merging them in a screen sentence tells the reader that rejection can be taken back.
|
|
89
|
+
*/
|
|
90
|
+
reversibility = "reversibility",
|
|
91
|
+
/**
|
|
92
|
+
* What the decision was made from — which inspection, against which spec, decided when.
|
|
93
|
+
*
|
|
94
|
+
* Neither consequence nor currentState: consequence is the opposite direction in time, and under
|
|
95
|
+
* currentState a past finding reads as the present state of the subject. plant's disposition
|
|
96
|
+
* approval kept its own screen for exactly these three values, and it survived the comparison not
|
|
97
|
+
* because a slot could not hold them but because no slot existed — those are different reasons,
|
|
98
|
+
* and only the second one means the set is short.
|
|
99
|
+
*
|
|
100
|
+
* plant's case for it: someone approving a concession in a regulated plant who cannot see which
|
|
101
|
+
* inspection produced the finding is approving without grounds.
|
|
102
|
+
*
|
|
103
|
+
* Repeats, because grounds come in several — there were three.
|
|
104
|
+
*/
|
|
105
|
+
evidence = "evidence"
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One value the approver reads, and the part it plays.
|
|
109
|
+
*
|
|
110
|
+
* ── Why this points at input rather than at a model item ──────────────────
|
|
111
|
+
* inputPath names a value inside the instance's input and does not go through the activity's model.
|
|
112
|
+
* That is the whole reason it is a separate array: the values an approver most needs are computed by
|
|
113
|
+
* the registering module and put on the input, not declared as model items — plant's `policy` and
|
|
114
|
+
* its `shift.passes` / `shift.passedBy` are exactly that. A role hung on a model item could not
|
|
115
|
+
* reach them.
|
|
116
|
+
*
|
|
117
|
+
* ── inputPath, not inputKey ───────────────────────────────────────────────
|
|
118
|
+
* ActivitySearchKeyItem.inputKey names one top-level key, because its value goes into a searchable
|
|
119
|
+
* column and a path into a list is not something that can be indexed. This field takes a path,
|
|
120
|
+
* because it points at a value to draw. The two do different work, so they carry different names —
|
|
121
|
+
* one word with two meanings would make the wrong one silent, and a name that does not exist is
|
|
122
|
+
* read as a mistake instead. (2026-09-09, architect's ruling)
|
|
123
|
+
*
|
|
124
|
+
* 'policy' → input.policy
|
|
125
|
+
* 'shift.passes' → input.shift.passes
|
|
126
|
+
*
|
|
127
|
+
* Dots only. No array index — drawing a list is the slot's job, not the path's — and no wildcard. A
|
|
128
|
+
* path that leads nowhere resolves to undefined and is not folded into an empty value: a `reason`
|
|
129
|
+
* that is missing and a `reason` someone left blank are different facts, and the screen draws them
|
|
130
|
+
* differently.
|
|
131
|
+
*
|
|
132
|
+
* The two declarations also address different readers. model declares the boxes a performer fills;
|
|
133
|
+
* this declares what an approver reads. Putting both on one item leaves "is this an input or
|
|
134
|
+
* something to judge by" unanswerable.
|
|
135
|
+
*
|
|
136
|
+
* Order matters. plant wrote four of these screens by hand and all four laid the slots out in the
|
|
137
|
+
* same order, which makes the order the screen's hierarchy rather than a preference — so this is an
|
|
138
|
+
* ordered array and not an object keyed by role.
|
|
139
|
+
*/
|
|
140
|
+
export declare class ActivityDecisionKeyItem {
|
|
141
|
+
role: ActivityDecisionRole;
|
|
142
|
+
inputPath: string;
|
|
143
|
+
name?: string;
|
|
144
|
+
tKey?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Unit to print after the value, when the activity fixes the unit.
|
|
147
|
+
*
|
|
148
|
+
* Declared once, so it holds for every instance of the activity — 「30 건」 works, and a unit that
|
|
149
|
+
* varies per record does not: plant's disposition carries the unit alongside the quantity, so the
|
|
150
|
+
* same activity produces 「3 EA」 and 「12.5 KG」. Writing one of them here would attach the wrong
|
|
151
|
+
* unit to the other records, with nothing to catch it.
|
|
152
|
+
*
|
|
153
|
+
* A unit that varies belongs in the headline sentence the module builds, which is what plant did:
|
|
154
|
+
* 「LOT-A 3 EA 를 폐기합니다」. Leave this empty then.
|
|
155
|
+
*
|
|
156
|
+
* Delete this field if no activity ever turns out to fix a unit. A field nobody writes stays
|
|
157
|
+
* forever and the next person reads a meaning into it. (2026-09-10, architect's ruling)
|
|
158
|
+
*/
|
|
159
|
+
unit?: string;
|
|
160
|
+
}
|