@things-factory/worklist 10.1.268 → 10.1.270
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/activity-cells.d.ts +4 -0
- package/dist-client/activity-cells.js +11 -0
- package/dist-client/activity-cells.js.map +1 -1
- package/dist-client/components/activity-instance-ribon.js +3 -2
- package/dist-client/components/activity-instance-ribon.js.map +1 -1
- package/dist-client/components/activity-thread-ribon.js +3 -2
- package/dist-client/components/activity-thread-ribon.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.js +3 -1
- package/dist-client/components/activity-thread-timeline.js.map +1 -1
- package/dist-client/components/decision-content.js +1 -1
- package/dist-client/components/decision-content.js.map +1 -1
- package/dist-client/components/ribon-description.d.ts +13 -0
- package/dist-client/components/ribon-description.js +14 -0
- package/dist-client/components/ribon-description.js.map +1 -0
- package/dist-client/pages/activity-thread/activity-thread-view-page.js +3 -0
- package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
- package/dist-client/pages/todo/approval-pending-list-page.js +2 -6
- package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
- package/dist-client/pages/todo/done-list-calendar-page.js +7 -2
- package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
- package/dist-client/pages/todo/todo-list-page.js +2 -12
- package/dist-client/pages/todo/todo-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-installation/activity-installation-controller.js +5 -0
- package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
- package/dist-server/controllers/activity-instance/complete.d.ts +6 -1
- package/dist-server/controllers/activity-instance/complete.js +6 -1
- package/dist-server/controllers/activity-instance/complete.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +5 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/activity/finished-case.d.ts +28 -0
- package/dist-server/service/activity/finished-case.js +38 -0
- package/dist-server/service/activity/finished-case.js.map +1 -0
- package/dist-server/service/activity-approval/activity-approval-query.d.ts +5 -0
- package/dist-server/service/activity-approval/activity-approval-query.js +5 -0
- package/dist-server/service/activity-approval/activity-approval-query.js.map +1 -1
- package/dist-server/service/activity-approval/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +3 -3
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/tests/open-doors.test.ts +1 -1
- package/translations/en.json +2 -1
- package/translations/es.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +6 -5
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
|
@@ -7,6 +7,7 @@ const activity_js_1 = require("../../service/activity/activity.js");
|
|
|
7
7
|
const registry_conflict_js_1 = require("./registry-conflict.js");
|
|
8
8
|
const decision_key_declaration_js_1 = require("../../service/activity/decision-key-declaration.js");
|
|
9
9
|
const installable_to_activity_js_1 = require("./installable-to-activity.js");
|
|
10
|
+
const finished_case_js_1 = require("../../service/activity/finished-case.js");
|
|
10
11
|
/**
|
|
11
12
|
* 등록된 양식을 **도메인에 실제로 세운다.**
|
|
12
13
|
*
|
|
@@ -57,6 +58,10 @@ class ActivityInstallations {
|
|
|
57
58
|
static installActivityTemplate(template) {
|
|
58
59
|
(0, registry_conflict_js_1.assertNotDeclaredIntended)(template.name, ActivityInstallations.intended);
|
|
59
60
|
(0, registry_conflict_js_1.assertDoorSlugIsFree)(template.name, decision_key_declaration_js_1.doorSlug, ActivityInstallations.templates);
|
|
61
|
+
/* Said, not refused: activities registered before the rule still install; their module's test holds them to it. */
|
|
62
|
+
const empty = (0, finished_case_js_1.finishedCaseShowsNothing)(template);
|
|
63
|
+
if (empty)
|
|
64
|
+
console.warn(`[worklist] ${empty} (finished-case.ts)`);
|
|
60
65
|
ActivityInstallations.templates[template.name] = template;
|
|
61
66
|
}
|
|
62
67
|
/**
|
package/dist-server/controllers/activity-installation/activity-installation-controller.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-installation-controller.js","sourceRoot":"","sources":["../../../server/controllers/activity-installation/activity-installation-controller.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"activity-installation-controller.js","sourceRoot":"","sources":["../../../server/controllers/activity-installation/activity-installation-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { Activity, ActivityStatus } from '../../service/activity/activity.js'\nimport { InstallableActivity } from '../../service/installable-activity/installable-activity.js'\nimport { ActivityReadiness } from '../../service/installable-activity/activity-readiness.js'\nimport { assertDoorSlugIsFree, assertNotAlreadyInstalled, assertNotDeclaredIntended } from './registry-conflict.js'\nimport { doorSlug } from '../../service/activity/decision-key-declaration.js'\nimport { activityDefaultsFrom, activityRowFrom } from './installable-to-activity.js'\nimport { finishedCaseShowsNothing } from '../../service/activity/finished-case.js'\n\n/**\n * 등록된 양식을 **도메인에 실제로 세운다.**\n *\n * ── 왜 함수로 내놓나 ───────────────────────────────────────────────────────\n * 앞서는 이 일이 화면 뮤테이션 안에만 있었다. 그래서 양식을 세우는 길이 **사람이 화면에서 누르는 것\n * 하나뿐**이었고, 새로 배포하거나 도메인을 하나 더 만들 때마다 그 클릭을 기억해야 했다. 기억하지\n * 못하면 그 기능이 오류 보고 없이 없다.\n *\n * 앱이 「내 기능이 서려면 이 양식이 있어야 한다」를 아는 경우가 있다. 그런 앱은 기동할 때 스스로\n * 세울 수 있어야 한다. 언제 세울지는 앱이 정하고, 세우는 방법은 여기가 한 벌만 갖는다.\n *\n * ── 있는 것을 덮되 결재선은 건드리지 않는다 ────────────────────────────────\n * 이미 있으면 양식의 선언(모델 · 보는 화면 · 시작 방식)으로 덮는다. 버전이 오르면 그 값이 따라와야\n * 하기 때문이다.\n *\n * 그런데 **결재선은 양식의 것이 아니라 그 현장의 것**이다. 누가 결재하는지는 공장이 정한다. 그래서\n * 양식에 결재선 선언이 없으면 이미 있는 결재선이 그대로 남는다 — 버전을 올릴 때마다 결재자가 지워지면\n * 그 뒤로 상신이 곧 완료가 된다.\n */\nexport async function installActivity(\n name: string,\n domain: Domain,\n options: { manager?: EntityManager; user?: any } = {}\n): Promise<Activity> {\n const template = ActivityInstallations.get(name)\n\n if (!template) {\n /*\n * 없는 이름을 세울 수는 없다. 조용히 넘기면 부르는 쪽은 세운 줄 알고, 그 기능은 없는 채로 선다.\n */\n throw new Error(\n `installActivity: there is no installable activity named \"${name}\". ` +\n `known: ${ActivityInstallations.list()\n .map(one => one.name)\n .join(', ')}`\n )\n }\n\n const { manager, user } = options\n const repository = getRepository(Activity, manager)\n\n const existing = await repository.findOneBy({ domain: { id: domain.id }, name })\n\n return await repository.save({\n ...(existing ?? {}),\n ...activityRowFrom(template),\n ...activityDefaultsFrom(template, existing),\n state: ActivityStatus.Released,\n domain,\n creator: existing ? (existing as any).creator : user,\n updater: user\n } as Activity)\n}\n\n/**\n * Work a module has identified as a person's decision and **has not built.**\n *\n * Deliberately not an InstallableActivity. A template with no callback can be installed and\n * issued, and then someone approves a request and nothing happens — on the twin side that means a\n * person presses approve and no equipment moves. So these live in their own registry: the catalog\n * reads them, `installActivity` cannot reach them.\n *\n * They belong on the screen because the catalog is the plan of record. The question \"what is left\"\n * should be answered where people look, not in one person's head.\n */\nexport type IntendedActivity = {\n /** What the activity's `name` will be once it exists. */\n key: string\n /** What the catalog shows. The activity's own name is usually not a sentence a person reads. */\n label: string\n category?: string\n /** What this work is, for the person who will receive it. Draft wording is fine. */\n guide?: string[]\n readiness: ActivityReadiness\n}\n\nexport class ActivityInstallations {\n static templates: { [name: string]: InstallableActivity } = {}\n\n /** Keyed separately from `templates`, so nothing here can be installed by mistake. */\n static intended: { [key: string]: IntendedActivity } = {}\n\n static installActivityTemplate(template: InstallableActivity) {\n assertNotDeclaredIntended(template.name, ActivityInstallations.intended)\n assertDoorSlugIsFree(template.name, doorSlug, ActivityInstallations.templates)\n /* Said, not refused: activities registered before the rule still install; their module's test holds them to it. */\n const empty = finishedCaseShowsNothing(template as any)\n if (empty) console.warn(`[worklist] ${empty} (finished-case.ts)`)\n\n ActivityInstallations.templates[template.name] = template\n }\n\n /**\n * Declare work that should reach someone's worklist and does not yet.\n *\n * A module calls this for each item it has identified. When the real thing is built, delete the\n * call and register the template instead — the catalog row moves from 예정 to 동작 확인됨 on its\n * own.\n */\n static declareIntendedActivity(intended: IntendedActivity) {\n assertNotAlreadyInstalled(intended.key, ActivityInstallations.templates)\n\n ActivityInstallations.intended[intended.key] = intended\n }\n\n static get(name: string): InstallableActivity {\n return ActivityInstallations.templates[name]\n }\n\n static list(): InstallableActivity[] {\n return Object.values(ActivityInstallations.templates)\n }\n\n static listIntended(): IntendedActivity[] {\n return Object.values(ActivityInstallations.intended)\n }\n\n /**\n * 액티비티인스턴스의 변화가 발생했을 때, 호출되는 콜백함수\n * @param name 액티비티인스턴스가 속한 액티비티명\n * @param activityInstance 해당 액티비티인스턴스\n * @param activityThreads 액티비티인스턴스의 변화를 일으킨 원인이 되는 액티비티쓰레드들\n * @param context 서비스 컨텍스트 : domain, user, translation, ..\n * @returns\n */\n static async callback(name: string, activityInstance: any, activityThreads: any[], context: ResolverContext) {\n const installableActivity = ActivityInstallations.templates[name]\n if (!installableActivity) {\n return\n }\n\n const { callback } = installableActivity\n\n callback && (await callback(activityInstance, { causedBy: activityThreads }, context))\n }\n}\n"]}
|
|
@@ -6,8 +6,13 @@ import { ActivityInstance } from '../../service/activity-instance/activity-insta
|
|
|
6
6
|
*
|
|
7
7
|
* For the module that issued the case and judges its work (an application's own to-do); a person ends their own
|
|
8
8
|
* thread through the thread's `end`.
|
|
9
|
+
*
|
|
10
|
+
* `output` is what was done, in the activity's declared output fields - the finished case shows it, as it shows what a
|
|
11
|
+
* person wrote when they end their own thread. A module that closed its to-do with only a reason left the finished
|
|
12
|
+
* screen with nothing to show of how it ended (2026-11-11, 문의 할 일).
|
|
9
13
|
*/
|
|
10
|
-
export declare function complete({ id, reason }: {
|
|
14
|
+
export declare function complete({ id, reason, output }: {
|
|
11
15
|
id: string;
|
|
12
16
|
reason?: string;
|
|
17
|
+
output?: Record<string, any>;
|
|
13
18
|
}, context: ResolverContext): Promise<ActivityInstance>;
|
|
@@ -12,8 +12,12 @@ const common_js_1 = require("../common.js");
|
|
|
12
12
|
*
|
|
13
13
|
* For the module that issued the case and judges its work (an application's own to-do); a person ends their own
|
|
14
14
|
* thread through the thread's `end`.
|
|
15
|
+
*
|
|
16
|
+
* `output` is what was done, in the activity's declared output fields - the finished case shows it, as it shows what a
|
|
17
|
+
* person wrote when they end their own thread. A module that closed its to-do with only a reason left the finished
|
|
18
|
+
* screen with nothing to show of how it ended (2026-11-11, 문의 할 일).
|
|
15
19
|
*/
|
|
16
|
-
async function complete({ id, reason }, context) {
|
|
20
|
+
async function complete({ id, reason, output }, context) {
|
|
17
21
|
const { domain, user, tx } = context.state;
|
|
18
22
|
const repository = tx.getRepository(activity_instance_js_1.ActivityInstance);
|
|
19
23
|
const activityInstance = await repository.findOne({
|
|
@@ -35,6 +39,7 @@ async function complete({ id, reason }, context) {
|
|
|
35
39
|
await threads.save({
|
|
36
40
|
...thread,
|
|
37
41
|
reason,
|
|
42
|
+
...(output ? { output: { ...(thread.output ?? {}), ...output } } : {}),
|
|
38
43
|
state: activity_thread_js_1.ActivityThreadStatus.Ended,
|
|
39
44
|
transaction: 'end',
|
|
40
45
|
startedAt: thread.startedAt ?? now,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complete.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/complete.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"complete.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/complete.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { evalActivityInstanceState } from '../common.js'\n\n/**\n * Ends a case whose work is done - every thread still open ends as done, then the case ends. The counterpart of\n * `abort` for a to-do the records show finished: a case handed to several people, or to whoever holds a role, is\n * done for all of them when its work is done, and its history says ended, not aborted.\n *\n * For the module that issued the case and judges its work (an application's own to-do); a person ends their own\n * thread through the thread's `end`.\n *\n * `output` is what was done, in the activity's declared output fields - the finished case shows it, as it shows what a\n * person wrote when they end their own thread. A module that closed its to-do with only a reason left the finished\n * screen with nothing to show of how it ended (2026-11-11, 문의 할 일).\n */\nexport async function complete(\n { id, reason, output }: { id: string; reason?: string; output?: Record<string, any> },\n context: ResolverContext\n): Promise<ActivityInstance> {\n const { domain, user, tx } = context.state\n const repository = tx.getRepository(ActivityInstance)\n\n const activityInstance = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['domain', 'activity', 'activityThreads', 'assigneeRoles', 'supervisoryRole', 'updater', 'creator', 'starter', 'terminator']\n })\n\n if (!activityInstance) {\n throw new Error(context.t('error.activity-instance not found', { activityInstance: id }))\n }\n\n if (activityInstance.state == ActivityInstanceStatus.Aborted || activityInstance.state == ActivityInstanceStatus.Ended) {\n throw new Error(context.t(`error.activity-instance is already terminated`, { id, state: activityInstance.state }))\n }\n\n const TERMINATED = [ActivityThreadStatus.Aborted, ActivityThreadStatus.Ended]\n const open = (activityInstance.activityThreads ?? []).filter(thread => !TERMINATED.includes(thread.state))\n const now = new Date()\n if (open.length) {\n const threads = tx.getRepository(ActivityThread)\n for (const thread of await threads.find({ where: { id: In(open.map(one => one.id)) } })) {\n await threads.save({\n ...thread,\n reason,\n ...(output ? { output: { ...(thread.output ?? {}), ...output } } : {}),\n state: ActivityThreadStatus.Ended,\n transaction: 'end',\n startedAt: thread.startedAt ?? now,\n updater: user,\n terminatedAt: now\n })\n }\n }\n\n return await repository.save({\n ...activityInstance,\n activityThreads: undefined,\n reason,\n ...(await evalActivityInstanceState(id, context)),\n startedAt: activityInstance.startedAt ?? now,\n transaction: 'end',\n updater: user,\n terminatedAt: now,\n terminator: user\n } as any)\n}\n"]}
|
package/dist-server/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export * from './controllers/index.js';
|
|
|
3
3
|
export * from './service/index.js';
|
|
4
4
|
export { freezeDecisionSentences, DecisionSentenceMissing } from '../shared/decision-sentences.js';
|
|
5
5
|
export { freezeDecisionSentencesFor } from './service/activity/freeze-decision-sentences-for.js';
|
|
6
|
+
export { assertFinishedCaseShows, finishedCaseShowsNothing, outputFieldsOf } from './service/activity/finished-case.js';
|
|
6
7
|
export type { DecisionSentenceKey, Translate as DecisionSentenceTranslate } from '../shared/decision-sentences.js';
|
package/dist-server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.freezeDecisionSentencesFor = exports.DecisionSentenceMissing = exports.freezeDecisionSentences = void 0;
|
|
3
|
+
exports.outputFieldsOf = exports.finishedCaseShowsNothing = exports.assertFinishedCaseShows = exports.freezeDecisionSentencesFor = exports.DecisionSentenceMissing = exports.freezeDecisionSentences = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
require("./routes.js");
|
|
6
6
|
tslib_1.__exportStar(require("./controllers/index.js"), exports);
|
|
@@ -11,6 +11,10 @@ Object.defineProperty(exports, "freezeDecisionSentences", { enumerable: true, ge
|
|
|
11
11
|
Object.defineProperty(exports, "DecisionSentenceMissing", { enumerable: true, get: function () { return decision_sentences_js_1.DecisionSentenceMissing; } });
|
|
12
12
|
var freeze_decision_sentences_for_js_1 = require("./service/activity/freeze-decision-sentences-for.js");
|
|
13
13
|
Object.defineProperty(exports, "freezeDecisionSentencesFor", { enumerable: true, get: function () { return freeze_decision_sentences_for_js_1.freezeDecisionSentencesFor; } });
|
|
14
|
+
var finished_case_js_1 = require("./service/activity/finished-case.js");
|
|
15
|
+
Object.defineProperty(exports, "assertFinishedCaseShows", { enumerable: true, get: function () { return finished_case_js_1.assertFinishedCaseShows; } });
|
|
16
|
+
Object.defineProperty(exports, "finishedCaseShowsNothing", { enumerable: true, get: function () { return finished_case_js_1.finishedCaseShowsNothing; } });
|
|
17
|
+
Object.defineProperty(exports, "outputFieldsOf", { enumerable: true, get: function () { return finished_case_js_1.outputFieldsOf; } });
|
|
14
18
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
15
19
|
const role_tasks_js_1 = require("./role-tasks.js");
|
|
16
20
|
for (const task of role_tasks_js_1.ROLE_TASKS)
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,uBAAoB;AAEpB,iEAAsC;AACtC,6DAAkC;AAElC,sGAAsG;AACtG,yEAAkG;AAAzF,gIAAA,uBAAuB,OAAA;AAAE,gIAAA,uBAAuB,OAAA;AACzD,wGAAgG;AAAvF,8IAAA,0BAA0B,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,uBAAoB;AAEpB,iEAAsC;AACtC,6DAAkC;AAElC,sGAAsG;AACtG,yEAAkG;AAAzF,gIAAA,uBAAuB,OAAA;AAAE,gIAAA,uBAAuB,OAAA;AACzD,wGAAgG;AAAvF,8IAAA,0BAA0B,OAAA;AACnC,wEAAuH;AAA9G,2HAAA,uBAAuB,OAAA;AAAE,4HAAA,wBAAwB,OAAA;AAAE,kHAAA,cAAc,OAAA;AAG1E,yDAA4D;AAC5D,mDAA4C;AAE5C,KAAK,MAAM,IAAI,IAAI,0BAAU;IAAE,IAAA,4BAAgB,EAAC,IAAI,CAAC,CAAA","sourcesContent":["import './routes.js'\n\nexport * from './controllers/index.js'\nexport * from './service/index.js'\n\n/* Freezing an approval's value sentences — the same reading of the input the decision screen uses. */\nexport { freezeDecisionSentences, DecisionSentenceMissing } from '../shared/decision-sentences.js'\nexport { freezeDecisionSentencesFor } from './service/activity/freeze-decision-sentences-for.js'\nexport { assertFinishedCaseShows, finishedCaseShowsNothing, outputFieldsOf } from './service/activity/finished-case.js'\nexport type { DecisionSentenceKey, Translate as DecisionSentenceTranslate } from '../shared/decision-sentences.js'\n\nimport { registerRoleTask } from '@things-factory/auth-base'\nimport { ROLE_TASKS } from './role-tasks.js'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a finished case of an activity shows anything - what was asked and what was done.
|
|
3
|
+
*
|
|
4
|
+
* The finished screen (activity-thread-view) draws, in order: the activity's own view when it names one (viewType with a
|
|
5
|
+
* viewSource), else the declared decision over the input (decisionKeys), the input fields a person can read, and the
|
|
6
|
+
* output fields what was done was written into. A module's own to-do that kept only a hidden key in its input (an
|
|
7
|
+
* inquiry id, a week), declared no decision and no output, and named no view, finished as an empty screen: the state, the
|
|
8
|
+
* times and nothing of what it was or how it ended (2026-11-11, 문의 할 일 and five more like it).
|
|
9
|
+
*
|
|
10
|
+
* Kept free of imports so the test runner can hold it.
|
|
11
|
+
*/
|
|
12
|
+
type Declared = {
|
|
13
|
+
name?: string;
|
|
14
|
+
viewType?: string;
|
|
15
|
+
viewSource?: string;
|
|
16
|
+
decisionKeys?: unknown[];
|
|
17
|
+
model?: {
|
|
18
|
+
hidden?: boolean;
|
|
19
|
+
inout?: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
/** Why a finished case of this activity would show nothing - undefined when it shows something. */
|
|
23
|
+
export declare function finishedCaseShowsNothing(activity: Declared): string | undefined;
|
|
24
|
+
/** For a module's own test: its activities' finished cases show what was asked and what was done. */
|
|
25
|
+
export declare function assertFinishedCaseShows(activity: Declared): void;
|
|
26
|
+
/** The output fields an activity declares - where `complete` writes what was done. */
|
|
27
|
+
export declare function outputFieldsOf(activity: Declared): string[];
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a finished case of an activity shows anything - what was asked and what was done.
|
|
4
|
+
*
|
|
5
|
+
* The finished screen (activity-thread-view) draws, in order: the activity's own view when it names one (viewType with a
|
|
6
|
+
* viewSource), else the declared decision over the input (decisionKeys), the input fields a person can read, and the
|
|
7
|
+
* output fields what was done was written into. A module's own to-do that kept only a hidden key in its input (an
|
|
8
|
+
* inquiry id, a week), declared no decision and no output, and named no view, finished as an empty screen: the state, the
|
|
9
|
+
* times and nothing of what it was or how it ended (2026-11-11, 문의 할 일 and five more like it).
|
|
10
|
+
*
|
|
11
|
+
* Kept free of imports so the test runner can hold it.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.finishedCaseShowsNothing = finishedCaseShowsNothing;
|
|
15
|
+
exports.assertFinishedCaseShows = assertFinishedCaseShows;
|
|
16
|
+
exports.outputFieldsOf = outputFieldsOf;
|
|
17
|
+
const MODULE_VIEWS = ['custom-element', 'template', 'board', 'page', 'external'];
|
|
18
|
+
/** Why a finished case of this activity would show nothing - undefined when it shows something. */
|
|
19
|
+
function finishedCaseShowsNothing(activity) {
|
|
20
|
+
if (activity.viewType && MODULE_VIEWS.includes(activity.viewType) && activity.viewSource)
|
|
21
|
+
return undefined;
|
|
22
|
+
if ((activity.decisionKeys ?? []).length)
|
|
23
|
+
return undefined;
|
|
24
|
+
if ((activity.model ?? []).some(field => !field.hidden && ['in', 'out', 'inout'].includes(String(field.inout))))
|
|
25
|
+
return undefined;
|
|
26
|
+
return `"${activity.name}" finishes as an empty screen: it names no view, declares no decision, and has no visible input or output field`;
|
|
27
|
+
}
|
|
28
|
+
/** For a module's own test: its activities' finished cases show what was asked and what was done. */
|
|
29
|
+
function assertFinishedCaseShows(activity) {
|
|
30
|
+
const empty = finishedCaseShowsNothing(activity);
|
|
31
|
+
if (empty)
|
|
32
|
+
throw new Error(empty);
|
|
33
|
+
}
|
|
34
|
+
/** The output fields an activity declares - where `complete` writes what was done. */
|
|
35
|
+
function outputFieldsOf(activity) {
|
|
36
|
+
return (activity.model ?? []).filter(field => ['out', 'inout'].includes(String(field.inout))).map(field => field.name);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=finished-case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finished-case.js","sourceRoot":"","sources":["../../../server/service/activity/finished-case.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAaH,4DAMC;AAGD,0DAGC;AAGD,wCAEC;AA5BD,MAAM,YAAY,GAAG,CAAC,gBAAgB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;AAUhF,mGAAmG;AACnG,SAAgB,wBAAwB,CAAC,QAAkB;IACzD,IAAI,QAAQ,CAAC,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IAC1G,IAAI,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC1D,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IAEjI,OAAO,IAAI,QAAQ,CAAC,IAAI,iHAAiH,CAAA;AAC3I,CAAC;AAED,qGAAqG;AACrG,SAAgB,uBAAuB,CAAC,QAAkB;IACxD,MAAM,KAAK,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAA;IAChD,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC;AAED,sFAAsF;AACtF,SAAgB,cAAc,CAAC,QAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAE,KAAa,CAAC,IAAI,CAAC,CAAA;AACjI,CAAC","sourcesContent":["/**\n * Whether a finished case of an activity shows anything - what was asked and what was done.\n *\n * The finished screen (activity-thread-view) draws, in order: the activity's own view when it names one (viewType with a\n * viewSource), else the declared decision over the input (decisionKeys), the input fields a person can read, and the\n * output fields what was done was written into. A module's own to-do that kept only a hidden key in its input (an\n * inquiry id, a week), declared no decision and no output, and named no view, finished as an empty screen: the state, the\n * times and nothing of what it was or how it ended (2026-11-11, 문의 할 일 and five more like it).\n *\n * Kept free of imports so the test runner can hold it.\n */\n\nconst MODULE_VIEWS = ['custom-element', 'template', 'board', 'page', 'external']\n\ntype Declared = {\n name?: string\n viewType?: string\n viewSource?: string\n decisionKeys?: unknown[]\n model?: { hidden?: boolean; inout?: string }[]\n}\n\n/** Why a finished case of this activity would show nothing - undefined when it shows something. */\nexport function finishedCaseShowsNothing(activity: Declared): string | undefined {\n if (activity.viewType && MODULE_VIEWS.includes(activity.viewType) && activity.viewSource) return undefined\n if ((activity.decisionKeys ?? []).length) return undefined\n if ((activity.model ?? []).some(field => !field.hidden && ['in', 'out', 'inout'].includes(String(field.inout)))) return undefined\n\n return `\"${activity.name}\" finishes as an empty screen: it names no view, declares no decision, and has no visible input or output field`\n}\n\n/** For a module's own test: its activities' finished cases show what was asked and what was done. */\nexport function assertFinishedCaseShows(activity: Declared): void {\n const empty = finishedCaseShowsNothing(activity)\n if (empty) throw new Error(empty)\n}\n\n/** The output fields an activity declares - where `complete` writes what was done. */\nexport function outputFieldsOf(activity: Declared): string[] {\n return (activity.model ?? []).filter(field => ['out', 'inout'].includes(String(field.inout))).map(field => (field as any).name)\n}\n"]}
|
|
@@ -22,6 +22,11 @@ export declare class ActivityApprovalQuery {
|
|
|
22
22
|
*
|
|
23
23
|
* `doneList`는 업무 담당자 기준이라, 결재·참조 참여자에게 완료 이력을 보장하지 않는다. 참여 행
|
|
24
24
|
* 자신을 기준으로 읽는 목록을 둬야 「내 결재함」에서 열린 건을 치운 뒤에도 이력을 다시 찾을 수 있다.
|
|
25
|
+
*
|
|
26
|
+
* **A row addressed to a role I hold is mine too**, whoever of that role acted on it, and so is one
|
|
27
|
+
* withdrawn while it waited for that role. The step was put to the role, so it reached every holder's
|
|
28
|
+
* 「결재할 것」, and every holder may look up how it ended (2026-11-10 user: 역할을 기반해서 완료에
|
|
29
|
+
* 보이는 것은 잘못되었다고 할 수 없다).
|
|
25
30
|
*/
|
|
26
31
|
approvalCompletedList(params: ListParam, context: ResolverContext): Promise<ActivityApprovalList>;
|
|
27
32
|
approvalDoneList(params: ListParam, context: ResolverContext): Promise<ActivityApprovalList>;
|
|
@@ -204,6 +204,11 @@ let ActivityApprovalQuery = class ActivityApprovalQuery {
|
|
|
204
204
|
*
|
|
205
205
|
* `doneList`는 업무 담당자 기준이라, 결재·참조 참여자에게 완료 이력을 보장하지 않는다. 참여 행
|
|
206
206
|
* 자신을 기준으로 읽는 목록을 둬야 「내 결재함」에서 열린 건을 치운 뒤에도 이력을 다시 찾을 수 있다.
|
|
207
|
+
*
|
|
208
|
+
* **A row addressed to a role I hold is mine too**, whoever of that role acted on it, and so is one
|
|
209
|
+
* withdrawn while it waited for that role. The step was put to the role, so it reached every holder's
|
|
210
|
+
* 「결재할 것」, and every holder may look up how it ended (2026-11-10 user: 역할을 기반해서 완료에
|
|
211
|
+
* 보이는 것은 잘못되었다고 할 수 없다).
|
|
207
212
|
*/
|
|
208
213
|
async approvalCompletedList(params, context) {
|
|
209
214
|
var { domain, user } = context.state;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-approval-query.js","sourceRoot":"","sources":["../../../server/service/activity-approval/activity-approval-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Brackets } from 'typeorm'\nimport { DriverUtils } from 'typeorm/driver/DriverUtils.js'\nimport { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\nimport { Role, User } from '@things-factory/auth-base'\nimport { ActivityThread } from '../activity-thread/activity-thread.js'\nimport { ActivityApproval } from './activity-approval.js'\nimport { ActivityApprovalList } from './activity-approval-type.js'\nimport { TERMINATED_JUDGMENTS } from '../../controllers/activity-approval/approval-line.js'\nimport { myPendingApprovalsWhere } from './my-pending-approvals.js'\nimport { assertMayReadApproval } from '../../controllers/activity-read/assert-may-read.js'\nimport { ActivityInstance } from '../activity-instance/activity-instance.js'\nimport { NULL_RANK_ALIAS, nullsLastSelect } from '../nulls-last.js'\n\n/*\n * **결재 목록이 걸러 볼 수 있는 칸** — 네 목록이 같은 표를 쓴다.\n *\n * ── 왜 하나로 모으나 (2026-09-05) ──────────────────────────────────────────\n * 이 칸들은 `ActivityApproval` 에 없다. 활동 인스턴스에 있고, 결재는 스레드를 거쳐 그것을 본다.\n * 그래서 목록마다 `filtersMap` 으로 길을 알려 줘야 하고, 안 알려 주면 **그 칸으로 거르는 순간\n * 500 이 난다.**\n *\n * 표를 손으로 네 벌 들고 있었고, 실제로 갈라졌다.\n *\n * approvalPendingList name · description · priority · dueAt\n * approvalDoneList name · description · priority · dueAt\n * approvalReferenceList name · description ← `dueAt` 에서 터졌다\n * activityApprovals (표가 아예 없다) ← `name` 에서도 터진다\n *\n * 화면은 넷에 같은 칩을 보낸다(`client/chip-filters.ts` 의 「24시간 내 마감」이 `dueAt lte` 를\n * 보낸다). 그러니 목록마다 표가 다르면 **어느 목록에서만 터지는** 모양이 된다 — 실측된 것이 그\n * 모양이었다.\n *\n * 다섯 번째 목록이 생길 때 또 갈라지지 않게 한 곳에 둔다.\n */\nconst APPROVAL_FILTERS_MAP: Record<string, { relationColumn: string; columnName: string }> = {\n ...['name', 'description', 'priority'].reduce(\n (map, columnName) => ({ ...map, [columnName]: { relationColumn: 'activityThread.activityInstance', columnName } }),\n {} as Record<string, { relationColumn: string; columnName: string }>\n ),\n\n /*\n * 기한은 **스레드의 것**이다 — 인스턴스에도 같은 이름의 칸이 있지만 화면이 보여 주는 것은 스레드\n * 쪽이다(approval-pending-list-page 는 인스턴스를 펼친 뒤 `dueAt: thread.dueAt` 으로 덮어쓴다).\n *\n * 매핑이 인스턴스를 가리키고 있었다. 지금 자료에서는 두 값이 같아 드러나지 않지만 다른 칸이고,\n * 어긋나는 순간 목록이 **보이지 않는 값으로** 정렬되고 「만기」 칩도 보이지 않는 값으로 거른다.\n * 화면이 보여 주는 것과 같은 칸을 본다.\n */\n dueAt: { relationColumn: 'activityThread', columnName: 'dueAt' }\n}\n\n/** 결재선 스레드가 아니라 업무 인스턴스 쪽 칸으로 정렬하는 이름들. 나머지는 결재 행(ap) 자신이다. */\nconst APPROVAL_INSTANCE_SORTS = new Set(['name', 'description', 'priority'])\n\n/** 스레드(at) 쪽 칸으로 정렬하는 이름들. 화면이 보여 주는 것과 같은 칸이다. */\nconst APPROVAL_THREAD_SORTS = new Set(['dueAt', 'assignedAt', 'state'])\n\n/** 값이 없으면 뒤로 보낼 칸. 기한이 없는 건은 「언제까지」를 묻는 정렬에서 맨 뒤가 맞다. */\nconst APPROVAL_NULLS_LAST = new Set(['dueAt'])\n\n/**\n * 내 결재함의 정렬 — **기한이 없는 건은 뒤로 간다.**\n *\n * 공통 빌더에 맡기면 NULL 자리를 정할 수 없다. sqlite 는 오름차순에서 NULL 을 먼저 내므로 기본\n * 정렬이 기한 순인 목록의 맨 위가 **기한 없는 건**으로 채워진다 — 급한 것부터 보라는 정렬인데\n * 정작 급한 것이 아래로 밀린다. 「할 일」 목록이 같은 이유로 이미 이 처리를 갖고 있고, 규칙은\n * nulls-last.ts 한 곳에 있다.\n *\n * 정렬에 쓸 join 은 여기서 직접 건다. 공통 빌더의 join 은 filter 가 있을 때만 생기므로, 정렬만\n * 하는 요청에서는 기댈 것이 없다.\n */\nfunction applyApprovalSortings(queryBuilder: any, sortings: { name: string; desc?: boolean }[]) {\n const escape = (part: string) => queryBuilder.connection.driver.escape(part)\n\n const fieldOf = (name: string) => {\n const metadata = getRepository(APPROVAL_INSTANCE_SORTS.has(name) ? ActivityInstance : ActivityThread).metadata\n const column = metadata.findColumnWithPropertyName(name)\n\n /* 이름을 못 찾으면 SQL 을 지어내지 않는다. */\n return column ? `${escape(APPROVAL_INSTANCE_SORTS.has(name) ? 'ai' : 'at')}.${escape(column.databaseName)}` : undefined\n }\n\n const terms: { expression: string; direction: 'ASC' | 'DESC'; select?: string }[] = []\n\n for (const sorting of sortings || []) {\n if (!sorting?.name) {\n continue\n }\n\n const direction: 'ASC' | 'DESC' = sorting.desc ? 'DESC' : 'ASC'\n\n if (APPROVAL_NULLS_LAST.has(sorting.name)) {\n const field = fieldOf(sorting.name)\n\n /* 방향이 뒤집혀도 값 없는 것은 뒤다 — 이 항목만 늘 오름차순이다. */\n if (field) {\n terms.push({ expression: NULL_RANK_ALIAS, direction: 'ASC', select: nullsLastSelect(field) })\n }\n }\n\n const alias = APPROVAL_INSTANCE_SORTS.has(sorting.name)\n ? 'ai'\n : APPROVAL_THREAD_SORTS.has(sorting.name)\n ? 'at'\n : 'ap'\n\n /*\n * join 한 칸으로 정렬하면 그 칸이 select 에 있어야 한다.\n *\n * 여기 join 은 leftJoin 이라 칸을 싣지 않는데, 정렬 + 페이지네이션은 TypeORM 의 distinct 경로를\n * 타고 모든 것을 서브쿼리로 감싼 뒤 ORDER BY 를 `distinctAlias.<별칭>` 으로 다시 쓴다. 실을 것이\n * 없으면 그 이름이 없다 — 실측: `no such column: distinctAlias.at_due_at`.\n *\n * 별칭은 TypeORM 이 찾을 때 쓰는 그 함수로 만든다. property 이름으로 지으면 dueAt 처럼 DB 칸\n * 이름과 철자가 다른 칸에서 어긋난다(shell 의 applyJoins 가 같은 이유로 고쳐졌다).\n */\n if (alias !== 'ap') {\n const metadata = getRepository(alias === 'ai' ? ActivityInstance : ActivityThread).metadata\n const column = metadata.findColumnWithPropertyName(sorting.name)\n\n if (column) {\n queryBuilder.addSelect(\n `${alias}.${sorting.name}`,\n DriverUtils.buildAlias(queryBuilder.connection.driver, undefined, alias, column.databaseName)\n )\n }\n }\n\n terms.push({ expression: `${alias}.${sorting.name}`, direction })\n }\n\n terms.forEach(({ expression, direction, select }, index) => {\n /* 식은 select 에 이름 붙여 올린다 — orderBy 는 점이 든 식을 alias 로 읽는다(nulls-last 머리말). */\n if (select) {\n queryBuilder.addSelect(select, expression)\n }\n\n index === 0 ? queryBuilder.orderBy(expression, direction) : queryBuilder.addOrderBy(expression, direction)\n })\n}\n\n@Resolver(ActivityApproval)\nexport class ActivityApprovalQuery {\n @Query(returns => ActivityApproval!, { nullable: true, description: 'To fetch a ActivityApproval' })\n async activityApproval(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ActivityApproval> {\n const { domain } = context.state\n\n const approval = await getRepository(ActivityApproval).findOne({\n where: { domain: { id: domain.id }, id }\n })\n\n /* 없는 건은 없다고(null). 있는데 못 읽는 건은 이름 붙여 거절한다 — § assert-may-read.ts */\n if (approval) await assertMayReadApproval(approval, context)\n\n return approval\n }\n\n @Directive('@privilege(category: \"activity-instance\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => ActivityApprovalList, { description: 'To fetch multiple ActivityApprovals' })\n async activityApprovals(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(ActivityApproval),\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Query(returns => ActivityApprovalList, { description: 'To fetch the list of work results I need to review' })\n async approvalPendingList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n const roles = user.roles.filter(role => role.domainId === domain.id).map(role => role.id)\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n /* 정렬은 아래에서 직접 건다(applyApprovalSortings 머리말) — 여기에 넘기면 NULL 자리를 못 정한다. */\n params: { ...params, sortings: [] },\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n /* 정렬이 쓸 join — 정렬만 하는 요청에는 filter 쪽 join 이 없다. */\n .leftJoin('ap.activityThread', 'at')\n .leftJoin('at.activityInstance', 'ai')\n /* 「내가 판단해야 할 것」의 정의는 한 자리에 있다 — 달력도 같은 것을 읽는다. */\n .andWhere(myPendingApprovalsWhere({ userId: user.id, roles }))\n\n applyApprovalSortings(queryBuilder, params.sortings || [])\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * **내가 참조로 걸린 것** — 결재하는 것이 아니라 보고 의견을 낼 수 있는 것.\n *\n * 승인 대기 목록과 나눈다. 섞으면 참조자가 「내가 승인해야 하나」로 읽고, 결재자는 남의 참조 건까지\n * 훑게 된다. 두 목록의 물음이 다르다 — 「내가 결정할 것」과 「내가 알아야 할 것」.\n *\n * 내 결재함은 아직 확인할 일이 있는 건만 보인다. 업무가 끝나며 참조 행도 `closed`로 닫히므로,\n * 끝난 참조는 여기에서 빼고 완료 이력에서 확인한다.\n */\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch the approvals where I am a reference — I may comment, I do not decide'\n })\n async approvalReferenceList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })\n const roles = (user.roles ?? []).filter(role => role.domainId === domain.id).map(role => role.id)\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere(`ap.participation = 'reference'`)\n /* `NULL`은 오래된 행을 위한 열린 상태다. SQL의 NOT IN은 NULL을 걸러 버리므로 따로 포함한다. */\n .andWhere('(ap.judgment IS NULL OR ap.judgment NOT IN (:...judgment))', { judgment: [...TERMINATED_JUDGMENTS] })\n .andWhere(\n new Brackets(qb => {\n qb.where('ap.approver = :user', { user: user.id })\n /* 역할로 걸린 참조는 그 역할을 가진 사람 모두가 본다 — 결재와 같은 규칙이다. */\n if (roles.length > 0) qb.orWhere('ap.approverRole IN (:...roles)', { roles })\n })\n )\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 내가 결재하거나 참조로 받은 건 중 끝난 것.\n *\n * `doneList`는 업무 담당자 기준이라, 결재·참조 참여자에게 완료 이력을 보장하지 않는다. 참여 행\n * 자신을 기준으로 읽는 목록을 둬야 「내 결재함」에서 열린 건을 치운 뒤에도 이력을 다시 찾을 수 있다.\n */\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch completed approvals where I participated'\n })\n async approvalCompletedList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })\n const roles = (user.roles ?? []).filter(role => role.domainId === domain.id).map(role => role.id)\n const judgment = [...TERMINATED_JUDGMENTS]\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere('ap.judgment IN (:...judgment)', { judgment })\n .andWhere(\n new Brackets(qb => {\n qb.where('ap.approver = :user', { user: user.id })\n if (roles.length > 0) qb.orWhere('ap.approverRole IN (:...roles)', { roles })\n })\n )\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch the approvals(ActivityApprovals) which has done by me'\n })\n async approvalDoneList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n /* 「끝난 것」의 정의는 한 자리에 있다 — 여기서 다시 세면 값이 늘 때 한쪽만 늘어난다. */\n const judgment = [...TERMINATED_JUDGMENTS]\n\n const [items, total] = await getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere('ap.judgment IN (:...judgment)', { judgment })\n /*\n * 역할로 걸린 결재를 처리하면 이제 `approver` 에 처리한 사람이 들어간다(`approve.ts`·`reject.ts`).\n * 그래서 이 한 줄로 자기가 처리한 것이 다 걸린다 — 개인으로 걸린 것이든 역할로 걸린 것이든.\n *\n * 고치기 전에 처리한 역할 결재는 `approver` 가 비어 있어 여기 안 나온다. 지난 기록을 뒤에서 고쳐\n * 쓰지 않는다는 규율에 따라 그대로 둔다.\n */\n .andWhere('ap.approver = :user', { user: user.id })\n .getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => ActivityThread)\n async activityThread(@Root() activityApproval: ActivityApproval): Promise<ActivityThread> {\n return await getRepository(ActivityThread).findOneBy({ id: activityApproval.activityThreadId })\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() activityApproval: ActivityApproval): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: activityApproval.domainId })\n }\n\n @FieldResolver(type => User)\n async approver(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.approverId && (await getRepository(User).findOneBy({ id: activityApproval.approverId }))\n }\n\n /**\n * 누가 결재해야 하나가 **역할**일 때.\n *\n * 이 리졸버가 없어서 `approverRole` 이 늘 `null` 로 나갔다. DB 에는 들어 있는데 질의가 관계를 안\n * 싣기 때문이다. 그래서 결재 목록이 「누가 결재해야 하나」를 못 보였다 — 개인으로 지정한 건만 보이고\n * 역할로 지정한 건은 빈칸이었다.\n */\n @FieldResolver(type => Role, { nullable: true })\n async approverRole(@Root() activityApproval: ActivityApproval): Promise<Role> {\n return activityApproval.approverRoleId && (await getRepository(Role).findOneBy({ id: activityApproval.approverRoleId }))\n }\n\n @FieldResolver(type => User)\n async updater(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.updaterId && (await getRepository(User).findOneBy({ id: activityApproval.updaterId }))\n }\n\n @FieldResolver(type => User)\n async creator(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.creatorId && (await getRepository(User).findOneBy({ id: activityApproval.creatorId }))\n }\n\n @FieldResolver(type => User)\n async terminator(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.terminatorId && (await getRepository(User).findOneBy({ id: activityApproval.terminatorId }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity-approval-query.js","sourceRoot":"","sources":["../../../server/service/activity-approval/activity-approval-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Brackets } from 'typeorm'\nimport { DriverUtils } from 'typeorm/driver/DriverUtils.js'\nimport { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\nimport { Role, User } from '@things-factory/auth-base'\nimport { ActivityThread } from '../activity-thread/activity-thread.js'\nimport { ActivityApproval } from './activity-approval.js'\nimport { ActivityApprovalList } from './activity-approval-type.js'\nimport { TERMINATED_JUDGMENTS } from '../../controllers/activity-approval/approval-line.js'\nimport { myPendingApprovalsWhere } from './my-pending-approvals.js'\nimport { assertMayReadApproval } from '../../controllers/activity-read/assert-may-read.js'\nimport { ActivityInstance } from '../activity-instance/activity-instance.js'\nimport { NULL_RANK_ALIAS, nullsLastSelect } from '../nulls-last.js'\n\n/*\n * **결재 목록이 걸러 볼 수 있는 칸** — 네 목록이 같은 표를 쓴다.\n *\n * ── 왜 하나로 모으나 (2026-09-05) ──────────────────────────────────────────\n * 이 칸들은 `ActivityApproval` 에 없다. 활동 인스턴스에 있고, 결재는 스레드를 거쳐 그것을 본다.\n * 그래서 목록마다 `filtersMap` 으로 길을 알려 줘야 하고, 안 알려 주면 **그 칸으로 거르는 순간\n * 500 이 난다.**\n *\n * 표를 손으로 네 벌 들고 있었고, 실제로 갈라졌다.\n *\n * approvalPendingList name · description · priority · dueAt\n * approvalDoneList name · description · priority · dueAt\n * approvalReferenceList name · description ← `dueAt` 에서 터졌다\n * activityApprovals (표가 아예 없다) ← `name` 에서도 터진다\n *\n * 화면은 넷에 같은 칩을 보낸다(`client/chip-filters.ts` 의 「24시간 내 마감」이 `dueAt lte` 를\n * 보낸다). 그러니 목록마다 표가 다르면 **어느 목록에서만 터지는** 모양이 된다 — 실측된 것이 그\n * 모양이었다.\n *\n * 다섯 번째 목록이 생길 때 또 갈라지지 않게 한 곳에 둔다.\n */\nconst APPROVAL_FILTERS_MAP: Record<string, { relationColumn: string; columnName: string }> = {\n ...['name', 'description', 'priority'].reduce(\n (map, columnName) => ({ ...map, [columnName]: { relationColumn: 'activityThread.activityInstance', columnName } }),\n {} as Record<string, { relationColumn: string; columnName: string }>\n ),\n\n /*\n * 기한은 **스레드의 것**이다 — 인스턴스에도 같은 이름의 칸이 있지만 화면이 보여 주는 것은 스레드\n * 쪽이다(approval-pending-list-page 는 인스턴스를 펼친 뒤 `dueAt: thread.dueAt` 으로 덮어쓴다).\n *\n * 매핑이 인스턴스를 가리키고 있었다. 지금 자료에서는 두 값이 같아 드러나지 않지만 다른 칸이고,\n * 어긋나는 순간 목록이 **보이지 않는 값으로** 정렬되고 「만기」 칩도 보이지 않는 값으로 거른다.\n * 화면이 보여 주는 것과 같은 칸을 본다.\n */\n dueAt: { relationColumn: 'activityThread', columnName: 'dueAt' }\n}\n\n/** 결재선 스레드가 아니라 업무 인스턴스 쪽 칸으로 정렬하는 이름들. 나머지는 결재 행(ap) 자신이다. */\nconst APPROVAL_INSTANCE_SORTS = new Set(['name', 'description', 'priority'])\n\n/** 스레드(at) 쪽 칸으로 정렬하는 이름들. 화면이 보여 주는 것과 같은 칸이다. */\nconst APPROVAL_THREAD_SORTS = new Set(['dueAt', 'assignedAt', 'state'])\n\n/** 값이 없으면 뒤로 보낼 칸. 기한이 없는 건은 「언제까지」를 묻는 정렬에서 맨 뒤가 맞다. */\nconst APPROVAL_NULLS_LAST = new Set(['dueAt'])\n\n/**\n * 내 결재함의 정렬 — **기한이 없는 건은 뒤로 간다.**\n *\n * 공통 빌더에 맡기면 NULL 자리를 정할 수 없다. sqlite 는 오름차순에서 NULL 을 먼저 내므로 기본\n * 정렬이 기한 순인 목록의 맨 위가 **기한 없는 건**으로 채워진다 — 급한 것부터 보라는 정렬인데\n * 정작 급한 것이 아래로 밀린다. 「할 일」 목록이 같은 이유로 이미 이 처리를 갖고 있고, 규칙은\n * nulls-last.ts 한 곳에 있다.\n *\n * 정렬에 쓸 join 은 여기서 직접 건다. 공통 빌더의 join 은 filter 가 있을 때만 생기므로, 정렬만\n * 하는 요청에서는 기댈 것이 없다.\n */\nfunction applyApprovalSortings(queryBuilder: any, sortings: { name: string; desc?: boolean }[]) {\n const escape = (part: string) => queryBuilder.connection.driver.escape(part)\n\n const fieldOf = (name: string) => {\n const metadata = getRepository(APPROVAL_INSTANCE_SORTS.has(name) ? ActivityInstance : ActivityThread).metadata\n const column = metadata.findColumnWithPropertyName(name)\n\n /* 이름을 못 찾으면 SQL 을 지어내지 않는다. */\n return column ? `${escape(APPROVAL_INSTANCE_SORTS.has(name) ? 'ai' : 'at')}.${escape(column.databaseName)}` : undefined\n }\n\n const terms: { expression: string; direction: 'ASC' | 'DESC'; select?: string }[] = []\n\n for (const sorting of sortings || []) {\n if (!sorting?.name) {\n continue\n }\n\n const direction: 'ASC' | 'DESC' = sorting.desc ? 'DESC' : 'ASC'\n\n if (APPROVAL_NULLS_LAST.has(sorting.name)) {\n const field = fieldOf(sorting.name)\n\n /* 방향이 뒤집혀도 값 없는 것은 뒤다 — 이 항목만 늘 오름차순이다. */\n if (field) {\n terms.push({ expression: NULL_RANK_ALIAS, direction: 'ASC', select: nullsLastSelect(field) })\n }\n }\n\n const alias = APPROVAL_INSTANCE_SORTS.has(sorting.name)\n ? 'ai'\n : APPROVAL_THREAD_SORTS.has(sorting.name)\n ? 'at'\n : 'ap'\n\n /*\n * join 한 칸으로 정렬하면 그 칸이 select 에 있어야 한다.\n *\n * 여기 join 은 leftJoin 이라 칸을 싣지 않는데, 정렬 + 페이지네이션은 TypeORM 의 distinct 경로를\n * 타고 모든 것을 서브쿼리로 감싼 뒤 ORDER BY 를 `distinctAlias.<별칭>` 으로 다시 쓴다. 실을 것이\n * 없으면 그 이름이 없다 — 실측: `no such column: distinctAlias.at_due_at`.\n *\n * 별칭은 TypeORM 이 찾을 때 쓰는 그 함수로 만든다. property 이름으로 지으면 dueAt 처럼 DB 칸\n * 이름과 철자가 다른 칸에서 어긋난다(shell 의 applyJoins 가 같은 이유로 고쳐졌다).\n */\n if (alias !== 'ap') {\n const metadata = getRepository(alias === 'ai' ? ActivityInstance : ActivityThread).metadata\n const column = metadata.findColumnWithPropertyName(sorting.name)\n\n if (column) {\n queryBuilder.addSelect(\n `${alias}.${sorting.name}`,\n DriverUtils.buildAlias(queryBuilder.connection.driver, undefined, alias, column.databaseName)\n )\n }\n }\n\n terms.push({ expression: `${alias}.${sorting.name}`, direction })\n }\n\n terms.forEach(({ expression, direction, select }, index) => {\n /* 식은 select 에 이름 붙여 올린다 — orderBy 는 점이 든 식을 alias 로 읽는다(nulls-last 머리말). */\n if (select) {\n queryBuilder.addSelect(select, expression)\n }\n\n index === 0 ? queryBuilder.orderBy(expression, direction) : queryBuilder.addOrderBy(expression, direction)\n })\n}\n\n@Resolver(ActivityApproval)\nexport class ActivityApprovalQuery {\n @Query(returns => ActivityApproval!, { nullable: true, description: 'To fetch a ActivityApproval' })\n async activityApproval(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ActivityApproval> {\n const { domain } = context.state\n\n const approval = await getRepository(ActivityApproval).findOne({\n where: { domain: { id: domain.id }, id }\n })\n\n /* 없는 건은 없다고(null). 있는데 못 읽는 건은 이름 붙여 거절한다 — § assert-may-read.ts */\n if (approval) await assertMayReadApproval(approval, context)\n\n return approval\n }\n\n @Directive('@privilege(category: \"activity-instance\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => ActivityApprovalList, { description: 'To fetch multiple ActivityApprovals' })\n async activityApprovals(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(ActivityApproval),\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Query(returns => ActivityApprovalList, { description: 'To fetch the list of work results I need to review' })\n async approvalPendingList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n const roles = user.roles.filter(role => role.domainId === domain.id).map(role => role.id)\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n /* 정렬은 아래에서 직접 건다(applyApprovalSortings 머리말) — 여기에 넘기면 NULL 자리를 못 정한다. */\n params: { ...params, sortings: [] },\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n /* 정렬이 쓸 join — 정렬만 하는 요청에는 filter 쪽 join 이 없다. */\n .leftJoin('ap.activityThread', 'at')\n .leftJoin('at.activityInstance', 'ai')\n /* 「내가 판단해야 할 것」의 정의는 한 자리에 있다 — 달력도 같은 것을 읽는다. */\n .andWhere(myPendingApprovalsWhere({ userId: user.id, roles }))\n\n applyApprovalSortings(queryBuilder, params.sortings || [])\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * **내가 참조로 걸린 것** — 결재하는 것이 아니라 보고 의견을 낼 수 있는 것.\n *\n * 승인 대기 목록과 나눈다. 섞으면 참조자가 「내가 승인해야 하나」로 읽고, 결재자는 남의 참조 건까지\n * 훑게 된다. 두 목록의 물음이 다르다 — 「내가 결정할 것」과 「내가 알아야 할 것」.\n *\n * 내 결재함은 아직 확인할 일이 있는 건만 보인다. 업무가 끝나며 참조 행도 `closed`로 닫히므로,\n * 끝난 참조는 여기에서 빼고 완료 이력에서 확인한다.\n */\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch the approvals where I am a reference — I may comment, I do not decide'\n })\n async approvalReferenceList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })\n const roles = (user.roles ?? []).filter(role => role.domainId === domain.id).map(role => role.id)\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere(`ap.participation = 'reference'`)\n /* `NULL`은 오래된 행을 위한 열린 상태다. SQL의 NOT IN은 NULL을 걸러 버리므로 따로 포함한다. */\n .andWhere('(ap.judgment IS NULL OR ap.judgment NOT IN (:...judgment))', { judgment: [...TERMINATED_JUDGMENTS] })\n .andWhere(\n new Brackets(qb => {\n qb.where('ap.approver = :user', { user: user.id })\n /* 역할로 걸린 참조는 그 역할을 가진 사람 모두가 본다 — 결재와 같은 규칙이다. */\n if (roles.length > 0) qb.orWhere('ap.approverRole IN (:...roles)', { roles })\n })\n )\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 내가 결재하거나 참조로 받은 건 중 끝난 것.\n *\n * `doneList`는 업무 담당자 기준이라, 결재·참조 참여자에게 완료 이력을 보장하지 않는다. 참여 행\n * 자신을 기준으로 읽는 목록을 둬야 「내 결재함」에서 열린 건을 치운 뒤에도 이력을 다시 찾을 수 있다.\n *\n * **A row addressed to a role I hold is mine too**, whoever of that role acted on it, and so is one\n * withdrawn while it waited for that role. The step was put to the role, so it reached every holder's\n * 「결재할 것」, and every holder may look up how it ended (2026-11-10 user: 역할을 기반해서 완료에\n * 보이는 것은 잘못되었다고 할 수 없다).\n */\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch completed approvals where I participated'\n })\n async approvalCompletedList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n user = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })\n const roles = (user.roles ?? []).filter(role => role.domainId === domain.id).map(role => role.id)\n const judgment = [...TERMINATED_JUDGMENTS]\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere('ap.judgment IN (:...judgment)', { judgment })\n .andWhere(\n new Brackets(qb => {\n qb.where('ap.approver = :user', { user: user.id })\n if (roles.length > 0) qb.orWhere('ap.approverRole IN (:...roles)', { roles })\n })\n )\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Query(returns => ActivityApprovalList, {\n description: 'To fetch the approvals(ActivityApprovals) which has done by me'\n })\n async approvalDoneList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ActivityApprovalList> {\n var { domain, user } = context.state\n\n /* 「끝난 것」의 정의는 한 자리에 있다 — 여기서 다시 세면 값이 늘 때 한쪽만 늘어난다. */\n const judgment = [...TERMINATED_JUDGMENTS]\n\n const [items, total] = await getQueryBuilderFromListParams({\n repository: getRepository(ActivityApproval),\n params,\n domain,\n alias: 'ap',\n searchables: ['name', 'description'],\n filtersMap: APPROVAL_FILTERS_MAP\n })\n .andWhere('ap.judgment IN (:...judgment)', { judgment })\n /*\n * 역할로 걸린 결재를 처리하면 이제 `approver` 에 처리한 사람이 들어간다(`approve.ts`·`reject.ts`).\n * 그래서 이 한 줄로 자기가 처리한 것이 다 걸린다 — 개인으로 걸린 것이든 역할로 걸린 것이든.\n *\n * 고치기 전에 처리한 역할 결재는 `approver` 가 비어 있어 여기 안 나온다. 지난 기록을 뒤에서 고쳐\n * 쓰지 않는다는 규율에 따라 그대로 둔다.\n */\n .andWhere('ap.approver = :user', { user: user.id })\n .getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => ActivityThread)\n async activityThread(@Root() activityApproval: ActivityApproval): Promise<ActivityThread> {\n return await getRepository(ActivityThread).findOneBy({ id: activityApproval.activityThreadId })\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() activityApproval: ActivityApproval): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: activityApproval.domainId })\n }\n\n @FieldResolver(type => User)\n async approver(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.approverId && (await getRepository(User).findOneBy({ id: activityApproval.approverId }))\n }\n\n /**\n * 누가 결재해야 하나가 **역할**일 때.\n *\n * 이 리졸버가 없어서 `approverRole` 이 늘 `null` 로 나갔다. DB 에는 들어 있는데 질의가 관계를 안\n * 싣기 때문이다. 그래서 결재 목록이 「누가 결재해야 하나」를 못 보였다 — 개인으로 지정한 건만 보이고\n * 역할로 지정한 건은 빈칸이었다.\n */\n @FieldResolver(type => Role, { nullable: true })\n async approverRole(@Root() activityApproval: ActivityApproval): Promise<Role> {\n return activityApproval.approverRoleId && (await getRepository(Role).findOneBy({ id: activityApproval.approverRoleId }))\n }\n\n @FieldResolver(type => User)\n async updater(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.updaterId && (await getRepository(User).findOneBy({ id: activityApproval.updaterId }))\n }\n\n @FieldResolver(type => User)\n async creator(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.creatorId && (await getRepository(User).findOneBy({ id: activityApproval.creatorId }))\n }\n\n @FieldResolver(type => User)\n async terminator(@Root() activityApproval: ActivityApproval): Promise<User> {\n return activityApproval.terminatorId && (await getRepository(User).findOneBy({ id: activityApproval.terminatorId }))\n }\n}\n"]}
|
|
@@ -4,5 +4,5 @@ import { ActivityApprovalMutation } from "./activity-approval-mutation.js";
|
|
|
4
4
|
import { ActivityApprovalSubscription } from "./activity-approval-subscription.js";
|
|
5
5
|
import { ActivityApprovalSubscriber } from "./event-subscriber.js";
|
|
6
6
|
export declare const entities: (typeof ActivityApproval)[];
|
|
7
|
-
export declare const resolvers: (typeof
|
|
7
|
+
export declare const resolvers: (typeof ActivityApprovalMutation | typeof ActivityApprovalQuery | typeof ActivityApprovalSubscription)[];
|
|
8
8
|
export declare const subscribers: (typeof ActivityApprovalSubscriber)[];
|
|
@@ -17,8 +17,8 @@ export * from './work-door/work-door-journal.js';
|
|
|
17
17
|
export * from './work-door/work-door-journal-service.js';
|
|
18
18
|
export * from './work-door/work-door-service.js';
|
|
19
19
|
export { waitingOn } from './approval-progress/waiting-on.js';
|
|
20
|
-
export declare const entities: (typeof import("./activity/activity.js").Activity | typeof import("./activity-
|
|
21
|
-
export declare const subscribers: (typeof import("./activity-approval/event-subscriber.js").ActivityApprovalSubscriber | typeof import("./activity/event-subscriber.js").
|
|
20
|
+
export declare const entities: (typeof import("./activity/activity.js").Activity | typeof import("./activity-instance/activity-instance.js").ActivityInstance | typeof import("./activity-thread/activity-thread.js").ActivityThread | typeof import("./installable-activity/installable-activity.js").InstallableActivity | typeof import("./activity-approval/activity-approval.js").ActivityApproval | typeof import("./activity-instance/activity-instance-history.js").ActivityInstanceHistory | typeof import("./activity-thread/activity-thread-history.js").ActivityThreadHistory | typeof import("./activity/activity-history.js").ActivityHistory | typeof import("./activity-template/activity-template.js").ActivityTemplate | typeof import("./activity-template/activity-template-history.js").ActivityTemplateHistory | typeof import("./activity-summary/activity-summary.js").ActivitySummary | typeof import("./work-door/work-door-not-used.js").WorkDoorNotUsed | typeof import("./work-door/work-door-journal.js").WorkDoorJournal)[];
|
|
21
|
+
export declare const subscribers: (typeof import("./activity-approval/event-subscriber.js").ActivityApprovalSubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceSubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceHistoryEntitySubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadSubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadHistoryEntitySubscriber | typeof import("./activity/event-subscriber.js").ActivityHistoryEntitySubscriber | typeof import("./activity/declaration-guard-subscriber.js").ActivityDeclarationGuardSubscriber)[];
|
|
22
22
|
export declare const schema: {
|
|
23
|
-
resolverClasses: (typeof import("./activity-
|
|
23
|
+
resolverClasses: (typeof import("./activity-approval/activity-approval-mutation.js").ActivityApprovalMutation | typeof import("./activity-approval/activity-approval-query.js").ActivityApprovalQuery | typeof import("./activity-approval/activity-approval-subscription.js").ActivityApprovalSubscription | typeof import("./activity-instance/activity-instance-mutation.js").ActivityInstanceMutation | typeof import("./activity-instance/activity-instance-query.js").ActivityInstanceQuery | typeof import("./activity-instance/activity-instance-subscription.js").ActivityInstanceSubscription | typeof import("./activity-thread/activity-thread-mutation.js").ActivityThreadMutation | typeof import("./activity-thread/activity-thread-query.js").ActivityThreadQuery | typeof import("./activity-thread/activity-thread-history-query.js").ActivityThreadHistoryQuery | typeof import("./activity-thread/activity-thread-subscription.js").ActivityThreadSubscription | typeof import("./installable-activity/installable-activity-query.js").InstallableActivityQuery | typeof import("./installable-activity/installable-activity-mutation.js").InstallableActivityMutation | typeof import("./activity-stats/activity-stats-query.js").ActivityStatsQuery | typeof import("./activity/activity-query.js").ActivityQuery | typeof import("./activity/activity-mutation.js").ActivityMutation | typeof import("./activity-template/activity-template-query.js").ActivityTemplateQuery | typeof import("./activity-template/activity-template-mutation.js").ActivityTemplateMutation | typeof import("./activity-summary/activity-summary-query.js").ActivitySummaryQuery | typeof import("./activity-summary/person-open-work.js").PersonOpenWorkQuery | typeof import("./work-door/work-door-resolver.js").WorkDoorResolver | typeof import("./approval-progress/approval-progress-query.js").ApprovalProgressQuery)[];
|
|
24
24
|
};
|