@things-factory/worklist 10.1.156 → 10.1.159

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.
@@ -6,6 +6,7 @@ const graphql_yoga_1 = require("graphql-yoga");
6
6
  const type_graphql_1 = require("type-graphql");
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const activity_approval_js_1 = require("./activity-approval.js");
9
+ const roles_of_js_1 = require("../subscription/roles-of.js");
9
10
  const subscription_filters_js_1 = require("../subscription/subscription-filters.js");
10
11
  let ActivityApprovalSubscription = class ActivityApprovalSubscription {
11
12
  activityApproval(payload) {
@@ -24,7 +25,7 @@ tslib_1.__decorate([
24
25
  if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {
25
26
  throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`);
26
27
  }
27
- return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe('activity-approval'), (0, graphql_yoga_1.filter)((payload) => (0, subscription_filters_js_1.hearsApproval)(payload.activityApproval, domain, user.id)));
28
+ return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe('activity-approval'), (0, graphql_yoga_1.filter)((payload) => (0, subscription_filters_js_1.hearsApproval)(payload.activityApproval, domain, user.id, () => (0, roles_of_js_1.rolesOf)(domain, user.id))));
28
29
  }
29
30
  }),
30
31
  tslib_1.__param(0, (0, type_graphql_1.Root)()),
@@ -1 +1 @@
1
- {"version":3,"file":"activity-approval-subscription.js","sourceRoot":"","sources":["../../../server/service/activity-approval/activity-approval-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA2D;AAE3D,iDAA8C;AAE9C,iEAAyD;AACzD,qFAAuE;AAGhE,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IAsBvC,gBAAgB,CAAS,OAA+C;QACtE,OAAO,OAAO,CAAC,gBAAgB,CAAA;IACjC,CAAC;CACF,CAAA;AAzBY,oEAA4B;AAsBvC;IArBC,IAAA,2BAAY,EAAC;QACZ,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAA;YAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;YACpC,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3G,MAAM,IAAI,KAAK,CAAC,UAAU,SAAS,6BAA6B,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YACjF,CAAC;YAED,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,EACrC,IAAA,qBAAM,EAAC,CAAC,OAA+C,EAAE,EAAE,CACzD,IAAA,uCAAa,EAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CACzD,CACF,CAAA;QACH,CAAC;KACF,CAAC;IACgB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;;4CAAmD,uCAAgB;oEAE1F;uCAxBU,4BAA4B;IADxC,IAAA,uBAAQ,EAAC,uCAAgB,CAAC;GACd,4BAA4B,CAyBxC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Resolver, Root, Subscription } from 'type-graphql'\n\nimport { pubsub } from '@things-factory/shell'\n\nimport { ActivityApproval } from './activity-approval.js'\nimport { hearsApproval } from '../subscription/subscription-filters.js'\n\n@Resolver(ActivityApproval)\nexport class ActivityApprovalSubscription {\n @Subscription({\n subscribe: async ({ args, context, info }) => {\n const { domain, user } = context.state\n const subdomain = domain?.subdomain\n\n if (!domain) {\n throw new Error('domain required')\n }\n\n if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {\n throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)\n }\n\n return pipe(\n pubsub.subscribe('activity-approval'),\n filter((payload: { activityApproval: ActivityApproval }) =>\n hearsApproval(payload.activityApproval, domain, user.id)\n )\n )\n }\n })\n activityApproval(@Root() payload: { activityApproval: ActivityApproval }): ActivityApproval {\n return payload.activityApproval\n }\n}\n"]}
1
+ {"version":3,"file":"activity-approval-subscription.js","sourceRoot":"","sources":["../../../server/service/activity-approval/activity-approval-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA2D;AAE3D,iDAA8C;AAE9C,iEAAyD;AACzD,6DAAqD;AACrD,qFAAuE;AAGhE,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IAsBvC,gBAAgB,CAAS,OAA+C;QACtE,OAAO,OAAO,CAAC,gBAAgB,CAAA;IACjC,CAAC;CACF,CAAA;AAzBY,oEAA4B;AAsBvC;IArBC,IAAA,2BAAY,EAAC;QACZ,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAA;YAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;YACpC,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3G,MAAM,IAAI,KAAK,CAAC,UAAU,SAAS,6BAA6B,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YACjF,CAAC;YAED,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,EACrC,IAAA,qBAAM,EAAC,CAAC,OAA+C,EAAE,EAAE,CACzD,IAAA,uCAAa,EAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAA,qBAAO,EAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CACzF,CACF,CAAA;QACH,CAAC;KACF,CAAC;IACgB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;;4CAAmD,uCAAgB;oEAE1F;uCAxBU,4BAA4B;IADxC,IAAA,uBAAQ,EAAC,uCAAgB,CAAC;GACd,4BAA4B,CAyBxC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Resolver, Root, Subscription } from 'type-graphql'\n\nimport { pubsub } from '@things-factory/shell'\n\nimport { ActivityApproval } from './activity-approval.js'\nimport { rolesOf } from '../subscription/roles-of.js'\nimport { hearsApproval } from '../subscription/subscription-filters.js'\n\n@Resolver(ActivityApproval)\nexport class ActivityApprovalSubscription {\n @Subscription({\n subscribe: async ({ args, context, info }) => {\n const { domain, user } = context.state\n const subdomain = domain?.subdomain\n\n if (!domain) {\n throw new Error('domain required')\n }\n\n if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {\n throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)\n }\n\n return pipe(\n pubsub.subscribe('activity-approval'),\n filter((payload: { activityApproval: ActivityApproval }) =>\n hearsApproval(payload.activityApproval, domain, user.id, () => rolesOf(domain, user.id))\n )\n )\n }\n })\n activityApproval(@Root() payload: { activityApproval: ActivityApproval }): ActivityApproval {\n return payload.activityApproval\n }\n}\n"]}
@@ -4,9 +4,9 @@ exports.ActivityInstanceSubscription = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const graphql_yoga_1 = require("graphql-yoga");
6
6
  const type_graphql_1 = require("type-graphql");
7
- const auth_base_1 = require("@things-factory/auth-base");
8
7
  const shell_1 = require("@things-factory/shell");
9
8
  const activity_instance_js_1 = require("./activity-instance.js");
9
+ const roles_of_js_1 = require("../subscription/roles-of.js");
10
10
  const subscription_filters_js_1 = require("../subscription/subscription-filters.js");
11
11
  let ActivityInstanceSubscription = class ActivityInstanceSubscription {
12
12
  activityInstance(payload) {
@@ -25,13 +25,7 @@ tslib_1.__decorate([
25
25
  if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {
26
26
  throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`);
27
27
  }
28
- return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe('activity-instance'), (0, graphql_yoga_1.filter)((payload) => (0, subscription_filters_js_1.hearsInstance)(payload.activityInstance, domain, user.id, async () => {
29
- const userWithRoles = await (0, shell_1.getRepository)(auth_base_1.User).findOne({
30
- where: { id: user.id },
31
- relations: ['roles']
32
- });
33
- return userWithRoles?.roles || [];
34
- })));
28
+ return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe('activity-instance'), (0, graphql_yoga_1.filter)((payload) => (0, subscription_filters_js_1.hearsInstance)(payload.activityInstance, domain, user.id, () => (0, roles_of_js_1.rolesOf)(domain, user.id))));
35
29
  }
