@things-factory/saas 0.0.0-stage → 10.1.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +19 -2
  2. package/dist-client/bootstrap.d.ts +1 -0
  3. package/dist-client/bootstrap.js +5 -0
  4. package/dist-client/bootstrap.js.map +1 -0
  5. package/dist-client/components/saas-bill-editor.d.ts +59 -0
  6. package/dist-client/components/saas-bill-editor.js +211 -0
  7. package/dist-client/components/saas-bill-editor.js.map +1 -0
  8. package/dist-client/entity360/saas-account-360.d.ts +12 -0
  9. package/dist-client/entity360/saas-account-360.js +231 -0
  10. package/dist-client/entity360/saas-account-360.js.map +1 -0
  11. package/dist-client/index.d.ts +3 -0
  12. package/dist-client/index.js +4 -0
  13. package/dist-client/index.js.map +1 -0
  14. package/dist-client/pages/saas-account-list.d.ts +57 -0
  15. package/dist-client/pages/saas-account-list.js +175 -0
  16. package/dist-client/pages/saas-account-list.js.map +1 -0
  17. package/dist-client/pages/saas-account-page.d.ts +54 -0
  18. package/dist-client/pages/saas-account-page.js +352 -0
  19. package/dist-client/pages/saas-account-page.js.map +1 -0
  20. package/dist-client/pages/saas-cost-trend.d.ts +23 -0
  21. package/dist-client/pages/saas-cost-trend.js +219 -0
  22. package/dist-client/pages/saas-cost-trend.js.map +1 -0
  23. package/dist-client/route.d.ts +1 -0
  24. package/dist-client/route.js +16 -0
  25. package/dist-client/route.js.map +1 -0
  26. package/dist-client/tsconfig.tsbuildinfo +1 -0
  27. package/dist-client/words.d.ts +47 -0
  28. package/dist-client/words.js +39 -0
  29. package/dist-client/words.js.map +1 -0
  30. package/dist-server/index.d.ts +1 -0
  31. package/dist-server/index.js +9 -0
  32. package/dist-server/index.js.map +1 -0
  33. package/dist-server/role-tasks.d.ts +2 -0
  34. package/dist-server/role-tasks.js +16 -0
  35. package/dist-server/role-tasks.js.map +1 -0
  36. package/dist-server/service/checks.d.ts +32 -0
  37. package/dist-server/service/checks.js +83 -0
  38. package/dist-server/service/checks.js.map +1 -0
  39. package/dist-server/service/index.d.ts +20 -0
  40. package/dist-server/service/index.js +38 -0
  41. package/dist-server/service/index.js.map +1 -0
  42. package/dist-server/service/saas-access.d.ts +20 -0
  43. package/dist-server/service/saas-access.js +36 -0
  44. package/dist-server/service/saas-access.js.map +1 -0
  45. package/dist-server/service/saas-account.d.ts +43 -0
  46. package/dist-server/service/saas-account.js +166 -0
  47. package/dist-server/service/saas-account.js.map +1 -0
  48. package/dist-server/service/saas-bill.d.ts +41 -0
  49. package/dist-server/service/saas-bill.js +160 -0
  50. package/dist-server/service/saas-bill.js.map +1 -0
  51. package/dist-server/service/saas-mutation.d.ts +12 -0
  52. package/dist-server/service/saas-mutation.js +103 -0
  53. package/dist-server/service/saas-mutation.js.map +1 -0
  54. package/dist-server/service/saas-query.d.ts +69 -0
  55. package/dist-server/service/saas-query.js +215 -0
  56. package/dist-server/service/saas-query.js.map +1 -0
  57. package/dist-server/service/saas-rules.d.ts +138 -0
  58. package/dist-server/service/saas-rules.js +217 -0
  59. package/dist-server/service/saas-rules.js.map +1 -0
  60. package/dist-server/service/saas-seat.d.ts +25 -0
  61. package/dist-server/service/saas-seat.js +105 -0
  62. package/dist-server/service/saas-seat.js.map +1 -0
  63. package/dist-server/service/saas-service.d.ts +119 -0
  64. package/dist-server/service/saas-service.js +492 -0
  65. package/dist-server/service/saas-service.js.map +1 -0
  66. package/dist-server/service/saas-subscription.d.ts +44 -0
  67. package/dist-server/service/saas-subscription.js +174 -0
  68. package/dist-server/service/saas-subscription.js.map +1 -0
  69. package/dist-server/service/saas-trend-type.d.ts +9 -0
  70. package/dist-server/service/saas-trend-type.js +37 -0
  71. package/dist-server/service/saas-trend-type.js.map +1 -0
  72. package/dist-server/service/saas-type.d.ts +113 -0
  73. package/dist-server/service/saas-type.js +430 -0
  74. package/dist-server/service/saas-type.js.map +1 -0
  75. package/dist-server/tsconfig.tsbuildinfo +1 -0
  76. package/package.json +49 -4
  77. package/things-factory.config.js +13 -0
  78. package/translations/en.json +185 -0
  79. package/translations/es.json +185 -0
  80. package/translations/ja.json +185 -0
  81. package/translations/ko.json +185 -0
  82. package/translations/ms.json +185 -0
  83. package/translations/zh.json +185 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-tasks.js","sourceRoot":"","sources":["../server/role-tasks.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAIU,QAAA,UAAU,GAAe;IACpC,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACnH,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC3I,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;CAC/F,CAAA","sourcesContent":["/*\n * The work this module names, for the seats applications build (ADR-0139 decision 11, ADR-0177 decision 8).\n *\n * `saas.administering` is for every seat: what it opens is only the accounts the signed-in person is named an admin of\n * (the service scopes it). Data only - `server/index.ts` registers it - so an application's tests read it without\n * starting the framework.\n */\n\nimport type { RoleTask } from '@things-factory/auth-base'\n\nexport const ROLE_TASKS: RoleTask[] = [\n { id: 'saas.reading', grants: [{ category: 'saas', axes: ['query'] }, { category: 'saas-self', axes: ['query'] }] },\n { id: 'saas.keeping', grants: [{ category: 'saas', axes: ['query', 'mutation'] }, { category: 'saas-self', axes: ['query', 'mutation'] }] },\n { id: 'saas.administering', grants: [{ category: 'saas-self', axes: ['query', 'mutation'] }] }\n]\n"]}
