@things-factory/worklist 10.1.203 → 10.1.206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist-client/index.d.ts +1 -0
  2. package/dist-client/index.js +1 -0
  3. package/dist-client/index.js.map +1 -1
  4. package/dist-client/pages/todo/done-list-calendar-page.d.ts +2 -0
  5. package/dist-client/pages/todo/done-list-calendar-page.js +63 -64
  6. package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
  7. package/dist-client/tsconfig.tsbuildinfo +1 -1
  8. package/dist-client/work-calendar-sources.d.ts +53 -0
  9. package/dist-client/work-calendar-sources.js +47 -0
  10. package/dist-client/work-calendar-sources.js.map +1 -0
  11. package/dist-server/controllers/activity-instance/pick.js +34 -1
  12. package/dist-server/controllers/activity-instance/pick.js.map +1 -1
  13. package/dist-server/controllers/activity-thread/check-authority.js +19 -4
  14. package/dist-server/controllers/activity-thread/check-authority.js.map +1 -1
  15. package/dist-server/controllers/common.d.ts +13 -0
  16. package/dist-server/controllers/common.js +26 -3
  17. package/dist-server/controllers/common.js.map +1 -1
  18. package/dist-server/service/activity-thread/my-open-threads.js +16 -2
  19. package/dist-server/service/activity-thread/my-open-threads.js.map +1 -1
  20. package/dist-server/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +11 -11
  22. package/tests/pick-race.pg.test.ts +79 -1
  23. package/tests/pool-pick-waiting-thread.test.ts +254 -0
  24. package/translations/en.json +1 -0
  25. package/translations/es.json +1 -0
  26. package/translations/ja.json +1 -0
  27. package/translations/ko.json +1 -0
  28. package/translations/ms.json +1 -0
  29. package/translations/zh.json +1 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * What else a person's work calendar shows — **the calendar is worklist's, what goes on it is not all worklist's.**
3
+ *
4
+ * 「내 업무 달력」 answers "what must I do on which day". The to-dos and approvals are worklist's own; a meeting I am
5
+ * called to, a day my leave starts, are other modules' facts on the same day. A product registers a source for each:
6
+ * it reads only the caller's own entries, through doors of its own, and says where an entry opens. worklist places
7
+ * them with its own on the grid, in one order, under one per-day limit.
8
+ *
9
+ * A source that fails is said as such on the calendar - never read as a day with nothing on it.
10
+ */
11
+ export interface WorkCalendarEntry {
12
+ /** Unique within its source. */
13
+ id: string;
14
+ /** What the entry is, in a line. */
15
+ name: string;
16
+ /** When it falls - its time is shown, its day places it. */
17
+ date: string | Date;
18
+ /** 0 - to act on that day (a due item, a meeting) · 1 - arrived that day · 2 - over (done, cancelled). */
19
+ rank: 0 | 1 | 2;
20
+ /** 0..3, the most urgent last - the colour of the entry. */
21
+ priority?: number;
22
+ /** Material Symbols name drawn before it. */
23
+ icon?: string;
24
+ /** Where it opens - none when the person may not open it; the entry is then shown, not linked. */
25
+ href?: string;
26
+ /** What the entry's place means, for the tooltip - 「due this day」, 「meeting at 09:00」. */
27
+ note?: string;
28
+ }
29
+ export interface WorkCalendarSource {
30
+ /** Unique - a second registration with the same key replaces the first. */
31
+ key: string;
32
+ /** The caller's own entries that fall in [from, to). */
33
+ load(from: Date, to: Date): Promise<WorkCalendarEntry[]>;
34
+ }
35
+ export declare function registerWorkCalendarSource(source: WorkCalendarSource): void;
36
+ export declare function workCalendarSources(): WorkCalendarSource[];
37
+ /**
38
+ * Every source asked apart, for one window: one that fails leaves the others' entries, and `failed` says so - a
39
+ * calendar that read nothing from a source must not look like a week with nothing in it.
40
+ */
41
+ export declare function loadWorkCalendar(loads: Promise<WorkCalendarEntry[]>[]): Promise<{
42
+ entries: WorkCalendarEntry[];
43
+ failed: boolean;
44
+ }>;
45
+ /**
46
+ * One day's entries in the order they are drawn, cut to the limit **after** ordering: what is to be done that day
47
+ * first, then what arrived that day, then what is over; within each the more urgent, then the earlier. `hidden` is
48
+ * how many the limit left out - said as a number, never as more rows.
49
+ */
50
+ export declare function entriesOfDay(entries: WorkCalendarEntry[], localDay: string, limit: number): {
51
+ shown: WorkCalendarEntry[];
52
+ hidden: number;
53
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What else a person's work calendar shows — **the calendar is worklist's, what goes on it is not all worklist's.**
3
+ *
4
+ * 「내 업무 달력」 answers "what must I do on which day". The to-dos and approvals are worklist's own; a meeting I am
5
+ * called to, a day my leave starts, are other modules' facts on the same day. A product registers a source for each:
6
+ * it reads only the caller's own entries, through doors of its own, and says where an entry opens. worklist places
7
+ * them with its own on the grid, in one order, under one per-day limit.
8
+ *
9
+ * A source that fails is said as such on the calendar - never read as a day with nothing on it.
10
+ */
11
+ const SOURCES = new Map();
12
+ export function registerWorkCalendarSource(source) {
13
+ SOURCES.set(source.key, source);
14
+ }
15
+ export function workCalendarSources() {
16
+ return [...SOURCES.values()];
17
+ }
18
+ /**
19
+ * Every source asked apart, for one window: one that fails leaves the others' entries, and `failed` says so - a
20
+ * calendar that read nothing from a source must not look like a week with nothing in it.
21
+ */
22
+ export async function loadWorkCalendar(loads) {
23
+ const entries = [];
24
+ let failed = false;
25
+ for (const outcome of await Promise.allSettled(loads)) {
26
+ if (outcome.status === 'fulfilled')
27
+ entries.push(...outcome.value);
28
+ else {
29
+ failed = true;
30
+ console.error(outcome.reason);
31
+ }
32
+ }
33
+ return { entries, failed };
34
+ }
35
+ /**
36
+ * One day's entries in the order they are drawn, cut to the limit **after** ordering: what is to be done that day
37
+ * first, then what arrived that day, then what is over; within each the more urgent, then the earlier. `hidden` is
38
+ * how many the limit left out - said as a number, never as more rows.
39
+ */
40
+ export function entriesOfDay(entries, localDay, limit) {
41
+ const ofTheDay = entries
42
+ .filter(entry => new Date(entry.date).toLocaleDateString() == localDay)
43
+ .sort((a, b) => a.rank - b.rank || (b.priority || 0) - (a.priority || 0) || new Date(a.date).getTime() - new Date(b.date).getTime());
44
+ const shown = ofTheDay.slice(0, limit);
45
+ return { shown, hidden: ofTheDay.length - shown.length };
46
+ }
47
+ //# sourceMappingURL=work-calendar-sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-calendar-sources.js","sourceRoot":"","sources":["../client/work-calendar-sources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA4BH,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAA;AAErD,MAAM,UAAU,0BAA0B,CAAC,MAA0B;IACnE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAqC;IAC1E,MAAM,OAAO,GAAwB,EAAE,CAAA;IACvC,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,KAAK,MAAM,OAAO,IAAI,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;aAC7D,CAAC;YACJ,MAAM,GAAG,IAAI,CAAA;YACb,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B,EAAE,QAAgB,EAAE,KAAa;IACxF,MAAM,QAAQ,GAAG,OAAO;SACrB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,kBAAkB,EAAE,IAAI,QAAQ,CAAC;SACtE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IACtI,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACtC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAA;AAC1D,CAAC","sourcesContent":["/**\n * What else a person's work calendar shows — **the calendar is worklist's, what goes on it is not all worklist's.**\n *\n * 「내 업무 달력」 answers \"what must I do on which day\". The to-dos and approvals are worklist's own; a meeting I am\n * called to, a day my leave starts, are other modules' facts on the same day. A product registers a source for each:\n * it reads only the caller's own entries, through doors of its own, and says where an entry opens. worklist places\n * them with its own on the grid, in one order, under one per-day limit.\n *\n * A source that fails is said as such on the calendar - never read as a day with nothing on it.\n */\n\nexport interface WorkCalendarEntry {\n /** Unique within its source. */\n id: string\n /** What the entry is, in a line. */\n name: string\n /** When it falls - its time is shown, its day places it. */\n date: string | Date\n /** 0 - to act on that day (a due item, a meeting) · 1 - arrived that day · 2 - over (done, cancelled). */\n rank: 0 | 1 | 2\n /** 0..3, the most urgent last - the colour of the entry. */\n priority?: number\n /** Material Symbols name drawn before it. */\n icon?: string\n /** Where it opens - none when the person may not open it; the entry is then shown, not linked. */\n href?: string\n /** What the entry's place means, for the tooltip - 「due this day」, 「meeting at 09:00」. */\n note?: string\n}\n\nexport interface WorkCalendarSource {\n /** Unique - a second registration with the same key replaces the first. */\n key: string\n /** The caller's own entries that fall in [from, to). */\n load(from: Date, to: Date): Promise<WorkCalendarEntry[]>\n}\n\nconst SOURCES = new Map<string, WorkCalendarSource>()\n\nexport function registerWorkCalendarSource(source: WorkCalendarSource): void {\n SOURCES.set(source.key, source)\n}\n\nexport function workCalendarSources(): WorkCalendarSource[] {\n return [...SOURCES.values()]\n}\n\n/**\n * Every source asked apart, for one window: one that fails leaves the others' entries, and `failed` says so - a\n * calendar that read nothing from a source must not look like a week with nothing in it.\n */\nexport async function loadWorkCalendar(loads: Promise<WorkCalendarEntry[]>[]): Promise<{ entries: WorkCalendarEntry[]; failed: boolean }> {\n const entries: WorkCalendarEntry[] = []\n let failed = false\n for (const outcome of await Promise.allSettled(loads)) {\n if (outcome.status === 'fulfilled') entries.push(...outcome.value)\n else {\n failed = true\n console.error(outcome.reason)\n }\n }\n return { entries, failed }\n}\n\n/**\n * One day's entries in the order they are drawn, cut to the limit **after** ordering: what is to be done that day\n * first, then what arrived that day, then what is over; within each the more urgent, then the earlier. `hidden` is\n * how many the limit left out - said as a number, never as more rows.\n */\nexport function entriesOfDay(entries: WorkCalendarEntry[], localDay: string, limit: number) {\n const ofTheDay = entries\n .filter(entry => new Date(entry.date).toLocaleDateString() == localDay)\n .sort((a, b) => a.rank - b.rank || (b.priority || 0) - (a.priority || 0) || new Date(a.date).getTime() - new Date(b.date).getTime())\n const shown = ofTheDay.slice(0, limit)\n return { shown, hidden: ofTheDay.length - shown.length }\n}\n"]}
@@ -4,6 +4,7 @@ exports.pick = pick;
4
4
  const typeorm_1 = require("typeorm");
5
5
  const shell_1 = require("@things-factory/shell");
6
6
  const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
7
+ const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
7
8
  const common_js_1 = require("../common.js");
8
9
  const entitlement_js_1 = require("./entitlement.js");
9
10
  async function pick(id, context) {
@@ -69,11 +70,43 @@ async function pick(id, context) {
69
70
  : {})
70
71
  });