36
30
  }),
37
31
  tslib_1.__param(0, (0, type_graphql_1.Root)()),
@@ -1 +1 @@
1
- {"version":3,"file":"activity-instance-subscription.js","sourceRoot":"","sources":["../../../server/service/activity-instance/activity-instance-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA2D;AAE3D,yDAAgD;AAChD,iDAA6D;AAE7D,iEAAyD;AACzD,qFAAuE;AAGhE,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IA6BvC,gBAAgB,CAAS,OAA+C;QACtE,OAAO,OAAO,CAAC,gBAAgB,CAAA;IACjC,CAAC;CACF,CAAA;AAhCY,oEAA4B;AA6BvC;IA5BC,IAAA,2BAAY,EAAC;QACZ,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAA;YAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;YACpC,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3G,MAAM,IAAI,KAAK,CAAC,UAAU,SAAS,6BAA6B,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YACjF,CAAC;YAED,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,EACrC,IAAA,qBAAM,EAAC,CAAC,OAA+C,EAAE,EAAE,CACzD,IAAA,uCAAa,EAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;gBAClE,MAAM,aAAa,GAAS,MAAM,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,OAAO,CAAC;oBAC5D,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;oBACtB,SAAS,EAAE,CAAC,OAAO,CAAC;iBACrB,CAAC,CAAA;gBAEF,OAAO,aAAa,EAAE,KAAK,IAAI,EAAE,CAAA;YACnC,CAAC,CAAC,CACH,CACF,CAAA;QACH,CAAC;KACF,CAAC;IACgB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;;4CAAmD,uCAAgB;oEAE1F;uCA/BU,4BAA4B;IADxC,IAAA,uBAAQ,EAAC,uCAAgB,CAAC;GACd,4BAA4B,CAgCxC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Resolver, Root, Subscription } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { getRepository, pubsub } from '@things-factory/shell'\n\nimport { ActivityInstance } from './activity-instance.js'\nimport { hearsInstance } from '../subscription/subscription-filters.js'\n\n@Resolver(ActivityInstance)\nexport class ActivityInstanceSubscription {\n @Subscription({\n subscribe: async ({ args, context, info }) => {\n const { domain, user } = context.state\n const subdomain = domain?.subdomain\n\n if (!domain) {\n throw new Error('domain required')\n }\n\n if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {\n throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)\n }\n\n return pipe(\n pubsub.subscribe('activity-instance'),\n filter((payload: { activityInstance: ActivityInstance }) =>\n hearsInstance(payload.activityInstance, domain, user.id, async () => {\n const userWithRoles: User = await getRepository(User).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n\n return userWithRoles?.roles || []\n })\n )\n )\n }\n })\n activityInstance(@Root() payload: { activityInstance: ActivityInstance }): ActivityInstance {\n return payload.activityInstance\n }\n}\n"]}
1
+ {"version":3,"file":"activity-instance-subscription.js","sourceRoot":"","sources":["../../../server/service/activity-instance/activity-instance-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA2D;AAE3D,iDAA8C;AAE9C,iEAAyD;AACzD,6DAAqD;AACrD,qFAAuE;AAGhE,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IAsBvC,gBAAgB,CAAS,OAA+C;QACtE,OAAO,OAAO,CAAC,gBAAgB,CAAA;IACjC,CAAC;CACF,CAAA;AAzBY,oEAA4B;AAsBvC;IArBC,IAAA,2BAAY,EAAC;QACZ,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAA;YAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;YACpC,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3G,MAAM,IAAI,KAAK,CAAC,UAAU,SAAS,6BAA6B,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YACjF,CAAC;YAED,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,EACrC,IAAA,qBAAM,EAAC,CAAC,OAA+C,EAAE,EAAE,CACzD,IAAA,uCAAa,EAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAA,qBAAO,EAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CACzF,CACF,CAAA;QACH,CAAC;KACF,CAAC;IACgB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;;4CAAmD,uCAAgB;oEAE1F;uCAxBU,4BAA4B;IADxC,IAAA,uBAAQ,EAAC,uCAAgB,CAAC;GACd,4BAA4B,CAyBxC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Resolver, Root, Subscription } from 'type-graphql'\n\nimport { pubsub } from '@things-factory/shell'\n\nimport { ActivityInstance } from './activity-instance.js'\nimport { rolesOf } from '../subscription/roles-of.js'\nimport { hearsInstance } from '../subscription/subscription-filters.js'\n\n@Resolver(ActivityInstance)\nexport class ActivityInstanceSubscription {\n @Subscription({\n subscribe: async ({ args, context, info }) => {\n const { domain, user } = context.state\n const subdomain = domain?.subdomain\n\n if (!domain) {\n throw new Error('domain required')\n }\n\n if (!user.domains?.find(d => d.subdomain === subdomain) && !(await process.superUserGranted(domain, user))) {\n throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)\n }\n\n return pipe(\n pubsub.subscribe('activity-instance'),\n filter((payload: { activityInstance: ActivityInstance }) =>\n hearsInstance(payload.activityInstance, domain, user.id, () => rolesOf(domain, user.id))\n )\n )\n }\n })\n activityInstance(@Root() payload: { activityInstance: ActivityInstance }): ActivityInstance {\n return payload.activityInstance\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The roles a subscriber holds in this domain - what the subscription filters match a role-addressed
3
+ * instance or approval against. One lookup for both subscriptions, so the two cannot drift apart.
4
+ */
5
+ export declare function rolesOf(domain: {
6
+ id: string;
7
+ }, userId: string): Promise<{
8
+ id: string;
9
+ }[]>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rolesOf = rolesOf;
4
+ const auth_base_1 = require("@things-factory/auth-base");
5
+ const shell_1 = require("@things-factory/shell");
6
+ /**
7
+ * The roles a subscriber holds in this domain - what the subscription filters match a role-addressed
8
+ * instance or approval against. One lookup for both subscriptions, so the two cannot drift apart.
9
+ */
10
+ async function rolesOf(domain, userId) {
11
+ const withRoles = await (0, shell_1.getRepository)(auth_base_1.User).findOne({
12
+ where: { id: userId },
13
+ relations: ['roles']
14
+ });
15
+ return (withRoles?.roles || []).filter(role => role.domainId === domain.id);
16
+ }
17
+ //# sourceMappingURL=roles-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles-of.js","sourceRoot":"","sources":["../../../server/service/subscription/roles-of.ts"],"names":[],"mappings":";;AAOA,0BAOC;AAdD,yDAAgD;AAChD,iDAAqD;AAErD;;;GAGG;AACI,KAAK,UAAU,OAAO,CAAC,MAAsB,EAAE,MAAc;IAClE,MAAM,SAAS,GAAS,MAAM,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,OAAO,CAAC;QACxD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,SAAS,EAAE,CAAC,OAAO,CAAC;KACrB,CAAC,CAAA;IAEF,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["import { User } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\n/**\n * The roles a subscriber holds in this domain - what the subscription filters match a role-addressed\n * instance or approval against. One lookup for both subscriptions, so the two cannot drift apart.\n */\nexport async function rolesOf(domain: { id: string }, userId: string): Promise<{ id: string }[]> {\n const withRoles: User = await getRepository(User).findOne({\n where: { id: userId },\n relations: ['roles']\n })\n\n return (withRoles?.roles || []).filter(role => role.domainId === domain.id)\n}\n"]}
@@ -17,11 +17,21 @@ export declare function hearsThread(thread: {
17
17
  domainId?: string;
18
18
  assigneeId?: string;
19
19
  }, domain: DomainLike, userId: string): boolean;
20
- /** An approval reaches its approver. */
20
+ /**
21
+ * An approval reaches its approver, and anyone holding the role it is addressed to. A role-addressed
22
+ * step has no approver - only the role - so without the second half nobody hears it. The roles are
23
+ * asked only after the domain matched, as for an instance.
24
+ *
25
+ * Hearing is not being offered: the client refetches its summary, and the summary leaves out what the
26
+ * listener issued (own-request.ts). So the issuer may hear their own step and still not see it.
27
+ */
21
28
  export declare function hearsApproval(approval: {
22
29
  domainId?: string;
23
30
  approverId?: string;
24
- }, domain: DomainLike, userId: string): boolean;
31
+ approverRoleId?: string;
32
+ }, domain: DomainLike, userId: string, rolesOf: () => Promise<{
33
+ id: string;
34
+ }[]>): Promise<boolean>;
25
35
  /**
26
36
  * An instance reaches whoever made it, and anyone holding the role it is waiting for. The roles are
27
37
  * asked only after the domain matched — the lookup is not free and is never needed for another tenant.
@@ -21,9 +21,22 @@ function inDomain(eventDomainId, domain) {
21
21
  function hearsThread(thread, domain, userId) {
22
22
  return inDomain(thread?.domainId, domain) && thread.assigneeId === userId;
23
23
  }
24
- /** An approval reaches its approver. */
25
- function hearsApproval(approval, domain, userId) {
26
- return inDomain(approval?.domainId, domain) && approval.approverId === userId;
24
+ /**
25
+ * An approval reaches its approver, and anyone holding the role it is addressed to. A role-addressed
26
+ * step has no approver - only the role - so without the second half nobody hears it. The roles are
27
+ * asked only after the domain matched, as for an instance.
28
+ *
29
+ * Hearing is not being offered: the client refetches its summary, and the summary leaves out what the
30
+ * listener issued (own-request.ts). So the issuer may hear their own step and still not see it.
31
+ */
32
+ async function hearsApproval(approval, domain, userId, rolesOf) {
33
+ if (!inDomain(approval?.domainId, domain))
34
+ return false;
35
+ if (approval.approverId === userId)
36
+ return true;
37
+ if (!approval.approverRoleId)
38
+ return false;
39
+ return (await rolesOf()).some(role => role.id === approval.approverRoleId);
27
40
  }
28
41
  /**
29
42
  * An instance reaches whoever made it, and anyone holding the role it is waiting for. The roles are
@@ -1 +1 @@
1
- {"version":3,"file":"subscription-filters.js","sourceRoot":"","sources":["../../../server/service/subscription/subscription-filters.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AASH,kCAEC;AAGD,sCAEC;AAMD,sCAWC;AA7BD,SAAS,QAAQ,CAAC,aAAiC,EAAE,MAAkB;IACrE,OAAO,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,aAAa,KAAK,MAAM,CAAC,EAAE,CAAA;AACvE,CAAC;AAED,qDAAqD;AACrD,SAAgB,WAAW,CAAC,MAAkD,EAAE,MAAkB,EAAE,MAAc;IAChH,OAAO,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAA;AAC3E,CAAC;AAED,wCAAwC;AACxC,SAAgB,aAAa,CAAC,QAAoD,EAAE,MAAkB,EAAE,MAAc;IACpH,OAAO,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,QAAQ,CAAC,UAAU,KAAK,MAAM,CAAA;AAC/E,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,aAAa,CACjC,QAA4E,EAC5E,MAAkB,EAClB,MAAc,EACd,OAAwC;IAExC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,QAAQ,CAAC,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAC9C,IAAI,CAAC,QAAQ,CAAC,cAAc;QAAE,OAAO,KAAK,CAAA;IAE1C,OAAO,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,cAAc,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * Who hears which worklist event — the filters behind the three subscriptions, kept free of\n * entities and pubsub so they can be tested.\n *\n * Every filter compares the event's domain to the subscriber's first. A thread or an approval in\n * another tenant must never reach this socket, even when the ids beside it happen to match.\n * An event whose domain is not known is dropped: without it the filter cannot tell tenants apart.\n *\n * The ids are the entity's @RelationId fields. TypeORM fills them on the entity it hands the\n * subscriber, on insert as well as update - measured, and held by tests/published-shape.test.ts.\n */\n\ntype DomainLike = { id?: string } | null | undefined\n\nfunction inDomain(eventDomainId: string | undefined, domain: DomainLike): boolean {\n return !!eventDomainId && !!domain?.id && eventDomainId === domain.id\n}\n\n/** A thread reaches the person it is assigned to. */\nexport function hearsThread(thread: { domainId?: string; assigneeId?: string }, domain: DomainLike, userId: string) {\n return inDomain(thread?.domainId, domain) && thread.assigneeId === userId\n}\n\n/** An approval reaches its approver. */\nexport function hearsApproval(approval: { domainId?: string; approverId?: string }, domain: DomainLike, userId: string) {\n return inDomain(approval?.domainId, domain) && approval.approverId === userId\n}\n\n/**\n * An instance reaches whoever made it, and anyone holding the role it is waiting for. The roles are\n * asked only after the domain matched — the lookup is not free and is never needed for another tenant.\n */\nexport async function hearsInstance(\n instance: { domainId?: string; creatorId?: string; assigneeRoleId?: string },\n domain: DomainLike,\n userId: string,\n rolesOf: () => Promise<{ id: string }[]>\n): Promise<boolean> {\n if (!inDomain(instance?.domainId, domain)) return false\n if (instance.creatorId === userId) return true\n if (!instance.assigneeRoleId) return false\n\n return (await rolesOf()).some(role => role.id === instance.assigneeRoleId)\n}\n"]}
1
+ {"version":3,"file":"subscription-filters.js","sourceRoot":"","sources":["../../../server/service/subscription/subscription-filters.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AASH,kCAEC;AAUD,sCAWC;AAMD,sCAWC;AA7CD,SAAS,QAAQ,CAAC,aAAiC,EAAE,MAAkB;IACrE,OAAO,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,aAAa,KAAK,MAAM,CAAC,EAAE,CAAA;AACvE,CAAC;AAED,qDAAqD;AACrD,SAAgB,WAAW,CAAC,MAAkD,EAAE,MAAkB,EAAE,MAAc;IAChH,OAAO,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAA;AAC3E,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,aAAa,CACjC,QAA6E,EAC7E,MAAkB,EAClB,MAAc,EACd,OAAwC;IAExC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,QAAQ,CAAC,UAAU,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAC/C,IAAI,CAAC,QAAQ,CAAC,cAAc;QAAE,OAAO,KAAK,CAAA;IAE1C,OAAO,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,cAAc,CAAC,CAAA;AAC5E,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,aAAa,CACjC,QAA4E,EAC5E,MAAkB,EAClB,MAAc,EACd,OAAwC;IAExC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,QAAQ,CAAC,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAC9C,IAAI,CAAC,QAAQ,CAAC,cAAc;QAAE,OAAO,KAAK,CAAA;IAE1C,OAAO,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,cAAc,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * Who hears which worklist event — the filters behind the three subscriptions, kept free of\n * entities and pubsub so they can be tested.\n *\n * Every filter compares the event's domain to the subscriber's first. A thread or an approval in\n * another tenant must never reach this socket, even when the ids beside it happen to match.\n * An event whose domain is not known is dropped: without it the filter cannot tell tenants apart.\n *\n * The ids are the entity's @RelationId fields. TypeORM fills them on the entity it hands the\n * subscriber, on insert as well as update - measured, and held by tests/published-shape.test.ts.\n */\n\ntype DomainLike = { id?: string } | null | undefined\n\nfunction inDomain(eventDomainId: string | undefined, domain: DomainLike): boolean {\n return !!eventDomainId && !!domain?.id && eventDomainId === domain.id\n}\n\n/** A thread reaches the person it is assigned to. */\nexport function hearsThread(thread: { domainId?: string; assigneeId?: string }, domain: DomainLike, userId: string) {\n return inDomain(thread?.domainId, domain) && thread.assigneeId === userId\n}\n\n/**\n * An approval reaches its approver, and anyone holding the role it is addressed to. A role-addressed\n * step has no approver - only the role - so without the second half nobody hears it. The roles are\n * asked only after the domain matched, as for an instance.\n *\n * Hearing is not being offered: the client refetches its summary, and the summary leaves out what the\n * listener issued (own-request.ts). So the issuer may hear their own step and still not see it.\n */\nexport async function hearsApproval(\n approval: { domainId?: string; approverId?: string; approverRoleId?: string },\n domain: DomainLike,\n userId: string,\n rolesOf: () => Promise<{ id: string }[]>\n): Promise<boolean> {\n if (!inDomain(approval?.domainId, domain)) return false\n if (approval.approverId === userId) return true\n if (!approval.approverRoleId) return false\n\n return (await rolesOf()).some(role => role.id === approval.approverRoleId)\n}\n\n/**\n * An instance reaches whoever made it, and anyone holding the role it is waiting for. The roles are\n * asked only after the domain matched — the lookup is not free and is never needed for another tenant.\n */\nexport async function hearsInstance(\n instance: { domainId?: string; creatorId?: string; assigneeRoleId?: string },\n domain: DomainLike,\n userId: string,\n rolesOf: () => Promise<{ id: string }[]>\n): Promise<boolean> {\n if (!inDomain(instance?.domainId, domain)) return false\n if (instance.creatorId === userId) return true\n if (!instance.assigneeRoleId) return false\n\n return (await rolesOf()).some(role => role.id === instance.assigneeRoleId)\n}\n"]}
@@ -7,7 +7,7 @@ const shell_1 = require("@things-factory/shell");
7
7
  const LINE_REFUSAL_ENGLISH = {
8
8
  'approval-line-missing': 'this form has no approval line. set who approves it.',
9
9
  'approval-role-empty': 'nobody holds the role {role} named in the approval line. add a person to that role.',
10
- 'approval-person-absent': 'the person named in the approval line is not in this plant. name someone else in the approval line.'
10
+ 'approval-person-absent': 'the person named in the approval line is not in this site. name someone else in the approval line.'
11
11
  };
12
12
  /** A line that does not stand, as a refusal. The params are the one thing the code points at. */
13
13
  function approvalLineRefusal(refusal) {
@@ -1 +1 @@
1
- {"version":3,"file":"approval-line-standing.js","sourceRoot":"","sources":["../../../server/service/work-door/approval-line-standing.ts"],"names":[],"mappings":";;AAyFA,kDAIC;AAcD,8CA2BC;AAGD,oCAKC;AA9ID,iDAA+C;AAkF/C,MAAM,oBAAoB,GAAwC;IAChE,uBAAuB,EAAE,sDAAsD;IAC/E,qBAAqB,EAAE,qFAAqF;IAC5G,wBAAwB,EAAE,qGAAqG;CAChI,CAAA;AAED,iGAAiG;AACjG,SAAgB,mBAAmB,CAAC,OAAqD;IACvF,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAA;IAEjG,OAAO,IAAI,eAAO,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;AACnH,CAAC;AAUD,MAAM,SAAS,GAAG,WAAW,CAAA;AAC7B,MAAM,IAAI,GAAG,MAAM,CAAA;AACnB,MAAM,MAAM,GAAG,UAAU,CAAA;AAElB,KAAK,UAAU,iBAAiB,CACrC,IAAuC,EACvC,MAA0B;IAE1B,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,CAAC,CAAA;IAE5E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,EAAE,CAAA;IACrE,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC9D,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,CAAA;YACvG,CAAC;YAED,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC5D,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,CAAA;YAC9G,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;AACpC,CAAC;AAED,wDAAwD;AACxD,SAAgB,YAAY,CAAC,IAAuC;IAClE,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;SAChB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;SACxE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;SACxB,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC1C,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\n/**\n * 결재선이 **차 있나** — 순수 판단. DB 도 TypeORM 도 모른다.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 「있다」와 「찼다」가 다르다 ───────────────────────────────────────────\n * 앞서는 `approvalLine?.length` 만 셌다. 그러면 **역할을 지목해 놓고 그 역할에 사람이 하나도 없는**\n * 결재선이 「찼다」로 읽힌다. 문 현황 화면은 「이 문은 섰습니다」라고 그리고, 현장이 상신을 누르는\n * 순간 워크리스트가 `approver is not set` 으로 던진다 — **준비됐다고 말해 놓고 쓸 때 터지는** 부류다.\n *\n * 실 테넌트에서 이것이 예외가 아니다. 미라텍에는 이 제품이 세운 역할이 하나도 없다\n * (§`project_mes_roles_locked_behind_demo`). 역할을 지목한 결재선은 거기서 전부 빈 자리를 가리킨다.\n *\n * ── 불을 돌려주지 않는다. 그리고 **union 으로도 안 돌려준다** ────────────\n * 막은 쪽이 이유를 말한다. `true`/`false` 하나면 화면이 「왜 안 되나」를 **다시 세게 되고**, 그러면\n * 정의가 두 곳이 된다 — 이 판단을 한 곳으로 모은 이유가 없어진다.\n *\n * 그래서 처음에 `{ok:true} | {ok:false, code}` 로 두었는데, 이 패키지의 `server/tsconfig.json` 은\n * **`strict: false`** 라 `strictNullChecks` 가 없고, 그러면 TS 가 **boolean 판별자로 union 을 안\n * 좁힌다.** `if (r.ok) return` 뒤에서 `r.code` 를 읽으면 컴파일이 깨진다 — 하루에 소비자 둘이\n * 연달아 걸렸고 그중 하나는 서버를 아예 못 뜨게 했다.\n *\n * 그래서 **한 객체에 선택 칸**이다. `if (standing.refusal)` 하나로 어디서나 읽힌다.\n * [합의했다] ADR-0047 ⑤(개정)\n *\n * ```\n * approval-line-missing 결재선 자체가 없다 누가 결재하는지를 정해야 한다\n * approval-role-empty 역할은 있는데 사람이 없다 그 역할에 사람을 넣어야 한다\n * approval-person-absent 지목한 사람이 여기 없다 다른 사람을 지목하거나 그 사람을 넣어야 한다\n * ```\n *\n * 관리자가 할 일이 다르므로 코드가 셋이다.\n *\n * ── 참조는 세지 않는다 ───────────────────────────────────────────────────\n * `participation: 'reference'` 는 의견만 내고 결재를 막지 않는다. 참조자 역할이 비었다고 거절하면\n * **결재와 무관한 이유로** 막는 것이 된다. 참조만 있는 결재선은 결재 없이 끝나므로 「없다」로 읽는다.\n *\n * ── 읽는 것은 부르는 쪽이 넘긴다 ─────────────────────────────────────────\n * 포트 하나를 받는다. 이 판단이 `manager` 를 받으면 TypeORM 을 알게 되어 순수 시험이 서지 않고,\n * **어느 도메인으로 좁혔나**가 부르는 쪽에서 안 보이게 된다. 포트의 수명은 **판정 하나**다 — 더\n * 오래 살면 역할에 사람을 넣은 직후에도 옛 답을 내놓는다.\n *\n * ── 사람을 직접 지목한 자리도 읽는다 ─────────────────────────────────────\n * 「이 제품은 역할만 쓴다」로 닫지 않는다. 그렇게 두면 **없는 사람을 지목한 결재선이 「찼다」로\n * 그려지고 상신 순간 터진다** — 역할에서 고친 그 모양이 사람 축으로 다시 나는 것이고, 「없음」이\n * 안전하지 않은 쪽으로 기우는 것이다. 게다가 이 정의는 워크리스트의 `ApprovalLineItem` 을 읽는\n * 공용 자리라, 다른 제품이 사람을 지목하는 날 조용히 틀린다.\n *\n * `Myself` · `MyDepartment` · `MySupervisor` 는 **상신할 때 그 사람으로 풀리는** 자리라 미리 셀 것이\n * 없다. 부서 지목(`Department`)은 부서장이 있어야 풀리는데 그 코드는 아직 없다 — 지어내지 않는다.\n *\n * [합의했다] operato-application/adr/ADR-0047.md ③⑤\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** 결재선 한 자리 — `ApprovalLineItem` 중 이 판단이 보는 칸만. */\nexport interface ApprovalSeat {\n /** `OrgMemberTargetType` — 역할이면 `'Role'`. */\n type?: string\n /** 역할이면 role id, 사람이면 user id. */\n value?: string\n /** 비어 있으면 결재다(`ApprovalLineItem.participation` 의 규약). */\n participation?: string\n}\n\nexport type ApprovalLineRefusal = 'approval-line-missing' | 'approval-role-empty' | 'approval-person-absent'\n\nexport interface ApprovalLineStanding {\n /** 이 결재선이 지나는 자리 수. 안 서는 결재선이면 0. */\n approvers: number\n /**\n * 왜 안 서나 — **있으면 안 서는 것이다.** 없으면 선다.\n *\n * `params` 는 그 코드가 가리키는 것 하나다: 역할이 비었으면 그 역할, 사람이 없으면 그 사람.\n */\n refusal?: {\n code: ApprovalLineRefusal\n params?: { role?: string; personId?: string }\n }\n}\n\nconst LINE_REFUSAL_ENGLISH: Record<ApprovalLineRefusal, string> = {\n 'approval-line-missing': 'this form has no approval line. set who approves it.',\n 'approval-role-empty': 'nobody holds the role {role} named in the approval line. add a person to that role.',\n 'approval-person-absent': 'the person named in the approval line is not in this plant. name someone else in the approval line.'\n}\n\n/** A line that does not stand, as a refusal. The params are the one thing the code points at. */\nexport function approvalLineRefusal(refusal: NonNullable<ApprovalLineStanding['refusal']>): Refusal {\n const params = { role: refusal.params?.role ?? null, personId: refusal.params?.personId ?? null }\n\n return new Refusal(refusal.code, params, LINE_REFUSAL_ENGLISH[refusal.code].replace('{role}', params.role ?? ''))\n}\n\n/** 결재선의 자리가 실제로 사람으로 풀리나 — 읽는 것은 부르는 쪽 일이다. */\nexport interface ApprovalSeatReader {\n /** 그 역할에 사람이 하나 이상 있나. */\n roleHasMembers(roleId: string): Promise<boolean>\n /** 지목한 그 사람이 이 공장에 있고 계정이 붙어 있나. */\n personIsHere(personId: string): Promise<boolean>\n}\n\nconst REFERENCE = 'reference'\nconst ROLE = 'Role'\nconst PERSON = 'Employee'\n\nexport async function judgeApprovalLine(\n line: ApprovalSeat[] | undefined | null,\n reader: ApprovalSeatReader\n): Promise<ApprovalLineStanding> {\n const seats = (line ?? []).filter(seat => seat?.participation !== REFERENCE)\n\n if (!seats.length) {\n return { approvers: 0, refusal: { code: 'approval-line-missing' } }\n }\n\n for (const seat of seats) {\n if (seat?.type === ROLE) {\n if (!seat.value || !(await reader.roleHasMembers(seat.value))) {\n return { approvers: 0, refusal: { code: 'approval-role-empty', params: { role: seat.value ?? '' } } }\n }\n\n continue\n }\n\n if (seat?.type === PERSON) {\n if (!seat.value || !(await reader.personIsHere(seat.value))) {\n return { approvers: 0, refusal: { code: 'approval-person-absent', params: { personId: seat.value ?? '' } } }\n }\n }\n }\n\n return { approvers: seats.length }\n}\n\n/** 결재선에서 **역할을 지목한 자리**의 id 들 — 부르는 쪽이 미리 읽어 둘 때 쓴다. */\nexport function rolesNamedIn(line: ApprovalSeat[] | undefined | null): string[] {\n return (line ?? [])\n .filter(seat => seat?.participation !== REFERENCE && seat?.type === ROLE)\n .map(seat => seat?.value)\n .filter((one): one is string => !!one)\n}\n"]}
1
+ {"version":3,"file":"approval-line-standing.js","sourceRoot":"","sources":["../../../server/service/work-door/approval-line-standing.ts"],"names":[],"mappings":";;AAyFA,kDAIC;AAcD,8CA2BC;AAGD,oCAKC;AA9ID,iDAA+C;AAkF/C,MAAM,oBAAoB,GAAwC;IAChE,uBAAuB,EAAE,sDAAsD;IAC/E,qBAAqB,EAAE,qFAAqF;IAC5G,wBAAwB,EAAE,oGAAoG;CAC/H,CAAA;AAED,iGAAiG;AACjG,SAAgB,mBAAmB,CAAC,OAAqD;IACvF,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAA;IAEjG,OAAO,IAAI,eAAO,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;AACnH,CAAC;AAUD,MAAM,SAAS,GAAG,WAAW,CAAA;AAC7B,MAAM,IAAI,GAAG,MAAM,CAAA;AACnB,MAAM,MAAM,GAAG,UAAU,CAAA;AAElB,KAAK,UAAU,iBAAiB,CACrC,IAAuC,EACvC,MAA0B;IAE1B,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,CAAC,CAAA;IAE5E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,EAAE,CAAA;IACrE,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC9D,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,CAAA;YACvG,CAAC;YAED,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC5D,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,CAAA;YAC9G,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;AACpC,CAAC;AAED,wDAAwD;AACxD,SAAgB,YAAY,CAAC,IAAuC;IAClE,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;SAChB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;SACxE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;SACxB,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC1C,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\n/**\n * 결재선이 **차 있나** — 순수 판단. DB 도 TypeORM 도 모른다.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 「있다」와 「찼다」가 다르다 ───────────────────────────────────────────\n * 앞서는 `approvalLine?.length` 만 셌다. 그러면 **역할을 지목해 놓고 그 역할에 사람이 하나도 없는**\n * 결재선이 「찼다」로 읽힌다. 문 현황 화면은 「이 문은 섰습니다」라고 그리고, 현장이 상신을 누르는\n * 순간 워크리스트가 `approver is not set` 으로 던진다 — **준비됐다고 말해 놓고 쓸 때 터지는** 부류다.\n *\n * 실 테넌트에서 이것이 예외가 아니다. 미라텍에는 이 제품이 세운 역할이 하나도 없다\n * (§`project_mes_roles_locked_behind_demo`). 역할을 지목한 결재선은 거기서 전부 빈 자리를 가리킨다.\n *\n * ── 불을 돌려주지 않는다. 그리고 **union 으로도 안 돌려준다** ────────────\n * 막은 쪽이 이유를 말한다. `true`/`false` 하나면 화면이 「왜 안 되나」를 **다시 세게 되고**, 그러면\n * 정의가 두 곳이 된다 — 이 판단을 한 곳으로 모은 이유가 없어진다.\n *\n * 그래서 처음에 `{ok:true} | {ok:false, code}` 로 두었는데, 이 패키지의 `server/tsconfig.json` 은\n * **`strict: false`** 라 `strictNullChecks` 가 없고, 그러면 TS 가 **boolean 판별자로 union 을 안\n * 좁힌다.** `if (r.ok) return` 뒤에서 `r.code` 를 읽으면 컴파일이 깨진다 — 하루에 소비자 둘이\n * 연달아 걸렸고 그중 하나는 서버를 아예 못 뜨게 했다.\n *\n * 그래서 **한 객체에 선택 칸**이다. `if (standing.refusal)` 하나로 어디서나 읽힌다.\n * [합의했다] ADR-0047 ⑤(개정)\n *\n * ```\n * approval-line-missing 결재선 자체가 없다 누가 결재하는지를 정해야 한다\n * approval-role-empty 역할은 있는데 사람이 없다 그 역할에 사람을 넣어야 한다\n * approval-person-absent 지목한 사람이 여기 없다 다른 사람을 지목하거나 그 사람을 넣어야 한다\n * ```\n *\n * 관리자가 할 일이 다르므로 코드가 셋이다.\n *\n * ── 참조는 세지 않는다 ───────────────────────────────────────────────────\n * `participation: 'reference'` 는 의견만 내고 결재를 막지 않는다. 참조자 역할이 비었다고 거절하면\n * **결재와 무관한 이유로** 막는 것이 된다. 참조만 있는 결재선은 결재 없이 끝나므로 「없다」로 읽는다.\n *\n * ── 읽는 것은 부르는 쪽이 넘긴다 ─────────────────────────────────────────\n * 포트 하나를 받는다. 이 판단이 `manager` 를 받으면 TypeORM 을 알게 되어 순수 시험이 서지 않고,\n * **어느 도메인으로 좁혔나**가 부르는 쪽에서 안 보이게 된다. 포트의 수명은 **판정 하나**다 — 더\n * 오래 살면 역할에 사람을 넣은 직후에도 옛 답을 내놓는다.\n *\n * ── 사람을 직접 지목한 자리도 읽는다 ─────────────────────────────────────\n * 「이 제품은 역할만 쓴다」로 닫지 않는다. 그렇게 두면 **없는 사람을 지목한 결재선이 「찼다」로\n * 그려지고 상신 순간 터진다** — 역할에서 고친 그 모양이 사람 축으로 다시 나는 것이고, 「없음」이\n * 안전하지 않은 쪽으로 기우는 것이다. 게다가 이 정의는 워크리스트의 `ApprovalLineItem` 을 읽는\n * 공용 자리라, 다른 제품이 사람을 지목하는 날 조용히 틀린다.\n *\n * `Myself` · `MyDepartment` · `MySupervisor` 는 **상신할 때 그 사람으로 풀리는** 자리라 미리 셀 것이\n * 없다. 부서 지목(`Department`)은 부서장이 있어야 풀리는데 그 코드는 아직 없다 — 지어내지 않는다.\n *\n * [합의했다] operato-application/adr/ADR-0047.md ③⑤\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** 결재선 한 자리 — `ApprovalLineItem` 중 이 판단이 보는 칸만. */\nexport interface ApprovalSeat {\n /** `OrgMemberTargetType` — 역할이면 `'Role'`. */\n type?: string\n /** 역할이면 role id, 사람이면 user id. */\n value?: string\n /** 비어 있으면 결재다(`ApprovalLineItem.participation` 의 규약). */\n participation?: string\n}\n\nexport type ApprovalLineRefusal = 'approval-line-missing' | 'approval-role-empty' | 'approval-person-absent'\n\nexport interface ApprovalLineStanding {\n /** 이 결재선이 지나는 자리 수. 안 서는 결재선이면 0. */\n approvers: number\n /**\n * 왜 안 서나 — **있으면 안 서는 것이다.** 없으면 선다.\n *\n * `params` 는 그 코드가 가리키는 것 하나다: 역할이 비었으면 그 역할, 사람이 없으면 그 사람.\n */\n refusal?: {\n code: ApprovalLineRefusal\n params?: { role?: string; personId?: string }\n }\n}\n\nconst LINE_REFUSAL_ENGLISH: Record<ApprovalLineRefusal, string> = {\n 'approval-line-missing': 'this form has no approval line. set who approves it.',\n 'approval-role-empty': 'nobody holds the role {role} named in the approval line. add a person to that role.',\n 'approval-person-absent': 'the person named in the approval line is not in this site. name someone else in the approval line.'\n}\n\n/** A line that does not stand, as a refusal. The params are the one thing the code points at. */\nexport function approvalLineRefusal(refusal: NonNullable<ApprovalLineStanding['refusal']>): Refusal {\n const params = { role: refusal.params?.role ?? null, personId: refusal.params?.personId ?? null }\n\n return new Refusal(refusal.code, params, LINE_REFUSAL_ENGLISH[refusal.code].replace('{role}', params.role ?? ''))\n}\n\n/** 결재선의 자리가 실제로 사람으로 풀리나 — 읽는 것은 부르는 쪽 일이다. */\nexport interface ApprovalSeatReader {\n /** 그 역할에 사람이 하나 이상 있나. */\n roleHasMembers(roleId: string): Promise<boolean>\n /** 지목한 그 사람이 이 공장에 있고 계정이 붙어 있나. */\n personIsHere(personId: string): Promise<boolean>\n}\n\nconst REFERENCE = 'reference'\nconst ROLE = 'Role'\nconst PERSON = 'Employee'\n\nexport async function judgeApprovalLine(\n line: ApprovalSeat[] | undefined | null,\n reader: ApprovalSeatReader\n): Promise<ApprovalLineStanding> {\n const seats = (line ?? []).filter(seat => seat?.participation !== REFERENCE)\n\n if (!seats.length) {\n return { approvers: 0, refusal: { code: 'approval-line-missing' } }\n }\n\n for (const seat of seats) {\n if (seat?.type === ROLE) {\n if (!seat.value || !(await reader.roleHasMembers(seat.value))) {\n return { approvers: 0, refusal: { code: 'approval-role-empty', params: { role: seat.value ?? '' } } }\n }\n\n continue\n }\n\n if (seat?.type === PERSON) {\n if (!seat.value || !(await reader.personIsHere(seat.value))) {\n return { approvers: 0, refusal: { code: 'approval-person-absent', params: { personId: seat.value ?? '' } } }\n }\n }\n }\n\n return { approvers: seats.length }\n}\n\n/** 결재선에서 **역할을 지목한 자리**의 id 들 — 부르는 쪽이 미리 읽어 둘 때 쓴다. */\nexport function rolesNamedIn(line: ApprovalSeat[] | undefined | null): string[] {\n return (line ?? [])\n .filter(seat => seat?.participation !== REFERENCE && seat?.type === ROLE)\n .map(seat => seat?.value)\n .filter((one): one is string => !!one)\n}\n"]}