@@ -0,0 +1,32 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import { EntityManager } from 'typeorm';
3
+ import { SaasAccount } from './saas-account';
4
+ import { SaasSeat } from './saas-seat';
5
+ import { SaasSubscription } from './saas-subscription';
6
+ /** Subscriptions in use whose renewal is near - from their notice days before it to the day (decision 7). */
7
+ export declare function saasRenewalsDue(tx: EntityManager, domain: Domain, today: string): Promise<{
8
+ subscription: SaasSubscription;
9
+ account: SaasAccount;
10
+ }[]>;
11
+ /** Seats still held by people who have retired. */
12
+ export declare function saasSeatsOfLeavers(tx: EntityManager, domain: Domain, today: string): Promise<{
13
+ seat: SaasSeat;
14
+ subscription: SaasSubscription;
15
+ account: SaasAccount;
16
+ }[]>;
17
+ /** Accounts in use with nobody left to administer them - no admin named, or every admin retired. */
18
+ export declare function saasAccountsWithoutAdmin(tx: EntityManager, domain: Domain, today: string): Promise<SaasAccount[]>;
19
+ /** Seat plans in use whose seats held today are not as many as they are billed for. */
20
+ export declare function saasSeatCountsDiffer(tx: EntityManager, domain: Domain, today: string): Promise<{
21
+ subscription: SaasSubscription;
22
+ account: SaasAccount;
23
+ held: number;
24
+ }[]>;
25
+ /**
26
+ * Accounts in use that were to be billed last month and have no bill for it (decision 9) - the trend holds only while the
27
+ * bills are written every month.
28
+ */
29
+ export declare function saasBillsMissing(tx: EntityManager, domain: Domain, today: string): Promise<{
30
+ account: SaasAccount;
31
+ month: string;
32
+ }[]>;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.saasRenewalsDue = saasRenewalsDue;
4
+ exports.saasSeatsOfLeavers = saasSeatsOfLeavers;
5
+ exports.saasAccountsWithoutAdmin = saasAccountsWithoutAdmin;
6
+ exports.saasSeatCountsDiffer = saasSeatCountsDiffer;
7
+ exports.saasBillsMissing = saasBillsMissing;
8
+ const organization_1 = require("@things-factory/organization");
9
+ const typeorm_1 = require("typeorm");
10
+ const saas_account_1 = require("./saas-account");
11
+ const saas_bill_1 = require("./saas-bill");
12
+ const saas_rules_1 = require("./saas-rules");
13
+ const saas_seat_1 = require("./saas-seat");
14
+ const saas_subscription_1 = require("./saas-subscription");
15
+ /*
16
+ * The subscriptions' part of an application's management check (ADR-0177 decisions 6, 7, 9): judged here, counted there.
17
+ * Each takes the day it judges on.
18
+ */
19
+ /** The employees who have retired by a day. */
20
+ async function retiredOf(tx, domain, ids, today) {
21
+ if (!ids.length)
22
+ return new Set();
23
+ const employees = await tx.getRepository(organization_1.Employee).find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(ids) } });
24
+ return new Set(employees.filter(one => one.retiredOn && one.retiredOn <= today).map(one => one.id));
25
+ }
26
+ async function inUse(tx, domain) {
27
+ const accounts = await tx.getRepository(saas_account_1.SaasAccount).find({ where: { domain: { id: domain.id }, status: saas_rules_1.SaasAccountStatus.ACTIVE }, relations: ['admins'] });
28
+ const subs = accounts.length
29
+ ? await tx.getRepository(saas_subscription_1.SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: (0, typeorm_1.In)(accounts.map(one => one.id)) }, status: (0, typeorm_1.In)([saas_rules_1.SaasSubscriptionStatus.ACTIVE, saas_rules_1.SaasSubscriptionStatus.CANCELLING]) } })
30
+ : [];
31
+ return { accounts, subs };
32
+ }
33
+ /** Subscriptions in use whose renewal is near - from their notice days before it to the day (decision 7). */
34
+ async function saasRenewalsDue(tx, domain, today) {
35
+ const { accounts, subs } = await inUse(tx, domain);
36
+ return subs.filter(sub => (0, saas_rules_1.renewalDue)(sub, today)).map(sub => ({ subscription: sub, account: accounts.find(one => one.id === sub.accountId) }));
37
+ }
38
+ /** Seats still held by people who have retired. */
39
+ async function saasSeatsOfLeavers(tx, domain, today) {
40
+ const { accounts, subs } = await inUse(tx, domain);
41
+ if (!subs.length)
42
+ return [];
43
+ const seats = (await tx.getRepository(saas_seat_1.SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: (0, typeorm_1.In)(subs.map(one => one.id)) } }, relations: ['employee'] })).filter(seat => seat.employeeId && (0, saas_rules_1.seatHeld)(seat, today));
44
+ const retired = await retiredOf(tx, domain, [...new Set(seats.map(one => one.employeeId))], today);
45
+ return seats
46
+ .filter(seat => retired.has(seat.employeeId))
47
+ .map(seat => {
48
+ const subscription = subs.find(one => one.id === seat.subscriptionId);
49
+ return { seat, subscription, account: accounts.find(one => one.id === subscription.accountId) };
50
+ });
51
+ }
52
+ /** Accounts in use with nobody left to administer them - no admin named, or every admin retired. */
53
+ async function saasAccountsWithoutAdmin(tx, domain, today) {
54
+ const { accounts } = await inUse(tx, domain);
55
+ const retired = await retiredOf(tx, domain, [...new Set(accounts.flatMap(one => (one.admins ?? []).map(admin => admin.id)))], today);
56
+ return accounts.filter(account => !(account.admins ?? []).some(admin => !retired.has(admin.id)));
57
+ }
58
+ /** Seat plans in use whose seats held today are not as many as they are billed for. */
59
+ async function saasSeatCountsDiffer(tx, domain, today) {
60
+ const { accounts, subs } = await inUse(tx, domain);
61
+ const plans = subs.filter(one => one.billing === saas_rules_1.SaasBilling.SEAT && one.startOn <= today);
62
+ if (!plans.length)
63
+ return [];
64
+ const seats = await tx.getRepository(saas_seat_1.SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: (0, typeorm_1.In)(plans.map(one => one.id)) } } });
65
+ return plans
66
+ .map(sub => ({ subscription: sub, account: accounts.find(one => one.id === sub.accountId), held: seats.filter(seat => seat.subscriptionId === sub.id && (0, saas_rules_1.seatHeld)(seat, today)).length }))
67
+ .filter(one => one.held !== Number(one.subscription.quantity ?? 0));
68
+ }
69
+ /**
70
+ * Accounts in use that were to be billed last month and have no bill for it (decision 9) - the trend holds only while the
71
+ * bills are written every month.
72
+ */
73
+ async function saasBillsMissing(tx, domain, today) {
74
+ const { accounts, subs } = await inUse(tx, domain);
75
+ const month = (0, saas_rules_1.shiftMonth)(today.slice(0, 7), -1);
76
+ const [year, m] = month.split('-').map(Number);
77
+ const due = accounts.filter(account => !account.parentId && subs.some(sub => sub.accountId === account.id && sub.billing !== saas_rules_1.SaasBilling.OWNED && (0, saas_rules_1.billedIn)(sub, year, m, account.billingDay)));
78
+ if (!due.length)
79
+ return [];
80
+ const billed = new Set((await tx.getRepository(saas_bill_1.SaasBill).find({ where: { domain: { id: domain.id }, month, account: { id: (0, typeorm_1.In)(due.map(one => one.id)) } } })).map(one => one.accountId));
81
+ return due.filter(one => !billed.has(one.id)).map(account => ({ account, month }));
82
+ }
83
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../server/service/checks.ts"],"names":[],"mappings":"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","sourcesContent":["import { Employee } from '@things-factory/organization'\nimport { Domain } from '@things-factory/shell'\nimport { EntityManager, In } from 'typeorm'\n\nimport { SaasAccount } from './saas-account'\nimport { SaasBill } from './saas-bill'\nimport { billedIn, renewalDue, SaasAccountStatus, SaasBilling, SaasSubscriptionStatus, seatHeld, shiftMonth } from './saas-rules'\nimport { SaasSeat } from './saas-seat'\nimport { SaasSubscription } from './saas-subscription'\n\n/*\n * The subscriptions' part of an application's management check (ADR-0177 decisions 6, 7, 9): judged here, counted there.\n * Each takes the day it judges on.\n */\n\n/** The employees who have retired by a day. */\nasync function retiredOf(tx: EntityManager, domain: Domain, ids: string[], today: string): Promise<Set<string>> {\n if (!ids.length) return new Set()\n const employees = await tx.getRepository(Employee).find({ where: { domain: { id: domain.id }, id: In(ids) } as any })\n return new Set(employees.filter(one => one.retiredOn && one.retiredOn <= today).map(one => one.id))\n}\n\nasync function inUse(tx: EntityManager, domain: Domain) {\n const accounts = await tx.getRepository(SaasAccount).find({ where: { domain: { id: domain.id }, status: SaasAccountStatus.ACTIVE } as any, relations: ['admins'] })\n const subs = accounts.length\n ? await tx.getRepository(SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: In(accounts.map(one => one.id)) }, status: In([SaasSubscriptionStatus.ACTIVE, SaasSubscriptionStatus.CANCELLING]) } as any })\n : []\n return { accounts, subs }\n}\n\n/** Subscriptions in use whose renewal is near - from their notice days before it to the day (decision 7). */\nexport async function saasRenewalsDue(tx: EntityManager, domain: Domain, today: string) {\n const { accounts, subs } = await inUse(tx, domain)\n return subs.filter(sub => renewalDue(sub, today)).map(sub => ({ subscription: sub, account: accounts.find(one => one.id === sub.accountId)! }))\n}\n\n/** Seats still held by people who have retired. */\nexport async function saasSeatsOfLeavers(tx: EntityManager, domain: Domain, today: string) {\n const { accounts, subs } = await inUse(tx, domain)\n if (!subs.length) return []\n const seats = (await tx.getRepository(SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: In(subs.map(one => one.id)) } } as any, relations: ['employee'] })).filter(\n seat => seat.employeeId && seatHeld(seat, today)\n )\n const retired = await retiredOf(tx, domain, [...new Set(seats.map(one => one.employeeId!))], today)\n return seats\n .filter(seat => retired.has(seat.employeeId!))\n .map(seat => {\n const subscription = subs.find(one => one.id === seat.subscriptionId)!\n return { seat, subscription, account: accounts.find(one => one.id === subscription.accountId)! }\n })\n}\n\n/** Accounts in use with nobody left to administer them - no admin named, or every admin retired. */\nexport async function saasAccountsWithoutAdmin(tx: EntityManager, domain: Domain, today: string) {\n const { accounts } = await inUse(tx, domain)\n const retired = await retiredOf(tx, domain, [...new Set(accounts.flatMap(one => (one.admins ?? []).map(admin => admin.id)))], today)\n return accounts.filter(account => !(account.admins ?? []).some(admin => !retired.has(admin.id)))\n}\n\n/** Seat plans in use whose seats held today are not as many as they are billed for. */\nexport async function saasSeatCountsDiffer(tx: EntityManager, domain: Domain, today: string) {\n const { accounts, subs } = await inUse(tx, domain)\n const plans = subs.filter(one => one.billing === SaasBilling.SEAT && one.startOn <= today)\n if (!plans.length) return []\n const seats = await tx.getRepository(SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: In(plans.map(one => one.id)) } } as any })\n return plans\n .map(sub => ({ subscription: sub, account: accounts.find(one => one.id === sub.accountId)!, held: seats.filter(seat => seat.subscriptionId === sub.id && seatHeld(seat, today)).length }))\n .filter(one => one.held !== Number(one.subscription.quantity ?? 0))\n}\n\n/**\n * Accounts in use that were to be billed last month and have no bill for it (decision 9) - the trend holds only while the\n * bills are written every month.\n */\nexport async function saasBillsMissing(tx: EntityManager, domain: Domain, today: string) {\n const { accounts, subs } = await inUse(tx, domain)\n const month = shiftMonth(today.slice(0, 7), -1)\n const [year, m] = month.split('-').map(Number)\n const due = accounts.filter(account => !account.parentId && subs.some(sub => sub.accountId === account.id && sub.billing !== SaasBilling.OWNED && billedIn(sub, year, m, account.billingDay)))\n if (!due.length) return []\n const billed = new Set(\n (await tx.getRepository(SaasBill).find({ where: { domain: { id: domain.id }, month, account: { id: In(due.map(one => one.id)) } } as any })).map(one => one.accountId)\n )\n return due.filter(one => !billed.has(one.id)).map(account => ({ account, month }))\n}\n"]}
@@ -0,0 +1,20 @@
1
+ export * from './saas-rules';
2
+ export * from './saas-account';
3
+ export * from './saas-subscription';
4
+ export * from './saas-seat';
5
+ export * from './saas-bill';
6
+ export * from './saas-type';
7
+ export * from './saas-trend-type';
8
+ export * from './saas-access';
9
+ export * from './saas-service';
10
+ export * from './checks';
11
+ import { SaasAccount } from './saas-account';
12
+ import { SaasBill, SaasBillLine } from './saas-bill';
13
+ import { SaasMutation } from './saas-mutation';
14
+ import { SaasAccountQuery } from './saas-query';
15
+ import { SaasSeat } from './saas-seat';
16
+ import { SaasSubscription } from './saas-subscription';
17
+ export declare const entities: (typeof SaasAccount | typeof SaasSubscription | typeof SaasSeat | typeof SaasBill | typeof SaasBillLine)[];
18
+ export declare const schema: {
19
+ resolverClasses: (typeof SaasMutation | typeof SaasAccountQuery)[];
20
+ };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.schema = exports.entities = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /* EXPORT ENTITY TYPES */
6
+ tslib_1.__exportStar(require("./saas-rules"), exports);
7
+ tslib_1.__exportStar(require("./saas-account"), exports);
8
+ tslib_1.__exportStar(require("./saas-subscription"), exports);
9
+ tslib_1.__exportStar(require("./saas-seat"), exports);
10
+ tslib_1.__exportStar(require("./saas-bill"), exports);
11
+ tslib_1.__exportStar(require("./saas-type"), exports);
12
+ tslib_1.__exportStar(require("./saas-trend-type"), exports);
13
+ tslib_1.__exportStar(require("./saas-access"), exports);
14
+ tslib_1.__exportStar(require("./saas-service"), exports);
15
+ tslib_1.__exportStar(require("./checks"), exports);
16
+ /* IMPORT ENTITIES AND RESOLVERS */
17
+ const saas_account_1 = require("./saas-account");
18
+ const saas_bill_1 = require("./saas-bill");
19
+ const saas_mutation_1 = require("./saas-mutation");
20
+ const saas_query_1 = require("./saas-query");
21
+ const saas_seat_1 = require("./saas-seat");
22
+ const saas_subscription_1 = require("./saas-subscription");
23
+ exports.entities = [
24
+ /* ENTITIES */
25
+ saas_account_1.SaasAccount,
26
+ saas_subscription_1.SaasSubscription,
27
+ saas_seat_1.SaasSeat,
28
+ saas_bill_1.SaasBill,
29
+ saas_bill_1.SaasBillLine
30
+ ];
31
+ exports.schema = {
32
+ resolverClasses: [
33
+ /* RESOLVER CLASSES */
34
+ saas_query_1.SaasAccountQuery,
35
+ saas_mutation_1.SaasMutation
36
+ ]
37
+ };
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,uDAA4B;AAC5B,yDAA8B;AAC9B,8DAAmC;AACnC,sDAA2B;AAC3B,sDAA2B;AAC3B,sDAA2B;AAC3B,4DAAiC;AACjC,wDAA6B;AAC7B,yDAA8B;AAC9B,mDAAwB;AAExB,mCAAmC;AACnC,iDAA4C;AAC5C,2CAAoD;AACpD,mDAA8C;AAC9C,6CAA+C;AAC/C,2CAAsC;AACtC,2DAAsD;AAEzC,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,0BAAW;IACX,oCAAgB;IAChB,oBAAQ;IACR,oBAAQ;IACR,wBAAY;CACb,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,6BAAgB;QAChB,4BAAY;KACb;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './saas-rules'\nexport * from './saas-account'\nexport * from './saas-subscription'\nexport * from './saas-seat'\nexport * from './saas-bill'\nexport * from './saas-type'\nexport * from './saas-trend-type'\nexport * from './saas-access'\nexport * from './saas-service'\nexport * from './checks'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { SaasAccount } from './saas-account'\nimport { SaasBill, SaasBillLine } from './saas-bill'\nimport { SaasMutation } from './saas-mutation'\nimport { SaasAccountQuery } from './saas-query'\nimport { SaasSeat } from './saas-seat'\nimport { SaasSubscription } from './saas-subscription'\n\nexport const entities = [\n /* ENTITIES */\n SaasAccount,\n SaasSubscription,\n SaasSeat,\n SaasBill,\n SaasBillLine\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n SaasAccountQuery,\n SaasMutation\n ]\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import { EntityManager } from 'typeorm';
3
+ import { SaasAccount } from './saas-account';
4
+ /**
5
+ * Whose accounts a person reaches (ADR-0177 Q4): every account with `saas` on the axis (or as the domain owner), else
6
+ * only the accounts they are named an admin of - whatever their seat. The doors that admins pass ask `saas-self`; this
7
+ * says how far it reaches.
8
+ */
9
+ export type SaasScope = 'all' | Set<string>;
10
+ export declare function scopeOf(state: {
11
+ domain: Domain;
12
+ user: any;
13
+ unsafeIP?: boolean;
14
+ prohibitedPrivileges?: any[];
15
+ tx?: EntityManager;
16
+ }, axis: 'query' | 'mutation', tx: EntityManager): Promise<SaasScope>;
17
+ /** The accounts a person is named an admin of - by their employee record of the domain, while not retired. */
18
+ export declare function administeredBy(tx: EntityManager, domain: Domain, userId?: string): Promise<string[]>;
19
+ /** The account, when it is of the domain and within the scope; refused otherwise - as not found, so nothing is told. */
20
+ export declare function accountIn(tx: EntityManager, domain: Domain, scope: SaasScope, accountId: string): Promise<SaasAccount>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scopeOf = scopeOf;
4
+ exports.administeredBy = administeredBy;
5
+ exports.accountIn = accountIn;
6
+ const auth_base_1 = require("@things-factory/auth-base");
7
+ const shell_1 = require("@things-factory/shell");
8
+ const organization_1 = require("@things-factory/organization");
9
+ const typeorm_1 = require("typeorm");
10
+ const saas_account_1 = require("./saas-account");
11
+ async function scopeOf(state, axis, tx) {
12
+ const { domain, user, unsafeIP, prohibitedPrivileges } = state;
13
+ if (await (0, auth_base_1.checkPermission)({ category: 'saas', privilege: axis, owner: true }, user, domain, unsafeIP, prohibitedPrivileges))
14
+ return 'all';
15
+ return new Set(await administeredBy(tx, domain, user?.id));
16
+ }
17
+ /** The accounts a person is named an admin of - by their employee record of the domain, while not retired. */
18
+ async function administeredBy(tx, domain, userId) {
19
+ if (!userId)
20
+ return [];
21
+ const today = new Date().toISOString().slice(0, 10);
22
+ /* A retired employee administers nothing any more. */
23
+ const employees = (await tx.getRepository(organization_1.Employee).find({ where: { domain: { id: domain.id }, user: { id: userId } } })).filter(one => !one.retiredOn || one.retiredOn > today);
24
+ if (!employees.length)
25
+ return [];
26
+ const rows = await tx.getRepository(saas_account_1.SaasAccount).find({ where: { domain: { id: domain.id }, admins: { id: (0, typeorm_1.In)(employees.map(one => one.id)) } }, relations: ['admins'] });
27
+ return [...new Set(rows.map(one => one.id))];
28
+ }
29
+ /** The account, when it is of the domain and within the scope; refused otherwise - as not found, so nothing is told. */
30
+ async function accountIn(tx, domain, scope, accountId) {
31
+ const account = accountId && (scope === 'all' || scope.has(accountId)) ? await tx.getRepository(saas_account_1.SaasAccount).findOne({ where: { domain: { id: domain.id }, id: accountId } }) : null;
32
+ if (!account)
33
+ throw new shell_1.Refusal('saas-account-not-found', {}, 'The account was not found - reload and try again');
34
+ return account;
35
+ }
36
+ //# sourceMappingURL=saas-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saas-access.js","sourceRoot":"","sources":["../../server/service/saas-access.ts"],"names":[],"mappings":";;AAcA,0BAIC;AAGD,wCAQC;AAGD,8BAIC;AApCD,yDAA2D;AAC3D,iDAAuD;AACvD,+DAAuD;AACvD,qCAA2C;AAE3C,iDAA4C;AASrC,KAAK,UAAU,OAAO,CAAC,KAA0G,EAAE,IAA0B,EAAE,EAAiB;IACrL,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,KAAK,CAAA;IAC9D,IAAI,MAAM,IAAA,2BAAe,EAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,CAAC;QAAE,OAAO,KAAK,CAAA;IAChJ,OAAO,IAAI,GAAG,CAAC,MAAM,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,8GAA8G;AACvG,KAAK,UAAU,cAAc,CAAC,EAAiB,EAAE,MAAc,EAAE,MAAe;IACrF,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IACtB,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACnD,sDAAsD;IACtD,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,uBAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAS,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,CAAA;IACvL,IAAI,CAAC,SAAS,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,aAAa,CAAC,0BAAW,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,EAAS,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC/K,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAC9C,CAAC;AAED,wHAAwH;AACjH,KAAK,UAAU,SAAS,CAAC,EAAiB,EAAE,MAAc,EAAE,KAAgB,EAAE,SAAiB;IACpG,MAAM,OAAO,GAAG,SAAS,IAAI,CAAC,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,0BAAW,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAS,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3L,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,eAAO,CAAC,wBAAwB,EAAE,EAAE,EAAE,kDAAkD,CAAC,CAAA;IACjH,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import { checkPermission } from '@things-factory/auth-base'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { Employee } from '@things-factory/organization'\nimport { EntityManager, In } from 'typeorm'\n\nimport { SaasAccount } from './saas-account'\n\n/**\n * Whose accounts a person reaches (ADR-0177 Q4): every account with `saas` on the axis (or as the domain owner), else\n * only the accounts they are named an admin of - whatever their seat. The doors that admins pass ask `saas-self`; this\n * says how far it reaches.\n */\nexport type SaasScope = 'all' | Set<string>\n\nexport async function scopeOf(state: { domain: Domain; user: any; unsafeIP?: boolean; prohibitedPrivileges?: any[]; tx?: EntityManager }, axis: 'query' | 'mutation', tx: EntityManager): Promise<SaasScope> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = state\n if (await checkPermission({ category: 'saas', privilege: axis, owner: true } as any, user, domain, unsafeIP, prohibitedPrivileges)) return 'all'\n return new Set(await administeredBy(tx, domain, user?.id))\n}\n\n/** The accounts a person is named an admin of - by their employee record of the domain, while not retired. */\nexport async function administeredBy(tx: EntityManager, domain: Domain, userId?: string): Promise<string[]> {\n if (!userId) return []\n const today = new Date().toISOString().slice(0, 10)\n /* A retired employee administers nothing any more. */\n const employees = (await tx.getRepository(Employee).find({ where: { domain: { id: domain.id }, user: { id: userId } } as any })).filter(one => !one.retiredOn || one.retiredOn > today)\n if (!employees.length) return []\n const rows = await tx.getRepository(SaasAccount).find({ where: { domain: { id: domain.id }, admins: { id: In(employees.map(one => one.id)) } } as any, relations: ['admins'] })\n return [...new Set(rows.map(one => one.id))]\n}\n\n/** The account, when it is of the domain and within the scope; refused otherwise - as not found, so nothing is told. */\nexport async function accountIn(tx: EntityManager, domain: Domain, scope: SaasScope, accountId: string): Promise<SaasAccount> {\n const account = accountId && (scope === 'all' || scope.has(accountId)) ? await tx.getRepository(SaasAccount).findOne({ where: { domain: { id: domain.id }, id: accountId } as any }) : null\n if (!account) throw new Refusal('saas-account-not-found', {}, 'The account was not found - reload and try again')\n return account\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import { BankAccount } from '@things-factory/accounting';
2
+ import { User } from '@things-factory/auth-base';
3
+ import { Counterparty } from '@things-factory/biz-base';
4
+ import { Employee } from '@things-factory/organization';
5
+ import { Domain } from '@things-factory/shell';
6
+ import { SaasAccountStatus } from './saas-rules';
7
+ import { SaasTrend } from './saas-trend-type';
8
+ /**
9
+ * One account the domain holds with a vendor - the unit a bill comes from (ADR-0177 decision 2): an AWS organization's
10
+ * payer account, a Microsoft 365 tenant, a registrar. An account under another (an AWS member account) names its purpose
11
+ * and admins only; the money is the parent's. No password, key or recovery code is kept - only where it is kept.
12
+ */
13
+ export declare class SaasAccount {
14
+ readonly id: string;
15
+ domain?: Domain;
16
+ domainId?: string;
17
+ name: string;
18
+ counterparty?: Counterparty;
19
+ counterpartyId?: string;
20
+ identifier?: string;
21
+ parent?: SaasAccount;
22
+ parentId?: string;
23
+ purpose?: string;
24
+ admins?: Employee[];
25
+ billingEmail?: string;
26
+ paymentAccount?: BankAccount;
27
+ paymentAccountId?: string;
28
+ currency: string;
29
+ billingDay?: number;
30
+ secretLocation?: string;
31
+ increaseAlertRate?: number;
32
+ status: SaasAccountStatus;
33
+ closedOn?: string;
34
+ description?: string;
35
+ trend?: SaasTrend;
36
+ createdAt?: Date;
37
+ updatedAt?: Date;
38
+ deletedAt?: Date;
39
+ creator?: User;
40
+ creatorId?: string;
41
+ updater?: User;
42
+ updaterId?: string;
43
+ }
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SaasAccount = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const accounting_1 = require("@things-factory/accounting");
6
+ const auth_base_1 = require("@things-factory/auth-base");
7
+ const biz_base_1 = require("@things-factory/biz-base");
8
+ const organization_1 = require("@things-factory/organization");
9
+ const shell_1 = require("@things-factory/shell");
10
+ const type_graphql_1 = require("type-graphql");
11
+ const typeorm_1 = require("typeorm");
12
+ const saas_rules_1 = require("./saas-rules");
13
+ const saas_trend_type_1 = require("./saas-trend-type");
14
+ /**
15
+ * One account the domain holds with a vendor - the unit a bill comes from (ADR-0177 decision 2): an AWS organization's
16
+ * payer account, a Microsoft 365 tenant, a registrar. An account under another (an AWS member account) names its purpose
17
+ * and admins only; the money is the parent's. No password, key or recovery code is kept - only where it is kept.
18
+ */
19
+ let SaasAccount = class SaasAccount {
20
+ };
21
+ exports.SaasAccount = SaasAccount;
22
+ tslib_1.__decorate([
23
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
24
+ (0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Unique identifier of the account' }),
25
+ tslib_1.__metadata("design:type", String)
26
+ ], SaasAccount.prototype, "id", void 0);
27
+ tslib_1.__decorate([
28
+ (0, typeorm_1.ManyToOne)(type => shell_1.Domain),
29
+ (0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain the account belongs to' }),
30
+ tslib_1.__metadata("design:type", shell_1.Domain)
31
+ ], SaasAccount.prototype, "domain", void 0);
32
+ tslib_1.__decorate([
33
+ (0, typeorm_1.RelationId)((account) => account.domain),
34
+ tslib_1.__metadata("design:type", String)
35
+ ], SaasAccount.prototype, "domainId", void 0);
36
+ tslib_1.__decorate([
37
+ (0, typeorm_1.Column)(),
38
+ (0, type_graphql_1.Field)({ description: 'The service - AWS, Microsoft 365, Adobe Creative Cloud, a registrar' }),
39
+ tslib_1.__metadata("design:type", String)
40
+ ], SaasAccount.prototype, "name", void 0);
41
+ tslib_1.__decorate([
42
+ (0, typeorm_1.ManyToOne)(type => biz_base_1.Counterparty, { nullable: true, onDelete: 'SET NULL' }),
43
+ (0, type_graphql_1.Field)(type => biz_base_1.Counterparty, { nullable: true, description: 'The vendor it is paid to' }),
44
+ tslib_1.__metadata("design:type", biz_base_1.Counterparty)
45
+ ], SaasAccount.prototype, "counterparty", void 0);
46
+ tslib_1.__decorate([
47
+ (0, typeorm_1.RelationId)((account) => account.counterparty),
48
+ tslib_1.__metadata("design:type", String)
49
+ ], SaasAccount.prototype, "counterpartyId", void 0);
50
+ tslib_1.__decorate([
51
+ (0, typeorm_1.Column)({ nullable: true }),
52
+ (0, type_graphql_1.Field)({ nullable: true, description: "The vendor's name for the account - an AWS account number, a tenant domain, an organization id" }),
53
+ tslib_1.__metadata("design:type", String)
54
+ ], SaasAccount.prototype, "identifier", void 0);
55
+ tslib_1.__decorate([
56
+ (0, typeorm_1.ManyToOne)(type => SaasAccount, { nullable: true, onDelete: 'SET NULL' }),
57
+ (0, type_graphql_1.Field)(type => SaasAccount, { nullable: true, description: 'The account it is under - an AWS member account under its payer; the money is the parent’s' }),
58
+ tslib_1.__metadata("design:type", SaasAccount)
59
+ ], SaasAccount.prototype, "parent", void 0);
60
+ tslib_1.__decorate([
61
+ (0, typeorm_1.RelationId)((account) => account.parent),
62
+ tslib_1.__metadata("design:type", String)
63
+ ], SaasAccount.prototype, "parentId", void 0);
64
+ tslib_1.__decorate([
65
+ (0, typeorm_1.Column)({ nullable: true }),
66
+ (0, type_graphql_1.Field)({ nullable: true, description: 'What it is used for - production, development, a customer' }),
67
+ tslib_1.__metadata("design:type", String)
68
+ ], SaasAccount.prototype, "purpose", void 0);
69
+ tslib_1.__decorate([
70
+ (0, typeorm_1.ManyToMany)(type => organization_1.Employee),
71
+ (0, typeorm_1.JoinTable)({ name: 'saas_account_admins' }),
72
+ (0, type_graphql_1.Field)(type => [organization_1.Employee], { nullable: true, description: 'Who administers it at the vendor - they keep its subscriptions, seats and bills' }),
73
+ tslib_1.__metadata("design:type", Array)
74
+ ], SaasAccount.prototype, "admins", void 0);
75
+ tslib_1.__decorate([
76
+ (0, typeorm_1.Column)({ nullable: true }),
77
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Where the vendor sends the bills' }),
78
+ tslib_1.__metadata("design:type", String)
79
+ ], SaasAccount.prototype, "billingEmail", void 0);
80
+ tslib_1.__decorate([
81
+ (0, typeorm_1.ManyToOne)(type => accounting_1.BankAccount, { nullable: true, onDelete: 'SET NULL' }),
82
+ (0, type_graphql_1.Field)(type => accounting_1.BankAccount, { nullable: true, description: 'What it is paid from - a bank account or a card (accounting, ADR-0178)' }),
83
+ tslib_1.__metadata("design:type", accounting_1.BankAccount)
84
+ ], SaasAccount.prototype, "paymentAccount", void 0);
85
+ tslib_1.__decorate([
86
+ (0, typeorm_1.RelationId)((account) => account.paymentAccount),
87
+ tslib_1.__metadata("design:type", String)
88
+ ], SaasAccount.prototype, "paymentAccountId", void 0);
89
+ tslib_1.__decorate([
90
+ (0, typeorm_1.Column)({ default: 'KRW' }),
91
+ (0, type_graphql_1.Field)({ description: 'The currency it is billed in - ISO 4217' }),
92
+ tslib_1.__metadata("design:type", String)
93
+ ], SaasAccount.prototype, "currency", void 0);
94
+ tslib_1.__decorate([
95
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
96
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'The day of the month the bill is paid, 1-31; past the month’s end is its last day' }),
97
+ tslib_1.__metadata("design:type", Number)
98
+ ], SaasAccount.prototype, "billingDay", void 0);
99
+ tslib_1.__decorate([
100
+ (0, typeorm_1.Column)({ nullable: true }),
101
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Where its password, keys and recovery codes are kept - never the secrets themselves' }),
102
+ tslib_1.__metadata("design:type", String)
103
+ ], SaasAccount.prototype, "secretLocation", void 0);
104
+ tslib_1.__decorate([
105
+ (0, typeorm_1.Column)({ type: 'float', nullable: true }),
106
+ (0, type_graphql_1.Field)(type => type_graphql_1.Float, { nullable: true, description: 'A bill up more than this over the month before is told (0.2 for 20%); 20% when empty' }),
107
+ tslib_1.__metadata("design:type", Number)
108
+ ], SaasAccount.prototype, "increaseAlertRate", void 0);
109
+ tslib_1.__decorate([
110
+ (0, typeorm_1.Column)({ default: saas_rules_1.SaasAccountStatus.ACTIVE }),
111
+ (0, type_graphql_1.Field)(type => saas_rules_1.SaasAccountStatus, { description: 'In use or closed' }),
112
+ tslib_1.__metadata("design:type", String)
113
+ ], SaasAccount.prototype, "status", void 0);
114
+ tslib_1.__decorate([
115
+ (0, typeorm_1.Column)({ nullable: true }),
116
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The day it was closed - YYYY-MM-DD' }),
117
+ tslib_1.__metadata("design:type", String)
118
+ ], SaasAccount.prototype, "closedOn", void 0);
119
+ tslib_1.__decorate([
120
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
121
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Notes' }),
122
+ tslib_1.__metadata("design:type", String)
123
+ ], SaasAccount.prototype, "description", void 0);
124
+ tslib_1.__decorate([
125
+ (0, type_graphql_1.Field)(type => saas_trend_type_1.SaasTrend, { nullable: true, description: 'Its last twelve months billed and the next three expected - filled by the list queries' }),
126
+ tslib_1.__metadata("design:type", saas_trend_type_1.SaasTrend)
127
+ ], SaasAccount.prototype, "trend", void 0);
128
+ tslib_1.__decorate([
129
+ (0, typeorm_1.CreateDateColumn)(),
130
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When the account was registered' }),
131
+ tslib_1.__metadata("design:type", Date)
132
+ ], SaasAccount.prototype, "createdAt", void 0);
133
+ tslib_1.__decorate([
134
+ (0, typeorm_1.UpdateDateColumn)(),
135
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When the account last changed' }),
136
+ tslib_1.__metadata("design:type", Date)
137
+ ], SaasAccount.prototype, "updatedAt", void 0);
138
+ tslib_1.__decorate([
139
+ (0, typeorm_1.DeleteDateColumn)(),
140
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When the account was deleted' }),
141
+ tslib_1.__metadata("design:type", Date)
142
+ ], SaasAccount.prototype, "deletedAt", void 0);
143
+ tslib_1.__decorate([
144
+ (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
145
+ (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who registered it' }),
146
+ tslib_1.__metadata("design:type", auth_base_1.User)
147
+ ], SaasAccount.prototype, "creator", void 0);
148
+ tslib_1.__decorate([
149
+ (0, typeorm_1.RelationId)((account) => account.creator),
150
+ tslib_1.__metadata("design:type", String)
151
+ ], SaasAccount.prototype, "creatorId", void 0);
152
+ tslib_1.__decorate([
153
+ (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
154
+ (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who last changed it' }),
155
+ tslib_1.__metadata("design:type", auth_base_1.User)
156
+ ], SaasAccount.prototype, "updater", void 0);
157
+ tslib_1.__decorate([
158
+ (0, typeorm_1.RelationId)((account) => account.updater),
159
+ tslib_1.__metadata("design:type", String)
160
+ ], SaasAccount.prototype, "updaterId", void 0);
161
+ exports.SaasAccount = SaasAccount = tslib_1.__decorate([
162
+ (0, typeorm_1.Entity)(),
163
+ (0, typeorm_1.Index)('ix_saas_account_0', (account) => [account.domain, account.status]),
164
+ (0, type_graphql_1.ObjectType)({ description: 'Entity for SaasAccount — an account the domain holds with a vendor of something it subscribes to' })
165
+ ], SaasAccount);
166
+ //# sourceMappingURL=saas-account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saas-account.js","sourceRoot":"","sources":["../../server/service/saas-account.ts"],"names":[],"mappings":"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","sourcesContent":["import { BankAccount } from '@things-factory/accounting'\nimport { User } from '@things-factory/auth-base'\nimport { Counterparty } from '@things-factory/biz-base'\nimport { Employee } from '@things-factory/organization'\nimport { Domain } from '@things-factory/shell'\nimport { Field, Float, ID, Int, ObjectType } from 'type-graphql'\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n JoinTable,\n ManyToMany,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\n\nimport { SaasAccountStatus } from './saas-rules'\nimport { SaasTrend } from './saas-trend-type'\n\n/**\n * One account the domain holds with a vendor - the unit a bill comes from (ADR-0177 decision 2): an AWS organization's\n * payer account, a Microsoft 365 tenant, a registrar. An account under another (an AWS member account) names its purpose\n * and admins only; the money is the parent's. No password, key or recovery code is kept - only where it is kept.\n */\n@Entity()\n@Index('ix_saas_account_0', (account: SaasAccount) => [account.domain, account.status])\n@ObjectType({ description: 'Entity for SaasAccount — an account the domain holds with a vendor of something it subscribes to' })\nexport class SaasAccount {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the account' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain the account belongs to' })\n domain?: Domain\n\n @RelationId((account: SaasAccount) => account.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The service - AWS, Microsoft 365, Adobe Creative Cloud, a registrar' })\n name: string\n\n @ManyToOne(type => Counterparty, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => Counterparty, { nullable: true, description: 'The vendor it is paid to' })\n counterparty?: Counterparty\n\n @RelationId((account: SaasAccount) => account.counterparty)\n counterpartyId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"The vendor's name for the account - an AWS account number, a tenant domain, an organization id\" })\n identifier?: string\n\n @ManyToOne(type => SaasAccount, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => SaasAccount, { nullable: true, description: 'The account it is under - an AWS member account under its payer; the money is the parent’s' })\n parent?: SaasAccount\n\n @RelationId((account: SaasAccount) => account.parent)\n parentId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What it is used for - production, development, a customer' })\n purpose?: string\n\n @ManyToMany(type => Employee)\n @JoinTable({ name: 'saas_account_admins' })\n @Field(type => [Employee], { nullable: true, description: 'Who administers it at the vendor - they keep its subscriptions, seats and bills' })\n admins?: Employee[]\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Where the vendor sends the bills' })\n billingEmail?: string\n\n @ManyToOne(type => BankAccount, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => BankAccount, { nullable: true, description: 'What it is paid from - a bank account or a card (accounting, ADR-0178)' })\n paymentAccount?: BankAccount\n\n @RelationId((account: SaasAccount) => account.paymentAccount)\n paymentAccountId?: string\n\n @Column({ default: 'KRW' })\n @Field({ description: 'The currency it is billed in - ISO 4217' })\n currency: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'The day of the month the bill is paid, 1-31; past the month’s end is its last day' })\n billingDay?: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Where its password, keys and recovery codes are kept - never the secrets themselves' })\n secretLocation?: string\n\n @Column({ type: 'float', nullable: true })\n @Field(type => Float, { nullable: true, description: 'A bill up more than this over the month before is told (0.2 for 20%); 20% when empty' })\n increaseAlertRate?: number\n\n @Column({ default: SaasAccountStatus.ACTIVE })\n @Field(type => SaasAccountStatus, { description: 'In use or closed' })\n status: SaasAccountStatus\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The day it was closed - YYYY-MM-DD' })\n closedOn?: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Notes' })\n description?: string\n\n @Field(type => SaasTrend, { nullable: true, description: 'Its last twelve months billed and the next three expected - filled by the list queries' })\n trend?: SaasTrend\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When the account was registered' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When the account last changed' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'When the account was deleted' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who registered it' })\n creator?: User\n\n @RelationId((account: SaasAccount) => account.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who last changed it' })\n updater?: User\n\n @RelationId((account: SaasAccount) => account.updater)\n updaterId?: string\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { User } from '@things-factory/auth-base';
2
+ import { Domain } from '@things-factory/shell';
3
+ import { SaasAccount } from './saas-account';
4
+ import { SaasSubscription } from './saas-subscription';
5
+ /**
6
+ * What an account was actually billed for a month (ADR-0177 decisions 5, 9) - one a month, in the account's currency,
7
+ * one line a subscription and lines on none for tax and credits. Its total is the sum of its lines; the cost trend is
8
+ * computed from these.
9
+ */
10
+ export declare class SaasBill {
11
+ readonly id: string;
12
+ domain?: Domain;
13
+ domainId?: string;
14
+ account?: SaasAccount;
15
+ accountId?: string;
16
+ month: string;
17
+ billedOn?: string;
18
+ currency: string;
19
+ lines?: SaasBillLine[];
20
+ total?: number;
21
+ description?: string;
22
+ createdAt?: Date;
23
+ updatedAt?: Date;
24
+ deletedAt?: Date;
25
+ creator?: User;
26
+ creatorId?: string;
27
+ updater?: User;
28
+ updaterId?: string;
29
+ }
30
+ /** One line of a bill - what one subscription cost that month, or tax and credits on none. */
31
+ export declare class SaasBillLine {
32
+ readonly id: string;
33
+ domain?: Domain;
34
+ domainId?: string;
35
+ bill?: SaasBill;
36
+ billId?: string;
37
+ subscription?: SaasSubscription;
38
+ subscriptionId?: string;
39
+ label?: string;
40
+ amount: number;
41
+ }