71
72
  await (0, entitlement_js_1.assertMayPick)(current, context);
73
+ /*
74
+ * The case's threads, read under the hold on the case - case first, then thread, the order every way into a
75
+ * waiting case takes (assign.ts, activity-thread/check-authority.ts).
76
+ */
77
+ const threadRepository = tx.getRepository(activity_thread_js_1.ActivityThread);
78
+ const threads = await threadRepository.find({
79
+ where: { activityInstance: { id } },
80
+ /* A locking read is a current read - a plain one may answer from the snapshot taken before the wait. */
81
+ ...((0, shell_1.writeLockSupported)(tx) ? { lock: { mode: 'pessimistic_write' } } : {})
82
+ });
83
+ /* One person, one thread of a case - the database says so too (ix_activity_thread_0), only not by name. */
84
+ if (threads.some(thread => thread.assigneeId === user.id)) {
85
+ throw new Error(context.t('error.activity-instance already yours', { id }));
86
+ }
72
87
  const picked = await repository.save({
73
88
  ...current,
74
89
  transaction: 'pick',
75
90
  updater: user
76
91
  });
77
- return (await (0, common_js_1.createActivityThreadsForUsers)('pick', picked, [user], context))[0];
92
+ /*
93
+ * A case in a pool waits with one unassigned thread, and that thread is what puts it on the pool's to-do
94
+ * lists. The picker takes that very thread. A new thread beside it left the waiting one behind, and the case
95
+ * stayed on the list of everyone in the pool after one of them had taken it.
96
+ */
97
+ const waiting = threads.find(thread => thread.state === activity_thread_js_1.ActivityThreadStatus.Unassigned);
98
+ if (!waiting) {
99
+ return (await (0, common_js_1.createActivityThreadsForUsers)('pick', picked, [user], context))[0];
100
+ }
101
+ const taken = await threadRepository.save(Object.assign(waiting, {
102
+ assignee: user,
103
+ state: activity_thread_js_1.ActivityThreadStatus.Assigned,
104
+ transaction: 'pick',
105
+ updater: user,
106
+ assignedAt: new Date()
107
+ }));
108
+ await (0, common_js_1.leaveTheRestOfThePoolOneWaiting)('pick', picked, threads, context);
109
+ await (0, common_js_1.updateActivityInstanceState)(id, { causedBy: [taken] }, context);
110
+ return taken;
78
111
  }
79
112
  //# sourceMappingURL=pick.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pick.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/pick.ts"],"names":[],"mappings":";;AASA,oBAwFC;AAjGD,qCAA4B;AAE5B,iDAA0D;AAE1D,+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,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC;KACrH,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;;;;OAIG;IACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;QACvC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;QACxC,SAAS,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC;QACpH;;;;WAIG;QACH,GAAG,CAAC,IAAA,0BAAkB,EAAC,EAAE,CAAC;YACxB,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,mBAA4B,EAAE,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,EAAE;YACxG,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAA;IAEF,MAAM,IAAA,8BAAa,EAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAErC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC;QACnC,GAAG,OAAO;QACV,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 { writeLockSupported } from '@things-factory/shell'\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', 'assigneeRoles', '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 담당 역할을 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 /*\n * Read it again under the hold, and ask again who may take it. What was read above came before the wait:\n * a manager may have put the case in a role's pool meanwhile (assign.ts), and saving that old read back\n * would wipe the pool out - and let someone outside the role take it.\n */\n const current = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['domain', 'activity', 'assigneeRoles', 'supervisoryRole', 'updater', 'creator', 'starter', 'terminator'],\n /*\n * Lock the case's row only - postgres refuses FOR UPDATE on the nullable side of the relations' outer\n * joins. `tables` takes table names, not entity names (typeorm SelectQueryBuilder), so it is read off\n * the metadata rather than written here; 'ActivityInstance' failed every pick on postgres.\n */\n ...(writeLockSupported(tx)\n ? { lock: { mode: 'pessimistic_write' as const, tables: [repository.metadata.tableNameWithoutPrefix] } }\n : {})\n })\n\n await assertMayPick(current, context)\n\n const picked = await repository.save({\n ...current,\n transaction: 'pick',\n updater: user\n })\n\n return (await createActivityThreadsForUsers('pick', picked, [user], context))[0]\n}\n"]}
1
+ {"version":3,"file":"pick.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/pick.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { writeLockSupported } from '@things-factory/shell'\n\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { createActivityThreadsForUsers, leaveTheRestOfThePoolOneWaiting, updateActivityInstanceState } 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', 'assigneeRoles', '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 담당 역할을 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 /*\n * Read it again under the hold, and ask again who may take it. What was read above came before the wait:\n * a manager may have put the case in a role's pool meanwhile (assign.ts), and saving that old read back\n * would wipe the pool out - and let someone outside the role take it.\n */\n const current = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['domain', 'activity', 'assigneeRoles', 'supervisoryRole', 'updater', 'creator', 'starter', 'terminator'],\n /*\n * Lock the case's row only - postgres refuses FOR UPDATE on the nullable side of the relations' outer\n * joins. `tables` takes table names, not entity names (typeorm SelectQueryBuilder), so it is read off\n * the metadata rather than written here; 'ActivityInstance' failed every pick on postgres.\n */\n ...(writeLockSupported(tx)\n ? { lock: { mode: 'pessimistic_write' as const, tables: [repository.metadata.tableNameWithoutPrefix] } }\n : {})\n })\n\n await assertMayPick(current, context)\n\n /*\n * The case's threads, read under the hold on the case - case first, then thread, the order every way into a\n * waiting case takes (assign.ts, activity-thread/check-authority.ts).\n */\n const threadRepository = tx.getRepository(ActivityThread)\n const threads = await threadRepository.find({\n where: { activityInstance: { id } },\n /* A locking read is a current read - a plain one may answer from the snapshot taken before the wait. */\n ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' as const } } : {})\n })\n\n /* One person, one thread of a case - the database says so too (ix_activity_thread_0), only not by name. */\n if (threads.some(thread => thread.assigneeId === user.id)) {\n throw new Error(context.t('error.activity-instance already yours', { id }))\n }\n\n const picked = await repository.save({\n ...current,\n transaction: 'pick',\n updater: user\n })\n\n /*\n * A case in a pool waits with one unassigned thread, and that thread is what puts it on the pool's to-do\n * lists. The picker takes that very thread. A new thread beside it left the waiting one behind, and the case\n * stayed on the list of everyone in the pool after one of them had taken it.\n */\n const waiting = threads.find(thread => thread.state === ActivityThreadStatus.Unassigned)\n\n if (!waiting) {\n return (await createActivityThreadsForUsers('pick', picked, [user], context))[0]\n }\n\n const taken = await threadRepository.save(\n Object.assign(waiting, {\n assignee: user,\n state: ActivityThreadStatus.Assigned,\n transaction: 'pick',\n updater: user,\n assignedAt: new Date()\n })\n )\n\n await leaveTheRestOfThePoolOneWaiting('pick', picked, threads, context)\n await updateActivityInstanceState(id, { causedBy: [taken] }, context)\n\n return taken\n}\n"]}
@@ -6,31 +6,43 @@ const auth_base_1 = require("@things-factory/auth-base");
6
6
  const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
7
7
  const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
8
8
  const assignee_pool_js_1 = require("../../service/activity-instance/assignee-pool.js");
