@things-factory/worklist 10.1.36 → 10.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/components/activity-instance-preview.js +2 -13
- package/dist-client/components/activity-instance-preview.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.d.ts +2 -2
- package/dist-client/components/can-pick.d.ts +15 -0
- package/dist-client/components/can-pick.js +16 -0
- package/dist-client/components/can-pick.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/pages/activity-catalog/activity-catalog-page.js +50 -40
- package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -1
- package/dist-client/pages/activity-catalog/activity-detail-actions.d.ts +34 -0
- package/dist-client/pages/activity-catalog/activity-detail-actions.js +18 -0
- package/dist-client/pages/activity-catalog/activity-detail-actions.js.map +1 -0
- package/dist-client/pages/activity-catalog/activity-detail-page.d.ts +68 -0
- package/dist-client/pages/activity-catalog/activity-detail-page.js +489 -0
- package/dist-client/pages/activity-catalog/activity-detail-page.js.map +1 -0
- package/dist-client/pages/activity-catalog/activity-guide.d.ts +16 -0
- package/dist-client/pages/activity-catalog/activity-guide.js +5 -0
- package/dist-client/pages/activity-catalog/activity-guide.js.map +1 -0
- package/dist-client/pages/activity-catalog/activity-panels.d.ts +80 -0
- package/dist-client/pages/activity-catalog/activity-panels.js +53 -0
- package/dist-client/pages/activity-catalog/activity-panels.js.map +1 -0
- package/dist-client/pages/activity-catalog/activity-stage.d.ts +31 -0
- package/dist-client/pages/activity-catalog/activity-stage.js +44 -0
- package/dist-client/pages/activity-catalog/activity-stage.js.map +1 -0
- package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +1 -0
- package/dist-client/pages/activity-instance/activity-instance-start-page.js +33 -1
- package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
- package/dist-client/pages/activity-instance/discard-offer.d.ts +11 -0
- package/dist-client/pages/activity-instance/discard-offer.js +11 -0
- package/dist-client/pages/activity-instance/discard-offer.js.map +1 -0
- package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
- package/dist-client/pages/todo/approval-done-list-page.d.ts +5 -1
- package/dist-client/pages/todo/approval-done-list-page.js +8 -1
- package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
- package/dist-client/pages/todo/approval-pending-list-page.d.ts +5 -1
- package/dist-client/pages/todo/approval-pending-list-page.js +8 -1
- package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
- package/dist-client/pages/todo/done-list-page.d.ts +5 -1
- package/dist-client/pages/todo/done-list-page.js +8 -1
- package/dist-client/pages/todo/done-list-page.js.map +1 -1
- package/dist-client/pages/todo/draft-list-page.d.ts +5 -1
- package/dist-client/pages/todo/draft-list-page.js +18 -12
- package/dist-client/pages/todo/draft-list-page.js.map +1 -1
- package/dist-client/pages/todo/list-thumbnail.d.ts +25 -0
- package/dist-client/pages/todo/list-thumbnail.js +25 -0
- package/dist-client/pages/todo/list-thumbnail.js.map +1 -0
- package/dist-client/pages/todo/todo-list-page.d.ts +5 -1
- package/dist-client/pages/todo/todo-list-page.js +8 -1
- package/dist-client/pages/todo/todo-list-page.js.map +1 -1
- package/dist-client/pages/work-door/door-links.d.ts +30 -0
- package/dist-client/pages/work-door/door-links.js +30 -0
- package/dist-client/pages/work-door/door-links.js.map +1 -0
- package/dist-client/pages/work-door/work-doors-page.js +15 -5
- package/dist-client/pages/work-door/work-doors-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-approval/delegate.js +27 -2
- package/dist-server/controllers/activity-approval/delegate.js.map +1 -1
- package/dist-server/controllers/activity-approval/delegation.d.ts +31 -0
- package/dist-server/controllers/activity-approval/delegation.js +31 -0
- package/dist-server/controllers/activity-approval/delegation.js.map +1 -0
- package/dist-server/controllers/activity-instance/discard-draft.d.ts +22 -0
- package/dist-server/controllers/activity-instance/discard-draft.js +54 -0
- package/dist-server/controllers/activity-instance/discard-draft.js.map +1 -0
- package/dist-server/controllers/activity-read/assert-may-read.d.ts +15 -0
- package/dist-server/controllers/activity-read/assert-may-read.js +99 -0
- package/dist-server/controllers/activity-read/assert-may-read.js.map +1 -0
- package/dist-server/controllers/activity-read/participates.d.ts +41 -0
- package/dist-server/controllers/activity-read/participates.js +42 -0
- package/dist-server/controllers/activity-read/participates.js.map +1 -0
- package/dist-server/controllers/activity-thread/delegate.js +23 -1
- package/dist-server/controllers/activity-thread/delegate.js.map +1 -1
- package/dist-server/service/activity/activity-mutation.js +1 -0
- package/dist-server/service/activity/activity-mutation.js.map +1 -1
- package/dist-server/service/activity-approval/activity-approval-query.js +7 -1
- package/dist-server/service/activity-approval/activity-approval-query.js.map +1 -1
- package/dist-server/service/activity-approval/activity-approval-subscription.js +2 -11
- package/dist-server/service/activity-approval/activity-approval-subscription.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-mutation.js +12 -20
- package/dist-server/service/activity-instance/activity-instance-mutation.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-query.js +30 -9
- package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-subscription.js +4 -12
- package/dist-server/service/activity-instance/activity-instance-subscription.js.map +1 -1
- package/dist-server/service/activity-instance/draft-list-states.d.ts +39 -0
- package/dist-server/service/activity-instance/draft-list-states.js +51 -0
- package/dist-server/service/activity-instance/draft-list-states.js.map +1 -0
- package/dist-server/service/activity-instance/index.d.ts +1 -2
- package/dist-server/service/activity-instance/index.js +1 -2
- package/dist-server/service/activity-instance/index.js.map +1 -1
- package/dist-server/service/activity-template/activity-template-mutation.js +6 -0
- package/dist-server/service/activity-template/activity-template-mutation.js.map +1 -1
- package/dist-server/service/activity-thread/activity-thread-mutation.d.ts +0 -3
- package/dist-server/service/activity-thread/activity-thread-mutation.js +0 -19
- package/dist-server/service/activity-thread/activity-thread-mutation.js.map +1 -1
- package/dist-server/service/activity-thread/activity-thread-query.js +7 -1
- package/dist-server/service/activity-thread/activity-thread-query.js.map +1 -1
- package/dist-server/service/activity-thread/activity-thread-subscription.js +2 -8
- package/dist-server/service/activity-thread/activity-thread-subscription.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/subscription/subscription-filters.d.ts +36 -0
- package/dist-server/service/subscription/subscription-filters.js +41 -0
- package/dist-server/service/subscription/subscription-filters.js.map +1 -0
- package/dist-server/service/work-door/work-door-declaration.d.ts +15 -0
- package/dist-server/service/work-door/work-door-declaration.js +18 -0
- package/dist-server/service/work-door/work-door-declaration.js.map +1 -1
- package/dist-server/service/work-door/work-door-resolver.d.ts +3 -0
- package/dist-server/service/work-door/work-door-resolver.js +21 -1
- package/dist-server/service/work-door/work-door-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/tests/delegate-approval.test.ts +112 -0
- package/tests/discard-draft.test.ts +145 -0
- package/tests/may-read-case.test.ts +166 -0
- package/tests/open-doors.test.ts +274 -0
- package/tests/published-shape.test.ts +85 -0
- package/tests/work-door-not-used-refuse.test.ts +110 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +32 -3
- package/translations/ja.json +33 -4
- package/translations/ko.json +33 -4
- package/translations/ms.json +32 -3
- package/translations/zh.json +33 -4
- package/dist-server/service/activity-instance/activity-instance-history-query.d.ts +0 -12
- package/dist-server/service/activity-instance/activity-instance-history-query.js +0 -79
- package/dist-server/service/activity-instance/activity-instance-history-query.js.map +0 -1
- package/dist-server/service/activity-instance/activity-instance-history-type.d.ts +0 -5
- package/dist-server/service/activity-instance/activity-instance-history-type.js +0 -21
- package/dist-server/service/activity-instance/activity-instance-history-type.js.map +0 -1
|
@@ -6,6 +6,8 @@ const opinion_js_1 = require("./opinion.js");
|
|
|
6
6
|
const activity_approval_js_1 = require("../../service/activity-approval/activity-approval.js");
|
|
7
7
|
const check_authority_js_1 = require("./check-authority.js");
|
|
8
8
|
const claim_js_1 = require("./claim.js");
|
|
9
|
+
const delegation_js_1 = require("./delegation.js");
|
|
10
|
+
const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
|
|
9
11
|
/**
|
|
10
12
|
* 결재를 다른 사람에게 넘긴다.
|
|
11
13
|
*
|
|
@@ -45,11 +47,28 @@ async function delegate({ id, to, comment }, context) {
|
|
|
45
47
|
actual: activityApproval.judgment
|
|
46
48
|
}));
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const approver = await tx.getRepository(auth_base_1.User).findOne({
|
|
51
|
+
where: { id: to.id, status: auth_base_1.UserStatus.ACTIVATED },
|
|
52
|
+
relations: ['domains']
|
|
53
|
+
});
|
|
50
54
|
if (!approver) {
|
|
51
55
|
throw new Error(context.t(`error.user not found`));
|
|
52
56
|
}
|
|
57
|
+
/* 누구에게 넘길 수 있나 — 판단은 `delegation.ts` 가 한다(시험이 그 파일을 든다). */
|
|
58
|
+
const thread = await tx.getRepository(activity_thread_js_1.ActivityThread).findOne({
|
|
59
|
+
where: { domain: { id: domain.id }, id: activityApproval.activityThreadId },
|
|
60
|
+
relations: ['activityInstance']
|
|
61
|
+
});
|
|
62
|
+
const refusal = (0, delegation_js_1.delegationRefusal)({
|
|
63
|
+
participation: activityApproval.participation,
|
|
64
|
+
issuerId: thread?.activityInstance?.issuerId,
|
|
65
|
+
fromUserId: user.id,
|
|
66
|
+
toUserId: approver.id,
|
|
67
|
+
toInDomain: (approver.domains || []).some(one => one.id === domain.id)
|
|
68
|
+
});
|
|
69
|
+
if (refusal) {
|
|
70
|
+
throw Object.assign(new Error(context.t(delegation_js_1.DELEGATION_REFUSAL_KEYS[refusal])), { code: refusal });
|
|
71
|
+
}
|
|
53
72
|
/*
|
|
54
73
|
* **여기서 잡는다.** 위의 검사는 읽은 시점의 것이라 그 사이에 남이 처리할 수 있다. 잡기가 첫 쓰기여야
|
|
55
74
|
* 진 쪽이 다음 결재자를 만들기 전에 물러난다.
|
|
@@ -70,6 +89,12 @@ async function delegate({ id, to, comment }, context) {
|
|
|
70
89
|
originalApproval: activityApproval,
|
|
71
90
|
round: activityApproval.round,
|
|
72
91
|
order: activityApproval.order,
|
|
92
|
+
/*
|
|
93
|
+
* The seat keeps what it is. Left out, an empty participation reads as approval, so a reference
|
|
94
|
+
* handed on became a judging seat in the receiver's pending list that the line refused to let them
|
|
95
|
+
* approve, and it left their reference list.
|
|
96
|
+
*/
|
|
97
|
+
participation: activityApproval.participation,
|
|
73
98
|
judgment: activity_approval_js_1.ActivityApprovalJudgment.Pending,
|
|
74
99
|
transaction: 'delegate',
|
|
75
100
|
...(0, opinion_js_1.stampedComment)(comment),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/delegate.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/delegate.ts"],"names":[],"mappings":"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","sourcesContent":["import { User, UserStatus } from '@things-factory/auth-base'\nimport { stampedComment } from './opinion.js'\nimport { ObjectRef } from '@things-factory/shell'\nimport { ActivityApproval, ActivityApprovalJudgment } from '../../service/activity-approval/activity-approval.js'\nimport { checkAuthority } from './check-authority.js'\nimport { claimPendingApproval } from './claim.js'\nimport { DELEGATION_REFUSAL_KEYS, delegationRefusal } from './delegation.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\n\n/**\n * 결재를 다른 사람에게 넘긴다.\n *\n * ── 넘긴 사람의 행은 끝내고, 받은 사람의 행을 새로 만든다 ──────────────────\n * 두 행이 필요하다. 넘긴 기록이 남아야 「누가 왜 넘겼나」에 답할 수 있고, 받은 사람에게 대기 건이\n * 서야 결재가 이어진다.\n *\n * 앞서는 두 번 저장하면서 **둘 다 같은 행을 덮었다.** `{ ...activityApproval }` 이 `id` 까지 퍼뜨려서\n * TypeORM 이 새로 넣지 않고 고쳤기 때문이다. 그래서 위임하면\n *\n * 받은 사람에게 대기 건이 안 생기고\n * 원본은 `delegated` 로 끝나고\n * 스레드는 `submitted` 인 채로 멈춘다\n *\n * **결재가 죽었다.** 오류는 안 났다.\n */\nexport async function delegate(\n { id, to, comment }: { id: string; to: ObjectRef; comment: string },\n context: ResolverContext\n): Promise<ActivityApproval> {\n const { domain, user, tx } = context.state\n\n var repository = tx.getRepository(ActivityApproval)\n\n var activityApproval = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['domain', 'activityThread']\n })\n\n if (!activityApproval) {\n throw new Error(\n context.t('error.activity-approval not found', {\n activityApproval: id\n })\n )\n }\n\n await checkAuthority(activityApproval, context)\n\n /*\n Prerequisite of Delegation\n - Previous judgment should be 'pending'\n */\n if (activityApproval.judgment !== ActivityApprovalJudgment.Pending) {\n throw new Error(\n context.t(`error.activity-approval is already terminated`, {\n activityInstance: id,\n actual: activityApproval.judgment\n })\n )\n }\n\n const approver = await tx.getRepository(User).findOne({\n where: { id: to.id, status: UserStatus.ACTIVATED },\n relations: ['domains']\n })\n\n if (!approver) {\n throw new Error(context.t(`error.user not found`))\n }\n\n /* 누구에게 넘길 수 있나 — 판단은 `delegation.ts` 가 한다(시험이 그 파일을 든다). */\n const thread = await tx.getRepository(ActivityThread).findOne({\n where: { domain: { id: domain.id }, id: activityApproval.activityThreadId },\n relations: ['activityInstance']\n })\n\n const refusal = delegationRefusal({\n participation: activityApproval.participation,\n issuerId: thread?.activityInstance?.issuerId,\n fromUserId: user.id,\n toUserId: approver.id,\n toInDomain: (approver.domains || []).some(one => one.id === domain.id)\n })\n\n if (refusal) {\n throw Object.assign(new Error(context.t(DELEGATION_REFUSAL_KEYS[refusal])), { code: refusal })\n }\n\n\n /*\n * **여기서 잡는다.** 위의 검사는 읽은 시점의 것이라 그 사이에 남이 처리할 수 있다. 잡기가 첫 쓰기여야\n * 진 쪽이 다음 결재자를 만들기 전에 물러난다.\n */\n if (!(await claimPendingApproval(tx, activityApproval, ActivityApprovalJudgment.Delegated))) {\n /* 「이미 끝났다」보다 **그 사람이 할 일**을 말한다 — 목록을 새로 읽으면 지금 상태가 보인다. */\n throw new Error(context.t('error.activity-approval taken by someone else', { activityApproval: id }))\n }\n\n /*\n * 받은 사람의 대기 건을 **새로 만든다.**\n *\n * `id` 를 퍼뜨리지 않는다 — 퍼뜨리면 원본을 고치는 것이 되고 새 행이 안 생긴다. 차례(`order`)와\n * 차수(`round`)는 같다. 결재선의 같은 단계이고 사람만 바뀐 것이다.\n */\n await repository.save(\n repository.create({\n domain: activityApproval.domain,\n activityThread: activityApproval.activityThread,\n originalApproval: activityApproval,\n round: activityApproval.round,\n order: activityApproval.order,\n /*\n * The seat keeps what it is. Left out, an empty participation reads as approval, so a reference\n * handed on became a judging seat in the receiver's pending list that the line refused to let them\n * approve, and it left their reference list.\n */\n participation: activityApproval.participation,\n judgment: ActivityApprovalJudgment.Pending,\n transaction: 'delegate',\n ...stampedComment(comment),\n approver,\n creator: user,\n updater: user\n })\n )\n\n /* 넘긴 사람의 행을 끝낸다. 의견은 `comment` 에 남긴다 — `reason` 은 이 표에 없는 칸이다. */\n const result = await repository.save({\n ...activityApproval,\n judgment: ActivityApprovalJudgment.Delegated,\n ...stampedComment(comment),\n transaction: 'delegate',\n updater: user,\n terminator: user,\n terminatedAt: new Date()\n })\n\n return result\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* May this seat be handed to that person? Kept free of entities so it can be tested.
|
|
3
|
+
*
|
|
4
|
+
* Delegation moves a seat on the approval line from one person to another. It must not change what
|
|
5
|
+
* the seat is, and it must not hand it to someone who cannot sit in it:
|
|
6
|
+
*
|
|
7
|
+
* delegate-to-self handing a seat to its own holder changes nothing but the history
|
|
8
|
+
* delegate-outside-domain a person of another tenant would hold a case nobody here can act on,
|
|
9
|
+
* and their inbox, which reads their own domain, would never show it
|
|
10
|
+
* delegate-to-issuer a judging seat handed to whoever raised the case: the approve gate
|
|
11
|
+
* refuses them (self-approval), so the case would stop there. A reference
|
|
12
|
+
* seat may go to them - references do not judge.
|
|
13
|
+
*/
|
|
14
|
+
export type DelegationRefusal = 'delegate-to-self' | 'delegate-outside-domain' | 'delegate-to-issuer';
|
|
15
|
+
export declare const DELEGATION_REFUSAL_KEYS: Record<DelegationRefusal, string>;
|
|
16
|
+
/**
|
|
17
|
+
* The part every hand-over shares - an approval seat or a thread: not to its own holder, and not
|
|
18
|
+
* out of this tenant.
|
|
19
|
+
*/
|
|
20
|
+
export declare function handOverRefusal({ fromUserId, toUserId, toInDomain }: {
|
|
21
|
+
fromUserId: string;
|
|
22
|
+
toUserId: string;
|
|
23
|
+
toInDomain: boolean;
|
|
24
|
+
}): 'delegate-to-self' | 'delegate-outside-domain' | null;
|
|
25
|
+
export declare function delegationRefusal({ participation, issuerId, fromUserId, toUserId, toInDomain }: {
|
|
26
|
+
participation?: string | null;
|
|
27
|
+
issuerId?: string | null;
|
|
28
|
+
fromUserId: string;
|
|
29
|
+
toUserId: string;
|
|
30
|
+
toInDomain: boolean;
|
|
31
|
+
}): DelegationRefusal | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DELEGATION_REFUSAL_KEYS = void 0;
|
|
4
|
+
exports.handOverRefusal = handOverRefusal;
|
|
5
|
+
exports.delegationRefusal = delegationRefusal;
|
|
6
|
+
const own_request_js_1 = require("./own-request.js");
|
|
7
|
+
exports.DELEGATION_REFUSAL_KEYS = {
|
|
8
|
+
'delegate-to-self': 'error.activity-approval delegate to self',
|
|
9
|
+
'delegate-outside-domain': 'error.activity-approval delegate outside domain',
|
|
10
|
+
'delegate-to-issuer': 'error.activity-approval delegate to issuer'
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The part every hand-over shares - an approval seat or a thread: not to its own holder, and not
|
|
14
|
+
* out of this tenant.
|
|
15
|
+
*/
|
|
16
|
+
function handOverRefusal({ fromUserId, toUserId, toInDomain }) {
|
|
17
|
+
if (!toUserId || toUserId === fromUserId)
|
|
18
|
+
return 'delegate-to-self';
|
|
19
|
+
if (!toInDomain)
|
|
20
|
+
return 'delegate-outside-domain';
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function delegationRefusal({ participation, issuerId, fromUserId, toUserId, toInDomain }) {
|
|
24
|
+
const shared = handOverRefusal({ fromUserId, toUserId, toInDomain });
|
|
25
|
+
if (shared)
|
|
26
|
+
return shared;
|
|
27
|
+
if ((0, own_request_js_1.judges)(participation) && (0, own_request_js_1.refuseToJudge)({ participation, issuerId, userId: toUserId }))
|
|
28
|
+
return 'delegate-to-issuer';
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=delegation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation.js","sourceRoot":"","sources":["../../../server/controllers/activity-approval/delegation.ts"],"names":[],"mappings":";;;AA2BA,0CAaC;AAED,8CAkBC;AA5DD,qDAAwD;AAiB3C,QAAA,uBAAuB,GAAsC;IACxE,kBAAkB,EAAE,0CAA0C;IAC9D,yBAAyB,EAAE,iDAAiD;IAC5E,oBAAoB,EAAE,4CAA4C;CACnE,CAAA;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,QAAQ,EACR,UAAU,EAKX;IACC,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,UAAU;QAAE,OAAO,kBAAkB,CAAA;IACnE,IAAI,CAAC,UAAU;QAAE,OAAO,yBAAyB,CAAA;IAEjD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAgB,iBAAiB,CAAC,EAChC,aAAa,EACb,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,UAAU,EAOX;IACC,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;IACpE,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IACzB,IAAI,IAAA,uBAAM,EAAC,aAAa,CAAC,IAAI,IAAA,8BAAa,EAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAAE,OAAO,oBAAoB,CAAA;IAEtH,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { judges, refuseToJudge } from './own-request.js'\n\n/**\n * May this seat be handed to that person? Kept free of entities so it can be tested.\n *\n * Delegation moves a seat on the approval line from one person to another. It must not change what\n * the seat is, and it must not hand it to someone who cannot sit in it:\n *\n * delegate-to-self handing a seat to its own holder changes nothing but the history\n * delegate-outside-domain a person of another tenant would hold a case nobody here can act on,\n * and their inbox, which reads their own domain, would never show it\n * delegate-to-issuer a judging seat handed to whoever raised the case: the approve gate\n * refuses them (self-approval), so the case would stop there. A reference\n * seat may go to them - references do not judge.\n */\nexport type DelegationRefusal = 'delegate-to-self' | 'delegate-outside-domain' | 'delegate-to-issuer'\n\nexport const DELEGATION_REFUSAL_KEYS: Record<DelegationRefusal, string> = {\n 'delegate-to-self': 'error.activity-approval delegate to self',\n 'delegate-outside-domain': 'error.activity-approval delegate outside domain',\n 'delegate-to-issuer': 'error.activity-approval delegate to issuer'\n}\n\n/**\n * The part every hand-over shares - an approval seat or a thread: not to its own holder, and not\n * out of this tenant.\n */\nexport function handOverRefusal({\n fromUserId,\n toUserId,\n toInDomain\n}: {\n fromUserId: string\n toUserId: string\n toInDomain: boolean\n}): 'delegate-to-self' | 'delegate-outside-domain' | null {\n if (!toUserId || toUserId === fromUserId) return 'delegate-to-self'\n if (!toInDomain) return 'delegate-outside-domain'\n\n return null\n}\n\nexport function delegationRefusal({\n participation,\n issuerId,\n fromUserId,\n toUserId,\n toInDomain\n}: {\n participation?: string | null\n issuerId?: string | null\n fromUserId: string\n toUserId: string\n toInDomain: boolean\n}): DelegationRefusal | null {\n const shared = handOverRefusal({ fromUserId, toUserId, toInDomain })\n if (shared) return shared\n if (judges(participation) && refuseToJudge({ participation, issuerId, userId: toUserId })) return 'delegate-to-issuer'\n\n return null\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw away a draft - by whoever wrote it, or someone who manages instances (ruling 2026-09-24).
|
|
3
|
+
*
|
|
4
|
+
* The same entitlement as editing and issuing a draft (entitlement.ts). A draft is still the writer's
|
|
5
|
+
* own; once issued it is other people's work and cannot be thrown away here.
|
|
6
|
+
*
|
|
7
|
+
* ── Order, because a rollback cannot bring a file back ─────────────────────
|
|
8
|
+
* 1. rows that hang off the instance - approvals, then threads. A draft normally has none; they are
|
|
9
|
+
* cleared so that no row is left without its parent. These roll back with the transaction.
|
|
10
|
+
* 2. the instance, with `state = 'draft'` in the delete itself. Reading the state and deleting after
|
|
11
|
+
* would let an issue land in between and be deleted. No row deleted means it is not a draft any
|
|
12
|
+
* more (or not here), and the refusal says so - the transaction rolls step 1 back.
|
|
13
|
+
* 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage
|
|
14
|
+
* at once, and that cannot be rolled back.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ Last is not after the commit. @transaction commits after the resolver returns, so a commit that
|
|
17
|
+
* fails there brings the rows back without their files. There is no after-commit hook in @transaction
|
|
18
|
+
* and no rows-now-files-later shape in attachment-base; the fix waits for a ruling (2026-09-24).
|
|
19
|
+
* tests/discard-draft.test.ts holds the hole as test.failing. No attachment in this repository names
|
|
20
|
+
* an instance today, so nothing is lost yet - a product that attaches files to instances would.
|
|
21
|
+
*/
|
|
22
|
+
export declare function discardDraft(id: string, context: ResolverContext): Promise<true>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discardDraft = discardDraft;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
6
|
+
const activity_approval_js_1 = require("../../service/activity-approval/activity-approval.js");
|
|
7
|
+
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
8
|
+
const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
|
|
9
|
+
const entitlement_js_1 = require("./entitlement.js");
|
|
10
|
+
/**
|
|
11
|
+
* Throw away a draft - by whoever wrote it, or someone who manages instances (ruling 2026-09-24).
|
|
12
|
+
*
|
|
13
|
+
* The same entitlement as editing and issuing a draft (entitlement.ts). A draft is still the writer's
|
|
14
|
+
* own; once issued it is other people's work and cannot be thrown away here.
|
|
15
|
+
*
|
|
16
|
+
* ── Order, because a rollback cannot bring a file back ─────────────────────
|
|
17
|
+
* 1. rows that hang off the instance - approvals, then threads. A draft normally has none; they are
|
|
18
|
+
* cleared so that no row is left without its parent. These roll back with the transaction.
|
|
19
|
+
* 2. the instance, with `state = 'draft'` in the delete itself. Reading the state and deleting after
|
|
20
|
+
* would let an issue land in between and be deleted. No row deleted means it is not a draft any
|
|
21
|
+
* more (or not here), and the refusal says so - the transaction rolls step 1 back.
|
|
22
|
+
* 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage
|
|
23
|
+
* at once, and that cannot be rolled back.
|
|
24
|
+
*
|
|
25
|
+
* ⚠ Last is not after the commit. @transaction commits after the resolver returns, so a commit that
|
|
26
|
+
* fails there brings the rows back without their files. There is no after-commit hook in @transaction
|
|
27
|
+
* and no rows-now-files-later shape in attachment-base; the fix waits for a ruling (2026-09-24).
|
|
28
|
+
* tests/discard-draft.test.ts holds the hole as test.failing. No attachment in this repository names
|
|
29
|
+
* an instance today, so nothing is lost yet - a product that attaches files to instances would.
|
|
30
|
+
*/
|
|
31
|
+
async function discardDraft(id, context) {
|
|
32
|
+
const { domain, tx } = context.state;
|
|
33
|
+
const instances = tx.getRepository(activity_instance_js_1.ActivityInstance);
|
|
34
|
+
const instance = await instances.findOne({ where: { domain: { id: domain.id }, id } });
|
|
35
|
+
if (!instance) {
|
|
36
|
+
throw new Error(context.t('error.activity-instance not found', { activityInstance: id }));
|
|
37
|
+
}
|
|
38
|
+
await (0, entitlement_js_1.assertMayWorkOnDraft)(instance, context);
|
|
39
|
+
const threads = await tx.getRepository(activity_thread_js_1.ActivityThread).find({
|
|
40
|
+
where: { domain: { id: domain.id }, activityInstance: { id } }
|
|
41
|
+
});
|
|
42
|
+
if (threads.length) {
|
|
43
|
+
const threadIds = threads.map(one => one.id);
|
|
44
|
+
await tx.getRepository(activity_approval_js_1.ActivityApproval).delete({ domain: { id: domain.id }, activityThread: { id: (0, typeorm_1.In)(threadIds) } });
|
|
45
|
+
await tx.getRepository(activity_thread_js_1.ActivityThread).delete({ domain: { id: domain.id }, id: (0, typeorm_1.In)(threadIds) });
|
|
46
|
+
}
|
|
47
|
+
const removed = await instances.delete({ domain: { id: domain.id }, id, state: activity_instance_js_1.ActivityInstanceStatus.Draft });
|
|
48
|
+
if (!removed.affected) {
|
|
49
|
+
throw new Error(context.t('error.activity-instance not a draft any more', { id }));
|
|
50
|
+
}
|
|
51
|
+
await (0, attachment_base_1.deleteAttachmentsByRef)(null, { refBys: [id] }, context);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=discard-draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discard-draft.js","sourceRoot":"","sources":["../../../server/controllers/activity-instance/discard-draft.ts"],"names":[],"mappings":";;AA8BA,oCAgCC;AA9DD,qCAA4B;AAE5B,qEAAwE;AAExE,+FAAuF;AACvF,+FAA+G;AAC/G,yFAAiF;AACjF,qDAAuD;AAEvD;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,KAAK,UAAU,YAAY,CAAC,EAAU,EAAE,OAAwB;IACrE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACpC,MAAM,SAAS,GAAG,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAA;IAEpD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAEtF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAA,qCAAoB,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAE7C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,IAAI,CAAC;QAC1D,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE;KAC/D,CAAC,CAAA;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5C,MAAM,EAAE,CAAC,aAAa,CAAC,uCAAgB,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAA;QACrH,MAAM,EAAE,CAAC,aAAa,CAAC,mCAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACjG,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,6CAAsB,CAAC,KAAK,EAAE,CAAC,CAAA;IAE9G,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8CAA8C,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IACpF,CAAC;IAED,MAAM,IAAA,wCAAsB,EAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAE7D,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { In } from 'typeorm'\n\nimport { deleteAttachmentsByRef } from '@things-factory/attachment-base'\n\nimport { ActivityApproval } from '../../service/activity-approval/activity-approval.js'\nimport { ActivityInstance, ActivityInstanceStatus } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\nimport { assertMayWorkOnDraft } from './entitlement.js'\n\n/**\n * Throw away a draft - by whoever wrote it, or someone who manages instances (ruling 2026-09-24).\n *\n * The same entitlement as editing and issuing a draft (entitlement.ts). A draft is still the writer's\n * own; once issued it is other people's work and cannot be thrown away here.\n *\n * ── Order, because a rollback cannot bring a file back ─────────────────────\n * 1. rows that hang off the instance - approvals, then threads. A draft normally has none; they are\n * cleared so that no row is left without its parent. These roll back with the transaction.\n * 2. the instance, with `state = 'draft'` in the delete itself. Reading the state and deleting after\n * would let an issue land in between and be deleted. No row deleted means it is not a draft any\n * more (or not here), and the refusal says so - the transaction rolls step 1 back.\n * 3. attachments that name the instance, last: deleteAttachmentsByRef removes the files from storage\n * at once, and that cannot be rolled back.\n *\n * ⚠ Last is not after the commit. @transaction commits after the resolver returns, so a commit that\n * fails there brings the rows back without their files. There is no after-commit hook in @transaction\n * and no rows-now-files-later shape in attachment-base; the fix waits for a ruling (2026-09-24).\n * tests/discard-draft.test.ts holds the hole as test.failing. No attachment in this repository names\n * an instance today, so nothing is lost yet - a product that attaches files to instances would.\n */\nexport async function discardDraft(id: string, context: ResolverContext): Promise<true> {\n const { domain, tx } = context.state\n const instances = tx.getRepository(ActivityInstance)\n\n const instance = await instances.findOne({ where: { domain: { id: domain.id }, id } })\n\n if (!instance) {\n throw new Error(context.t('error.activity-instance not found', { activityInstance: id }))\n }\n\n await assertMayWorkOnDraft(instance, context)\n\n const threads = await tx.getRepository(ActivityThread).find({\n where: { domain: { id: domain.id }, activityInstance: { id } }\n })\n\n if (threads.length) {\n const threadIds = threads.map(one => one.id)\n\n await tx.getRepository(ActivityApproval).delete({ domain: { id: domain.id }, activityThread: { id: In(threadIds) } })\n await tx.getRepository(ActivityThread).delete({ domain: { id: domain.id }, id: In(threadIds) })\n }\n\n const removed = await instances.delete({ domain: { id: domain.id }, id, state: ActivityInstanceStatus.Draft })\n\n if (!removed.affected) {\n throw new Error(context.t('error.activity-instance not a draft any more', { id }))\n }\n\n await deleteAttachmentsByRef(null, { refBys: [id] }, context)\n\n return true\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ActivityApproval } from '../../service/activity-approval/activity-approval.js';
|
|
2
|
+
import { ActivityInstance } from '../../service/activity-instance/activity-instance.js';
|
|
3
|
+
import { ActivityThread } from '../../service/activity-thread/activity-thread.js';
|
|
4
|
+
/**
|
|
5
|
+
* Throws a named refusal unless the caller takes part in this instance or reads every case.
|
|
6
|
+
*
|
|
7
|
+
* The draft screen reopens an instance by id. Four of plant's working roles (OPERATOR and the other
|
|
8
|
+
* hands-on roles) hold no activity-instance query privilege, so while this door asked it they could
|
|
9
|
+
* write a draft, see it under "raised by me", and not open it again.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertMayReadInstance(instance: ActivityInstance, context: ResolverContext): Promise<void>;
|
|
12
|
+
/** Throws a named refusal unless the caller takes part in this thread or reads every case. */
|
|
13
|
+
export declare function assertMayReadThread(thread: ActivityThread, context: ResolverContext): Promise<void>;
|
|
14
|
+
/** An approval is read through its thread: the same people, the same refusal. */
|
|
15
|
+
export declare function assertMayReadApproval(approval: ActivityApproval, context: ResolverContext): Promise<void>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertMayReadInstance = assertMayReadInstance;
|
|
4
|
+
exports.assertMayReadThread = assertMayReadThread;
|
|
5
|
+
exports.assertMayReadApproval = assertMayReadApproval;
|
|
6
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const activity_approval_js_1 = require("../../service/activity-approval/activity-approval.js");
|
|
10
|
+
const activity_instance_js_1 = require("../../service/activity-instance/activity-instance.js");
|
|
11
|
+
const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
|
|
12
|
+
const participates_js_1 = require("./participates.js");
|
|
13
|
+
/** Whoever may read every instance may read this one. */
|
|
14
|
+
async function readsEveryCase(context) {
|
|
15
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state;
|
|
16
|
+
return await (0, auth_base_1.checkPermission)({ category: 'activity-instance', privilege: 'query', owner: true }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
17
|
+
}
|
|
18
|
+
async function factsOf(thread) {
|
|
19
|
+
const instance = thread.activityInstanceId
|
|
20
|
+
? await (0, shell_1.getRepository)(activity_instance_js_1.ActivityInstance).findOne({
|
|
21
|
+
where: { domain: { id: thread.domainId }, id: thread.activityInstanceId }
|
|
22
|
+
})
|
|
23
|
+
: null;
|
|
24
|
+
const approvals = await (0, shell_1.getRepository)(activity_approval_js_1.ActivityApproval).find({
|
|
25
|
+
where: { domain: { id: thread.domainId }, activityThread: { id: thread.id } }
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
domainId: thread.domainId,
|
|
29
|
+
assigneeIds: [thread.assigneeId],
|
|
30
|
+
issuerId: instance?.issuerId,
|
|
31
|
+
creatorId: instance?.creatorId,
|
|
32
|
+
starterId: instance?.starterId,
|
|
33
|
+
supervisoryRoleId: instance?.supervisoryRoleId,
|
|
34
|
+
approvals: approvals.map(one => ({ approverId: one.approverId, approverRoleId: one.approverRoleId }))
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function factsOfInstance(instance) {
|
|
38
|
+
const threads = await (0, shell_1.getRepository)(activity_thread_js_1.ActivityThread).find({
|
|
39
|
+
where: { domain: { id: instance.domainId }, activityInstance: { id: instance.id } }
|
|
40
|
+
});
|
|
41
|
+
const approvals = threads.length
|
|
42
|
+
? await (0, shell_1.getRepository)(activity_approval_js_1.ActivityApproval).find({
|
|
43
|
+
where: { domain: { id: instance.domainId }, activityThread: { id: (0, typeorm_1.In)(threads.map(one => one.id)) } }
|
|
44
|
+
})
|
|
45
|
+
: [];
|
|
46
|
+
return {
|
|
47
|
+
domainId: instance.domainId,
|
|
48
|
+
assigneeIds: threads.map(one => one.assigneeId),
|
|
49
|
+
waitingRoleId: instance.assigneeRoleId,
|
|
50
|
+
issuerId: instance.issuerId,
|
|
51
|
+
creatorId: instance.creatorId,
|
|
52
|
+
starterId: instance.starterId,
|
|
53
|
+
supervisoryRoleId: instance.supervisoryRoleId,
|
|
54
|
+
approvals: approvals.map(one => ({ approverId: one.approverId, approverRoleId: one.approverRoleId }))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function readerOf(context) {
|
|
58
|
+
const { domain, user } = context.state;
|
|
59
|
+
const withRoles = await (0, shell_1.getRepository)(auth_base_1.User).findOne({ where: { id: user.id }, relations: ['roles'] });
|
|
60
|
+
const roleIds = (withRoles?.roles || []).filter(role => role.domainId === domain.id).map(role => role.id);
|
|
61
|
+
return { domainId: domain.id, userId: user.id, roleIds };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Throws a named refusal unless the caller takes part in this instance or reads every case.
|
|
65
|
+
*
|
|
66
|
+
* The draft screen reopens an instance by id. Four of plant's working roles (OPERATOR and the other
|
|
67
|
+
* hands-on roles) hold no activity-instance query privilege, so while this door asked it they could
|
|
68
|
+
* write a draft, see it under "raised by me", and not open it again.
|
|
69
|
+
*/
|
|
70
|
+
async function assertMayReadInstance(instance, context) {
|
|
71
|
+
if ((0, participates_js_1.participates)(await factsOfInstance(instance), await readerOf(context)))
|
|
72
|
+
return;
|
|
73
|
+
if (await readsEveryCase(context))
|
|
74
|
+
return;
|
|
75
|
+
throw new Error(context.t('error.activity-case not a participant'));
|
|
76
|
+
}
|
|
77
|
+
/** Throws a named refusal unless the caller takes part in this thread or reads every case. */
|
|
78
|
+
async function assertMayReadThread(thread, context) {
|
|
79
|
+
if ((0, participates_js_1.participates)(await factsOf(thread), await readerOf(context)))
|
|
80
|
+
return;
|
|
81
|
+
if (await readsEveryCase(context))
|
|
82
|
+
return;
|
|
83
|
+
throw new Error(context.t('error.activity-case not a participant'));
|
|
84
|
+
}
|
|
85
|
+
/** An approval is read through its thread: the same people, the same refusal. */
|
|
86
|
+
async function assertMayReadApproval(approval, context) {
|
|
87
|
+
const { domain } = context.state;
|
|
88
|
+
const thread = approval.activityThreadId
|
|
89
|
+
? await (0, shell_1.getRepository)(activity_thread_js_1.ActivityThread).findOne({ where: { domain: { id: domain.id }, id: approval.activityThreadId } })
|
|
90
|
+
: null;
|
|
91
|
+
if (!thread) {
|
|
92
|
+
/* An approval with no thread in this domain has nobody to take part in it. */
|
|
93
|
+
if (await readsEveryCase(context))
|
|
94
|
+
return;
|
|
95
|
+
throw new Error(context.t('error.activity-case not a participant'));
|
|
96
|
+
}
|
|
97
|
+
await assertMayReadThread(thread, context);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=assert-may-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-may-read.js","sourceRoot":"","sources":["../../../server/controllers/activity-read/assert-may-read.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkPermission, User } from '@things-factory/auth-base'\nimport { In } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { ActivityApproval } from '../../service/activity-approval/activity-approval.js'\nimport { ActivityInstance } from '../../service/activity-instance/activity-instance.js'\nimport { ActivityThread } from '../../service/activity-thread/activity-thread.js'\nimport { CaseFacts, participates } from './participates.js'\n\n/** Whoever may read every instance may read this one. */\nasync function readsEveryCase(context: ResolverContext): Promise<boolean> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n return await checkPermission(\n { category: 'activity-instance', privilege: 'query', owner: true },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n}\n\nasync function factsOf(thread: ActivityThread): Promise<CaseFacts> {\n const instance = thread.activityInstanceId\n ? await getRepository(ActivityInstance).findOne({\n where: { domain: { id: thread.domainId }, id: thread.activityInstanceId }\n })\n : null\n\n const approvals = await getRepository(ActivityApproval).find({\n where: { domain: { id: thread.domainId }, activityThread: { id: thread.id } }\n })\n\n return {\n domainId: thread.domainId,\n assigneeIds: [thread.assigneeId],\n issuerId: instance?.issuerId,\n creatorId: instance?.creatorId,\n starterId: instance?.starterId,\n supervisoryRoleId: instance?.supervisoryRoleId,\n approvals: approvals.map(one => ({ approverId: one.approverId, approverRoleId: one.approverRoleId }))\n }\n}\n\nasync function factsOfInstance(instance: ActivityInstance): Promise<CaseFacts> {\n const threads = await getRepository(ActivityThread).find({\n where: { domain: { id: instance.domainId }, activityInstance: { id: instance.id } }\n })\n\n const approvals = threads.length\n ? await getRepository(ActivityApproval).find({\n where: { domain: { id: instance.domainId }, activityThread: { id: In(threads.map(one => one.id)) } }\n })\n : []\n\n return {\n domainId: instance.domainId,\n assigneeIds: threads.map(one => one.assigneeId),\n waitingRoleId: instance.assigneeRoleId,\n issuerId: instance.issuerId,\n creatorId: instance.creatorId,\n starterId: instance.starterId,\n supervisoryRoleId: instance.supervisoryRoleId,\n approvals: approvals.map(one => ({ approverId: one.approverId, approverRoleId: one.approverRoleId }))\n }\n}\n\nasync function readerOf(context: ResolverContext) {\n const { domain, user } = context.state\n\n const withRoles = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })\n const roleIds = (withRoles?.roles || []).filter(role => role.domainId === domain.id).map(role => role.id)\n\n return { domainId: domain.id, userId: user.id, roleIds }\n}\n\n/**\n * Throws a named refusal unless the caller takes part in this instance or reads every case.\n *\n * The draft screen reopens an instance by id. Four of plant's working roles (OPERATOR and the other\n * hands-on roles) hold no activity-instance query privilege, so while this door asked it they could\n * write a draft, see it under \"raised by me\", and not open it again.\n */\nexport async function assertMayReadInstance(instance: ActivityInstance, context: ResolverContext): Promise<void> {\n if (participates(await factsOfInstance(instance), await readerOf(context))) return\n if (await readsEveryCase(context)) return\n\n throw new Error(context.t('error.activity-case not a participant'))\n}\n\n/** Throws a named refusal unless the caller takes part in this thread or reads every case. */\nexport async function assertMayReadThread(thread: ActivityThread, context: ResolverContext): Promise<void> {\n if (participates(await factsOf(thread), await readerOf(context))) return\n if (await readsEveryCase(context)) return\n\n throw new Error(context.t('error.activity-case not a participant'))\n}\n\n/** An approval is read through its thread: the same people, the same refusal. */\nexport async function assertMayReadApproval(approval: ActivityApproval, context: ResolverContext): Promise<void> {\n const { domain } = context.state\n\n const thread = approval.activityThreadId\n ? await getRepository(ActivityThread).findOne({ where: { domain: { id: domain.id }, id: approval.activityThreadId } })\n : null\n\n if (!thread) {\n /* An approval with no thread in this domain has nobody to take part in it. */\n if (await readsEveryCase(context)) return\n throw new Error(context.t('error.activity-case not a participant'))\n }\n\n await assertMayReadThread(thread, context)\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may read one case - one instance, one thread, or one approval on it - by its id.
|
|
3
|
+
*
|
|
4
|
+
* The lists of every thread and every approval ask the activity-instance query privilege. Reading
|
|
5
|
+
* one by id cannot ask only that: the thread screen and the approval screen open the case for the
|
|
6
|
+
* people working on it, and most of them hold no management privilege. So the body decides.
|
|
7
|
+
*
|
|
8
|
+
* The domain comes first. Then the people the case is about:
|
|
9
|
+
*
|
|
10
|
+
* the assignee of the thread (of any thread, when the case is the instance)
|
|
11
|
+
* whoever raised the instance, wrote it, or started it
|
|
12
|
+
* a holder of the instance's supervisory role
|
|
13
|
+
* a holder of the role the instance waits for - they have to see it to pick it up (instance only)
|
|
14
|
+
* every approver on the thread(s) - by person or by role, references included, and a delegator
|
|
15
|
+
* keeps the row that was delegated
|
|
16
|
+
*
|
|
17
|
+
* Anyone else is refused by name (NOT_A_PARTICIPANT). A null would draw as "no such case" on the
|
|
18
|
+
* approval screen, which is untrue and sends the reader looking for something that exists.
|
|
19
|
+
*
|
|
20
|
+
* Kept free of entities and repositories so the rule can be tested on its own.
|
|
21
|
+
*/
|
|
22
|
+
export declare const NOT_A_PARTICIPANT = "not-a-participant";
|
|
23
|
+
export type CaseFacts = {
|
|
24
|
+
domainId?: string;
|
|
25
|
+
assigneeIds: (string | undefined)[];
|
|
26
|
+
waitingRoleId?: string;
|
|
27
|
+
issuerId?: string;
|
|
28
|
+
creatorId?: string;
|
|
29
|
+
starterId?: string;
|
|
30
|
+
supervisoryRoleId?: string;
|
|
31
|
+
approvals: {
|
|
32
|
+
approverId?: string;
|
|
33
|
+
approverRoleId?: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
export type Reader = {
|
|
37
|
+
domainId?: string;
|
|
38
|
+
userId: string;
|
|
39
|
+
roleIds: readonly string[];
|
|
40
|
+
};
|
|
41
|
+
export declare function participates(facts: CaseFacts, reader: Reader): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Who may read one case - one instance, one thread, or one approval on it - by its id.
|
|
4
|
+
*
|
|
5
|
+
* The lists of every thread and every approval ask the activity-instance query privilege. Reading
|
|
6
|
+
* one by id cannot ask only that: the thread screen and the approval screen open the case for the
|
|
7
|
+
* people working on it, and most of them hold no management privilege. So the body decides.
|
|
8
|
+
*
|
|
9
|
+
* The domain comes first. Then the people the case is about:
|
|
10
|
+
*
|
|
11
|
+
* the assignee of the thread (of any thread, when the case is the instance)
|
|
12
|
+
* whoever raised the instance, wrote it, or started it
|
|
13
|
+
* a holder of the instance's supervisory role
|
|
14
|
+
* a holder of the role the instance waits for - they have to see it to pick it up (instance only)
|
|
15
|
+
* every approver on the thread(s) - by person or by role, references included, and a delegator
|
|
16
|
+
* keeps the row that was delegated
|
|
17
|
+
*
|
|
18
|
+
* Anyone else is refused by name (NOT_A_PARTICIPANT). A null would draw as "no such case" on the
|
|
19
|
+
* approval screen, which is untrue and sends the reader looking for something that exists.
|
|
20
|
+
*
|
|
21
|
+
* Kept free of entities and repositories so the rule can be tested on its own.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.NOT_A_PARTICIPANT = void 0;
|
|
25
|
+
exports.participates = participates;
|
|
26
|
+
exports.NOT_A_PARTICIPANT = 'not-a-participant';
|
|
27
|
+
function participates(facts, reader) {
|
|
28
|
+
if (!facts?.domainId || !reader?.domainId || facts.domainId !== reader.domainId)
|
|
29
|
+
return false;
|
|
30
|
+
const { userId } = reader;
|
|
31
|
+
/* An empty id on either side is nobody - an unassigned thread must not match a reader without one. */
|
|
32
|
+
if (!userId)
|
|
33
|
+
return false;
|
|
34
|
+
const roles = new Set(reader.roleIds);
|
|
35
|
+
const holds = (roleId) => !!roleId && roles.has(roleId);
|
|
36
|
+
if ([...(facts.assigneeIds || []), facts.issuerId, facts.creatorId, facts.starterId].includes(userId))
|
|
37
|
+
return true;
|
|
38
|
+
if (holds(facts.supervisoryRoleId) || holds(facts.waitingRoleId))
|
|
39
|
+
return true;
|
|
40
|
+
return (facts.approvals || []).some(one => one.approverId === userId || holds(one.approverRoleId));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=participates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"participates.js","sourceRoot":"","sources":["../../../server/controllers/activity-read/participates.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAiBH,oCAcC;AA7BY,QAAA,iBAAiB,GAAG,mBAAmB,CAAA;AAepD,SAAgB,YAAY,CAAC,KAAgB,EAAE,MAAc;IAC3D,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE7F,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACzB,sGAAsG;IACtG,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACrC,MAAM,KAAK,GAAG,CAAC,MAAe,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAEhE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAClH,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAA;IAE7E,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAA;AACpG,CAAC","sourcesContent":["/**\n * Who may read one case - one instance, one thread, or one approval on it - by its id.\n *\n * The lists of every thread and every approval ask the activity-instance query privilege. Reading\n * one by id cannot ask only that: the thread screen and the approval screen open the case for the\n * people working on it, and most of them hold no management privilege. So the body decides.\n *\n * The domain comes first. Then the people the case is about:\n *\n * the assignee of the thread (of any thread, when the case is the instance)\n * whoever raised the instance, wrote it, or started it\n * a holder of the instance's supervisory role\n * a holder of the role the instance waits for - they have to see it to pick it up (instance only)\n * every approver on the thread(s) - by person or by role, references included, and a delegator\n * keeps the row that was delegated\n *\n * Anyone else is refused by name (NOT_A_PARTICIPANT). A null would draw as \"no such case\" on the\n * approval screen, which is untrue and sends the reader looking for something that exists.\n *\n * Kept free of entities and repositories so the rule can be tested on its own.\n */\n\nexport const NOT_A_PARTICIPANT = 'not-a-participant'\n\nexport type CaseFacts = {\n domainId?: string\n assigneeIds: (string | undefined)[]\n waitingRoleId?: string\n issuerId?: string\n creatorId?: string\n starterId?: string\n supervisoryRoleId?: string\n approvals: { approverId?: string; approverRoleId?: string }[]\n}\n\nexport type Reader = { domainId?: string; userId: string; roleIds: readonly string[] }\n\nexport function participates(facts: CaseFacts, reader: Reader): boolean {\n if (!facts?.domainId || !reader?.domainId || facts.domainId !== reader.domainId) return false\n\n const { userId } = reader\n /* An empty id on either side is nobody - an unassigned thread must not match a reader without one. */\n if (!userId) return false\n\n const roles = new Set(reader.roleIds)\n const holds = (roleId?: string) => !!roleId && roles.has(roleId)\n\n if ([...(facts.assigneeIds || []), facts.issuerId, facts.creatorId, facts.starterId].includes(userId)) return true\n if (holds(facts.supervisoryRoleId) || holds(facts.waitingRoleId)) return true\n\n return (facts.approvals || []).some(one => one.approverId === userId || holds(one.approverRoleId))\n}\n"]}
|
|
@@ -4,6 +4,8 @@ exports.delegate = delegate;
|
|
|
4
4
|
const activity_thread_js_1 = require("../../service/activity-thread/activity-thread.js");
|
|
5
5
|
const common_js_1 = require("../common.js");
|
|
6
6
|
const check_authority_js_1 = require("./check-authority.js");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
const delegation_js_1 = require("../activity-approval/delegation.js");
|
|
7
9
|
async function delegate({ id, assignee, dueAt, reason }, context) {
|
|
8
10
|
const { domain, user, tx } = context.state;
|
|
9
11
|
const repository = tx.getRepository(activity_thread_js_1.ActivityThread);
|
|
@@ -26,12 +28,32 @@ async function delegate({ id, assignee, dueAt, reason }, context) {
|
|
|
26
28
|
actual: activityThread.state
|
|
27
29
|
}));
|
|
28
30
|
}
|
|
31
|
+
/*
|
|
32
|
+
* The receiver is looked up, not taken as sent. The client's object was saved as it came - any id,
|
|
33
|
+
* of any tenant, active or not - so a thread could be handed to someone outside this domain, whose
|
|
34
|
+
* to-do list reads their own domain and would never show it.
|
|
35
|
+
*/
|
|
36
|
+
const receiverId = assignee?.id;
|
|
37
|
+
const receiver = receiverId
|
|
38
|
+
? await tx.getRepository(auth_base_1.User).findOne({ where: { id: receiverId, status: auth_base_1.UserStatus.ACTIVATED }, relations: ['domains'] })
|
|
39
|
+
: null;
|
|
40
|
+
if (!receiver) {
|
|
41
|
+
throw new Error(context.t(`error.user not found`));
|
|
42
|
+
}
|
|
43
|
+
const refusal = (0, delegation_js_1.handOverRefusal)({
|
|
44
|
+
fromUserId: user.id,
|
|
45
|
+
toUserId: receiver.id,
|
|
46
|
+
toInDomain: (receiver.domains || []).some(one => one.id === domain.id)
|
|
47
|
+
});
|
|
48
|
+
if (refusal) {
|
|
49
|
+
throw Object.assign(new Error(context.t(delegation_js_1.DELEGATION_REFUSAL_KEYS[refusal])), { code: refusal });
|
|
50
|
+
}
|
|
29
51
|
if (dueAt) {
|
|
30
52
|
activityThread.dueAt = dueAt;
|
|
31
53
|
}
|
|
32
54
|
const result = await repository.save({
|
|
33
55
|
...activityThread,
|
|
34
|
-
assignee,
|
|
56
|
+
assignee: receiver,
|
|
35
57
|
transaction: 'delegate',
|
|
36
58
|
reason,
|
|
37
59
|
state: activity_thread_js_1.ActivityThreadStatus.Assigned,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../../server/controllers/activity-thread/delegate.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../../server/controllers/activity-thread/delegate.ts"],"names":[],"mappings":"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","sourcesContent":["import { ActivityThread, ActivityThreadStatus } from '../../service/activity-thread/activity-thread.js'\nimport { updateActivityInstanceState } from '../common.js'\nimport { checkAuthority } from './check-authority.js'\nimport { User, UserStatus } from '@things-factory/auth-base'\nimport { DELEGATION_REFUSAL_KEYS, handOverRefusal } from '../activity-approval/delegation.js'\n\nexport async function delegate(\n {\n id,\n assignee,\n dueAt,\n reason\n }: {\n id: string\n assignee?: object\n dueAt?: Date\n reason?: string\n },\n context: ResolverContext\n): Promise<ActivityThread> {\n const { domain, user, tx } = context.state\n\n const repository = tx.getRepository(ActivityThread)\n\n var activityThread = await repository.findOne({\n where: { domain: { id: domain.id }, id }\n })\n\n if (!activityThread) {\n throw new Error(\n context.t('error.activity-thread not found', {\n activityThread: id\n })\n )\n }\n\n await checkAuthority(activityThread, context)\n\n /* \n Prerequisites for a activity thread to Be Delegated.\n - The previous state of the activity thread should be assigned or started.\n */\n if (activityThread.state !== ActivityThreadStatus.Assigned && activityThread.state !== ActivityThreadStatus.Started) {\n throw new Error(\n context.t(`error.activity-thread is already terminated`, {\n id,\n actual: activityThread.state\n })\n )\n }\n\n /*\n * The receiver is looked up, not taken as sent. The client's object was saved as it came - any id,\n * of any tenant, active or not - so a thread could be handed to someone outside this domain, whose\n * to-do list reads their own domain and would never show it.\n */\n const receiverId = (assignee as any)?.id\n const receiver = receiverId\n ? await tx.getRepository(User).findOne({ where: { id: receiverId, status: UserStatus.ACTIVATED }, relations: ['domains'] })\n : null\n\n if (!receiver) {\n throw new Error(context.t(`error.user not found`))\n }\n\n const refusal = handOverRefusal({\n fromUserId: user.id,\n toUserId: receiver.id,\n toInDomain: (receiver.domains || []).some(one => one.id === domain.id)\n })\n\n if (refusal) {\n throw Object.assign(new Error(context.t(DELEGATION_REFUSAL_KEYS[refusal])), { code: refusal })\n }\n\n if (dueAt) {\n activityThread.dueAt = dueAt\n }\n\n const result = await repository.save({\n ...activityThread,\n assignee: receiver,\n transaction: 'delegate',\n reason,\n state: ActivityThreadStatus.Assigned,\n updater: user,\n assignedAt: new Date()\n })\n\n await updateActivityInstanceState(activityThread.activityInstanceId, { causedBy: [result] }, context)\n\n return result\n}\n"]}
|
|
@@ -336,6 +336,7 @@ tslib_1.__decorate([
|
|
|
336
336
|
], ActivityMutation.prototype, "deleteActivity", null);
|
|
337
337
|
tslib_1.__decorate([
|
|
338
338
|
(0, type_graphql_1.Directive)('@transaction'),
|
|
339
|
+
(0, type_graphql_1.Directive)('@privilege(category: "activity", privilege: "mutation", domainOwnerGranted: true)'),
|
|
339
340
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To delete multiple Activities' }),
|
|
340
341
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('ids', type => [String])),
|
|
341
342
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|