@things-factory/saas 10.1.236 → 10.1.239
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/saas-candidates.d.ts +33 -0
- package/dist-client/components/saas-candidates.js +157 -0
- package/dist-client/components/saas-candidates.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/saas-account-list.js +3 -0
- package/dist-client/pages/saas-account-list.js.map +1 -1
- package/dist-client/pages/saas-account-page.js +7 -2
- package/dist-client/pages/saas-account-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/role-tasks.js +3 -1
- package/dist-server/role-tasks.js.map +1 -1
- package/dist-server/service/checks.d.ts +4 -1
- package/dist-server/service/checks.js +5 -2
- package/dist-server/service/checks.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -1
- package/dist-server/service/index.js +5 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/saas-bill.d.ts +1 -0
- package/dist-server/service/saas-bill.js +5 -0
- package/dist-server/service/saas-bill.js.map +1 -1
- package/dist-server/service/saas-hidden-vendor.d.ts +19 -0
- package/dist-server/service/saas-hidden-vendor.js +53 -0
- package/dist-server/service/saas-hidden-vendor.js.map +1 -0
- package/dist-server/service/saas-mutation.d.ts +8 -1
- package/dist-server/service/saas-mutation.js +69 -0
- package/dist-server/service/saas-mutation.js.map +1 -1
- package/dist-server/service/saas-query.d.ts +2 -1
- package/dist-server/service/saas-query.js +16 -0
- package/dist-server/service/saas-query.js.map +1 -1
- package/dist-server/service/saas-rules.d.ts +41 -0
- package/dist-server/service/saas-rules.js +49 -1
- package/dist-server/service/saas-rules.js.map +1 -1
- package/dist-server/service/saas-seat.d.ts +1 -0
- package/dist-server/service/saas-seat.js +5 -0
- package/dist-server/service/saas-seat.js.map +1 -1
- package/dist-server/service/saas-service.d.ts +30 -7
- package/dist-server/service/saas-service.js +136 -17
- package/dist-server/service/saas-service.js.map +1 -1
- package/dist-server/service/saas-subscription.d.ts +2 -0
- package/dist-server/service/saas-subscription.js +10 -0
- package/dist-server/service/saas-subscription.js.map +1 -1
- package/dist-server/service/saas-sync.d.ts +69 -0
- package/dist-server/service/saas-sync.js +166 -0
- package/dist-server/service/saas-sync.js.map +1 -0
- package/dist-server/service/saas-type.d.ts +69 -0
- package/dist-server/service/saas-type.js +257 -1
- package/dist-server/service/saas-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/translations/en.json +24 -0
- package/translations/es.json +24 -0
- package/translations/ja.json +24 -0
- package/translations/ko.json +24 -0
- package/translations/ms.json +24 -0
- package/translations/zh.json +24 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncSaasLicenses = syncSaasLicenses;
|
|
4
|
+
exports.syncSaasCosts = syncSaasCosts;
|
|
5
|
+
exports.syncSaasCostsOf = syncSaasCostsOf;
|
|
6
|
+
const organization_1 = require("@things-factory/organization");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const saas_account_1 = require("./saas-account");
|
|
9
|
+
const saas_bill_1 = require("./saas-bill");
|
|
10
|
+
const saas_rules_1 = require("./saas-rules");
|
|
11
|
+
const saas_seat_1 = require("./saas-seat");
|
|
12
|
+
const saas_service_1 = require("./saas-service");
|
|
13
|
+
const saas_subscription_1 = require("./saas-subscription");
|
|
14
|
+
const lower = (value) => String(value ?? '').trim().toLowerCase();
|
|
15
|
+
/** The account a vendor's names are: the one whose identifier is one of them - a tenant id, a sign-in domain, an organization id. */
|
|
16
|
+
async function accountOfVendor(tx, domain, vendorNames) {
|
|
17
|
+
const names = new Set(vendorNames.map(lower).filter(Boolean));
|
|
18
|
+
const accounts = (await tx.getRepository(saas_account_1.SaasAccount).find({ where: { domain: { id: domain.id }, status: saas_rules_1.SaasAccountStatus.ACTIVE } })).filter(one => names.has(lower(one.identifier)));
|
|
19
|
+
if (!accounts.length)
|
|
20
|
+
throw new shell_1.Refusal('saas-sync-account-not-found', { names: [...names].join(', ') }, 'No account has the vendor id or one of its domains as its identifier');
|
|
21
|
+
if (accounts.length > 1)
|
|
22
|
+
throw new shell_1.Refusal('saas-sync-account-ambiguous', { names: accounts.map(one => one.name).join(', ') }, 'Two accounts have the vendor as their identifier');
|
|
23
|
+
return accounts[0];
|
|
24
|
+
}
|
|
25
|
+
async function syncSaasLicenses(tx, domain, user, facts, today) {
|
|
26
|
+
const account = await accountOfVendor(tx, domain, [facts.tenantId, ...(facts.domains ?? [])]);
|
|
27
|
+
if (account.parentId)
|
|
28
|
+
throw new shell_1.Refusal('saas-subscription-on-sub-account', {}, 'An account under another is billed by its parent - subscribe there');
|
|
29
|
+
/* Whose mail a login is: an employee's - the mails on their contact card (organization keeps the work mail there), and their user account's. */
|
|
30
|
+
const employees = await tx.getRepository(organization_1.Employee).find({ where: { domain: { id: domain.id } }, relations: ['user', 'contact'] });
|
|
31
|
+
const byMail = new Map();
|
|
32
|
+
for (const one of employees) {
|
|
33
|
+
const mails = [one.user?.email, ...(one.contact?.items ?? []).filter(item => item?.type === 'email').map(item => item.value)];
|
|
34
|
+
for (const mail of mails)
|
|
35
|
+
if (lower(mail))
|
|
36
|
+
byMail.set(lower(mail), one);
|
|
37
|
+
}
|
|
38
|
+
const employeeOf = (holder) => byMail.get(lower(holder.mail)) ?? byMail.get(lower(holder.login));
|
|
39
|
+
const result = { accountId: account.id, accountName: account.name, subscriptions: 0, created: 0, opened: 0, closed: 0, linked: 0, unmatched: [] };
|
|
40
|
+
const subs = await tx.getRepository(saas_subscription_1.SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: account.id } } });
|
|
41
|
+
for (const sku of facts.skus) {
|
|
42
|
+
let sub = subs.find(one => one.vendorKey === sku.skuId);
|
|
43
|
+
const holders = facts.users.filter(one => one.skuIds.includes(sku.skuId));
|
|
44
|
+
if (!sub) {
|
|
45
|
+
if (!sku.consumed && !holders.length)
|
|
46
|
+
continue;
|
|
47
|
+
[sub] = await (0, saas_service_1.saveSaasSubscriptions)(tx, domain, user, 'all', account.id, [
|
|
48
|
+
{
|
|
49
|
+
cuFlag: '+',
|
|
50
|
+
name: sku.name,
|
|
51
|
+
billing: saas_rules_1.SaasBilling.SEAT,
|
|
52
|
+
vendorKey: sku.skuId,
|
|
53
|
+
quantity: Math.max(sku.enabled, holders.length, 1),
|
|
54
|
+
startOn: today,
|
|
55
|
+
status: saas_rules_1.SaasSubscriptionStatus.ACTIVE,
|
|
56
|
+
...(sku.renewalOn ? { renewalOn: sku.renewalOn } : {}),
|
|
57
|
+
trial: !!sku.trial
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
result.created++;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
/* The quantity, the renewal day and being a trial are the tenant's facts; the rest stays the person's. */
|
|
64
|
+
const change = {};
|
|
65
|
+
if (sku.enabled > 0 && Number(sub.quantity) !== sku.enabled)
|
|
66
|
+
change.quantity = sku.enabled;
|
|
67
|
+
if (sku.renewalOn && sub.renewalOn !== sku.renewalOn)
|
|
68
|
+
change.renewalOn = sku.renewalOn;
|
|
69
|
+
if (sku.trial !== undefined && sku.trial !== null && !!sub.trial !== !!sku.trial)
|
|
70
|
+
change.trial = !!sku.trial;
|
|
71
|
+
if (Object.keys(change).length)
|
|
72
|
+
await (0, saas_service_1.saveSaasSubscriptions)(tx, domain, user, 'all', account.id, [{ id: sub.id, cuFlag: 'M', ...change }]);
|
|
73
|
+
}
|
|
74
|
+
result.subscriptions++;
|
|
75
|
+
const seats = await tx.getRepository(saas_seat_1.SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: sub.id } } });
|
|
76
|
+
const synced = seats.filter(one => one.login);
|
|
77
|
+
/* The first sync does not know since when a seat is held. */
|
|
78
|
+
const since = synced.length ? today : null;
|
|
79
|
+
const open = synced.filter(one => !one.untilOn || one.untilOn > today);
|
|
80
|
+
const now = new Set(holders.map(one => lower(one.login)));
|
|
81
|
+
const patches = [];
|
|
82
|
+
for (const holder of holders) {
|
|
83
|
+
const employee = employeeOf(holder);
|
|
84
|
+
if (!employee)
|
|
85
|
+
result.unmatched.push(holder.login);
|
|
86
|
+
const held = open.find(one => lower(one.login) === lower(holder.login));
|
|
87
|
+
if (held) {
|
|
88
|
+
/* A seat held as a name takes its employee once their mail is known - every sync looks again. */
|
|
89
|
+
if (employee && held.employeeId !== employee.id) {
|
|
90
|
+
patches.push({ id: held.id, cuFlag: 'M', employeeId: employee.id, holder: null });
|
|
91
|
+
result.linked++;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
patches.push({ cuFlag: '+', login: holder.login, employeeId: employee?.id ?? null, holder: employee ? null : holder.name || holder.login, fromOn: since });
|
|
96
|
+
result.opened++;
|
|
97
|
+
}
|
|
98
|
+
for (const seat of open)
|
|
99
|
+
if (!now.has(lower(seat.login))) {
|
|
100
|
+
patches.push({ id: seat.id, cuFlag: 'M', untilOn: seat.fromOn && seat.fromOn > today ? seat.fromOn : today });
|
|
101
|
+
result.closed++;
|
|
102
|
+
}
|
|
103
|
+
if (patches.length)
|
|
104
|
+
await (0, saas_service_1.saveSaasSeats)(tx, domain, user, 'all', sub.id, patches);
|
|
105
|
+
}
|
|
106
|
+
result.unmatched = [...new Set(result.unmatched)];
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Write each closed month's bill from the vendor's cost report, a line a model, all on the account's usage subscription
|
|
111
|
+
* for the vendor (made when there is none: usage, `vendorKey` the vendor account). A month whose bill a person wrote is
|
|
112
|
+
* kept; one a sync wrote is rewritten. The report's currency must be the account's.
|
|
113
|
+
*/
|
|
114
|
+
async function syncSaasCosts(tx, domain, user, facts, today) {
|
|
115
|
+
const account = await accountOfVendor(tx, domain, [facts.vendorAccount]);
|
|
116
|
+
if (account.parentId)
|
|
117
|
+
throw new shell_1.Refusal('saas-bill-on-sub-account', {}, 'An account under another is billed by its parent');
|
|
118
|
+
if (String(facts.currency).toUpperCase() !== account.currency)
|
|
119
|
+
throw new shell_1.Refusal('saas-sync-currency-differs', { currency: facts.currency, account: account.currency }, `The vendor bills in ${facts.currency}; the account is in ${account.currency} - set the account's currency`);
|
|
120
|
+
const months = facts.months.filter(one => one.month < today.slice(0, 7) && one.lines.some(line => Number(line.amount)));
|
|
121
|
+
const result = { accountId: account.id, accountName: account.name, written: 0, kept: [] };
|
|
122
|
+
if (!months.length)
|
|
123
|
+
return result;
|
|
124
|
+
const subs = await tx.getRepository(saas_subscription_1.SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: account.id } } });
|
|
125
|
+
let usage = subs.find(one => one.vendorKey === facts.vendorAccount);
|
|
126
|
+
if (!usage)
|
|
127
|
+
[usage] = await (0, saas_service_1.saveSaasSubscriptions)(tx, domain, user, 'all', account.id, [
|
|
128
|
+
{ cuFlag: '+', name: `${facts.vendorName || account.name} API`, billing: saas_rules_1.SaasBilling.USAGE, vendorKey: facts.vendorAccount, startOn: `${months[0].month}-01`, status: saas_rules_1.SaasSubscriptionStatus.ACTIVE }
|
|
129
|
+
]);
|
|
130
|
+
const bills = await tx.getRepository(saas_bill_1.SaasBill).find({ where: { domain: { id: domain.id }, account: { id: account.id } } });
|
|
131
|
+
for (const one of months) {
|
|
132
|
+
const bill = bills.find(old => old.month === one.month);
|
|
133
|
+
if (bill && !bill.synced) {
|
|
134
|
+
result.kept.push(one.month);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const saved = await (0, saas_service_1.saveSaasBill)(tx, domain, user, 'all', account.id, {
|
|
138
|
+
id: bill?.id ?? null,
|
|
139
|
+
month: one.month,
|
|
140
|
+
lines: one.lines.filter(line => Number(line.amount)).map(line => ({ subscriptionId: usage.id, label: line.label, amount: Number(line.amount) }))
|
|
141
|
+
});
|
|
142
|
+
await tx.getRepository(saas_bill_1.SaasBill).update({ id: saved.id }, { synced: true });
|
|
143
|
+
result.written++;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Several accounts' cost reports at once - an AWS organization gives one an account. A vendor account no subscription
|
|
149
|
+
* account names is passed over and told (an organization holds accounts the domain does not keep); any other refusal
|
|
150
|
+
* stops the sync.
|
|
151
|
+
*/
|
|
152
|
+
async function syncSaasCostsOf(tx, domain, user, reports, today) {
|
|
153
|
+
const results = [];
|
|
154
|
+
const unmatched = [];
|
|
155
|
+
for (const facts of reports)
|
|
156
|
+
try {
|
|
157
|
+
results.push(await syncSaasCosts(tx, domain, user, facts, today));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error?.code !== 'saas-sync-account-not-found')
|
|
161
|
+
throw error;
|
|
162
|
+
unmatched.push(facts.vendorAccount);
|
|
163
|
+
}
|
|
164
|
+
return { results, unmatched };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=saas-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saas-sync.js","sourceRoot":"","sources":["../../server/service/saas-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { Employee } from '@things-factory/organization'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { EntityManager, In } from 'typeorm'\n\nimport { SaasAccount } from './saas-account'\nimport { SaasBill } from './saas-bill'\nimport { SaasAccountStatus, SaasBilling, SaasSubscriptionStatus } from './saas-rules'\nimport { SaasSeat } from './saas-seat'\nimport { saveSaasBill, saveSaasSeats, saveSaasSubscriptions } from './saas-service'\nimport { SaasSubscription } from './saas-subscription'\n\n/*\n * Seats as a vendor's tenant says (ADR-0177 개정 2) - what a Microsoft 365 tenant bought and who holds each, read by a\n * scenario (integration-msgraph `msgraph-license-assignments`) and handed here. Only quantities, renewal days, trials and\n * seats change: a plan's name, billing, price and status are a person's.\n */\n\nexport interface LicenseFacts {\n tenantId?: string | null\n domains?: string[] | null\n skus: { skuId: string; name: string; enabled: number; consumed: number; renewalOn?: string | null; trial?: boolean | null }[]\n users: { login: string; mail?: string | null; name?: string | null; enabled?: boolean | null; skuIds: string[] }[]\n}\n\nexport interface LicenseSyncResult {\n accountId: string\n accountName: string\n subscriptions: number\n created: number\n opened: number\n closed: number\n /** Open seats held as names that took their employee. */\n linked: number\n /** Logins no employee's mail matched - held as names. */\n unmatched: string[]\n}\n\nconst lower = (value?: string | null) => String(value ?? '').trim().toLowerCase()\n\n/** The account a vendor's names are: the one whose identifier is one of them - a tenant id, a sign-in domain, an organization id. */\nasync function accountOfVendor(tx: EntityManager, domain: Domain, vendorNames: (string | null | undefined)[]): Promise<SaasAccount> {\n const names = new Set(vendorNames.map(lower).filter(Boolean))\n const accounts = (await tx.getRepository(SaasAccount).find({ where: { domain: { id: domain.id }, status: SaasAccountStatus.ACTIVE } as any })).filter(\n one => names.has(lower(one.identifier))\n )\n if (!accounts.length)\n throw new Refusal('saas-sync-account-not-found', { names: [...names].join(', ') }, 'No account has the vendor id or one of its domains as its identifier')\n if (accounts.length > 1)\n throw new Refusal('saas-sync-account-ambiguous', { names: accounts.map(one => one.name).join(', ') }, 'Two accounts have the vendor as their identifier')\n return accounts[0]\n}\n\nexport async function syncSaasLicenses(tx: EntityManager, domain: Domain, user: any, facts: LicenseFacts, today: string): Promise<LicenseSyncResult> {\n const account = await accountOfVendor(tx, domain, [facts.tenantId, ...(facts.domains ?? [])])\n if (account.parentId) throw new Refusal('saas-subscription-on-sub-account', {}, 'An account under another is billed by its parent - subscribe there')\n\n /* Whose mail a login is: an employee's - the mails on their contact card (organization keeps the work mail there), and their user account's. */\n const employees = await tx.getRepository(Employee).find({ where: { domain: { id: domain.id } } as any, relations: ['user', 'contact'] })\n const byMail = new Map<string, Employee>()\n for (const one of employees) {\n const mails = [one.user?.email, ...((one.contact?.items ?? []) as any[]).filter(item => item?.type === 'email').map(item => item.value)]\n for (const mail of mails) if (lower(mail)) byMail.set(lower(mail), one)\n }\n const employeeOf = (holder: LicenseFacts['users'][number]) => byMail.get(lower(holder.mail)) ?? byMail.get(lower(holder.login))\n\n const result: LicenseSyncResult = { accountId: account.id, accountName: account.name, subscriptions: 0, created: 0, opened: 0, closed: 0, linked: 0, unmatched: [] }\n const subs = await tx.getRepository(SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: account.id } } as any })\n for (const sku of facts.skus) {\n let sub = subs.find(one => one.vendorKey === sku.skuId)\n const holders = facts.users.filter(one => one.skuIds.includes(sku.skuId))\n if (!sub) {\n if (!sku.consumed && !holders.length) continue\n ;[sub] = await saveSaasSubscriptions(tx, domain, user, 'all', account.id, [\n {\n cuFlag: '+',\n name: sku.name,\n billing: SaasBilling.SEAT,\n vendorKey: sku.skuId,\n quantity: Math.max(sku.enabled, holders.length, 1),\n startOn: today,\n status: SaasSubscriptionStatus.ACTIVE,\n ...(sku.renewalOn ? { renewalOn: sku.renewalOn } : {}),\n trial: !!sku.trial\n }\n ])\n result.created++\n } else {\n /* The quantity, the renewal day and being a trial are the tenant's facts; the rest stays the person's. */\n const change: Record<string, any> = {}\n if (sku.enabled > 0 && Number(sub.quantity) !== sku.enabled) change.quantity = sku.enabled\n if (sku.renewalOn && sub.renewalOn !== sku.renewalOn) change.renewalOn = sku.renewalOn\n if (sku.trial !== undefined && sku.trial !== null && !!sub.trial !== !!sku.trial) change.trial = !!sku.trial\n if (Object.keys(change).length) await saveSaasSubscriptions(tx, domain, user, 'all', account.id, [{ id: sub.id, cuFlag: 'M', ...change }])\n }\n result.subscriptions++\n\n const seats = await tx.getRepository(SaasSeat).find({ where: { domain: { id: domain.id }, subscription: { id: sub.id } } as any })\n const synced = seats.filter(one => one.login)\n /* The first sync does not know since when a seat is held. */\n const since = synced.length ? today : null\n const open = synced.filter(one => !one.untilOn || one.untilOn > today)\n const now = new Set(holders.map(one => lower(one.login)))\n const patches: Record<string, any>[] = []\n for (const holder of holders) {\n const employee = employeeOf(holder)\n if (!employee) result.unmatched.push(holder.login)\n const held = open.find(one => lower(one.login) === lower(holder.login))\n if (held) {\n /* A seat held as a name takes its employee once their mail is known - every sync looks again. */\n if (employee && held.employeeId !== employee.id) {\n patches.push({ id: held.id, cuFlag: 'M', employeeId: employee.id, holder: null })\n result.linked++\n }\n continue\n }\n patches.push({ cuFlag: '+', login: holder.login, employeeId: employee?.id ?? null, holder: employee ? null : holder.name || holder.login, fromOn: since })\n result.opened++\n }\n for (const seat of open)\n if (!now.has(lower(seat.login))) {\n patches.push({ id: seat.id, cuFlag: 'M', untilOn: seat.fromOn && seat.fromOn > today ? seat.fromOn : today })\n result.closed++\n }\n if (patches.length) await saveSaasSeats(tx, domain, user, 'all', sub.id, patches)\n }\n result.unmatched = [...new Set(result.unmatched)]\n return result\n}\n\n/* ----------------------------------------------------------------------------------------------------------------- */\n/* Bills as a vendor's cost report says (ADR-0177 개정 3) */\n\nexport interface CostFacts {\n /** The vendor's id for the account - an Anthropic organization id. */\n vendorAccount: string\n vendorName?: string | null\n currency: string\n months: { month: string; lines: { label: string; amount: number }[] }[]\n}\n\nexport interface CostSyncResult {\n accountId: string\n accountName: string\n written: number\n /** Months a person wrote the bill of - left as they are. */\n kept: string[]\n}\n\n/**\n * Write each closed month's bill from the vendor's cost report, a line a model, all on the account's usage subscription\n * for the vendor (made when there is none: usage, `vendorKey` the vendor account). A month whose bill a person wrote is\n * kept; one a sync wrote is rewritten. The report's currency must be the account's.\n */\nexport async function syncSaasCosts(tx: EntityManager, domain: Domain, user: any, facts: CostFacts, today: string): Promise<CostSyncResult> {\n const account = await accountOfVendor(tx, domain, [facts.vendorAccount])\n if (account.parentId) throw new Refusal('saas-bill-on-sub-account', {}, 'An account under another is billed by its parent')\n if (String(facts.currency).toUpperCase() !== account.currency)\n throw new Refusal('saas-sync-currency-differs', { currency: facts.currency, account: account.currency }, `The vendor bills in ${facts.currency}; the account is in ${account.currency} - set the account's currency`)\n\n const months = facts.months.filter(one => one.month < today.slice(0, 7) && one.lines.some(line => Number(line.amount)))\n const result: CostSyncResult = { accountId: account.id, accountName: account.name, written: 0, kept: [] }\n if (!months.length) return result\n\n const subs = await tx.getRepository(SaasSubscription).find({ where: { domain: { id: domain.id }, account: { id: account.id } } as any })\n let usage = subs.find(one => one.vendorKey === facts.vendorAccount)\n if (!usage)\n [usage] = await saveSaasSubscriptions(tx, domain, user, 'all', account.id, [\n { cuFlag: '+', name: `${facts.vendorName || account.name} API`, billing: SaasBilling.USAGE, vendorKey: facts.vendorAccount, startOn: `${months[0].month}-01`, status: SaasSubscriptionStatus.ACTIVE }\n ])\n\n const bills = await tx.getRepository(SaasBill).find({ where: { domain: { id: domain.id }, account: { id: account.id } } as any })\n for (const one of months) {\n const bill = bills.find(old => old.month === one.month)\n if (bill && !bill.synced) {\n result.kept.push(one.month)\n continue\n }\n const saved = await saveSaasBill(tx, domain, user, 'all', account.id, {\n id: bill?.id ?? null,\n month: one.month,\n lines: one.lines.filter(line => Number(line.amount)).map(line => ({ subscriptionId: usage!.id, label: line.label, amount: Number(line.amount) }))\n })\n await tx.getRepository(SaasBill).update({ id: saved.id } as any, { synced: true } as any)\n result.written++\n }\n return result\n}\n\n/**\n * Several accounts' cost reports at once - an AWS organization gives one an account. A vendor account no subscription\n * account names is passed over and told (an organization holds accounts the domain does not keep); any other refusal\n * stops the sync.\n */\nexport async function syncSaasCostsOf(tx: EntityManager, domain: Domain, user: any, reports: CostFacts[], today: string): Promise<{ results: CostSyncResult[]; unmatched: string[] }> {\n const results: CostSyncResult[] = []\n const unmatched: string[] = []\n for (const facts of reports)\n try {\n results.push(await syncSaasCosts(tx, domain, user, facts, today))\n } catch (error: any) {\n if (error?.code !== 'saas-sync-account-not-found') throw error\n unmatched.push(facts.vendorAccount)\n }\n return { results, unmatched }\n}\n"]}
|
|
@@ -111,3 +111,72 @@ export declare class SaasChange {
|
|
|
111
111
|
at: Date;
|
|
112
112
|
changedBy?: string;
|
|
113
113
|
}
|
|
114
|
+
export declare class SaasCandidate {
|
|
115
|
+
key: string;
|
|
116
|
+
counterpartyId?: string;
|
|
117
|
+
counterpartyName: string;
|
|
118
|
+
months: number;
|
|
119
|
+
average: number;
|
|
120
|
+
firstDate: string;
|
|
121
|
+
lastDate: string;
|
|
122
|
+
lastAmount: number;
|
|
123
|
+
currency: string;
|
|
124
|
+
paymentAccountId?: string;
|
|
125
|
+
billingDay?: number;
|
|
126
|
+
hidden: boolean;
|
|
127
|
+
}
|
|
128
|
+
export declare class SaasLicenseSkuInput {
|
|
129
|
+
skuId: string;
|
|
130
|
+
name: string;
|
|
131
|
+
enabled: number;
|
|
132
|
+
consumed: number;
|
|
133
|
+
renewalOn?: string;
|
|
134
|
+
trial?: boolean;
|
|
135
|
+
}
|
|
136
|
+
export declare class SaasLicenseUserInput {
|
|
137
|
+
login: string;
|
|
138
|
+
mail?: string;
|
|
139
|
+
name?: string;
|
|
140
|
+
enabled?: boolean;
|
|
141
|
+
skuIds: string[];
|
|
142
|
+
}
|
|
143
|
+
export declare class SaasLicensesInput {
|
|
144
|
+
tenantId?: string;
|
|
145
|
+
domains?: string[];
|
|
146
|
+
skus: SaasLicenseSkuInput[];
|
|
147
|
+
users: SaasLicenseUserInput[];
|
|
148
|
+
}
|
|
149
|
+
export declare class SaasLicenseSync {
|
|
150
|
+
accountId: string;
|
|
151
|
+
accountName: string;
|
|
152
|
+
subscriptions: number;
|
|
153
|
+
created: number;
|
|
154
|
+
opened: number;
|
|
155
|
+
closed: number;
|
|
156
|
+
linked: number;
|
|
157
|
+
unmatched: string[];
|
|
158
|
+
}
|
|
159
|
+
export declare class SaasCostLineInput {
|
|
160
|
+
label: string;
|
|
161
|
+
amount: number;
|
|
162
|
+
}
|
|
163
|
+
export declare class SaasCostMonthInput {
|
|
164
|
+
month: string;
|
|
165
|
+
lines: SaasCostLineInput[];
|
|
166
|
+
}
|
|
167
|
+
export declare class SaasCostsInput {
|
|
168
|
+
vendorAccount: string;
|
|
169
|
+
vendorName?: string;
|
|
170
|
+
currency: string;
|
|
171
|
+
months: SaasCostMonthInput[];
|
|
172
|
+
}
|
|
173
|
+
export declare class SaasCostSync {
|
|
174
|
+
accountId: string;
|
|
175
|
+
accountName: string;
|
|
176
|
+
written: number;
|
|
177
|
+
kept: string[];
|
|
178
|
+
}
|
|
179
|
+
export declare class SaasCostSyncs {
|
|
180
|
+
results: SaasCostSync[];
|
|
181
|
+
unmatched: string[];
|
|
182
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SaasChange = exports.SaasCostTrend = exports.SaasAlert = exports.SaasDepartmentSeries = exports.SaasTrendSeries = exports.SaasAccountList = exports.SaasBillInput = exports.SaasBillLineInput = exports.SaasSeatPatch = exports.SaasSubscriptionPatch = exports.SaasAccountPatch = void 0;
|
|
3
|
+
exports.SaasCostSyncs = exports.SaasCostSync = exports.SaasCostsInput = exports.SaasCostMonthInput = exports.SaasCostLineInput = exports.SaasLicenseSync = exports.SaasLicensesInput = exports.SaasLicenseUserInput = exports.SaasLicenseSkuInput = exports.SaasCandidate = exports.SaasChange = exports.SaasCostTrend = exports.SaasAlert = exports.SaasDepartmentSeries = exports.SaasTrendSeries = exports.SaasAccountList = exports.SaasBillInput = exports.SaasBillLineInput = exports.SaasSeatPatch = exports.SaasSubscriptionPatch = exports.SaasAccountPatch = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const type_graphql_1 = require("type-graphql");
|
|
6
6
|
const saas_account_1 = require("./saas-account");
|
|
@@ -427,4 +427,260 @@ tslib_1.__decorate([
|
|
|
427
427
|
exports.SaasChange = SaasChange = tslib_1.__decorate([
|
|
428
428
|
(0, type_graphql_1.ObjectType)({ description: 'What moved on an account or one of its subscriptions' })
|
|
429
429
|
], SaasChange);
|
|
430
|
+
let SaasCandidate = class SaasCandidate {
|
|
431
|
+
};
|
|
432
|
+
exports.SaasCandidate = SaasCandidate;
|
|
433
|
+
tslib_1.__decorate([
|
|
434
|
+
(0, type_graphql_1.Field)({ description: 'Whom it is - the counterparty id, or name: and the written name for lines no counterparty is linked to' }),
|
|
435
|
+
tslib_1.__metadata("design:type", String)
|
|
436
|
+
], SaasCandidate.prototype, "key", void 0);
|
|
437
|
+
tslib_1.__decorate([
|
|
438
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The vendor - a counterparty id; empty when the lines only name it' }),
|
|
439
|
+
tslib_1.__metadata("design:type", String)
|
|
440
|
+
], SaasCandidate.prototype, "counterpartyId", void 0);
|
|
441
|
+
tslib_1.__decorate([
|
|
442
|
+
(0, type_graphql_1.Field)({ description: "The vendor's name - the counterparty's, or the one written on the lines" }),
|
|
443
|
+
tslib_1.__metadata("design:type", String)
|
|
444
|
+
], SaasCandidate.prototype, "counterpartyName", void 0);
|
|
445
|
+
tslib_1.__decorate([
|
|
446
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'The months of the last twelve money went out to it in' }),
|
|
447
|
+
tslib_1.__metadata("design:type", Number)
|
|
448
|
+
], SaasCandidate.prototype, "months", void 0);
|
|
449
|
+
tslib_1.__decorate([
|
|
450
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'What went out a month, on average over those months' }),
|
|
451
|
+
tslib_1.__metadata("design:type", Number)
|
|
452
|
+
], SaasCandidate.prototype, "average", void 0);
|
|
453
|
+
tslib_1.__decorate([
|
|
454
|
+
(0, type_graphql_1.Field)({ description: 'The first day money went out to it in the window - YYYY-MM-DD' }),
|
|
455
|
+
tslib_1.__metadata("design:type", String)
|
|
456
|
+
], SaasCandidate.prototype, "firstDate", void 0);
|
|
457
|
+
tslib_1.__decorate([
|
|
458
|
+
(0, type_graphql_1.Field)({ description: 'The last day money went out to it - YYYY-MM-DD' }),
|
|
459
|
+
tslib_1.__metadata("design:type", String)
|
|
460
|
+
], SaasCandidate.prototype, "lastDate", void 0);
|
|
461
|
+
tslib_1.__decorate([
|
|
462
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'How much went out that day' }),
|
|
463
|
+
tslib_1.__metadata("design:type", Number)
|
|
464
|
+
], SaasCandidate.prototype, "lastAmount", void 0);
|
|
465
|
+
tslib_1.__decorate([
|
|
466
|
+
(0, type_graphql_1.Field)({ description: 'The currency' }),
|
|
467
|
+
tslib_1.__metadata("design:type", String)
|
|
468
|
+
], SaasCandidate.prototype, "currency", void 0);
|
|
469
|
+
tslib_1.__decorate([
|
|
470
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The bank account or card it went out of most - an id' }),
|
|
471
|
+
tslib_1.__metadata("design:type", String)
|
|
472
|
+
], SaasCandidate.prototype, "paymentAccountId", void 0);
|
|
473
|
+
tslib_1.__decorate([
|
|
474
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'The day of the month it went out on most; 31 for the last day' }),
|
|
475
|
+
tslib_1.__metadata("design:type", Number)
|
|
476
|
+
], SaasCandidate.prototype, "billingDay", void 0);
|
|
477
|
+
tslib_1.__decorate([
|
|
478
|
+
(0, type_graphql_1.Field)({ description: 'Whether a keeper hid it from the candidates' }),
|
|
479
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
480
|
+
], SaasCandidate.prototype, "hidden", void 0);
|
|
481
|
+
exports.SaasCandidate = SaasCandidate = tslib_1.__decorate([
|
|
482
|
+
(0, type_graphql_1.ObjectType)({ description: 'A vendor the books show paid month after month that is no account yet (ADR-0177 amendment 1)' })
|
|
483
|
+
], SaasCandidate);
|
|
484
|
+
let SaasLicenseSkuInput = class SaasLicenseSkuInput {
|
|
485
|
+
};
|
|
486
|
+
exports.SaasLicenseSkuInput = SaasLicenseSkuInput;
|
|
487
|
+
tslib_1.__decorate([
|
|
488
|
+
(0, type_graphql_1.Field)({ description: "The vendor's id for the plan - a Microsoft 365 SKU id" }),
|
|
489
|
+
tslib_1.__metadata("design:type", String)
|
|
490
|
+
], SaasLicenseSkuInput.prototype, "skuId", void 0);
|
|
491
|
+
tslib_1.__decorate([
|
|
492
|
+
(0, type_graphql_1.Field)({ description: "The vendor's name for the plan" }),
|
|
493
|
+
tslib_1.__metadata("design:type", String)
|
|
494
|
+
], SaasLicenseSkuInput.prototype, "name", void 0);
|
|
495
|
+
tslib_1.__decorate([
|
|
496
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'How many were bought' }),
|
|
497
|
+
tslib_1.__metadata("design:type", Number)
|
|
498
|
+
], SaasLicenseSkuInput.prototype, "enabled", void 0);
|
|
499
|
+
tslib_1.__decorate([
|
|
500
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'How many are assigned' }),
|
|
501
|
+
tslib_1.__metadata("design:type", Number)
|
|
502
|
+
], SaasLicenseSkuInput.prototype, "consumed", void 0);
|
|
503
|
+
tslib_1.__decorate([
|
|
504
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The day it renews, or a trial ends - YYYY-MM-DD' }),
|
|
505
|
+
tslib_1.__metadata("design:type", String)
|
|
506
|
+
], SaasLicenseSkuInput.prototype, "renewalOn", void 0);
|
|
507
|
+
tslib_1.__decorate([
|
|
508
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Whether it is a trial' }),
|
|
509
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
510
|
+
], SaasLicenseSkuInput.prototype, "trial", void 0);
|
|
511
|
+
exports.SaasLicenseSkuInput = SaasLicenseSkuInput = tslib_1.__decorate([
|
|
512
|
+
(0, type_graphql_1.InputType)({ description: 'A plan a vendor tenant bought (ADR-0177 amendment 2)' })
|
|
513
|
+
], SaasLicenseSkuInput);
|
|
514
|
+
let SaasLicenseUserInput = class SaasLicenseUserInput {
|
|
515
|
+
};
|
|
516
|
+
exports.SaasLicenseUserInput = SaasLicenseUserInput;
|
|
517
|
+
tslib_1.__decorate([
|
|
518
|
+
(0, type_graphql_1.Field)({ description: 'The login - a Microsoft 365 user name' }),
|
|
519
|
+
tslib_1.__metadata("design:type", String)
|
|
520
|
+
], SaasLicenseUserInput.prototype, "login", void 0);
|
|
521
|
+
tslib_1.__decorate([
|
|
522
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The mail' }),
|
|
523
|
+
tslib_1.__metadata("design:type", String)
|
|
524
|
+
], SaasLicenseUserInput.prototype, "mail", void 0);
|
|
525
|
+
tslib_1.__decorate([
|
|
526
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The name shown' }),
|
|
527
|
+
tslib_1.__metadata("design:type", String)
|
|
528
|
+
], SaasLicenseUserInput.prototype, "name", void 0);
|
|
529
|
+
tslib_1.__decorate([
|
|
530
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Whether the login can sign in' }),
|
|
531
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
532
|
+
], SaasLicenseUserInput.prototype, "enabled", void 0);
|
|
533
|
+
tslib_1.__decorate([
|
|
534
|
+
(0, type_graphql_1.Field)(type => [String], { description: 'The plans held - SKU ids' }),
|
|
535
|
+
tslib_1.__metadata("design:type", Array)
|
|
536
|
+
], SaasLicenseUserInput.prototype, "skuIds", void 0);
|
|
537
|
+
exports.SaasLicenseUserInput = SaasLicenseUserInput = tslib_1.__decorate([
|
|
538
|
+
(0, type_graphql_1.InputType)({ description: 'Who holds licenses at a vendor tenant' })
|
|
539
|
+
], SaasLicenseUserInput);
|
|
540
|
+
let SaasLicensesInput = class SaasLicensesInput {
|
|
541
|
+
};
|
|
542
|
+
exports.SaasLicensesInput = SaasLicensesInput;
|
|
543
|
+
tslib_1.__decorate([
|
|
544
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The tenant id' }),
|
|
545
|
+
tslib_1.__metadata("design:type", String)
|
|
546
|
+
], SaasLicensesInput.prototype, "tenantId", void 0);
|
|
547
|
+
tslib_1.__decorate([
|
|
548
|
+
(0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'The domains its users sign in with' }),
|
|
549
|
+
tslib_1.__metadata("design:type", Array)
|
|
550
|
+
], SaasLicensesInput.prototype, "domains", void 0);
|
|
551
|
+
tslib_1.__decorate([
|
|
552
|
+
(0, type_graphql_1.Field)(type => [SaasLicenseSkuInput], { description: 'The plans bought' }),
|
|
553
|
+
tslib_1.__metadata("design:type", Array)
|
|
554
|
+
], SaasLicensesInput.prototype, "skus", void 0);
|
|
555
|
+
tslib_1.__decorate([
|
|
556
|
+
(0, type_graphql_1.Field)(type => [SaasLicenseUserInput], { description: 'Who holds them' }),
|
|
557
|
+
tslib_1.__metadata("design:type", Array)
|
|
558
|
+
], SaasLicensesInput.prototype, "users", void 0);
|
|
559
|
+
exports.SaasLicensesInput = SaasLicensesInput = tslib_1.__decorate([
|
|
560
|
+
(0, type_graphql_1.InputType)({ description: 'What a vendor tenant bought and who holds each, as a scenario read it' })
|
|
561
|
+
], SaasLicensesInput);
|
|
562
|
+
let SaasLicenseSync = class SaasLicenseSync {
|
|
563
|
+
};
|
|
564
|
+
exports.SaasLicenseSync = SaasLicenseSync;
|
|
565
|
+
tslib_1.__decorate([
|
|
566
|
+
(0, type_graphql_1.Field)({ description: 'The account the tenant is' }),
|
|
567
|
+
tslib_1.__metadata("design:type", String)
|
|
568
|
+
], SaasLicenseSync.prototype, "accountId", void 0);
|
|
569
|
+
tslib_1.__decorate([
|
|
570
|
+
(0, type_graphql_1.Field)({ description: "The account's name" }),
|
|
571
|
+
tslib_1.__metadata("design:type", String)
|
|
572
|
+
], SaasLicenseSync.prototype, "accountName", void 0);
|
|
573
|
+
tslib_1.__decorate([
|
|
574
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Plans kept in step' }),
|
|
575
|
+
tslib_1.__metadata("design:type", Number)
|
|
576
|
+
], SaasLicenseSync.prototype, "subscriptions", void 0);
|
|
577
|
+
tslib_1.__decorate([
|
|
578
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Plans added as subscriptions' }),
|
|
579
|
+
tslib_1.__metadata("design:type", Number)
|
|
580
|
+
], SaasLicenseSync.prototype, "created", void 0);
|
|
581
|
+
tslib_1.__decorate([
|
|
582
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Seats opened' }),
|
|
583
|
+
tslib_1.__metadata("design:type", Number)
|
|
584
|
+
], SaasLicenseSync.prototype, "opened", void 0);
|
|
585
|
+
tslib_1.__decorate([
|
|
586
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Seats closed' }),
|
|
587
|
+
tslib_1.__metadata("design:type", Number)
|
|
588
|
+
], SaasLicenseSync.prototype, "closed", void 0);
|
|
589
|
+
tslib_1.__decorate([
|
|
590
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Open seats held as names that took their employee' }),
|
|
591
|
+
tslib_1.__metadata("design:type", Number)
|
|
592
|
+
], SaasLicenseSync.prototype, "linked", void 0);
|
|
593
|
+
tslib_1.__decorate([
|
|
594
|
+
(0, type_graphql_1.Field)(type => [String], { description: 'Logins no employee mail matched - held as names' }),
|
|
595
|
+
tslib_1.__metadata("design:type", Array)
|
|
596
|
+
], SaasLicenseSync.prototype, "unmatched", void 0);
|
|
597
|
+
exports.SaasLicenseSync = SaasLicenseSync = tslib_1.__decorate([
|
|
598
|
+
(0, type_graphql_1.ObjectType)({ description: 'What a sync of a vendor tenant changed' })
|
|
599
|
+
], SaasLicenseSync);
|
|
600
|
+
let SaasCostLineInput = class SaasCostLineInput {
|
|
601
|
+
};
|
|
602
|
+
exports.SaasCostLineInput = SaasCostLineInput;
|
|
603
|
+
tslib_1.__decorate([
|
|
604
|
+
(0, type_graphql_1.Field)({ description: 'What it is for - a model' }),
|
|
605
|
+
tslib_1.__metadata("design:type", String)
|
|
606
|
+
], SaasCostLineInput.prototype, "label", void 0);
|
|
607
|
+
tslib_1.__decorate([
|
|
608
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'What it cost, in the report currency' }),
|
|
609
|
+
tslib_1.__metadata("design:type", Number)
|
|
610
|
+
], SaasCostLineInput.prototype, "amount", void 0);
|
|
611
|
+
exports.SaasCostLineInput = SaasCostLineInput = tslib_1.__decorate([
|
|
612
|
+
(0, type_graphql_1.InputType)({ description: 'A line of a month a vendor cost report gives (ADR-0177 amendment 3)' })
|
|
613
|
+
], SaasCostLineInput);
|
|
614
|
+
let SaasCostMonthInput = class SaasCostMonthInput {
|
|
615
|
+
};
|
|
616
|
+
exports.SaasCostMonthInput = SaasCostMonthInput;
|
|
617
|
+
tslib_1.__decorate([
|
|
618
|
+
(0, type_graphql_1.Field)({ description: 'The month - YYYY-MM' }),
|
|
619
|
+
tslib_1.__metadata("design:type", String)
|
|
620
|
+
], SaasCostMonthInput.prototype, "month", void 0);
|
|
621
|
+
tslib_1.__decorate([
|
|
622
|
+
(0, type_graphql_1.Field)(type => [SaasCostLineInput], { description: 'Its lines' }),
|
|
623
|
+
tslib_1.__metadata("design:type", Array)
|
|
624
|
+
], SaasCostMonthInput.prototype, "lines", void 0);
|
|
625
|
+
exports.SaasCostMonthInput = SaasCostMonthInput = tslib_1.__decorate([
|
|
626
|
+
(0, type_graphql_1.InputType)({ description: 'A month of a vendor cost report' })
|
|
627
|
+
], SaasCostMonthInput);
|
|
628
|
+
let SaasCostsInput = class SaasCostsInput {
|
|
629
|
+
};
|
|
630
|
+
exports.SaasCostsInput = SaasCostsInput;
|
|
631
|
+
tslib_1.__decorate([
|
|
632
|
+
(0, type_graphql_1.Field)({ description: "The vendor's id for the account - an Anthropic organization id" }),
|
|
633
|
+
tslib_1.__metadata("design:type", String)
|
|
634
|
+
], SaasCostsInput.prototype, "vendorAccount", void 0);
|
|
635
|
+
tslib_1.__decorate([
|
|
636
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "The vendor's name for the account" }),
|
|
637
|
+
tslib_1.__metadata("design:type", String)
|
|
638
|
+
], SaasCostsInput.prototype, "vendorName", void 0);
|
|
639
|
+
tslib_1.__decorate([
|
|
640
|
+
(0, type_graphql_1.Field)({ description: 'The currency of the amounts' }),
|
|
641
|
+
tslib_1.__metadata("design:type", String)
|
|
642
|
+
], SaasCostsInput.prototype, "currency", void 0);
|
|
643
|
+
tslib_1.__decorate([
|
|
644
|
+
(0, type_graphql_1.Field)(type => [SaasCostMonthInput], { description: 'The months' }),
|
|
645
|
+
tslib_1.__metadata("design:type", Array)
|
|
646
|
+
], SaasCostsInput.prototype, "months", void 0);
|
|
647
|
+
exports.SaasCostsInput = SaasCostsInput = tslib_1.__decorate([
|
|
648
|
+
(0, type_graphql_1.InputType)({ description: "What a vendor's cost report says, month by month, as a scenario read it" })
|
|
649
|
+
], SaasCostsInput);
|
|
650
|
+
let SaasCostSync = class SaasCostSync {
|
|
651
|
+
};
|
|
652
|
+
exports.SaasCostSync = SaasCostSync;
|
|
653
|
+
tslib_1.__decorate([
|
|
654
|
+
(0, type_graphql_1.Field)({ description: 'The account' }),
|
|
655
|
+
tslib_1.__metadata("design:type", String)
|
|
656
|
+
], SaasCostSync.prototype, "accountId", void 0);
|
|
657
|
+
tslib_1.__decorate([
|
|
658
|
+
(0, type_graphql_1.Field)({ description: "The account's name" }),
|
|
659
|
+
tslib_1.__metadata("design:type", String)
|
|
660
|
+
], SaasCostSync.prototype, "accountName", void 0);
|
|
661
|
+
tslib_1.__decorate([
|
|
662
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Bills written' }),
|
|
663
|
+
tslib_1.__metadata("design:type", Number)
|
|
664
|
+
], SaasCostSync.prototype, "written", void 0);
|
|
665
|
+
tslib_1.__decorate([
|
|
666
|
+
(0, type_graphql_1.Field)(type => [String], { description: 'Months whose bill a person wrote - kept' }),
|
|
667
|
+
tslib_1.__metadata("design:type", Array)
|
|
668
|
+
], SaasCostSync.prototype, "kept", void 0);
|
|
669
|
+
exports.SaasCostSync = SaasCostSync = tslib_1.__decorate([
|
|
670
|
+
(0, type_graphql_1.ObjectType)({ description: 'What a sync of a vendor cost report wrote' })
|
|
671
|
+
], SaasCostSync);
|
|
672
|
+
let SaasCostSyncs = class SaasCostSyncs {
|
|
673
|
+
};
|
|
674
|
+
exports.SaasCostSyncs = SaasCostSyncs;
|
|
675
|
+
tslib_1.__decorate([
|
|
676
|
+
(0, type_graphql_1.Field)(type => [SaasCostSync], { description: 'The accounts written' }),
|
|
677
|
+
tslib_1.__metadata("design:type", Array)
|
|
678
|
+
], SaasCostSyncs.prototype, "results", void 0);
|
|
679
|
+
tslib_1.__decorate([
|
|
680
|
+
(0, type_graphql_1.Field)(type => [String], { description: 'Vendor accounts no subscription account names - passed over' }),
|
|
681
|
+
tslib_1.__metadata("design:type", Array)
|
|
682
|
+
], SaasCostSyncs.prototype, "unmatched", void 0);
|
|
683
|
+
exports.SaasCostSyncs = SaasCostSyncs = tslib_1.__decorate([
|
|
684
|
+
(0, type_graphql_1.ObjectType)({ description: 'What a sync of vendor cost reports wrote, an account each' })
|
|
685
|
+
], SaasCostSyncs);
|
|
430
686
|
//# sourceMappingURL=saas-type.js.map
|