9
+ const common_js_1 = require("../common.js");
9
10
  async function checkAuthority(activityThread, context) {
10
11
  const { user, tx } = context.state;
11
12
  if (activityThread.assigneeId && activityThread.assigneeId === user.id) {
12
13
  return;
13
14
  }
14
- // TODO activityInstance의 cardinality를 기준으로 unassigned thread를 추가로 만들 지, assigned thread로 대체할 지를 구현해야한다.
15
15
  if (activityThread.state == activity_thread_js_1.ActivityThreadStatus.Unassigned) {
16
16
  /*
17
17
  * Claiming the pool's waiting thread. The case is held first - the order pick and a pool assign take
18
18
  * (activity-instance/pick.ts, assign.ts) - and the thread is read again under it. The thread handed in
19
19
  * was read before anything was held: someone else may have claimed it since, or a manager moved the
20
20
  * case to another role's pool, and what this claims must be what is there now.
21
+ *
22
+ * The claimer gets this very thread; a case still short of people leaves the rest of the pool a new
23
+ * waiting one (common.ts leaveTheRestOfThePoolOneWaiting) - the same as pick.
21
24
  */
22
25
  const activityInstance = await (0, shell_1.getRepository)(activity_instance_js_1.ActivityInstance, tx).findOne({
23
26
  where: { id: activityThread.activityInstanceId },
24
27
  ...((0, shell_1.writeLockSupported)(tx) ? { lock: { mode: 'pessimistic_write' } } : {})
25
28
  });
26
- /* A locking read is a current read - a plain one may answer from the snapshot taken before the wait. */
27
- const now = await (0, shell_1.getRepository)(activity_thread_js_1.ActivityThread, tx).findOne({
28
- where: { id: activityThread.id },
29
+ /*
30
+ * All the case's threads, not only this one: whether the rest of the pool is left a waiting thread depends
31
+ * on how many are held. A locking read is a current read - a plain one may answer from the snapshot taken
32
+ * before the wait.
33
+ */
34
+ const threads = await (0, shell_1.getRepository)(activity_thread_js_1.ActivityThread, tx).find({
35
+ where: { activityInstance: { id: activityThread.activityInstanceId } },
29
36
  ...((0, shell_1.writeLockSupported)(tx) ? { lock: { mode: 'pessimistic_write' } } : {})
30
37
  });
38
+ const now = threads.find(thread => thread.id === activityThread.id);
31
39
  if (now?.state !== activity_thread_js_1.ActivityThreadStatus.Unassigned) {
32
40
  throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: activityThread.id }));
33
41
  }
42
+ /* One person, one thread of a case - as pick refuses it (activity-instance/pick.ts). */
43
+ if (threads.some(thread => thread.assigneeId === user.id)) {
44
+ throw new Error(context.t('error.activity-instance already yours', { id: activityInstance.id }));
45
+ }
34
46
  const poolIds = (0, assignee_pool_js_1.poolIdsOf)(activityInstance);
35
47
  if (poolIds.length) {
36
48
  const roles = (await (0, shell_1.getRepository)(auth_base_1.User, tx).findOne({
@@ -41,6 +53,9 @@ async function checkAuthority(activityThread, context) {
41
53
  if ((0, assignee_pool_js_1.holdsAnyOf)((roles || []).map(role => role.id), poolIds)) {
42
54
  activityThread.assignee = user;
43
55
  activityThread.state = activity_thread_js_1.ActivityThreadStatus.Assigned;
56
+ /* The caller writes this thread; the count below reads it as held already. */
57
+ Object.assign(now, { assignee: user, state: activity_thread_js_1.ActivityThreadStatus.Assigned });
58
+ await (0, common_js_1.leaveTheRestOfThePoolOneWaiting)('claim', activityInstance, threads, context);
44
59
  return;
45
60
  }
46
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"check-authority.js","sourceRoot":"","sources":["../../../server/controllers/activity-thread/check-authority.ts"],"names":[],"mappings":";;AAMA,wCAmDC;AAzDD,iDAAyE;AACzE,yDAAgD;AAChD,yFAAuG;AACvG,+FAAuF;AACvF,uFAAwF;AAEjF,KAAK,UAAU,cAAc,CAAC,cAA8B,EAAE,OAAwB;IAC3F,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAElC,IAAI,cAAc,CAAC,UAAU,IAAI,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;QACvE,OAAM;IACR,CAAC;IAED,wGAAwG;IACxG,IAAI,cAAc,CAAC,KAAK,IAAI,yCAAoB,CAAC,UAAU,EAAE,CAAC;QAC5D;;;;;WAKG;QACH,MAAM,gBAAgB,GAAG,MAAM,IAAA,qBAAa,EAAC,uCAAgB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;YACzE,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,CAAC,kBAAkB,EAAE;YAChD,GAAG,CAAC,IAAA,0BAAkB,EAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,mBAA4B,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpF,CAAC,CAAA;QAEF,wGAAwG;QACxG,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,mCAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;YAC1D,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,CAAC,EAAE,EAAE;YAChC,GAAG,CAAC,IAAA,0BAAkB,EAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,mBAA4B,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpF,CAAC,CAAA;QAEF,IAAI,GAAG,EAAE,KAAK,KAAK,yCAAoB,CAAC,UAAU,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,+CAA+C,EAAE,EAAE,gBAAgB,EAAE,cAAc,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;QACtH,CAAC;QAED,MAAM,OAAO,GAAG,IAAA,4BAAS,EAAC,gBAAgB,CAAC,CAAA;QAE3C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,CACZ,MAAM,IAAA,qBAAa,EAAC,gBAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;gBACpC,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;gBACtB,SAAS,EAAE,CAAC,OAAO,CAAC;aACrB,CAAC,CACH,EAAE,KAAK,CAAA;YAER,kEAAkE;YAClE,IAAI,IAAA,6BAAU,EAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC5D,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAA;gBAC9B,cAAc,CAAC,KAAK,GAAG,yCAAoB,CAAC,QAAQ,CAAA;gBAEpD,OAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAA;AACxE,CAAC","sourcesContent":["import { getRepository, writeLockSupported } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityInstance } from '../../service/activity-instance/activity-instance.js'\nimport { holdsAnyOf, poolIdsOf } from '../../service/activity-instance/assignee-pool.js'\n\nexport async function checkAuthority(activityThread: ActivityThread, context: ResolverContext) {\n const { user, tx } = context.state\n\n if (activityThread.assigneeId && activityThread.assigneeId === user.id) {\n return\n }\n\n // TODO activityInstance의 cardinality를 기준으로 unassigned thread를 추가로 만들 지, assigned thread로 대체할 지를 구현해야한다.\n if (activityThread.state == ActivityThreadStatus.Unassigned) {\n /*\n * Claiming the pool's waiting thread. The case is held first - the order pick and a pool assign take\n * (activity-instance/pick.ts, assign.ts) - and the thread is read again under it. The thread handed in\n * was read before anything was held: someone else may have claimed it since, or a manager moved the\n * case to another role's pool, and what this claims must be what is there now.\n */\n const activityInstance = await getRepository(ActivityInstance, tx).findOne({\n where: { id: activityThread.activityInstanceId },\n ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' as const } } : {})\n })\n\n /* A locking read is a current read - a plain one may answer from the snapshot taken before the wait. */\n const now = await getRepository(ActivityThread, tx).findOne({\n where: { id: activityThread.id },\n ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' as const } } : {})\n })\n\n if (now?.state !== ActivityThreadStatus.Unassigned) {\n throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: activityThread.id }))\n }\n\n const poolIds = poolIdsOf(activityInstance)\n\n if (poolIds.length) {\n const roles = (\n await getRepository(User, tx).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n )?.roles\n\n /* Any one of the pool's roles is enough (ADR-0091 decision 9). */\n if (holdsAnyOf((roles || []).map(role => role.id), poolIds)) {\n activityThread.assignee = user\n activityThread.state = ActivityThreadStatus.Assigned\n\n return\n }\n }\n }\n\n throw new Error(context.t(`error.not authorized to handle this task`))\n}\n"]}
1
+ {"version":3,"file":"check-authority.js","sourceRoot":"","sources":["../../../server/controllers/activity-thread/check-authority.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository, writeLockSupported } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityInstance } from '../../service/activity-instance/activity-instance.js'\nimport { holdsAnyOf, poolIdsOf } from '../../service/activity-instance/assignee-pool.js'\nimport { leaveTheRestOfThePoolOneWaiting } from '../common.js'\n\nexport async function checkAuthority(activityThread: ActivityThread, context: ResolverContext) {\n const { user, tx } = context.state\n\n if (activityThread.assigneeId && activityThread.assigneeId === user.id) {\n return\n }\n\n if (activityThread.state == ActivityThreadStatus.Unassigned) {\n /*\n * Claiming the pool's waiting thread. The case is held first - the order pick and a pool assign take\n * (activity-instance/pick.ts, assign.ts) - and the thread is read again under it. The thread handed in\n * was read before anything was held: someone else may have claimed it since, or a manager moved the\n * case to another role's pool, and what this claims must be what is there now.\n *\n * The claimer gets this very thread; a case still short of people leaves the rest of the pool a new\n * waiting one (common.ts leaveTheRestOfThePoolOneWaiting) - the same as pick.\n */\n const activityInstance = await getRepository(ActivityInstance, tx).findOne({\n where: { id: activityThread.activityInstanceId },\n ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' as const } } : {})\n })\n\n /*\n * All the case's threads, not only this one: whether the rest of the pool is left a waiting thread depends\n * on how many are held. A locking read is a current read - a plain one may answer from the snapshot taken\n * before the wait.\n */\n const threads = await getRepository(ActivityThread, tx).find({\n where: { activityInstance: { id: activityThread.activityInstanceId } },\n ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' as const } } : {})\n })\n const now = threads.find(thread => thread.id === activityThread.id)\n\n if (now?.state !== ActivityThreadStatus.Unassigned) {\n throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: activityThread.id }))\n }\n\n /* One person, one thread of a case - as pick refuses it (activity-instance/pick.ts). */\n if (threads.some(thread => thread.assigneeId === user.id)) {\n throw new Error(context.t('error.activity-instance already yours', { id: activityInstance.id }))\n }\n\n const poolIds = poolIdsOf(activityInstance)\n\n if (poolIds.length) {\n const roles = (\n await getRepository(User, tx).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n )?.roles\n\n /* Any one of the pool's roles is enough (ADR-0091 decision 9). */\n if (holdsAnyOf((roles || []).map(role => role.id), poolIds)) {\n activityThread.assignee = user\n activityThread.state = ActivityThreadStatus.Assigned\n\n /* The caller writes this thread; the count below reads it as held already. */\n Object.assign(now, { assignee: user, state: ActivityThreadStatus.Assigned })\n await leaveTheRestOfThePoolOneWaiting('claim', activityInstance, threads, context)\n\n return\n }\n }\n }\n\n throw new Error(context.t(`error.not authorized to handle this task`))\n}\n"]}
@@ -10,6 +10,19 @@ export type UpdateActivityInstanceStateAddendum = {
10
10
  export declare function updateActivityInstanceState(id: string, { causedBy }: UpdateActivityInstanceStateAddendum, context: ResolverContext): Promise<void>;
11
11
  export declare function evalActivityInstanceState(id: string, context: ResolverContext): Promise<Partial<ActivityInstance>>;
12
12
  export declare function createActivityThreadWithUnassigned(transaction: string, activityInstance: ActivityInstance, context: ResolverContext): Promise<ActivityThread>;
13
+ /**
14
+ * Someone of the pool has just taken a thread of this case - by pick, or by claiming the waiting thread. Is
15
+ * the case still short of people (fewer threads someone holds than `threadsMin`)? Then the rest of the pool
16
+ * is left one waiting thread to take; once it has enough, none.
17
+ *
18
+ * The waiting thread is what puts the case on the pool's to-do lists (my-open-threads.ts). Left behind after
19
+ * the case had its people, it kept the case on the list of everyone in the pool; gone while the case still
20
+ * waited, nobody else in the pool saw it any more.
21
+ *
22
+ * `threads` are the case's threads as they stand once the taker holds theirs, read under the hold on the case.
23
+ * The case's state is not worked out here - the taker does that once, after writing its own thread.
24
+ */
25
+ export declare function leaveTheRestOfThePoolOneWaiting(transaction: string, activityInstance: ActivityInstance, threads: ActivityThread[], context: ResolverContext): Promise<void>;
13
26
  export declare function createActivityThreadForUser(transaction: string, activityInstance: ActivityInstance, assignee: User, context: ResolverContext): Promise<ActivityThread>;
14
27
  export declare function createActivityThreadsForUsers(transaction: string, activityInstance: ActivityInstance, assignees: User[], context: ResolverContext): Promise<ActivityThread[]>;
15
28
  export declare function createActivityThreadsForAllRoleUsers(transaction: string, activityInstance: ActivityInstance, context: ResolverContext): Promise<ActivityThread[]>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateActivityInstanceState = updateActivityInstanceState;
4
4
  exports.evalActivityInstanceState = evalActivityInstanceState;
5
5
  exports.createActivityThreadWithUnassigned = createActivityThreadWithUnassigned;
6
+ exports.leaveTheRestOfThePoolOneWaiting = leaveTheRestOfThePoolOneWaiting;
6
7
  exports.createActivityThreadForUser = createActivityThreadForUser;
7
8
  exports.createActivityThreadsForUsers = createActivityThreadsForUsers;
8
9
  exports.createActivityThreadsForAllRoleUsers = createActivityThreadsForAllRoleUsers;
@@ -149,10 +150,34 @@ async function evalActivityInstanceState(id, context) {
149
150
  }
150
151
  }
151
152
  async function createActivityThreadWithUnassigned(transaction, activityInstance, context) {
153
+ const result = await saveWaitingThread(transaction, activityInstance, context);
154
+ await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context);
155
+ return result;
156
+ }
157
+ /**
158
+ * Someone of the pool has just taken a thread of this case - by pick, or by claiming the waiting thread. Is
159
+ * the case still short of people (fewer threads someone holds than `threadsMin`)? Then the rest of the pool
160
+ * is left one waiting thread to take; once it has enough, none.
161
+ *
162
+ * The waiting thread is what puts the case on the pool's to-do lists (my-open-threads.ts). Left behind after
163
+ * the case had its people, it kept the case on the list of everyone in the pool; gone while the case still
164
+ * waited, nobody else in the pool saw it any more.
165
+ *
166
+ * `threads` are the case's threads as they stand once the taker holds theirs, read under the hold on the case.
167
+ * The case's state is not worked out here - the taker does that once, after writing its own thread.
168
+ */
169
+ async function leaveTheRestOfThePoolOneWaiting(transaction, activityInstance, threads, context) {
170
+ const held = threads.filter(thread => thread.isValid()).length;
171
+ const waiting = threads.some(thread => thread.state === activity_thread_js_1.ActivityThreadStatus.Unassigned);
172
+ if (held < activityInstance.threadsMin && !waiting) {
173
+ await saveWaitingThread(transaction, activityInstance, context);
174
+ }
175
+ }
176
+ async function saveWaitingThread(transaction, activityInstance, context) {
152
177
  const { domain, user, tx } = context.state;
153
178
  const threadRepo = tx.getRepository(activity_thread_js_1.ActivityThread);
154
179
  const now = new Date();
155
- const result = await threadRepo.save({
180
+ return await threadRepo.save({
156
181
  version: 1,
157
182
  domain,
158
183
  state: activity_thread_js_1.ActivityThreadStatus.Unassigned,
@@ -165,8 +190,6 @@ async function createActivityThreadWithUnassigned(transaction, activityInstance,
165
190
  updater: user,
166
191
  createdAt: now
167
192
  });
168
- await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context);
169
- return result;
170
193
  }
171
194
  async function createActivityThreadForUser(transaction, activityInstance, assignee, context) {
172
195
  const { domain, user, tx } = context.state;
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","sourceRoot":"","sources":["../../server/controllers/common.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { Role, User } from '@things-factory/auth-base'\n\nimport { Department, Employee, ApprovalLineItem, OrgMemberTargetType } from '@things-factory/organization'\nimport { ActivitySearchKeyItem } from '../service/activity/activity-search-key-item-type.js'\nimport { AssigneeItem, MultipleInstanceType } from '../service/activity/activity.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../service/activity-instance/activity-instance.js'\nimport { ActivityThread, ActivityThreadStatus } from '../service/activity-thread/activity-thread.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../service/activity-approval/activity-approval.js'\nimport { ActivityInstallations } from './activity-installation/activity-installation-controller.js'\nimport { poolIdsOf, poolMembers } from '../service/activity-instance/assignee-pool.js'\nimport { ROLE_IS_NOT_AN_ASSIGNEE } from './activity-instance/assignee-input.js'\n\nexport type UpdateActivityInstanceStateAddendum = {\n causedBy?: ActivityThread[]\n}\n\nexport async function updateActivityInstanceState(\n id: string,\n { causedBy }: UpdateActivityInstanceStateAddendum,\n context: ResolverContext\n) {\n const { tx } = context.state\n\n var activityInstance = await tx.getRepository(ActivityInstance).findOne({\n where: { id },\n relations: [\n 'domain',\n 'activity',\n 'activityThreads',\n 'assigneeRoles',\n 'supervisoryRole',\n 'updater',\n 'creator',\n 'starter',\n 'terminator'\n ]\n })\n\n activityInstance.transaction = 'thread' /* activity thread에 의해서 변경된 것임을 의미 */\n Object.assign(activityInstance, await evalActivityInstanceState(id, context))\n\n /*\n * **건이 끝나면 참조도 닫는다.**\n *\n * 참조는 결재하지 않으므로 승인·반려로 끝나지 않는다. 닫아 주지 않으면 그 행이 `Pending` 으로 남아\n * 참조 목록에 영원히 나온다 — 오늘 결재자 쪽에서 고친 것과 같은 부류를, 참조를 넣으면서 새로 만들 뻔했다.\n *\n * 중단은 이미 닫힌다(§`activity-thread/_abort.ts` 가 대기 행을 `aborted` 로 닫는다). 여기서 닫는 것은\n * **정상 종료**다.\n */\n if (activityInstance.state === ActivityInstanceStatus.Ended) {\n await closePendingReferences(id, context)\n }\n\n /* callback에서 업무적인 처리도 하고, activityInstance를 일부(주로 input) 수정할 수 있는 기회도 제공. */\n await ActivityInstallations.callback(activityInstance.activity.name, activityInstance, causedBy, context)\n\n await tx.getRepository(ActivityInstance).save(activityInstance)\n}\n\n/**\n * 이 건의 아직 안 닫힌 참조들을 닫는다 — **판정한 것이 아니라 건이 끝난 것이다.**\n *\n * 의견을 냈는지는 `comment` 가 말한다. 비어 있으면 「통지받았고 말하지 않았다」이고, **그것은 동의가\n * 아니다** — 나중에 결재 이력을 읽는 사람이 침묵을 동의로 읽으면 안 된다.\n *\n * `terminator` 를 적지 않는다. 그 사람이 끝낸 것이 아니라 건이 끝난 것이다.\n */\nasync function closePendingReferences(instanceId: string, context: ResolverContext): Promise<void> {\n const { tx } = context.state\n\n const threads = await tx.getRepository(ActivityThread).find({\n where: { activityInstance: { id: instanceId } }\n })\n if (!threads.length) return\n\n const repo = tx.getRepository(ActivityApproval)\n const open = await repo.find({\n where: threads.map(t => ({\n activityThread: { id: t.id },\n participation: 'reference',\n judgment: ActivityApprovalJudgment.Pending\n }))\n })\n if (!open.length) return\n\n const at = new Date()\n await repo.save(\n open.map(row => ({\n ...row,\n judgment: ActivityApprovalJudgment.Closed,\n transaction: 'close',\n terminatedAt: at\n })) as any\n )\n}\n\nexport async function evalActivityInstanceState(\n id: string,\n context: ResolverContext\n): Promise<Partial<ActivityInstance>> {\n const { tx, user } = context.state\n\n const activityInstance = await tx.getRepository(ActivityInstance).findOne({\n where: { id },\n relations: ['activityThreads', 'activity']\n })\n const { threadsMin, activityThreads, state, dueAt } = activityInstance\n\n const validThreads = activityThreads.filter(thread => thread.isValid())\n const pendingStartedThreads = activityThreads.filter(thread => thread.isPendingStarted())\n const pendingEndedThreads = activityThreads.filter(thread => thread.isPendingEnded())\n\n switch (true) {\n case state === ActivityInstanceStatus.Draft:\n case state === ActivityInstanceStatus.Ended:\n case state === ActivityInstanceStatus.Aborted:\n return {}\n case validThreads.length === 0:\n return {\n state: ActivityInstanceStatus.Issued\n }\n case threadsMin > validThreads.length:\n return {\n state: ActivityInstanceStatus.Unassigned\n }\n case pendingStartedThreads.length > 0:\n return {\n state: ActivityInstanceStatus.Assigned,\n assignedAt: new Date()\n }\n case pendingEndedThreads.length > 0:\n var started = !dueAt || dueAt.getTime() > Date.now()\n var changed =\n (started && state !== ActivityInstanceStatus.Started) || (!started && state !== ActivityInstanceStatus.Pending)\n\n return !changed\n ? null\n : started\n ? {\n state: ActivityInstanceStatus.Started,\n startedAt: new Date(),\n starter: user\n }\n : {\n state: ActivityInstanceStatus.Pending\n }\n default:\n return {\n state: activityThreads.some(thread => thread.state == ActivityThreadStatus.Aborted)\n ? ActivityInstanceStatus.Aborted\n : ActivityInstanceStatus.Ended,\n reason: '',\n output: [MultipleInstanceType.Parallel, MultipleInstanceType.Sequential].includes(\n activityInstance.activity.multiple\n )\n ? activityThreads\n .filter(thread => (thread.state = ActivityThreadStatus.Ended))\n .reduce((sum, thread) => {\n sum[thread.assigneeId] = thread.output\n return sum\n }, {} as any)\n : activityThreads[0].output,\n terminatedAt: new Date(),\n terminator: user\n }\n }\n}\n\nexport async function createActivityThreadWithUnassigned(\n transaction: string,\n activityInstance: ActivityInstance,\n context: ResolverContext\n): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n const result = await threadRepo.save({\n version: 1,\n domain,\n state: ActivityThreadStatus.Unassigned,\n activityInstance,\n transaction,\n output: activityInstance.output,\n dueAt: activityInstance.dueAt,\n assignedAt: now,\n creator: user,\n updater: user,\n createdAt: now\n })\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context)\n\n return result\n}\n\nexport async function createActivityThreadForUser(\n transaction: string,\n activityInstance: ActivityInstance,\n assignee: User,\n context: ResolverContext\n): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n const result = await threadRepo.save({\n version: 1,\n domain,\n assignee,\n state: ActivityThreadStatus.Assigned,\n activityInstance,\n transaction,\n output: activityInstance.output,\n dueAt: activityInstance.dueAt,\n assignedAt: now,\n creator: user,\n updater: user,\n createdAt: now\n })\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context)\n\n return result\n}\n\nexport async function createActivityThreadsForUsers(\n transaction: string,\n activityInstance: ActivityInstance,\n assignees: User[],\n context: ResolverContext\n): Promise<ActivityThread[]> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n const distinct = assignees.filter((assignee, idx) => {\n return assignee && !assignees.slice(0, idx).find(x => x.id == assignee.id)\n })\n\n const result = await threadRepo.save(\n distinct.map(assignee => {\n return {\n version: 1,\n assignee,\n state: ActivityThreadStatus.Assigned,\n activityInstance,\n domain,\n transaction,\n output: activityInstance.output,\n creator: user,\n updater: user,\n dueAt: activityInstance.dueAt,\n assignedAt: now\n } as any\n })\n )\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: result }, context)\n\n return result\n}\n\nexport async function createActivityThreadsForAllRoleUsers(\n transaction: string,\n activityInstance: ActivityInstance,\n context: ResolverContext\n): Promise<ActivityThread[]> {\n const { tx } = context.state\n\n /* Everyone the pool reaches - any one of its roles, each person once (assignee-pool.ts). */\n const poolIds = poolIdsOf(activityInstance)\n const roles = poolIds.length ? await tx.getRepository(Role).find({ where: { id: In(poolIds) }, relations: ['users'] }) : []\n const assignees = poolMembers(roles)\n\n if (assignees.length === 0) {\n throw new Error(\"there's no users having given role\")\n }\n\n // create activity-thread for all of users having given role\n return await createActivityThreadsForUsers(transaction, activityInstance, assignees, context)\n}\n\nexport async function getSystemUserFromMyDepartmentManager(context: ResolverContext): Promise<User> {\n const { domain, user, tx } = context.state\n\n const employee = await tx.getRepository(Employee).findOneBy({\n domain: { id: domain.id },\n user: { id: user.id }\n })\n\n if (!employee || !employee.departmentId) {\n return\n }\n\n const department = await tx.getRepository(Department).findOne({\n where: {\n id: employee.departmentId\n }\n })\n\n if (!department || !department.managerId) return\n\n const manager = await tx.getRepository(Employee).findOne({\n where: {\n domain: { id: domain.id },\n id: department.managerId\n },\n relations: ['user']\n })\n\n return manager?.user\n}\n\nexport async function getSystemUserFromMySupervosor(context: ResolverContext): Promise<User> {\n const { domain, user, tx } = context.state\n\n const employee = await tx.getRepository(Employee).findOneBy({\n domain: { id: domain.id },\n user: { id: user.id }\n })\n\n if (!employee || !employee.supervisorId) {\n return\n }\n\n const supervisor = await tx.getRepository(Employee).findOne({\n where: {\n id: employee.supervisorId\n },\n relations: ['user']\n })\n\n return supervisor?.user\n}\n\nexport async function getSystemUserFromOrgMemberItem(\n item: AssigneeItem | ApprovalLineItem,\n context: ResolverContext\n): Promise<User> {\n const { domain, user, tx } = context.state\n\n const { type, approver, assignee, value } = item as AssigneeItem & ApprovalLineItem\n const id = approver?.id || assignee?.id || value\n\n if (type == OrgMemberTargetType.Employee) {\n /* This domain's only - an id from another tenant made that tenant's person an approver here. */\n const employee =\n id && (await tx.getRepository(Employee).findOne({ where: { domain: { id: domain.id }, id }, relations: ['user'] }))\n\n if (!employee || !employee.user) {\n return\n }\n return employee.user\n } else if (type == OrgMemberTargetType.Department) {\n const department =\n id && (await tx.getRepository(Department).findOne({ where: { domain: { id: domain.id }, id }, relations: ['manager'] }))\n\n if (!department || !department.manager) {\n return\n }\n return await tx.getRepository(User).findOneBy({ id: department.manager.userId })\n } else if (type == OrgMemberTargetType.Role) {\n /*\n * A role is not one person. This answered the role's first user - an unordered relation, so an\n * arbitrary one - and issue() gave that person work meant for the whole role. Each caller decides\n * what a role means there before asking this: the approval line seats the role (create-approval.ts),\n * assignees refuse it (assignee-input.ts). Reaching here is a caller that skipped that step.\n */\n throw Object.assign(new Error(context.t('error.activity-instance a role is not an assignee')), {\n code: ROLE_IS_NOT_AN_ASSIGNEE\n })\n } else if (type == OrgMemberTargetType.Myself) {\n return user\n } else if (type == OrgMemberTargetType.MyDepartment) {\n return await getSystemUserFromMyDepartmentManager(context)\n } else if (type == OrgMemberTargetType.MySupervisor) {\n return await getSystemUserFromMySupervosor(context)\n }\n}\n\nexport function fillActivitySearchKeys(searchKeys: ActivitySearchKeyItem[], input: { [key: string]: any }) {\n if (!input || typeof input !== 'object') {\n return\n }\n\n return searchKeys?.reduce((sum, key, index) => {\n const value = input[key.inputKey]\n if (value != null) {\n sum[`key0${index + 1}`] = value instanceof Array ? value[0] : value\n }\n return sum\n }, {})\n}\n"]}
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../server/controllers/common.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { Role, User } from '@things-factory/auth-base'\n\nimport { Department, Employee, ApprovalLineItem, OrgMemberTargetType } from '@things-factory/organization'\nimport { ActivitySearchKeyItem } from '../service/activity/activity-search-key-item-type.js'\nimport { AssigneeItem, MultipleInstanceType } from '../service/activity/activity.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../service/activity-instance/activity-instance.js'\nimport { ActivityThread, ActivityThreadStatus } from '../service/activity-thread/activity-thread.js'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../service/activity-approval/activity-approval.js'\nimport { ActivityInstallations } from './activity-installation/activity-installation-controller.js'\nimport { poolIdsOf, poolMembers } from '../service/activity-instance/assignee-pool.js'\nimport { ROLE_IS_NOT_AN_ASSIGNEE } from './activity-instance/assignee-input.js'\n\nexport type UpdateActivityInstanceStateAddendum = {\n causedBy?: ActivityThread[]\n}\n\nexport async function updateActivityInstanceState(\n id: string,\n { causedBy }: UpdateActivityInstanceStateAddendum,\n context: ResolverContext\n) {\n const { tx } = context.state\n\n var activityInstance = await tx.getRepository(ActivityInstance).findOne({\n where: { id },\n relations: [\n 'domain',\n 'activity',\n 'activityThreads',\n 'assigneeRoles',\n 'supervisoryRole',\n 'updater',\n 'creator',\n 'starter',\n 'terminator'\n ]\n })\n\n activityInstance.transaction = 'thread' /* activity thread에 의해서 변경된 것임을 의미 */\n Object.assign(activityInstance, await evalActivityInstanceState(id, context))\n\n /*\n * **건이 끝나면 참조도 닫는다.**\n *\n * 참조는 결재하지 않으므로 승인·반려로 끝나지 않는다. 닫아 주지 않으면 그 행이 `Pending` 으로 남아\n * 참조 목록에 영원히 나온다 — 오늘 결재자 쪽에서 고친 것과 같은 부류를, 참조를 넣으면서 새로 만들 뻔했다.\n *\n * 중단은 이미 닫힌다(§`activity-thread/_abort.ts` 가 대기 행을 `aborted` 로 닫는다). 여기서 닫는 것은\n * **정상 종료**다.\n */\n if (activityInstance.state === ActivityInstanceStatus.Ended) {\n await closePendingReferences(id, context)\n }\n\n /* callback에서 업무적인 처리도 하고, activityInstance를 일부(주로 input) 수정할 수 있는 기회도 제공. */\n await ActivityInstallations.callback(activityInstance.activity.name, activityInstance, causedBy, context)\n\n await tx.getRepository(ActivityInstance).save(activityInstance)\n}\n\n/**\n * 이 건의 아직 안 닫힌 참조들을 닫는다 — **판정한 것이 아니라 건이 끝난 것이다.**\n *\n * 의견을 냈는지는 `comment` 가 말한다. 비어 있으면 「통지받았고 말하지 않았다」이고, **그것은 동의가\n * 아니다** — 나중에 결재 이력을 읽는 사람이 침묵을 동의로 읽으면 안 된다.\n *\n * `terminator` 를 적지 않는다. 그 사람이 끝낸 것이 아니라 건이 끝난 것이다.\n */\nasync function closePendingReferences(instanceId: string, context: ResolverContext): Promise<void> {\n const { tx } = context.state\n\n const threads = await tx.getRepository(ActivityThread).find({\n where: { activityInstance: { id: instanceId } }\n })\n if (!threads.length) return\n\n const repo = tx.getRepository(ActivityApproval)\n const open = await repo.find({\n where: threads.map(t => ({\n activityThread: { id: t.id },\n participation: 'reference',\n judgment: ActivityApprovalJudgment.Pending\n }))\n })\n if (!open.length) return\n\n const at = new Date()\n await repo.save(\n open.map(row => ({\n ...row,\n judgment: ActivityApprovalJudgment.Closed,\n transaction: 'close',\n terminatedAt: at\n })) as any\n )\n}\n\nexport async function evalActivityInstanceState(\n id: string,\n context: ResolverContext\n): Promise<Partial<ActivityInstance>> {\n const { tx, user } = context.state\n\n const activityInstance = await tx.getRepository(ActivityInstance).findOne({\n where: { id },\n relations: ['activityThreads', 'activity']\n })\n const { threadsMin, activityThreads, state, dueAt } = activityInstance\n\n const validThreads = activityThreads.filter(thread => thread.isValid())\n const pendingStartedThreads = activityThreads.filter(thread => thread.isPendingStarted())\n const pendingEndedThreads = activityThreads.filter(thread => thread.isPendingEnded())\n\n switch (true) {\n case state === ActivityInstanceStatus.Draft:\n case state === ActivityInstanceStatus.Ended:\n case state === ActivityInstanceStatus.Aborted:\n return {}\n case validThreads.length === 0:\n return {\n state: ActivityInstanceStatus.Issued\n }\n case threadsMin > validThreads.length:\n return {\n state: ActivityInstanceStatus.Unassigned\n }\n case pendingStartedThreads.length > 0:\n return {\n state: ActivityInstanceStatus.Assigned,\n assignedAt: new Date()\n }\n case pendingEndedThreads.length > 0:\n var started = !dueAt || dueAt.getTime() > Date.now()\n var changed =\n (started && state !== ActivityInstanceStatus.Started) || (!started && state !== ActivityInstanceStatus.Pending)\n\n return !changed\n ? null\n : started\n ? {\n state: ActivityInstanceStatus.Started,\n startedAt: new Date(),\n starter: user\n }\n : {\n state: ActivityInstanceStatus.Pending\n }\n default:\n return {\n state: activityThreads.some(thread => thread.state == ActivityThreadStatus.Aborted)\n ? ActivityInstanceStatus.Aborted\n : ActivityInstanceStatus.Ended,\n reason: '',\n output: [MultipleInstanceType.Parallel, MultipleInstanceType.Sequential].includes(\n activityInstance.activity.multiple\n )\n ? activityThreads\n .filter(thread => (thread.state = ActivityThreadStatus.Ended))\n .reduce((sum, thread) => {\n sum[thread.assigneeId] = thread.output\n return sum\n }, {} as any)\n : activityThreads[0].output,\n terminatedAt: new Date(),\n terminator: user\n }\n }\n}\n\nexport async function createActivityThreadWithUnassigned(\n transaction: string,\n activityInstance: ActivityInstance,\n context: ResolverContext\n): Promise<ActivityThread> {\n const result = await saveWaitingThread(transaction, activityInstance, context)\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context)\n\n return result\n}\n\n/**\n * Someone of the pool has just taken a thread of this case - by pick, or by claiming the waiting thread. Is\n * the case still short of people (fewer threads someone holds than `threadsMin`)? Then the rest of the pool\n * is left one waiting thread to take; once it has enough, none.\n *\n * The waiting thread is what puts the case on the pool's to-do lists (my-open-threads.ts). Left behind after\n * the case had its people, it kept the case on the list of everyone in the pool; gone while the case still\n * waited, nobody else in the pool saw it any more.\n *\n * `threads` are the case's threads as they stand once the taker holds theirs, read under the hold on the case.\n * The case's state is not worked out here - the taker does that once, after writing its own thread.\n */\nexport async function leaveTheRestOfThePoolOneWaiting(\n transaction: string,\n activityInstance: ActivityInstance,\n threads: ActivityThread[],\n context: ResolverContext\n): Promise<void> {\n const held = threads.filter(thread => thread.isValid()).length\n const waiting = threads.some(thread => thread.state === ActivityThreadStatus.Unassigned)\n\n if (held < activityInstance.threadsMin && !waiting) {\n await saveWaitingThread(transaction, activityInstance, context)\n }\n}\n\nasync function saveWaitingThread(\n transaction: string,\n activityInstance: ActivityInstance,\n context: ResolverContext\n): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n return await threadRepo.save({\n version: 1,\n domain,\n state: ActivityThreadStatus.Unassigned,\n activityInstance,\n transaction,\n output: activityInstance.output,\n dueAt: activityInstance.dueAt,\n assignedAt: now,\n creator: user,\n updater: user,\n createdAt: now\n })\n}\n\nexport async function createActivityThreadForUser(\n transaction: string,\n activityInstance: ActivityInstance,\n assignee: User,\n context: ResolverContext\n): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n const result = await threadRepo.save({\n version: 1,\n domain,\n assignee,\n state: ActivityThreadStatus.Assigned,\n activityInstance,\n transaction,\n output: activityInstance.output,\n dueAt: activityInstance.dueAt,\n assignedAt: now,\n creator: user,\n updater: user,\n createdAt: now\n })\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: [result] }, context)\n\n return result\n}\n\nexport async function createActivityThreadsForUsers(\n transaction: string,\n activityInstance: ActivityInstance,\n assignees: User[],\n context: ResolverContext\n): Promise<ActivityThread[]> {\n const { domain, user, tx } = context.state\n\n const threadRepo = tx.getRepository(ActivityThread)\n const now = new Date()\n\n const distinct = assignees.filter((assignee, idx) => {\n return assignee && !assignees.slice(0, idx).find(x => x.id == assignee.id)\n })\n\n const result = await threadRepo.save(\n distinct.map(assignee => {\n return {\n version: 1,\n assignee,\n state: ActivityThreadStatus.Assigned,\n activityInstance,\n domain,\n transaction,\n output: activityInstance.output,\n creator: user,\n updater: user,\n dueAt: activityInstance.dueAt,\n assignedAt: now\n } as any\n })\n )\n\n await updateActivityInstanceState(activityInstance.id, { causedBy: result }, context)\n\n return result\n}\n\nexport async function createActivityThreadsForAllRoleUsers(\n transaction: string,\n activityInstance: ActivityInstance,\n context: ResolverContext\n): Promise<ActivityThread[]> {\n const { tx } = context.state\n\n /* Everyone the pool reaches - any one of its roles, each person once (assignee-pool.ts). */\n const poolIds = poolIdsOf(activityInstance)\n const roles = poolIds.length ? await tx.getRepository(Role).find({ where: { id: In(poolIds) }, relations: ['users'] }) : []\n const assignees = poolMembers(roles)\n\n if (assignees.length === 0) {\n throw new Error(\"there's no users having given role\")\n }\n\n // create activity-thread for all of users having given role\n return await createActivityThreadsForUsers(transaction, activityInstance, assignees, context)\n}\n\nexport async function getSystemUserFromMyDepartmentManager(context: ResolverContext): Promise<User> {\n const { domain, user, tx } = context.state\n\n const employee = await tx.getRepository(Employee).findOneBy({\n domain: { id: domain.id },\n user: { id: user.id }\n })\n\n if (!employee || !employee.departmentId) {\n return\n }\n\n const department = await tx.getRepository(Department).findOne({\n where: {\n id: employee.departmentId\n }\n })\n\n if (!department || !department.managerId) return\n\n const manager = await tx.getRepository(Employee).findOne({\n where: {\n domain: { id: domain.id },\n id: department.managerId\n },\n relations: ['user']\n })\n\n return manager?.user\n}\n\nexport async function getSystemUserFromMySupervosor(context: ResolverContext): Promise<User> {\n const { domain, user, tx } = context.state\n\n const employee = await tx.getRepository(Employee).findOneBy({\n domain: { id: domain.id },\n user: { id: user.id }\n })\n\n if (!employee || !employee.supervisorId) {\n return\n }\n\n const supervisor = await tx.getRepository(Employee).findOne({\n where: {\n id: employee.supervisorId\n },\n relations: ['user']\n })\n\n return supervisor?.user\n}\n\nexport async function getSystemUserFromOrgMemberItem(\n item: AssigneeItem | ApprovalLineItem,\n context: ResolverContext\n): Promise<User> {\n const { domain, user, tx } = context.state\n\n const { type, approver, assignee, value } = item as AssigneeItem & ApprovalLineItem\n const id = approver?.id || assignee?.id || value\n\n if (type == OrgMemberTargetType.Employee) {\n /* This domain's only - an id from another tenant made that tenant's person an approver here. */\n const employee =\n id && (await tx.getRepository(Employee).findOne({ where: { domain: { id: domain.id }, id }, relations: ['user'] }))\n\n if (!employee || !employee.user) {\n return\n }\n return employee.user\n } else if (type == OrgMemberTargetType.Department) {\n const department =\n id && (await tx.getRepository(Department).findOne({ where: { domain: { id: domain.id }, id }, relations: ['manager'] }))\n\n if (!department || !department.manager) {\n return\n }\n return await tx.getRepository(User).findOneBy({ id: department.manager.userId })\n } else if (type == OrgMemberTargetType.Role) {\n /*\n * A role is not one person. This answered the role's first user - an unordered relation, so an\n * arbitrary one - and issue() gave that person work meant for the whole role. Each caller decides\n * what a role means there before asking this: the approval line seats the role (create-approval.ts),\n * assignees refuse it (assignee-input.ts). Reaching here is a caller that skipped that step.\n */\n throw Object.assign(new Error(context.t('error.activity-instance a role is not an assignee')), {\n code: ROLE_IS_NOT_AN_ASSIGNEE\n })\n } else if (type == OrgMemberTargetType.Myself) {\n return user\n } else if (type == OrgMemberTargetType.MyDepartment) {\n return await getSystemUserFromMyDepartmentManager(context)\n } else if (type == OrgMemberTargetType.MySupervisor) {\n return await getSystemUserFromMySupervosor(context)\n }\n}\n\nexport function fillActivitySearchKeys(searchKeys: ActivitySearchKeyItem[], input: { [key: string]: any }) {\n if (!input || typeof input !== 'object') {\n return\n }\n\n return searchKeys?.reduce((sum, key, index) => {\n const value = input[key.inputKey]\n if (value != null) {\n sum[`key0${index + 1}`] = value instanceof Array ? value[0] : value\n }\n return sum\n }, {})\n}\n"]}
@@ -23,6 +23,12 @@ const NOT_MY_TURN = [
23
23
  activity_thread_js_1.ActivityThreadStatus.Ended,
24
24
  activity_thread_js_1.ActivityThreadStatus.Aborted
25
25
  ];
26
+ /** The thread table, for the raw EXISTS below - this module reads no entity metadata, as assignee-pool.ts. */
27
+ const THREADS = {
28
+ table: 'activity_threads',
29
+ caseColumn: 'activity_instance_id',
30
+ assigneeColumn: 'assignee_id'
31
+ };
26
32
  /** A thread I submitted, waiting on someone's judgment. */
27
33
  exports.WAITING_ON_OTHERS = [activity_thread_js_1.ActivityThreadStatus.Submitted, activity_thread_js_1.ActivityThreadStatus.Escalated];
28
34
  /** A thread that is over. */
@@ -42,12 +48,20 @@ function myOpenThreadsWhere({ userId, roles }) {
42
48
  qb.where('at.state NOT IN (:...notMyTurn)', { notMyTurn: NOT_MY_TURN }).andWhere('at.assignee = :userId', {
43
49
  userId
44
50
  });
45
- /* 역할로 걸린 것은 아직 임자가 없다 — 그 역할들 중 하나라도 가진 사람 모두의 할 일이다. */
51
+ /*
52
+ * 역할로 걸린 것은 아직 임자가 없다 — 그 역할들 중 하나라도 가진 사람 모두의 할 일이다.
53
+ *
54
+ * Except someone who holds a thread of the case already: in a case waiting for more than one person
55
+ * (threadsMin > 1) they would see it twice - their own thread and the waiting one beside it - and taking
56
+ * the waiting one is refused (one person, one thread of a case).
57
+ */
46
58
  if (roles.length > 0) {
47
59
  qb.orWhere(new typeorm_1.Brackets(subQb => {
48
60
  subQb
49
61
  .where((0, assignee_pool_js_1.inPoolOf)('ai'), { roles })
50
- .andWhere('at.state = :unassigned', { unassigned: activity_thread_js_1.ActivityThreadStatus.Unassigned });
62
+ .andWhere('at.state = :unassigned', { unassigned: activity_thread_js_1.ActivityThreadStatus.Unassigned })
63
+ .andWhere(`NOT EXISTS (SELECT 1 FROM ${THREADS.table} mine` +
64
+ ` WHERE mine.${THREADS.caseColumn} = ai.id AND mine.${THREADS.assigneeColumn} = :userId)`, { userId });
51
65
  }));
52
66
  }
53
67
  });
