@things-factory/biz-base 10.1.206 → 10.1.212

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.
@@ -18,8 +18,8 @@ type Saved = (tx: EntityManager, domain: {
18
18
  export declare function onCounterpartyBankAccountsSaved(listener: Saved): void;
19
19
  /**
20
20
  * Apply grid rows to a domain's counterparty accounts: `+` (or no id) registers, `M` changes, `-` deletes (softly — its
21
- * passbook copy stays with it). An account number held by two live accounts is refused, naming the counterparty that
22
- * holds it.
21
+ * passbook copy stays with it). An account a payment request or any other row still names is not deleted
22
+ * (§`refuseTiedRows`). An account number held by two live accounts is refused, naming the counterparty that holds it.
23
23
  */
24
24
  export declare function saveCounterpartyBankAccounts(tx: EntityManager, domain: {
25
25
  id: string;
@@ -44,8 +44,8 @@ function rowOf(patch, isNew) {
44
44
  }
45
45
  /**
46
46
  * Apply grid rows to a domain's counterparty accounts: `+` (or no id) registers, `M` changes, `-` deletes (softly — its
47
- * passbook copy stays with it). An account number held by two live accounts is refused, naming the counterparty that
48
- * holds it.
47
+ * passbook copy stays with it). An account a payment request or any other row still names is not deleted
48
+ * (§`refuseTiedRows`). An account number held by two live accounts is refused, naming the counterparty that holds it.
49
49
  */
50
50
  async function saveCounterpartyBankAccounts(tx, domain, user, patches) {
51
51
  const repo = tx.getRepository(counterparty_bank_account_1.CounterpartyBankAccount);
@@ -69,8 +69,10 @@ async function saveCounterpartyBankAccounts(tx, domain, user, patches) {
69
69
  })), deletes);
70
70
  if (clash)
71
71
  throw new shell_1.Refusal('counterparty-account-clash', clash, `account ${clash.number} is already registered to '${clash.existing}'`);
72
- if (deletes.length)
72
+ if (deletes.length) {
73
+ await (0, shell_1.refuseTiedRows)(tx, counterparty_bank_account_1.CounterpartyBankAccount, deletes);
73
74
  await repo.softDelete({ id: (0, typeorm_1.In)(deletes) });
75
+ }
74
76
  const ids = [];
75
77
  for (const { counterpartyId, ...row } of updates) {
76
78
  const current = owned.get(row.id);
@@ -1 +1 @@
1
- {"version":3,"file":"counterparty-bank-account-service.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-bank-account-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { Counterparty } from './counterparty'\nimport { CounterpartyBankAccount } from './counterparty-bank-account'\nimport { accountKey, keyClash } from './counterparty-names'\nimport { CounterpartyBankAccountPatch } from './counterparty-type'\n\n/**\n * Saving the accounts counterparties are paid into (ADR-0134), and what a module does once they are saved.\n *\n * A module whose rows carry an account number the bank wrote (accounting's bank lines) links them to the counterparty\n * the number is registered to — that is its rule, not this one's — by listening here (§`onCounterpartyBankAccountsSaved`).\n */\n\n/** The refType of a counterparty's business registration certificate. */\nexport const BUSINESS_REGISTRATION_REF_TYPE = 'Counterparty Business Registration'\n/** The refType of the passbook copy of a counterparty account - it shows the account number. */\nexport const PASSBOOK_REF_TYPE = 'Counterparty Passbook'\n\ntype Saved = (tx: EntityManager, domain: { id: string }, saved: CounterpartyBankAccount[], user: any) => Promise<void>\nconst listeners: Saved[] = []\n\n/** Run after every save of counterparty accounts, in the same transaction. */\nexport function onCounterpartyBankAccountsSaved(listener: Saved): void {\n listeners.push(listener)\n}\n\nconst TEXT = ['bankName', 'holderName', 'notes'] as const\n\nfunction rowOf(patch: CounterpartyBankAccountPatch, isNew: boolean): Partial<CounterpartyBankAccount> & { counterpartyId?: string } {\n const row: Record<string, any> = {}\n for (const field of TEXT) if (patch[field] !== undefined) row[field] = String(patch[field] ?? '').trim() || null\n if (patch.accountNumber !== undefined || isNew) {\n const key = accountKey(patch.accountNumber)\n if (!key) throw new Refusal('counterparty-account-number-required', {}, 'an account needs its number')\n row.accountNumber = key\n }\n if (patch.counterpartyId !== undefined || isNew) {\n if (!patch.counterpartyId) throw new Refusal('counterparty-not-found', {}, 'an account needs its counterparty')\n row.counterpartyId = patch.counterpartyId\n }\n return row\n}\n\n/**\n * Apply grid rows to a domain's counterparty accounts: `+` (or no id) registers, `M` changes, `-` deletes (softly — its\n * passbook copy stays with it). An account number held by two live accounts is refused, naming the counterparty that\n * holds it.\n */\nexport async function saveCounterpartyBankAccounts(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n patches: CounterpartyBankAccountPatch[]\n): Promise<CounterpartyBankAccount[]> {\n const repo = tx.getRepository(CounterpartyBankAccount)\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n const updates = patches.filter(one => one.id && one.cuFlag !== '-' && one.cuFlag !== '+').map(one => ({ id: one.id!, ...rowOf(one, false) }))\n const creates = patches.filter(one => !one.id || one.cuFlag === '+').filter(one => one.cuFlag !== '-').map(one => rowOf(one, true))\n\n const parties = new Map(\n (await tx.getRepository(Counterparty).find({ where: { domain: { id: domain.id } }, select: ['id', 'name'] })).map(one => [one.id, one])\n )\n for (const row of [...updates, ...creates])\n if (row.counterpartyId && !parties.has(row.counterpartyId))\n throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n\n const existing = await repo.find({ where: { domain: { id: domain.id } } })\n const owned = new Map(existing.map(one => [one.id, one]))\n for (const id of [...deletes, ...updates.map(one => one.id)])\n if (!owned.has(id)) throw new Refusal('counterparty-account-not-found', {}, 'an account was not found - reload and try again')\n\n const holder = (counterpartyId?: string) => parties.get(counterpartyId ?? '')?.name ?? ''\n const clash = keyClash(\n existing.map(one => ({ id: one.id, label: holder(one.counterpartyId), key: one.accountNumber })),\n [...updates, ...creates].map(one => ({\n id: (one as any).id,\n label: one.counterpartyId ? holder(one.counterpartyId) : undefined,\n key: one.accountNumber\n })),\n deletes\n )\n if (clash)\n throw new Refusal('counterparty-account-clash', clash, `account ${clash.number} is already registered to '${clash.existing}'`)\n\n if (deletes.length) await repo.softDelete({ id: In(deletes) })\n\n const ids: string[] = []\n for (const { counterpartyId, ...row } of updates) {\n const current = owned.get(row.id)!\n ids.push((await repo.save({ ...current, ...row, ...(counterpartyId ? { counterparty: { id: counterpartyId } } : {}), updater: user } as CounterpartyBankAccount)).id)\n }\n for (const { counterpartyId, ...row } of creates)\n ids.push((await repo.save({ ...row, counterparty: { id: counterpartyId }, domain, creator: user, updater: user } as CounterpartyBankAccount)).id)\n\n /* Read back, so a listener has the counterparty of each as well as its number. */\n const saved = ids.length ? await repo.find({ where: { id: In(ids) } }) : []\n for (const listener of listeners) await listener(tx, domain, saved, user)\n return saved\n}\n"]}
1
+ {"version":3,"file":"counterparty-bank-account-service.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-bank-account-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal, refuseTiedRows } from '@things-factory/shell'\n\nimport { Counterparty } from './counterparty'\nimport { CounterpartyBankAccount } from './counterparty-bank-account'\nimport { accountKey, keyClash } from './counterparty-names'\nimport { CounterpartyBankAccountPatch } from './counterparty-type'\n\n/**\n * Saving the accounts counterparties are paid into (ADR-0134), and what a module does once they are saved.\n *\n * A module whose rows carry an account number the bank wrote (accounting's bank lines) links them to the counterparty\n * the number is registered to — that is its rule, not this one's — by listening here (§`onCounterpartyBankAccountsSaved`).\n */\n\n/** The refType of a counterparty's business registration certificate. */\nexport const BUSINESS_REGISTRATION_REF_TYPE = 'Counterparty Business Registration'\n/** The refType of the passbook copy of a counterparty account - it shows the account number. */\nexport const PASSBOOK_REF_TYPE = 'Counterparty Passbook'\n\ntype Saved = (tx: EntityManager, domain: { id: string }, saved: CounterpartyBankAccount[], user: any) => Promise<void>\nconst listeners: Saved[] = []\n\n/** Run after every save of counterparty accounts, in the same transaction. */\nexport function onCounterpartyBankAccountsSaved(listener: Saved): void {\n listeners.push(listener)\n}\n\nconst TEXT = ['bankName', 'holderName', 'notes'] as const\n\nfunction rowOf(patch: CounterpartyBankAccountPatch, isNew: boolean): Partial<CounterpartyBankAccount> & { counterpartyId?: string } {\n const row: Record<string, any> = {}\n for (const field of TEXT) if (patch[field] !== undefined) row[field] = String(patch[field] ?? '').trim() || null\n if (patch.accountNumber !== undefined || isNew) {\n const key = accountKey(patch.accountNumber)\n if (!key) throw new Refusal('counterparty-account-number-required', {}, 'an account needs its number')\n row.accountNumber = key\n }\n if (patch.counterpartyId !== undefined || isNew) {\n if (!patch.counterpartyId) throw new Refusal('counterparty-not-found', {}, 'an account needs its counterparty')\n row.counterpartyId = patch.counterpartyId\n }\n return row\n}\n\n/**\n * Apply grid rows to a domain's counterparty accounts: `+` (or no id) registers, `M` changes, `-` deletes (softly — its\n * passbook copy stays with it). An account a payment request or any other row still names is not deleted\n * (§`refuseTiedRows`). An account number held by two live accounts is refused, naming the counterparty that holds it.\n */\nexport async function saveCounterpartyBankAccounts(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n patches: CounterpartyBankAccountPatch[]\n): Promise<CounterpartyBankAccount[]> {\n const repo = tx.getRepository(CounterpartyBankAccount)\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n const updates = patches.filter(one => one.id && one.cuFlag !== '-' && one.cuFlag !== '+').map(one => ({ id: one.id!, ...rowOf(one, false) }))\n const creates = patches.filter(one => !one.id || one.cuFlag === '+').filter(one => one.cuFlag !== '-').map(one => rowOf(one, true))\n\n const parties = new Map(\n (await tx.getRepository(Counterparty).find({ where: { domain: { id: domain.id } }, select: ['id', 'name'] })).map(one => [one.id, one])\n )\n for (const row of [...updates, ...creates])\n if (row.counterpartyId && !parties.has(row.counterpartyId))\n throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n\n const existing = await repo.find({ where: { domain: { id: domain.id } } })\n const owned = new Map(existing.map(one => [one.id, one]))\n for (const id of [...deletes, ...updates.map(one => one.id)])\n if (!owned.has(id)) throw new Refusal('counterparty-account-not-found', {}, 'an account was not found - reload and try again')\n\n const holder = (counterpartyId?: string) => parties.get(counterpartyId ?? '')?.name ?? ''\n const clash = keyClash(\n existing.map(one => ({ id: one.id, label: holder(one.counterpartyId), key: one.accountNumber })),\n [...updates, ...creates].map(one => ({\n id: (one as any).id,\n label: one.counterpartyId ? holder(one.counterpartyId) : undefined,\n key: one.accountNumber\n })),\n deletes\n )\n if (clash)\n throw new Refusal('counterparty-account-clash', clash, `account ${clash.number} is already registered to '${clash.existing}'`)\n\n if (deletes.length) {\n await refuseTiedRows(tx, CounterpartyBankAccount, deletes)\n await repo.softDelete({ id: In(deletes) })\n }\n\n const ids: string[] = []\n for (const { counterpartyId, ...row } of updates) {\n const current = owned.get(row.id)!\n ids.push((await repo.save({ ...current, ...row, ...(counterpartyId ? { counterparty: { id: counterpartyId } } : {}), updater: user } as CounterpartyBankAccount)).id)\n }\n for (const { counterpartyId, ...row } of creates)\n ids.push((await repo.save({ ...row, counterparty: { id: counterpartyId }, domain, creator: user, updater: user } as CounterpartyBankAccount)).id)\n\n /* Read back, so a listener has the counterparty of each as well as its number. */\n const saved = ids.length ? await repo.find({ where: { id: In(ids) } }) : []\n for (const listener of listeners) await listener(tx, domain, saved, user)\n return saved\n}\n"]}
@@ -66,7 +66,7 @@ tslib_1.__decorate([
66
66
  (0, type_graphql_1.Directive)('@transaction'),
67
67
  (0, type_graphql_1.Directive)('@privilege(category: "counterparty", privilege: "mutation", domainOwnerGranted: true)'),
68
68
  (0, type_graphql_1.Mutation)(returns => Boolean, {
69
- description: 'To delete counterparties - the rows that name them keep the name and lose the link'
69
+ description: 'To delete counterparties - refused while another row still names one'
70
70
  }),
71
71
  tslib_1.__param(0, (0, type_graphql_1.Arg)('ids', type => [String])),
72
72
  tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
@@ -1 +1 @@
1
- {"version":3,"file":"counterparty-mutation.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { SpeakRefusals } from '@things-factory/shell'\n\nimport { Counterparty } from './counterparty'\nimport { saveCounterparties } from './counterparty-service'\nimport { CounterpartyPatch, NewCounterparty } from './counterparty-type'\n\n@Resolver(Counterparty)\n@UseMiddleware(SpeakRefusals)\nexport class CounterpartyMutation {\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Counterparty, { description: 'To register a counterparty' })\n async createCounterparty(\n @Arg('counterparty') input: NewCounterparty,\n @Ctx() context: ResolverContext\n ): Promise<Counterparty> {\n const { tx, domain, user } = context.state\n const [saved] = await saveCounterparties(tx, domain, user, [{ ...input, cuFlag: '+' }])\n return saved\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Counterparty, { description: 'To change a counterparty' })\n async updateCounterparty(\n @Arg('id') id: string,\n @Arg('patch') patch: CounterpartyPatch,\n @Ctx() context: ResolverContext\n ): Promise<Counterparty> {\n const { tx, domain, user } = context.state\n const [saved] = await saveCounterparties(tx, domain, user, [{ ...patch, id, cuFlag: 'M' }])\n return saved\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => [Counterparty], {\n description: 'To save the counterparty grid - new, changed and deleted rows together'\n })\n async updateMultipleCounterparty(\n @Arg('patches', type => [CounterpartyPatch]) patches: CounterpartyPatch[],\n @Ctx() context: ResolverContext\n ): Promise<Counterparty[]> {\n const { tx, domain, user } = context.state\n return await saveCounterparties(tx, domain, user, patches)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'To delete counterparties - the rows that name them keep the name and lose the link'\n })\n async deleteCounterparties(\n @Arg('ids', type => [String]) ids: string[],\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const { tx, domain, user } = context.state\n await saveCounterparties(\n tx,\n domain,\n user,\n ids.map(id => ({ id, cuFlag: '-' }))\n )\n return true\n }\n}\n"]}
1
+ {"version":3,"file":"counterparty-mutation.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { SpeakRefusals } from '@things-factory/shell'\n\nimport { Counterparty } from './counterparty'\nimport { saveCounterparties } from './counterparty-service'\nimport { CounterpartyPatch, NewCounterparty } from './counterparty-type'\n\n@Resolver(Counterparty)\n@UseMiddleware(SpeakRefusals)\nexport class CounterpartyMutation {\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Counterparty, { description: 'To register a counterparty' })\n async createCounterparty(\n @Arg('counterparty') input: NewCounterparty,\n @Ctx() context: ResolverContext\n ): Promise<Counterparty> {\n const { tx, domain, user } = context.state\n const [saved] = await saveCounterparties(tx, domain, user, [{ ...input, cuFlag: '+' }])\n return saved\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Counterparty, { description: 'To change a counterparty' })\n async updateCounterparty(\n @Arg('id') id: string,\n @Arg('patch') patch: CounterpartyPatch,\n @Ctx() context: ResolverContext\n ): Promise<Counterparty> {\n const { tx, domain, user } = context.state\n const [saved] = await saveCounterparties(tx, domain, user, [{ ...patch, id, cuFlag: 'M' }])\n return saved\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => [Counterparty], {\n description: 'To save the counterparty grid - new, changed and deleted rows together'\n })\n async updateMultipleCounterparty(\n @Arg('patches', type => [CounterpartyPatch]) patches: CounterpartyPatch[],\n @Ctx() context: ResolverContext\n ): Promise<Counterparty[]> {\n const { tx, domain, user } = context.state\n return await saveCounterparties(tx, domain, user, patches)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"counterparty\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'To delete counterparties - refused while another row still names one'\n })\n async deleteCounterparties(\n @Arg('ids', type => [String]) ids: string[],\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const { tx, domain, user } = context.state\n await saveCounterparties(\n tx,\n domain,\n user,\n ids.map(id => ({ id, cuFlag: '-' }))\n )\n return true\n }\n}\n"]}
@@ -13,9 +13,9 @@ type Saved = (tx: EntityManager, domain: {
13
13
  /** Run after every save of counterparties, in the same transaction. */
14
14
  export declare function onCounterpartiesSaved(listener: Saved): void;
15
15
  /**
16
- * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly — the rows
17
- * that name it keep the name). Two counterparties with one business registration number are refused (§`numberClash`);
18
- * a name may be shared.
16
+ * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly). A
17
+ * counterparty a contract, a bank line or any other row still names is not deleted (§`refuseTiedRows`). Two
18
+ * counterparties with one business registration number are refused (§`numberClash`); a name may be shared.
19
19
  */
20
20
  export declare function saveCounterparties(tx: EntityManager, domain: {
21
21
  id: string;
@@ -52,9 +52,9 @@ function rowOf(patch, isNew) {
52
52
  return row;
53
53
  }
54
54
  /**
55
- * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly — the rows
56
- * that name it keep the name). Two counterparties with one business registration number are refused (§`numberClash`);
57
- * a name may be shared.
55
+ * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly). A
56
+ * counterparty a contract, a bank line or any other row still names is not deleted (§`refuseTiedRows`). Two
57
+ * counterparties with one business registration number are refused (§`numberClash`); a name may be shared.
58
58
  */
59
59
  async function saveCounterparties(tx, domain, user, patches) {
60
60
  const repo = tx.getRepository(counterparty_1.Counterparty);
@@ -69,11 +69,12 @@ async function saveCounterparties(tx, domain, user, patches) {
69
69
  const owned = await repo.find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(deletes) }, select: ['id'] });
70
70
  if (owned.length !== deletes.length)
71
71
  throw new shell_1.Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again');
72
- /* Its accounts go with it - a number stays free for whoever holds it next. Found first: a deleted counterparty joins nothing. */
73
- const accounts = await tx.getRepository(counterparty_bank_account_1.CounterpartyBankAccount).find({ where: { counterparty: { id: (0, typeorm_1.In)(deletes) } }, select: ['id'] });
74
- await repo.softDelete({ id: (0, typeorm_1.In)(deletes) });
72
+ /* Its accounts are its parts and go first, so they are not counted as tied - a number stays free for whoever holds it next. */
73
+ const accounts = (await tx.getRepository(counterparty_bank_account_1.CounterpartyBankAccount).find({ where: { counterparty: { id: (0, typeorm_1.In)(deletes) } }, select: ['id'] })).map(one => one.id);
75
74
  if (accounts.length)
76
- await tx.getRepository(counterparty_bank_account_1.CounterpartyBankAccount).softDelete({ id: (0, typeorm_1.In)(accounts.map(one => one.id)) });
75
+ await tx.getRepository(counterparty_bank_account_1.CounterpartyBankAccount).softDelete({ id: (0, typeorm_1.In)(accounts) });
76
+ await (0, shell_1.refuseTiedRows)(tx, counterparty_1.Counterparty, deletes);
77
+ await repo.softDelete({ id: (0, typeorm_1.In)(deletes) });
77
78
  }
78
79
  const saved = [];
79
80
  for (const row of updates) {
@@ -1 +1 @@
1
- {"version":3,"file":"counterparty-service.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { Counterparty, CounterpartyKind } from './counterparty'\nimport { CounterpartyBankAccount } from './counterparty-bank-account'\nimport { CounterpartyPatch } from './counterparty-type'\nimport { normalizeBusinessNumber, numberClash } from './counterparty-names'\n\n/**\n * Saving the counterparty list of a domain, and what an app does once it is saved.\n *\n * A module whose documents keep a counterparty's name (a contract, a bank line) links the rows that carry a newly\n * registered name — that is its rule, not this one's — by listening here (§`onCounterpartiesSaved`).\n */\n\ntype Saved = (tx: EntityManager, domain: { id: string }, saved: Counterparty[]) => Promise<void>\nconst listeners: Saved[] = []\n\n/** Run after every save of counterparties, in the same transaction. */\nexport function onCounterpartiesSaved(listener: Saved): void {\n listeners.push(listener)\n}\n\n/** The words people write for the kind, as well as the values themselves. */\nconst KIND_WORDS: Record<CounterpartyKind, string[]> = {\n [CounterpartyKind.COMPANY]: ['COMPANY', '회사', '법인', '사업자', '기업'],\n [CounterpartyKind.PERSON]: ['PERSON', '개인']\n}\n\nfunction kindOf(value: unknown): CounterpartyKind | undefined {\n const text = String(value ?? '').trim()\n if (!text) return undefined\n const found = (Object.keys(KIND_WORDS) as CounterpartyKind[]).find(kind => KIND_WORDS[kind].some(word => word.toLowerCase() === text.toLowerCase()))\n if (!found) throw new Refusal('counterparty-kind-invalid', { text }, `'${text}' is neither a company nor a person`)\n return found\n}\n\nconst TEXT = ['aliases', 'branchNumber', 'representative', 'address', 'billingAddress', 'businessType', 'businessItem', 'notes'] as const\n\n/** A patch as the row it writes: names trimmed, the kind and the business number in their stored form. */\nfunction rowOf(patch: Partial<CounterpartyPatch>, isNew: boolean): Partial<Counterparty> {\n const row: Record<string, any> = {}\n if (patch.name !== undefined || isNew) {\n const name = String(patch.name ?? '').trim()\n if (!name) throw new Refusal('counterparty-name-required', {}, 'a counterparty needs a name')\n row.name = name\n }\n for (const field of TEXT) if (patch[field] !== undefined) row[field] = String(patch[field] ?? '').trim() || null\n if (patch.kind !== undefined) row.kind = kindOf(patch.kind) ?? CounterpartyKind.COMPANY\n else if (isNew) row.kind = CounterpartyKind.COMPANY\n if (patch.businessNumber !== undefined) row.businessNumber = normalizeBusinessNumber(patch.businessNumber)\n if (patch.zeroRated !== undefined) row.zeroRated = !!patch.zeroRated\n if (patch.active !== undefined) row.active = patch.active !== false\n return row\n}\n\n/**\n * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly — the rows\n * that name it keep the name). Two counterparties with one business registration number are refused (§`numberClash`);\n * a name may be shared.\n */\nexport async function saveCounterparties(tx: EntityManager, domain: { id: string }, user: any, patches: CounterpartyPatch[]): Promise<Counterparty[]> {\n const repo = tx.getRepository(Counterparty)\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n const updates = patches.filter(one => one.id && one.cuFlag !== '-' && one.cuFlag !== '+').map(one => ({ id: one.id!, ...rowOf(one, false) }))\n const creates = patches.filter(one => !one.id || one.cuFlag === '+').filter(one => one.cuFlag !== '-').map(one => rowOf(one, true))\n\n const existing = await repo.find({ where: { domain: { id: domain.id } }, select: ['id', 'name', 'businessNumber'] })\n const clash = numberClash(existing, [...updates, ...creates], deletes)\n if (clash)\n throw new Refusal('counterparty-number-clash', clash, `business number ${clash.number} is already '${clash.existing}' - edit that counterparty instead`)\n\n if (deletes.length) {\n const owned = await repo.find({ where: { domain: { id: domain.id }, id: In(deletes) }, select: ['id'] })\n if (owned.length !== deletes.length) throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n /* Its accounts go with it - a number stays free for whoever holds it next. Found first: a deleted counterparty joins nothing. */\n const accounts = await tx.getRepository(CounterpartyBankAccount).find({ where: { counterparty: { id: In(deletes) } }, select: ['id'] })\n await repo.softDelete({ id: In(deletes) })\n if (accounts.length) await tx.getRepository(CounterpartyBankAccount).softDelete({ id: In(accounts.map(one => one.id)) })\n }\n\n const saved: Counterparty[] = []\n for (const row of updates) {\n const current = await repo.findOne({ where: { domain: { id: domain.id }, id: row.id } })\n if (!current) throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n saved.push(await repo.save({ ...current, ...row, updater: user }))\n }\n for (const row of creates) saved.push(await repo.save({ ...row, domain, creator: user, updater: user } as Counterparty))\n\n for (const listener of listeners) await listener(tx, domain, saved)\n return saved\n}\n"]}
1
+ {"version":3,"file":"counterparty-service.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal, refuseTiedRows } from '@things-factory/shell'\n\nimport { Counterparty, CounterpartyKind } from './counterparty'\nimport { CounterpartyBankAccount } from './counterparty-bank-account'\nimport { CounterpartyPatch } from './counterparty-type'\nimport { normalizeBusinessNumber, numberClash } from './counterparty-names'\n\n/**\n * Saving the counterparty list of a domain, and what an app does once it is saved.\n *\n * A module whose documents keep a counterparty's name (a contract, a bank line) links the rows that carry a newly\n * registered name — that is its rule, not this one's — by listening here (§`onCounterpartiesSaved`).\n */\n\ntype Saved = (tx: EntityManager, domain: { id: string }, saved: Counterparty[]) => Promise<void>\nconst listeners: Saved[] = []\n\n/** Run after every save of counterparties, in the same transaction. */\nexport function onCounterpartiesSaved(listener: Saved): void {\n listeners.push(listener)\n}\n\n/** The words people write for the kind, as well as the values themselves. */\nconst KIND_WORDS: Record<CounterpartyKind, string[]> = {\n [CounterpartyKind.COMPANY]: ['COMPANY', '회사', '법인', '사업자', '기업'],\n [CounterpartyKind.PERSON]: ['PERSON', '개인']\n}\n\nfunction kindOf(value: unknown): CounterpartyKind | undefined {\n const text = String(value ?? '').trim()\n if (!text) return undefined\n const found = (Object.keys(KIND_WORDS) as CounterpartyKind[]).find(kind => KIND_WORDS[kind].some(word => word.toLowerCase() === text.toLowerCase()))\n if (!found) throw new Refusal('counterparty-kind-invalid', { text }, `'${text}' is neither a company nor a person`)\n return found\n}\n\nconst TEXT = ['aliases', 'branchNumber', 'representative', 'address', 'billingAddress', 'businessType', 'businessItem', 'notes'] as const\n\n/** A patch as the row it writes: names trimmed, the kind and the business number in their stored form. */\nfunction rowOf(patch: Partial<CounterpartyPatch>, isNew: boolean): Partial<Counterparty> {\n const row: Record<string, any> = {}\n if (patch.name !== undefined || isNew) {\n const name = String(patch.name ?? '').trim()\n if (!name) throw new Refusal('counterparty-name-required', {}, 'a counterparty needs a name')\n row.name = name\n }\n for (const field of TEXT) if (patch[field] !== undefined) row[field] = String(patch[field] ?? '').trim() || null\n if (patch.kind !== undefined) row.kind = kindOf(patch.kind) ?? CounterpartyKind.COMPANY\n else if (isNew) row.kind = CounterpartyKind.COMPANY\n if (patch.businessNumber !== undefined) row.businessNumber = normalizeBusinessNumber(patch.businessNumber)\n if (patch.zeroRated !== undefined) row.zeroRated = !!patch.zeroRated\n if (patch.active !== undefined) row.active = patch.active !== false\n return row\n}\n\n/**\n * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly). A\n * counterparty a contract, a bank line or any other row still names is not deleted (§`refuseTiedRows`). Two\n * counterparties with one business registration number are refused (§`numberClash`); a name may be shared.\n */\nexport async function saveCounterparties(tx: EntityManager, domain: { id: string }, user: any, patches: CounterpartyPatch[]): Promise<Counterparty[]> {\n const repo = tx.getRepository(Counterparty)\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n const updates = patches.filter(one => one.id && one.cuFlag !== '-' && one.cuFlag !== '+').map(one => ({ id: one.id!, ...rowOf(one, false) }))\n const creates = patches.filter(one => !one.id || one.cuFlag === '+').filter(one => one.cuFlag !== '-').map(one => rowOf(one, true))\n\n const existing = await repo.find({ where: { domain: { id: domain.id } }, select: ['id', 'name', 'businessNumber'] })\n const clash = numberClash(existing, [...updates, ...creates], deletes)\n if (clash)\n throw new Refusal('counterparty-number-clash', clash, `business number ${clash.number} is already '${clash.existing}' - edit that counterparty instead`)\n\n if (deletes.length) {\n const owned = await repo.find({ where: { domain: { id: domain.id }, id: In(deletes) }, select: ['id'] })\n if (owned.length !== deletes.length) throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n /* Its accounts are its parts and go first, so they are not counted as tied - a number stays free for whoever holds it next. */\n const accounts = (await tx.getRepository(CounterpartyBankAccount).find({ where: { counterparty: { id: In(deletes) } }, select: ['id'] })).map(one => one.id)\n if (accounts.length) await tx.getRepository(CounterpartyBankAccount).softDelete({ id: In(accounts) })\n await refuseTiedRows(tx, Counterparty, deletes)\n await repo.softDelete({ id: In(deletes) })\n }\n\n const saved: Counterparty[] = []\n for (const row of updates) {\n const current = await repo.findOne({ where: { domain: { id: domain.id }, id: row.id } })\n if (!current) throw new Refusal('counterparty-not-found', {}, 'a counterparty was not found - reload and try again')\n saved.push(await repo.save({ ...current, ...row, updater: user }))\n }\n for (const row of creates) saved.push(await repo.save({ ...row, domain, creator: user, updater: user } as Counterparty))\n\n for (const listener of listeners) await listener(tx, domain, saved)\n return saved\n}\n"]}