@things-factory/worklist 10.1.42 → 10.1.44
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/decision-slots.d.ts +14 -1
- package/dist-client/components/decision-slots.js +33 -1
- package/dist-client/components/decision-slots.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.js +3 -3
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-approval/approval-gate.d.ts +14 -6
- package/dist-client/pages/activity-approval/approval-gate.js +17 -5
- package/dist-client/pages/activity-approval/approval-gate.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-instance/approval-line-input.d.ts +29 -0
- package/dist-server/controllers/activity-instance/approval-line-input.js +44 -0
- package/dist-server/controllers/activity-instance/approval-line-input.js.map +1 -0
- package/dist-server/controllers/activity-instance/assign.js +12 -2
- package/dist-server/controllers/activity-instance/assign.js.map +1 -1
- package/dist-server/controllers/activity-instance/claim-draft.d.ts +17 -0
- package/dist-server/controllers/activity-instance/claim-draft.js +30 -0
- package/dist-server/controllers/activity-instance/claim-draft.js.map +1 -0
- package/dist-server/controllers/activity-instance/discard-draft.d.ts +5 -5
- package/dist-server/controllers/activity-instance/discard-draft.js +5 -5
- package/dist-server/controllers/activity-instance/discard-draft.js.map +1 -1
- package/dist-server/controllers/activity-instance/draft.js +11 -0
- package/dist-server/controllers/activity-instance/draft.js.map +1 -1
- package/dist-server/controllers/activity-instance/issue.js +20 -0
- package/dist-server/controllers/activity-instance/issue.js.map +1 -1
- package/dist-server/controllers/activity-instance/pick.js +16 -0
- package/dist-server/controllers/activity-instance/pick.js.map +1 -1
- package/dist-server/controllers/common.js +4 -3
- package/dist-server/controllers/common.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -3
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/tests/approval-line-input.test.ts +112 -0
- package/tests/delegate-thread.test.ts +101 -0
- package/tests/discard-draft.test.ts +53 -15
- package/tests/discard-versus-issue.pg.test.ts +127 -0
- package/tests/org-member-domain.test.ts +87 -0
- package/tests/pick-race.pg.test.ts +128 -0
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval line a caller may hand in when drafting or issuing (ruling 2026-09-24).
|
|
3
|
+
*
|
|
4
|
+
* The approval line of an activity is set in one place, the activity's detail. A case that carries
|
|
5
|
+
* its own line would let whoever raises it choose who approves it - a line of references only ends
|
|
6
|
+
* with nobody judging, and anyone can be named. So:
|
|
7
|
+
*
|
|
8
|
+
* the activity has a line the input is absent, or the same line - else a named refusal
|
|
9
|
+
* the activity has none the input is taken (an ad hoc line)
|
|
10
|
+
*
|
|
11
|
+
* No exception for a manager: a per-case way around the activity's line would make the line live in
|
|
12
|
+
* two places.
|
|
13
|
+
*
|
|
14
|
+
* "The same" is step by step - who (value), in what capacity (participation) and in what order - not
|
|
15
|
+
* the objects' key order. plant's server doors pass the line of the activity they read by name, which
|
|
16
|
+
* is this very line, and they must keep passing.
|
|
17
|
+
*/
|
|
18
|
+
type Step = {
|
|
19
|
+
value?: string;
|
|
20
|
+
approver?: {
|
|
21
|
+
id?: string;
|
|
22
|
+
} | null;
|
|
23
|
+
participation?: string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare const LINE_IS_SET = "approval-line-is-set";
|
|
26
|
+
export declare function sameApprovalLine(a: readonly Step[] | null | undefined, b: readonly Step[] | null | undefined): boolean;
|
|
27
|
+
/** null when the input may stand; LINE_IS_SET when it would replace the activity's line. */
|
|
28
|
+
export declare function approvalLineInputRefusal(activityLine: readonly Step[] | null | undefined, input: readonly Step[] | null | undefined): typeof LINE_IS_SET | null;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The approval line a caller may hand in when drafting or issuing (ruling 2026-09-24).
|
|
4
|
+
*
|
|
5
|
+
* The approval line of an activity is set in one place, the activity's detail. A case that carries
|
|
6
|
+
* its own line would let whoever raises it choose who approves it - a line of references only ends
|
|
7
|
+
* with nobody judging, and anyone can be named. So:
|
|
8
|
+
*
|
|
9
|
+
* the activity has a line the input is absent, or the same line - else a named refusal
|
|
10
|
+
* the activity has none the input is taken (an ad hoc line)
|
|
11
|
+
*
|
|
12
|
+
* No exception for a manager: a per-case way around the activity's line would make the line live in
|
|
13
|
+
* two places.
|
|
14
|
+
*
|
|
15
|
+
* "The same" is step by step - who (value), in what capacity (participation) and in what order - not
|
|
16
|
+
* the objects' key order. plant's server doors pass the line of the activity they read by name, which
|
|
17
|
+
* is this very line, and they must keep passing.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.LINE_IS_SET = void 0;
|
|
21
|
+
exports.sameApprovalLine = sameApprovalLine;
|
|
22
|
+
exports.approvalLineInputRefusal = approvalLineInputRefusal;
|
|
23
|
+
exports.LINE_IS_SET = 'approval-line-is-set';
|
|
24
|
+
/** Empty participation means approval (approval-line.ts). */
|
|
25
|
+
function capacity(step) {
|
|
26
|
+
return step?.participation === 'reference' ? 'reference' : 'approval';
|
|
27
|
+
}
|
|
28
|
+
function who(step) {
|
|
29
|
+
return String(step?.value || step?.approver?.id || '');
|
|
30
|
+
}
|
|
31
|
+
function sameApprovalLine(a, b) {
|
|
32
|
+
const left = a || [];
|
|
33
|
+
const right = b || [];
|
|
34
|
+
return left.length === right.length && left.every((step, order) => who(step) === who(right[order]) && capacity(step) === capacity(right[order]));
|
|
35
|
+
}
|
|
36
|
+
/** null when the input may stand; LINE_IS_SET when it would replace the activity's line. */
|
|
37
|
+
function approvalLineInputRefusal(activityLine, input) {
|
|
38
|
+
if (!input)
|
|
39
|
+
return null;
|
|
40
|
+
if (!activityLine?.length)
|
|
41
|
+
return null;
|
|
42
|
+
return sameApprovalLine(activityLine, input) ? null : exports.LINE_IS_SET;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=approval-line-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-line-input.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/approval-line-input.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAeH,4CAKC;AAGD,4DAQC;AA3BY,QAAA,WAAW,GAAG,sBAAsB,CAAA;AAEjD,6DAA6D;AAC7D,SAAS,QAAQ,CAAC,IAAU;IAC1B,OAAO,IAAI,EAAE,aAAa,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAA;AACvE,CAAC;AAED,SAAS,GAAG,CAAC,IAAU;IACrB,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AACxD,CAAC;AAED,SAAgB,gBAAgB,CAAC,CAAqC,EAAE,CAAqC;IAC3G,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAA;IACpB,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,CAAA;IAErB,OAAO,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClJ,CAAC;AAED,4FAA4F;AAC5F,SAAgB,wBAAwB,CACtC,YAAgD,EAChD,KAAyC;IAEzC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,IAAI,CAAC,YAAY,EAAE,MAAM;QAAE,OAAO,IAAI,CAAA;IAEtC,OAAO,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAW,CAAA;AACnE,CAAC","sourcesContent":["/**\n * The approval line a caller may hand in when drafting or issuing (ruling 2026-09-24).\n *\n * The approval line of an activity is set in one place, the activity's detail. A case that carries\n * its own line would let whoever raises it choose who approves it - a line of references only ends\n * with nobody judging, and anyone can be named. So:\n *\n * the activity has a line the input is absent, or the same line - else a named refusal\n * the activity has none the input is taken (an ad hoc line)\n *\n * No exception for a manager: a per-case way around the activity's line would make the line live in\n * two places.\n *\n * \"The same\" is step by step - who (value), in what capacity (participation) and in what order - not\n * the objects' key order. plant's server doors pass the line of the activity they read by name, which\n * is this very line, and they must keep passing.\n */\n\ntype Step = { value?: string; approver?: { id?: string } | null; participation?: string | null }\n\nexport const LINE_IS_SET = 'approval-line-is-set'\n\n/** Empty participation means approval (approval-line.ts). */\nfunction capacity(step: Step): string {\n return step?.participation === 'reference' ? 'reference' : 'approval'\n}\n\nfunction who(step: Step): string {\n return String(step?.value || step?.approver?.id || '')\n}\n\nexport function sameApprovalLine(a: readonly Step[] | null | undefined, b: readonly Step[] | null | undefined): boolean {\n const left = a || []\n const right = b || []\n\n return left.length === right.length && left.every((step, order) => who(step) === who(right[order]) && capacity(step) === capacity(right[order]))\n}\n\n/** null when the input may stand; LINE_IS_SET when it would replace the activity's line. */\nexport function approvalLineInputRefusal(\n activityLine: readonly Step[] | null | undefined,\n input: readonly Step[] | null | undefined\n): typeof LINE_IS_SET | null {\n if (!input) return null\n if (!activityLine?.length) return null\n\n return sameApprovalLine(activityLine, input) ? null : LINE_IS_SET\n}\n"]}
|
|
@@ -31,9 +31,19 @@ async function assign({ id, assignees, reason, dueAt }, context) {
|
|
|
31
31
|
if (dueAt) {
|
|
32
32
|
activityInstance.dueAt = dueAt;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/*
|
|
35
|
+
* People of this domain only. By email alone a manager could hand the task to someone of another
|
|
36
|
+
* tenant, whose to-do list reads their own domain and would never show it. An address that is not
|
|
37
|
+
* here is named rather than dropped - dropped, the task went to fewer people than were asked.
|
|
38
|
+
*/
|
|
39
|
+
const assignedUsers = await tx.getRepository(auth_base_1.User).find({
|
|
40
|
+
where: { email: (0, typeorm_1.In)(assignees || []), domains: { id: domain.id } },
|
|
41
|
+
relations: ['domains']
|
|
36
42
|
});
|
|
43
|
+
const missing = (assignees || []).filter(email => !assignedUsers.some(one => one.email === email));
|
|
44
|
+
if (missing.length) {
|
|
45
|
+
throw new Error(context.t('error.activity-instance assignee not in this domain', { emails: missing.join(', ') }));
|
|
46
|
+
}
|
|
37
47
|
await (0, common_js_1.createActivityThreadsForUsers)('assign', activityInstance, assignedUsers, context);
|
|
38
48
|
return await tx.getRepository(activity_instance_js_1.ActivityInstance).findOneBy({ id: activityInstance.id });
|
|
39
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assign.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/assign.ts"],"names":[],"mappings":";;AAOA,
|
|
1
|
+
{"version":3,"file":"assign.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/assign.ts"],"names":[],"mappings":";;AAOA,wBA4DC;AAnED,qCAA4B;AAE5B,yDAAgD;AAEhD,+FAA+G;AAC/G,4CAA4D;AAErD,KAAK,UAAU,MAAM,CAC1B,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAuE,EACrG,OAAwB;IAExB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE1C,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;;;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,IAAI,KAAK,EAAE,CAAC;QACV,gBAAgB,CAAC,KAAK,GAAG,KAAK,CAAA;IAChC,CAAC;IAED;;;;OAIG;IACH,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,gBAAI,CAAC,CAAC,IAAI,CAAC;QACtD,KAAK,EAAE,EAAE,KAAK,EAAE,IAAA,YAAE,EAAC,SAAS,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE;QACjE,SAAS,EAAE,CAAC,SAAS,CAAC;KACvB,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAA;IAElG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qDAAqD,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACnH,CAAC;IAED,MAAM,IAAA,yCAA6B,EAAC,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,OAAO,CAAC,CAAA;IAEvF,OAAO,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAA;AACxF,CAAC","sourcesContent":["import { In } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\n\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { createActivityThreadsForUsers } from '../common.js'\n\nexport async function assign(\n { id, assignees, reason, dueAt }: { id: string; assignees?: string[]; reason?: string; dueAt?: Date },\n context: ResolverContext\n): Promise<ActivityInstance> {\n const { domain, user, tx } = context.state\n\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 Prerequisites for a Task to Be Assigned.\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 if (dueAt) {\n activityInstance.dueAt = dueAt\n }\n\n /*\n * People of this domain only. By email alone a manager could hand the task to someone of another\n * tenant, whose to-do list reads their own domain and would never show it. An address that is not\n * here is named rather than dropped - dropped, the task went to fewer people than were asked.\n */\n const assignedUsers = await tx.getRepository(User).find({\n where: { email: In(assignees || []), domains: { id: domain.id } },\n relations: ['domains']\n })\n\n const missing = (assignees || []).filter(email => !assignedUsers.some(one => one.email === email))\n\n if (missing.length) {\n throw new Error(context.t('error.activity-instance assignee not in this domain', { emails: missing.join(', ') }))\n }\n\n await createActivityThreadsForUsers('assign', activityInstance, assignedUsers, context)\n\n return await tx.getRepository(ActivityInstance).findOneBy({ id: activityInstance.id })\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Take hold of a draft before working on it - so that a discard and an issue of the same draft cannot
|
|
3
|
+
* both succeed.
|
|
4
|
+
*
|
|
5
|
+
* Measured on postgres (tests/discard-versus-issue.pg.test.ts): issue read the draft, a discard deleted
|
|
6
|
+
* it, and issue then saved what it had read - the row came back, issued, and the writer had been told it
|
|
7
|
+
* was thrown away. Reading and saving later holds nothing.
|
|
8
|
+
*
|
|
9
|
+
* The claim is an UPDATE that only matches a draft. It takes the row's lock on every driver (SELECT ...
|
|
10
|
+
* FOR UPDATE is not supported on sqlite), and the lock is held until this transaction ends:
|
|
11
|
+
*
|
|
12
|
+
* this claims first a discard waits, then finds no draft and is refused by name
|
|
13
|
+
* a discard is first this waits, then matches no row and is refused by name
|
|
14
|
+
*
|
|
15
|
+
* No row matched means the draft went out or was thrown away in the meantime.
|
|
16
|
+
*/
|
|
17
|
+
export declare function claimDraft(id: string, context: ResolverContext): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claimDraft = claimDraft;
|
|
4
|
+
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
5
|
+
/**
|
|
6
|
+
* Take hold of a draft before working on it - so that a discard and an issue of the same draft cannot
|
|
7
|
+
* both succeed.
|
|
8
|
+
*
|
|
9
|
+
* Measured on postgres (tests/discard-versus-issue.pg.test.ts): issue read the draft, a discard deleted
|
|
10
|
+
* it, and issue then saved what it had read - the row came back, issued, and the writer had been told it
|
|
11
|
+
* was thrown away. Reading and saving later holds nothing.
|
|
12
|
+
*
|
|
13
|
+
* The claim is an UPDATE that only matches a draft. It takes the row's lock on every driver (SELECT ...
|
|
14
|
+
* FOR UPDATE is not supported on sqlite), and the lock is held until this transaction ends:
|
|
15
|
+
*
|
|
16
|
+
* this claims first a discard waits, then finds no draft and is refused by name
|
|
17
|
+
* a discard is first this waits, then matches no row and is refused by name
|
|
18
|
+
*
|
|
19
|
+
* No row matched means the draft went out or was thrown away in the meantime.
|
|
20
|
+
*/
|
|
21
|
+
async function claimDraft(id, context) {
|
|
22
|
+
const { domain, user, tx } = context.state;
|
|
23
|
+
const claimed = await tx
|
|
24
|
+
.getRepository(activity_instance_js_1.ActivityInstance)
|
|
25
|
+
.update({ domain: { id: domain.id }, id, state: activity_instance_js_1.ActivityInstanceStatus.Draft }, { updater: user });
|
|
26
|
+
if (!claimed.affected) {
|
|
27
|
+
throw new Error(context.t('error.activity-instance draft is gone', { id }));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=claim-draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/claim-draft.ts"],"names":[],"mappings":";;AAkBA,gCAUC;AA5BD,+FAA+G;AAE/G;;;;;;;;;;;;;;;GAeG;AACI,KAAK,UAAU,UAAU,CAAC,EAAU,EAAE,OAAwB;IACnE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE1C,MAAM,OAAO,GAAG,MAAM,EAAE;SACrB,aAAa,CAAC,uCAAgB,CAAC;SAC/B,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,6CAAsB,CAAC,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IAEpG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC7E,CAAC;AACH,CAAC","sourcesContent":["import { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\n\n/**\n * Take hold of a draft before working on it - so that a discard and an issue of the same draft cannot\n * both succeed.\n *\n * Measured on postgres (tests/discard-versus-issue.pg.test.ts): issue read the draft, a discard deleted\n * it, and issue then saved what it had read - the row came back, issued, and the writer had been told it\n * was thrown away. Reading and saving later holds nothing.\n *\n * The claim is an UPDATE that only matches a draft. It takes the row's lock on every driver (SELECT ...\n * FOR UPDATE is not supported on sqlite), and the lock is held until this transaction ends:\n *\n * this claims first a discard waits, then finds no draft and is refused by name\n * a discard is first this waits, then matches no row and is refused by name\n *\n * No row matched means the draft went out or was thrown away in the meantime.\n */\nexport async function claimDraft(id: string, context: ResolverContext): Promise<void> {\n const { domain, user, tx } = context.state\n\n const claimed = await tx\n .getRepository(ActivityInstance)\n .update({ domain: { id: domain.id }, id, state: ActivityInstanceStatus.Draft }, { updater: user })\n\n if (!claimed.affected) {\n throw new Error(context.t('error.activity-instance draft is gone', { id }))\n }\n}\n"]}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage
|
|
14
14
|
* at once, and that cannot be rolled back.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* The files wait for the commit. Under @transaction, deleteAttachmentsByRef deletes the rows now and puts
|
|
17
|
+
* the file deletes on context.state.afterCommit, which shell runs once the transaction has returned
|
|
18
|
+
* (ADR-0091 decision 8). A later refusal - the bulk door refusing its third id - rolls every row back
|
|
19
|
+
* and no file has gone. A transaction opened by hand without runTransaction has no list, and there the
|
|
20
|
+
* files still go at once.
|
|
21
21
|
*/
|
|
22
22
|
export declare function discardDraft(id: string, context: ResolverContext): Promise<true>;
|
|
@@ -22,11 +22,11 @@ const entitlement_js_1 = require("./entitlement.js");
|
|
|
22
22
|
* 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage
|
|
23
23
|
* at once, and that cannot be rolled back.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* The files wait for the commit. Under @transaction, deleteAttachmentsByRef deletes the rows now and puts
|
|
26
|
+
* the file deletes on context.state.afterCommit, which shell runs once the transaction has returned
|
|
27
|
+
* (ADR-0091 decision 8). A later refusal - the bulk door refusing its third id - rolls every row back
|
|
28
|
+
* and no file has gone. A transaction opened by hand without runTransaction has no list, and there the
|
|
29
|
+
* files still go at once.
|
|
30
30
|
*/
|
|
31
31
|
async function discardDraft(id, context) {
|
|
32
32
|
const { domain, tx } = context.state;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discard-draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/discard-draft.ts"],"names":[],"mappings":";;AA8BA,oCAgCC;AA9DD,qCAA4B;AAE5B,qEAAwE;AAExE,+FAAuF;AACvF,+FAA+G;AAC/G,yFAAiF;AACjF,qDAAuD;AAEvD;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,KAAK,UAAU,YAAY,CAAC,EAAU,EAAE,OAAwB;IACrE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACpC,MAAM,SAAS,GAAG,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAA;IAEpD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAEtF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAA,qCAAoB,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAE7C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,IAAI,CAAC;QAC1D,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE;KAC/D,CAAC,CAAA;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5C,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAA;QACrH,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACjG,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,6CAAsB,CAAC,KAAK,EAAE,CAAC,CAAA;IAE9G,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8CAA8C,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IACpF,CAAC;IAED,MAAM,IAAA,wCAAsB,EAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAE7D,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { In } from 'typeorm'\n\nimport { deleteAttachmentsByRef } from '@things-factory/attachment-base'\n\nimport { ActivityApproval } from '../../service/activity-approval/activity-approval.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\n\n/**\n * Throw away a draft - by whoever wrote it, or someone who manages instances (ruling 2026-09-24).\n *\n * The same entitlement as editing and issuing a draft (entitlement.ts). A draft is still the writer's\n * own; once issued it is other people's work and cannot be thrown away here.\n *\n * ── Order, because a rollback cannot bring a file back ─────────────────────\n * 1. rows that hang off the instance - approvals, then threads. A draft normally has none; they are\n * cleared so that no row is left without its parent. These roll back with the transaction.\n * 2. the instance, with `state = 'draft'` in the delete itself. Reading the state and deleting after\n * would let an issue land in between and be deleted. No row deleted means it is not a draft any\n * more (or not here), and the refusal says so - the transaction rolls step 1 back.\n * 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage\n * at once, and that cannot be rolled back.\n *\n *
|
|
1
|
+
{"version":3,"file":"discard-draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/discard-draft.ts"],"names":[],"mappings":";;AA8BA,oCAgCC;AA9DD,qCAA4B;AAE5B,qEAAwE;AAExE,+FAAuF;AACvF,+FAA+G;AAC/G,yFAAiF;AACjF,qDAAuD;AAEvD;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,KAAK,UAAU,YAAY,CAAC,EAAU,EAAE,OAAwB;IACrE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACpC,MAAM,SAAS,GAAG,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAA;IAEpD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAEtF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAA,qCAAoB,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAE7C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,IAAI,CAAC;QAC1D,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE;KAC/D,CAAC,CAAA;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5C,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAA;QACrH,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACjG,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,6CAAsB,CAAC,KAAK,EAAE,CAAC,CAAA;IAE9G,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8CAA8C,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IACpF,CAAC;IAED,MAAM,IAAA,wCAAsB,EAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAE7D,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { In } from 'typeorm'\n\nimport { deleteAttachmentsByRef } from '@things-factory/attachment-base'\n\nimport { ActivityApproval } from '../../service/activity-approval/activity-approval.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\n\n/**\n * Throw away a draft - by whoever wrote it, or someone who manages instances (ruling 2026-09-24).\n *\n * The same entitlement as editing and issuing a draft (entitlement.ts). A draft is still the writer's\n * own; once issued it is other people's work and cannot be thrown away here.\n *\n * ── Order, because a rollback cannot bring a file back ─────────────────────\n * 1. rows that hang off the instance - approvals, then threads. A draft normally has none; they are\n * cleared so that no row is left without its parent. These roll back with the transaction.\n * 2. the instance, with `state = 'draft'` in the delete itself. Reading the state and deleting after\n * would let an issue land in between and be deleted. No row deleted means it is not a draft any\n * more (or not here), and the refusal says so - the transaction rolls step 1 back.\n * 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage\n * at once, and that cannot be rolled back.\n *\n * The files wait for the commit. Under @transaction, deleteAttachmentsByRef deletes the rows now and puts\n * the file deletes on context.state.afterCommit, which shell runs once the transaction has returned\n * (ADR-0091 decision 8). A later refusal - the bulk door refusing its third id - rolls every row back\n * and no file has gone. A transaction opened by hand without runTransaction has no list, and there the\n * files still go at once.\n */\nexport async function discardDraft(id: string, context: ResolverContext): Promise<true> {\n const { domain, tx } = context.state\n const instances = tx.getRepository(ActivityInstance)\n\n const instance = await instances.findOne({ where: { domain: { id: domain.id }, id } })\n\n if (!instance) {\n throw new Error(context.t('error.activity-instance not found', { activityInstance: id }))\n }\n\n await assertMayWorkOnDraft(instance, context)\n\n const threads = await tx.getRepository(ActivityThread).find({\n where: { domain: { id: domain.id }, activityInstance: { id } }\n })\n\n if (threads.length) {\n const threadIds = threads.map(one => one.id)\n\n await tx.getRepository(ActivityApproval).delete({ domain: { id: domain.id }, activityThread: { id: In(threadIds) } })\n await tx.getRepository(ActivityThread).delete({ domain: { id: domain.id }, id: In(threadIds) })\n }\n\n const removed = await instances.delete({ domain: { id: domain.id }, id, state: ActivityInstanceStatus.Draft })\n\n if (!removed.affected) {\n throw new Error(context.t('error.activity-instance not a draft any more', { id }))\n }\n\n await deleteAttachmentsByRef(null, { refBys: [id] }, context)\n\n return true\n}\n"]}
|
|
@@ -5,6 +5,8 @@ const activity_instance_js_1 = require("../../service/activity-instance/activity
|
|
|
5
5
|
const activity_js_1 = require("../../service/activity/activity.js");
|
|
6
6
|
const common_js_1 = require("../common.js");
|
|
7
7
|
const entitlement_js_1 = require("./entitlement.js");
|
|
8
|
+
const claim_draft_js_1 = require("./claim-draft.js");
|
|
9
|
+
const approval_line_input_js_1 = require("./approval-line-input.js");
|
|
8
10
|
async function draft(activityInstance, context) {
|
|
9
11
|
const { domain, user, tx } = context.state;
|
|
10
12
|
const { id, activityId, input, dueAt } = activityInstance;
|
|
@@ -28,6 +30,8 @@ async function draft(activityInstance, context) {
|
|
|
28
30
|
state: previousActivityInstance.state
|
|
29
31
|
}));
|
|
30
32
|
}
|
|
33
|
+
/* Held until this transaction ends, so a discard cannot remove it between the read and the save. */
|
|
34
|
+
await (0, claim_draft_js_1.claimDraft)(id, context);
|
|
31
35
|
activity = previousActivityInstance.activity;
|
|
32
36
|
}
|
|
33
37
|
else {
|
|
@@ -45,6 +49,13 @@ async function draft(activityInstance, context) {
|
|
|
45
49
|
}));
|
|
46
50
|
}
|
|
47
51
|
}
|
|
52
|
+
/*
|
|
53
|
+
* The activity's approval line is set on its detail screen and nowhere else (ruling 2026-09-24).
|
|
54
|
+
* A line handed in must be absent or the same one - § approval-line-input.ts.
|
|
55
|
+
*/
|
|
56
|
+
if ((0, approval_line_input_js_1.approvalLineInputRefusal)(activity.approvalLine, activityInstance.approvalLine)) {
|
|
57
|
+
throw Object.assign(new Error(context.t('error.activity-instance approval line is set')), { code: approval_line_input_js_1.LINE_IS_SET });
|
|
58
|
+
}
|
|
48
59
|
if (!activityInstance.assignees) {
|
|
49
60
|
activityInstance.assignees = activity.assignees;
|
|
50
61
|
}
|
|
@@ -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'\nimport { claimDraft } from './claim-draft.js'\nimport { LINE_IS_SET, approvalLineInputRefusal } from './approval-line-input.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 /* Held until this transaction ends, so a discard cannot remove it between the read and the save. */\n await claimDraft(id, context)\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 /*\n * The activity's approval line is set on its detail screen and nowhere else (ruling 2026-09-24).\n * A line handed in must be absent or the same one - § approval-line-input.ts.\n */\n if (approvalLineInputRefusal(activity.approvalLine, activityInstance.approvalLine)) {\n throw Object.assign(new Error(context.t('error.activity-instance approval line is set')), { code: LINE_IS_SET })\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"]}
|
|
@@ -6,7 +6,9 @@ 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 approval_line_input_js_1 = require("./approval-line-input.js");
|
|
9
10
|
const entitlement_js_1 = require("./entitlement.js");
|
|
11
|
+
const claim_draft_js_1 = require("./claim-draft.js");
|
|
10
12
|
const approval_expectation_js_1 = require("./approval-expectation.js");
|
|
11
13
|
const own_request_js_1 = require("../activity-approval/own-request.js");
|
|
12
14
|
const resolve_judgeable_seats_js_1 = require("../activity-approval/resolve-judgeable-seats.js");
|
|
@@ -48,6 +50,17 @@ async function issue(activityInstance, context) {
|
|
|
48
50
|
state: origin.state
|
|
49
51
|
}));
|
|
50
52
|
}
|
|
53
|
+
/*
|
|
54
|
+
* An id that is not here is refused - not taken as a new case. Taken as new, a draft discarded a moment
|
|
55
|
+
* ago came back under the same id, issued. And a draft that is here is claimed before anything else is
|
|
56
|
+
* written, so a discard cannot slip in between - see claim-draft.ts.
|
|
57
|
+
*/
|
|
58
|
+
if (id && !origin) {
|
|
59
|
+
throw new Error(context.t('error.activity-instance not found', { activityInstance: id }));
|
|
60
|
+
}
|
|
61
|
+
if (origin) {
|
|
62
|
+
await (0, claim_draft_js_1.claimDraft)(origin.id, context);
|
|
63
|
+
}
|
|
51
64
|
var repository = tx.getRepository(activity_js_1.Activity);
|
|
52
65
|
var activity = origin?.activity ||
|
|
53
66
|
(await repository.findOne({
|
|
@@ -59,6 +72,13 @@ async function issue(activityInstance, context) {
|
|
|
59
72
|
activity: activityId
|
|
60
73
|
}));
|
|
61
74
|
}
|
|
75
|
+
/*
|
|
76
|
+
* The activity's approval line is set on its detail screen and nowhere else (ruling 2026-09-24).
|
|
77
|
+
* A line handed in must be absent or the same one - § approval-line-input.ts.
|
|
78
|
+
*/
|
|
79
|
+
if ((0, approval_line_input_js_1.approvalLineInputRefusal)(activity.approvalLine, approvalLine)) {
|
|
80
|
+
throw Object.assign(new Error(context.t('error.activity-instance approval line is set')), { code: approval_line_input_js_1.LINE_IS_SET });
|
|
81
|
+
}
|
|
62
82
|
if (!origin) {
|
|
63
83
|
activityInstance.name = name || activity.name;
|
|
64
84
|
activityInstance.description = description || activity.description;
|
|
@@ -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 { assertMayWorkOnDraft } from './entitlement.js'\nimport { expectationBroken, judgeApprovalExpectation } from './approval-expectation.js'\nimport { STANDING_REFUSAL_KEYS, judgeSomeoneElseCanJudge, standingBroken } from '../activity-approval/own-request.js'\nimport { resolveJudgeableSeats } from '../activity-approval/resolve-judgeable-seats.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 /*\n * **올린 사람 말고 설 사람이 있어야 올릴 수 있다.**\n *\n * 결재는 누르는 자리에서도 막지만(§`own-request.ts`), 거기서만 막으면 결재선에 올린 사람밖에 없는\n * 건이 **아무에게도 안 간다.** 정비 완료 확인이 그 모양이면 설비가 정비중으로 남고 라인이 선다.\n *\n * 그래서 올리는 이 자리에서 먼저 본다. 여기서 걸리면 결재선을 고칠 수 있는 사람이 그 자리에서\n * 무엇을 고쳐야 하는지 읽는다. (ADR-0052 ②)\n *\n * 판단은 `own-request.ts` 가 들고, 자리를 푸는 것은 `resolve-judgeable-seats.ts` 가 한다 — 여기에\n * 조건을 적으면 위의 결재선 검사와 같은 이유로 시험할 수 없다.\n */\n const standing = judgeSomeoneElseCanJudge({\n issuerId: user?.id,\n seats: await resolveJudgeableSeats(activityInstance.approvalLine ?? origin?.approvalLine, context)\n })\n\n if (standingBroken(standing)) {\n /*\n * 사유마다 한 문장씩 — **자리가 같다고 사유를 합치지 않는다.** 관리자가 할 일이 다르다: 하나는\n * 「그 자리에 다른 결재자를」이고 하나는 「그 역할에 사람을」이다. (ADR-0047 ② 의 낱말)\n */\n throw new Error(\n standing.refusals\n .map(refusal => {\n /* 이름이 안 풀린 자리는 결재선에 적힌 값이라도 보인다 — 관리자가 고칠 실마리가 그것뿐이다. */\n const seats = refusal.seats\n .map(seat => `${seat.order}차${seat.name ? ` (${seat.name})` : seat.pointsAt ? ` (${seat.pointsAt})` : ''}`)\n .join(', ')\n\n const said = {\n 'approval-role-empty': `${seats} 결재 자리가 지목한 역할에 사람이 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 역할에 사람을 넣어 주십시오.`,\n 'approval-person-missing': `${seats} 결재 자리가 지목한 사람을 찾을 수 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 자리에 지금 있는 사람을 다시 지정해 주십시오.`,\n 'only-the-issuer-could-judge': `${seats} 결재 자리에 올린 사람 말고 설 사람이 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 자리에 다른 결재자를 넣거나, 그 역할을 가진 사람을 늘려 주십시오.`\n }[refusal.because]\n\n return context.t(STANDING_REFUSAL_KEYS[refusal.because], { seats, defaultValue: said })\n })\n .join(' ')\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":"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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 { LINE_IS_SET, approvalLineInputRefusal } from './approval-line-input.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\nimport { claimDraft } from './claim-draft.js'\nimport { expectationBroken, judgeApprovalExpectation } from './approval-expectation.js'\nimport { STANDING_REFUSAL_KEYS, judgeSomeoneElseCanJudge, standingBroken } from '../activity-approval/own-request.js'\nimport { resolveJudgeableSeats } from '../activity-approval/resolve-judgeable-seats.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 /*\n * An id that is not here is refused - not taken as a new case. Taken as new, a draft discarded a moment\n * ago came back under the same id, issued. And a draft that is here is claimed before anything else is\n * written, so a discard cannot slip in between - see claim-draft.ts.\n */\n if (id && !origin) {\n throw new Error(context.t('error.activity-instance not found', { activityInstance: id }))\n }\n\n if (origin) {\n await claimDraft(origin.id, context)\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 /*\n * The activity's approval line is set on its detail screen and nowhere else (ruling 2026-09-24).\n * A line handed in must be absent or the same one - § approval-line-input.ts.\n */\n if (approvalLineInputRefusal(activity.approvalLine, approvalLine)) {\n throw Object.assign(new Error(context.t('error.activity-instance approval line is set')), { code: LINE_IS_SET })\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 /*\n * **올린 사람 말고 설 사람이 있어야 올릴 수 있다.**\n *\n * 결재는 누르는 자리에서도 막지만(§`own-request.ts`), 거기서만 막으면 결재선에 올린 사람밖에 없는\n * 건이 **아무에게도 안 간다.** 정비 완료 확인이 그 모양이면 설비가 정비중으로 남고 라인이 선다.\n *\n * 그래서 올리는 이 자리에서 먼저 본다. 여기서 걸리면 결재선을 고칠 수 있는 사람이 그 자리에서\n * 무엇을 고쳐야 하는지 읽는다. (ADR-0052 ②)\n *\n * 판단은 `own-request.ts` 가 들고, 자리를 푸는 것은 `resolve-judgeable-seats.ts` 가 한다 — 여기에\n * 조건을 적으면 위의 결재선 검사와 같은 이유로 시험할 수 없다.\n */\n const standing = judgeSomeoneElseCanJudge({\n issuerId: user?.id,\n seats: await resolveJudgeableSeats(activityInstance.approvalLine ?? origin?.approvalLine, context)\n })\n\n if (standingBroken(standing)) {\n /*\n * 사유마다 한 문장씩 — **자리가 같다고 사유를 합치지 않는다.** 관리자가 할 일이 다르다: 하나는\n * 「그 자리에 다른 결재자를」이고 하나는 「그 역할에 사람을」이다. (ADR-0047 ② 의 낱말)\n */\n throw new Error(\n standing.refusals\n .map(refusal => {\n /* 이름이 안 풀린 자리는 결재선에 적힌 값이라도 보인다 — 관리자가 고칠 실마리가 그것뿐이다. */\n const seats = refusal.seats\n .map(seat => `${seat.order}차${seat.name ? ` (${seat.name})` : seat.pointsAt ? ` (${seat.pointsAt})` : ''}`)\n .join(', ')\n\n const said = {\n 'approval-role-empty': `${seats} 결재 자리가 지목한 역할에 사람이 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 역할에 사람을 넣어 주십시오.`,\n 'approval-person-missing': `${seats} 결재 자리가 지목한 사람을 찾을 수 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 자리에 지금 있는 사람을 다시 지정해 주십시오.`,\n 'only-the-issuer-could-judge': `${seats} 결재 자리에 올린 사람 말고 설 사람이 없습니다. 그대로 올리면 아무도 결재할 수 없습니다 — 그 자리에 다른 결재자를 넣거나, 그 역할을 가진 사람을 늘려 주십시오.`\n }[refusal.because]\n\n return context.t(STANDING_REFUSAL_KEYS[refusal.because], { seats, defaultValue: said })\n })\n .join(' ')\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,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pick = pick;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
4
5
|
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
5
6
|
const common_js_1 = require("../common.js");
|
|
6
7
|
const entitlement_js_1 = require("./entitlement.js");
|
|
@@ -34,6 +35,21 @@ async function pick(id, context) {
|
|
|
34
35
|
actual: activityInstance.state
|
|
35
36
|
}));
|
|
36
37
|
}
|
|
38
|
+
/*
|
|
39
|
+
* Take hold of it before making a thread. Measured on postgres (tests/pick-race.pg.test.ts): two people
|
|
40
|
+
* picking a case that waits for one both read it as issued and both got a thread. The UPDATE matches
|
|
41
|
+
* only a case still waiting and holds its row to the end of this transaction; whoever is second waits,
|
|
42
|
+
* then finds it assigned and is refused by name. A case waiting for more than one person is still
|
|
43
|
+
* unassigned after the first pick, so the second person gets in - as intended.
|
|
44
|
+
*/
|
|
45
|
+
const claimed = await repository.update({
|
|
46
|
+
domain: { id: domain.id },
|
|
47
|
+
id,
|
|
48
|
+
state: (0, typeorm_1.In)([activity_instance_js_1.ActivityInstanceStatus.Issued, activity_instance_js_1.ActivityInstanceStatus.Unassigned])
|
|
49
|
+
}, { updater: user });
|
|
50
|
+
if (!claimed.affected) {
|
|
51
|
+
throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: id }));
|
|
52
|
+
}
|
|
37
53
|
const picked = await repository.save({
|
|
38
54
|
...activityInstance,
|
|
39
55
|
transaction: 'pick',
|
|
@@ -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":";;AAOA,oBAoEC;AA3ED,qCAA4B;AAE5B,+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;;;;;;OAMG;IACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,CACrC;QACE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE;QACzB,EAAE;QACF,KAAK,EAAE,IAAA,YAAE,EAAC,CAAC,6CAAsB,CAAC,MAAM,EAAE,6CAAsB,CAAC,UAAU,CAAC,CAAC;KAC9E,EACD,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAA;IAED,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,+CAA+C,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IACvG,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 { In } from 'typeorm'\n\nimport { 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 /*\n * Take hold of it before making a thread. Measured on postgres (tests/pick-race.pg.test.ts): two people\n * picking a case that waits for one both read it as issued and both got a thread. The UPDATE matches\n * only a case still waiting and holds its row to the end of this transaction; whoever is second waits,\n * then finds it assigned and is refused by name. A case waiting for more than one person is still\n * unassigned after the first pick, so the second person gets in - as intended.\n */\n const claimed = await repository.update(\n {\n domain: { id: domain.id },\n id,\n state: In([ActivityInstanceStatus.Issued, ActivityInstanceStatus.Unassigned])\n },\n { updater: user }\n )\n\n if (!claimed.affected) {\n throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: id }))\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"]}
|
|
@@ -272,21 +272,22 @@ async function getSystemUserFromOrgMemberItem(item, context) {
|
|
|
272
272
|
const { type, approver, assignee, value } = item;
|
|
273
273
|
const id = approver?.id || assignee?.id || value;
|
|
274
274
|
if (type == organization_1.OrgMemberTargetType.Employee) {
|
|
275
|
-
|
|
275
|
+
/* This domain's only - an id from another tenant made that tenant's person an approver here. */
|
|
276
|
+
const employee = id && (await tx.getRepository(organization_1.Employee).findOne({ where: { domain: { id: domain.id }, id }, relations: ['user'] }));
|
|
276
277
|
if (!employee || !employee.user) {
|
|
277
278
|
return;
|
|
278
279
|
}
|
|
279
280
|
return employee.user;
|
|
280
281
|
}
|
|
281
282
|
else if (type == organization_1.OrgMemberTargetType.Department) {
|
|
282
|
-
const department = id && (await tx.getRepository(organization_1.Department).findOne({ where: { id }, relations: ['manager'] }));
|
|
283
|
+
const department = id && (await tx.getRepository(organization_1.Department).findOne({ where: { domain: { id: domain.id }, id }, relations: ['manager'] }));
|
|
283
284
|
if (!department || !department.manager) {
|
|
284
285
|
return;
|
|
285
286
|
}
|
|
286
287
|
return await tx.getRepository(auth_base_1.User).findOneBy({ id: department.manager.userId });
|
|
287
288
|
}
|
|
288
289
|
else if (type == organization_1.OrgMemberTargetType.Role) {
|
|
289
|
-
const role = id && (await tx.getRepository(auth_base_1.Role).findOne({ where: { id }, relations: ['users'] }));
|
|
290
|
+
const role = id && (await tx.getRepository(auth_base_1.Role).findOne({ where: { domain: { id: domain.id }, id }, relations: ['users'] }));
|
|
290
291
|
if (!role || !role.users || !role.users[0]) {
|
|
291
292
|
return;
|
|
292
293
|
}
|