@@ -1 +1 @@
1
- {"version":3,"file":"my-open-threads.js","sourceRoot":"","sources":["../../../server/service/activity-thread/my-open-threads.ts"],"names":[],"mappings":";;;AAuCA,gDAiBC;AAYD,wDAMC;AA1ED,qCAAkC;AAElC,4EAAgE;AAChE,6DAA2D;AAE3D;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG;IAClB,yCAAoB,CAAC,UAAU;IAC/B,yCAAoB,CAAC,SAAS;IAC9B,yCAAoB,CAAC,SAAS;IAC9B,yCAAoB,CAAC,KAAK;IAC1B,yCAAoB,CAAC,OAAO;CAC7B,CAAA;AAED,2DAA2D;AAC9C,QAAA,iBAAiB,GAAG,CAAC,yCAAoB,CAAC,SAAS,EAAE,yCAAoB,CAAC,SAAS,CAAC,CAAA;AAEjG,6BAA6B;AAChB,QAAA,QAAQ,GAAG,CAAC,yCAAoB,CAAC,KAAK,EAAE,yCAAoB,CAAC,OAAO,CAAC,CAAA;AAElF;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAuC;IACvF,OAAO,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,uBAAuB,EAAE;YACxG,MAAM;SACP,CAAC,CAAA;QAEF,yDAAyD;QACzD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,EAAE,CAAC,OAAO,CACR,IAAI,kBAAQ,CAAC,KAAK,CAAC,EAAE;gBACnB,KAAK;qBACF,KAAK,CAAC,IAAA,2BAAQ,EAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;qBAChC,QAAQ,CAAC,wBAAwB,EAAE,EAAE,UAAU,EAAE,yCAAoB,CAAC,UAAU,EAAE,CAAC,CAAA;YACxF,CAAC,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,sBAAsB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAsD;IAC3G,OAAO,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC;aAC1C,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,CAAC;aAChD,QAAQ,CAAC,+CAA+C,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Brackets } from 'typeorm'\n\nimport { inPoolOf } from '../activity-instance/assignee-pool.js'\nimport { ActivityThreadStatus } from './activity-thread.js'\n\n/**\n * Thread states that are **not** my turn.\n *\n * Submitted and escalated wait on an approver: the thread is still assigned to me, but nothing I do\n * moves it. Counting them made 할 일 say 1 to a person who had just put up an expenditure request\n * and had nothing left to do (2026-10-16). They are the 「제출한 것」 group of the same box.\n *\n * Unassigned belongs here for the person branch only — a thread nobody holds is not on my plate\n * until I claim it, and the role branch below is what surfaces it to the people who could.\n */\nconst NOT_MY_TURN = [\n ActivityThreadStatus.Unassigned,\n ActivityThreadStatus.Submitted,\n ActivityThreadStatus.Escalated,\n ActivityThreadStatus.Ended,\n ActivityThreadStatus.Aborted\n]\n\n/** A thread I submitted, waiting on someone's judgment. */\nexport const WAITING_ON_OTHERS = [ActivityThreadStatus.Submitted, ActivityThreadStatus.Escalated]\n\n/** A thread that is over. */\nexport const FINISHED = [ActivityThreadStatus.Ended, ActivityThreadStatus.Aborted]\n\n/**\n * The work that is on my plate — one definition, three readers.\n *\n * 할 일 목록 asks this, and so do the calendar and the badge (open-work). Written twice they drift:\n * a new state, or a new way of being assigned, lands in one and not the other, and the badge says\n * a different number than the list it opens. Nothing errors.\n *\n * Expects `at` (ActivityThread) and `ai` (ActivityInstance) to be joined already — the role branch\n * reads the instance's pool (assignee-pool.ts).\n */\nexport function myOpenThreadsWhere({ userId, roles }: { userId: string; roles: string[] }): Brackets {\n return new Brackets(qb => {\n qb.where('at.state NOT IN (:...notMyTurn)', { notMyTurn: NOT_MY_TURN }).andWhere('at.assignee = :userId', {\n userId\n })\n\n /* 역할로 걸린 것은 아직 임자가 없다 — 그 역할들 중 하나라도 가진 사람 모두의 할 일이다. */\n if (roles.length > 0) {\n qb.orWhere(\n new Brackets(subQb => {\n subQb\n .where(inPoolOf('ai'), { roles })\n .andWhere('at.state = :unassigned', { unassigned: ActivityThreadStatus.Unassigned })\n })\n )\n }\n })\n}\n\n/**\n * My threads in these states, of work **someone else** put up.\n *\n * What I put up myself is followed in 상신함 — the box of what I gave others to do. A post thread\n * is mine only as the vehicle of my request: once submitted it waits on the approvers, and when it\n * ends it is my request that ended. Listing it again under 할 일 put one fact in two boxes. While it\n * is my turn - rejected and back with me - it stays in 할 일, which is myOpenThreadsWhere above.\n *\n * Expects `at` and `ai` joined.\n */\nexport function myThreadsOfOthersWhere({ userId, states }: { userId: string; states: ActivityThreadStatus[] }): Brackets {\n return new Brackets(qb => {\n qb.where('at.assignee = :userId', { userId })\n .andWhere('at.state IN (:...states)', { states })\n .andWhere('(ai.creator IS NULL OR ai.creator != :userId)', { userId })\n })\n}\n"]}
1
+ {"version":3,"file":"my-open-threads.js","sourceRoot":"","sources":["../../../server/service/activity-thread/my-open-threads.ts"],"names":[],"mappings":";;;AA8CA,gDA4BC;AAYD,wDAMC;AA5FD,qCAAkC;AAElC,4EAAgE;AAChE,6DAA2D;AAE3D;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG;IAClB,yCAAoB,CAAC,UAAU;IAC/B,yCAAoB,CAAC,SAAS;IAC9B,yCAAoB,CAAC,SAAS;IAC9B,yCAAoB,CAAC,KAAK;IAC1B,yCAAoB,CAAC,OAAO;CAC7B,CAAA;AAED,8GAA8G;AAC9G,MAAM,OAAO,GAAG;IACd,KAAK,EAAE,kBAAkB;IACzB,UAAU,EAAE,sBAAsB;IAClC,cAAc,EAAE,aAAa;CACrB,CAAA;AAEV,2DAA2D;AAC9C,QAAA,iBAAiB,GAAG,CAAC,yCAAoB,CAAC,SAAS,EAAE,yCAAoB,CAAC,SAAS,CAAC,CAAA;AAEjG,6BAA6B;AAChB,QAAA,QAAQ,GAAG,CAAC,yCAAoB,CAAC,KAAK,EAAE,yCAAoB,CAAC,OAAO,CAAC,CAAA;AAElF;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAuC;IACvF,OAAO,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,uBAAuB,EAAE;YACxG,MAAM;SACP,CAAC,CAAA;QAEF;;;;;;WAMG;QACH,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,EAAE,CAAC,OAAO,CACR,IAAI,kBAAQ,CAAC,KAAK,CAAC,EAAE;gBACnB,KAAK;qBACF,KAAK,CAAC,IAAA,2BAAQ,EAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;qBAChC,QAAQ,CAAC,wBAAwB,EAAE,EAAE,UAAU,EAAE,yCAAoB,CAAC,UAAU,EAAE,CAAC;qBACnF,QAAQ,CACP,6BAA6B,OAAO,CAAC,KAAK,OAAO;oBAC/C,eAAe,OAAO,CAAC,UAAU,qBAAqB,OAAO,CAAC,cAAc,aAAa,EAC3F,EAAE,MAAM,EAAE,CACX,CAAA;YACL,CAAC,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,sBAAsB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAsD;IAC3G,OAAO,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE;QACvB,EAAE,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC;aAC1C,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,CAAC;aAChD,QAAQ,CAAC,+CAA+C,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Brackets } from 'typeorm'\n\nimport { inPoolOf } from '../activity-instance/assignee-pool.js'\nimport { ActivityThreadStatus } from './activity-thread.js'\n\n/**\n * Thread states that are **not** my turn.\n *\n * Submitted and escalated wait on an approver: the thread is still assigned to me, but nothing I do\n * moves it. Counting them made 할 일 say 1 to a person who had just put up an expenditure request\n * and had nothing left to do (2026-10-16). They are the 「제출한 것」 group of the same box.\n *\n * Unassigned belongs here for the person branch only — a thread nobody holds is not on my plate\n * until I claim it, and the role branch below is what surfaces it to the people who could.\n */\nconst NOT_MY_TURN = [\n ActivityThreadStatus.Unassigned,\n ActivityThreadStatus.Submitted,\n ActivityThreadStatus.Escalated,\n ActivityThreadStatus.Ended,\n ActivityThreadStatus.Aborted\n]\n\n/** The thread table, for the raw EXISTS below - this module reads no entity metadata, as assignee-pool.ts. */\nconst THREADS = {\n table: 'activity_threads',\n caseColumn: 'activity_instance_id',\n assigneeColumn: 'assignee_id'\n} as const\n\n/** A thread I submitted, waiting on someone's judgment. */\nexport const WAITING_ON_OTHERS = [ActivityThreadStatus.Submitted, ActivityThreadStatus.Escalated]\n\n/** A thread that is over. */\nexport const FINISHED = [ActivityThreadStatus.Ended, ActivityThreadStatus.Aborted]\n\n/**\n * The work that is on my plate — one definition, three readers.\n *\n * 할 일 목록 asks this, and so do the calendar and the badge (open-work). Written twice they drift:\n * a new state, or a new way of being assigned, lands in one and not the other, and the badge says\n * a different number than the list it opens. Nothing errors.\n *\n * Expects `at` (ActivityThread) and `ai` (ActivityInstance) to be joined already — the role branch\n * reads the instance's pool (assignee-pool.ts).\n */\nexport function myOpenThreadsWhere({ userId, roles }: { userId: string; roles: string[] }): Brackets {\n return new Brackets(qb => {\n qb.where('at.state NOT IN (:...notMyTurn)', { notMyTurn: NOT_MY_TURN }).andWhere('at.assignee = :userId', {\n userId\n })\n\n /*\n * 역할로 걸린 것은 아직 임자가 없다 — 그 역할들 중 하나라도 가진 사람 모두의 할 일이다.\n *\n * Except someone who holds a thread of the case already: in a case waiting for more than one person\n * (threadsMin > 1) they would see it twice - their own thread and the waiting one beside it - and taking\n * the waiting one is refused (one person, one thread of a case).\n */\n if (roles.length > 0) {\n qb.orWhere(\n new Brackets(subQb => {\n subQb\n .where(inPoolOf('ai'), { roles })\n .andWhere('at.state = :unassigned', { unassigned: ActivityThreadStatus.Unassigned })\n .andWhere(\n `NOT EXISTS (SELECT 1 FROM ${THREADS.table} mine` +\n ` WHERE mine.${THREADS.caseColumn} = ai.id AND mine.${THREADS.assigneeColumn} = :userId)`,\n { userId }\n )\n })\n )\n }\n })\n}\n\n/**\n * My threads in these states, of work **someone else** put up.\n *\n * What I put up myself is followed in 상신함 — the box of what I gave others to do. A post thread\n * is mine only as the vehicle of my request: once submitted it waits on the approvers, and when it\n * ends it is my request that ended. Listing it again under 할 일 put one fact in two boxes. While it\n * is my turn - rejected and back with me - it stays in 할 일, which is myOpenThreadsWhere above.\n *\n * Expects `at` and `ai` joined.\n */\nexport function myThreadsOfOthersWhere({ userId, states }: { userId: string; states: ActivityThreadStatus[] }): Brackets {\n return new Brackets(qb => {\n qb.where('at.assignee = :userId', { userId })\n .andWhere('at.state IN (:...states)', { states })\n .andWhere('(ai.creator IS NULL OR ai.creator != :userId)', { userId })\n })\n}\n"]}