@things-factory/biz-base 10.1.150 → 10.1.154

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.
@@ -38,14 +38,24 @@ export declare function linkFor(patch: {
38
38
  */
39
39
  export declare function normalizeBusinessNumber(value: unknown): string | null;
40
40
  /**
41
- * Two counterparties whose names mean the same party (§`nameKey`) — after a batch would apply.
42
- * Returns the clashing pair, or undefined. `(주)한빛` and `한빛` are one party; registering both
43
- * would split its contracts in two.
41
+ * Two counterparties with one business registration number — after a batch would apply. Returns the clashing pair of
42
+ * names and the number, or undefined. A number is one business: two counterparties holding it would split its
43
+ * documents in two.
44
+ *
45
+ * Only a clash the batch makes counts: a number it registers or changes to. Names are not compared — two businesses
46
+ * may trade under one name.
44
47
  */
45
- export declare function nameClash(existing: readonly KnownCounterparty[], changes: readonly {
48
+ export declare function numberClash(existing: readonly (KnownCounterparty & {
49
+ businessNumber?: string | null;
50
+ })[], changes: readonly {
46
51
  id?: string;
47
52
  name?: string | null;
48
- }[], deleted?: readonly string[]): [string, string] | undefined;
53
+ businessNumber?: string | null;
54
+ }[], deleted?: readonly string[]): {
55
+ number: string;
56
+ existing: string;
57
+ name: string;
58
+ } | undefined;
49
59
  /** Names to register: one spelling per party, skipping blanks and parties already known. */
50
60
  export declare function namesToRegister(names: readonly (string | null | undefined)[], known: readonly KnownCounterparty[]): string[];
51
61
  export interface PartyForMatch {
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.nameKey = nameKey;
13
13
  exports.linkFor = linkFor;
14
14
  exports.normalizeBusinessNumber = normalizeBusinessNumber;
15
- exports.nameClash = nameClash;
15
+ exports.numberClash = numberClash;
16
16
  exports.namesToRegister = namesToRegister;
17
17
  exports.looksLikePersonName = looksLikePersonName;
18
18
  exports.partiesFromNames = partiesFromNames;
@@ -72,34 +72,44 @@ function normalizeBusinessNumber(value) {
72
72
  return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
73
73
  }
74
74
  /**
75
- * Two counterparties whose names mean the same party (§`nameKey`) — after a batch would apply.
76
- * Returns the clashing pair, or undefined. `(주)한빛` and `한빛` are one party; registering both
77
- * would split its contracts in two.
75
+ * Two counterparties with one business registration number — after a batch would apply. Returns the clashing pair of
76
+ * names and the number, or undefined. A number is one business: two counterparties holding it would split its
77
+ * documents in two.
78
+ *
79
+ * Only a clash the batch makes counts: a number it registers or changes to. Names are not compared — two businesses
80
+ * may trade under one name.
78
81
  */
79
- function nameClash(existing, changes, deleted = []) {
82
+ function numberClash(existing, changes, deleted = []) {
83
+ const before = new Map(existing.map(one => [one.id, one]));
80
84
  const after = new Map();
81
85
  for (const one of existing)
82
86
  if (!deleted.includes(one.id))
83
- after.set(one.id, one.name);
87
+ after.set(one.id, { name: one.name, number: one.businessNumber ?? '' });
88
+ const numbered = new Set();
84
89
  let fresh = 0;
85
90
  for (const change of changes) {
86
- if (change.id) {
87
- if (change.name)
88
- after.set(change.id, change.name);
89
- }
90
- else if (change.name) {
91
- after.set(`new:${fresh++}`, change.name);
92
- }
91
+ const id = change.id ?? `new:${fresh++}`;
92
+ const current = after.get(id) ?? { name: '', number: '' };
93
+ const next = {
94
+ name: change.name ?? current.name,
95
+ number: change.businessNumber !== undefined ? change.businessNumber ?? '' : current.number
96
+ };
97
+ after.set(id, next);
98
+ if (next.number && next.number !== (before.get(id)?.businessNumber ?? ''))
99
+ numbered.add(id);
93
100
  }
94
- const seen = new Map();
95
- for (const name of after.values()) {
96
- const key = nameKey(name);
97
- if (!key)
101
+ const holders = new Map();
102
+ for (const [id, one] of after)
103
+ if (one.number)
104
+ holders.set(one.number, [...(holders.get(one.number) ?? []), { id, name: one.name }]);
105
+ for (const [number, parties] of holders) {
106
+ if (parties.length < 2)
98
107
  continue;
99
- const other = seen.get(key);
100
- if (other !== undefined)
101
- return [other, name];
102
- seen.set(key, name);
108
+ const made = parties.find(one => numbered.has(one.id));
109
+ if (!made)
110
+ continue;
111
+ const other = parties.find(one => one !== made);
112
+ return { number, existing: other.name, name: made.name };
103
113
  }
104
114
  return undefined;
105
115
  }
@@ -157,11 +167,14 @@ function aliasesOf(party) {
157
167
  * `nameKey` folds (§ a contract's 상호). An exact match, not a search.
158
168
  */
159
169
  function partyNamed(parties) {
170
+ /* A name two parties go by answers neither — names are not unique. */
160
171
  const byKey = new Map();
161
172
  for (const party of parties)
162
- for (const name of [party.name, ...aliasesOf(party)])
163
- byKey.set(nameKey(name), party);
164
- return name => (nameKey(name) ? byKey.get(nameKey(name)) : undefined);
173
+ for (const name of [party.name, ...aliasesOf(party)]) {
174
+ const key = nameKey(name);
175
+ byKey.set(key, byKey.has(key) && byKey.get(key) !== party ? null : party);
176
+ }
177
+ return name => (nameKey(name) ? byKey.get(nameKey(name)) ?? undefined : undefined);
165
178
  }
166
179
  /**
167
180
  * A function telling which party a text names. The longest matching name or alias wins; two
@@ -1 +1 @@
1
- {"version":3,"file":"counterparty-names.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-names.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * How names point at counterparties — one place for every module that keeps a name and a link.\n *\n * A document (a contract, a bank line, an invoice) records **the name as it was** and, when the domain knows that\n * party, a link to it. The name stays even if the party is renamed later. Names are compared the way people mean\n * them (§`nameKey`), and a counterparty's aliases count as its names.\n *\n * Pure: the caller hands over what it looked up.\n */\n\nimport { Refusal } from '@things-factory/shell'\n\nexport interface KnownCounterparty {\n id: string\n name: string\n}\n\n/**\n * Compare names the way people mean them — case, spacing and `(주)` / `주식회사` do not make another party.\n * NFKC folds the compatibility forms Hometax writes (`(주)` in fullwidth, `㈜`) into the plain ones.\n */\nexport function nameKey(name: string | null | undefined): string {\n return (\n String(name ?? '')\n .normalize('NFKC')\n .replace(/\\(\\s*주\\s*\\)|㈜|주식회사|\\(\\s*유\\s*\\)|유한회사/g, '')\n /* The same company written in Hangul or in Latin — 씨제이올리브네트웍스 is CJ올리브네트웍스. */\n .replace(/씨제이/g, 'cj')\n .replace(/에스케이/g, 'sk')\n .replace(/엘지/g, 'lg')\n .replace(/[\\s\\-_.·,()]+/g, '')\n .toLowerCase()\n )\n}\n\nexport type LinkChange = { counterparty?: { id: string } | null; customerName?: string | null }\n\n/**\n * What to write for the link, given the row's patch. Returns only what changes; an update that\n * touches neither the name nor the counterparty returns `{}`.\n */\nexport function linkFor(\n patch: { counterparty?: { id?: string | null } | null; customerName?: string | null },\n byId: (id: string) => KnownCounterparty | undefined,\n byName: (key: string) => KnownCounterparty | undefined\n): LinkChange {\n if (patch.counterparty !== undefined) {\n const id = patch.counterparty?.id\n if (!id) return { counterparty: null, ...('customerName' in patch ? { customerName: patch.customerName ?? null } : {}) }\n\n const known = byId(id)\n if (!known) throw new Refusal('counterparty-not-found', { id }, 'the counterparty was not found. reload and try again.')\n return { counterparty: { id: known.id }, customerName: known.name }\n }\n\n if (!('customerName' in patch)) return {}\n\n const name = patch.customerName?.trim() || null\n if (!name) return { counterparty: null, customerName: null }\n\n const known = byName(nameKey(name))\n return known ? { counterparty: { id: known.id }, customerName: name } : { counterparty: null, customerName: name }\n}\n\n/**\n * 사업자등록번호 — ten digits, stored as `123-45-67890`. Empty stays empty; anything else that is\n * not ten digits is refused rather than stored half-right.\n */\nexport function normalizeBusinessNumber(value: unknown): string | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n\n const digits = text.replace(/[-\\s]/g, '')\n if (!/^\\d{10}$/.test(digits)) throw new Refusal('business-number-invalid', { text }, `'${text}' is not a business registration number (10 digits).`)\n\n return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`\n}\n\n/**\n * Two counterparties whose names mean the same party (§`nameKey`) — after a batch would apply.\n * Returns the clashing pair, or undefined. `(주)한빛` and `한빛` are one party; registering both\n * would split its contracts in two.\n */\nexport function nameClash(\n existing: readonly KnownCounterparty[],\n changes: readonly { id?: string; name?: string | null }[],\n deleted: readonly string[] = []\n): [string, string] | undefined {\n const after = new Map<string, string>()\n for (const one of existing) if (!deleted.includes(one.id)) after.set(one.id, one.name)\n\n let fresh = 0\n for (const change of changes) {\n if (change.id) {\n if (change.name) after.set(change.id, change.name)\n } else if (change.name) {\n after.set(`new:${fresh++}`, change.name)\n }\n }\n\n const seen = new Map<string, string>()\n for (const name of after.values()) {\n const key = nameKey(name)\n if (!key) continue\n const other = seen.get(key)\n if (other !== undefined) return [other, name]\n seen.set(key, name)\n }\n\n return undefined\n}\n\n/** Names to register: one spelling per party, skipping blanks and parties already known. */\nexport function namesToRegister(names: readonly (string | null | undefined)[], known: readonly KnownCounterparty[]): string[] {\n const taken = new Set(known.map(one => nameKey(one.name)))\n const out: string[] = []\n\n for (const name of names) {\n const trimmed = String(name ?? '').trim()\n const key = nameKey(trimmed)\n if (!key || taken.has(key)) continue\n taken.add(key)\n out.push(trimmed)\n }\n\n return out\n}\n\nexport interface PartyForMatch {\n id: string\n name: string\n aliases?: string | null\n}\n\nexport interface ProposedParty {\n name: string\n variants: string[]\n count: number\n maybePerson: boolean\n}\n\n/** Keys shorter than this match too much by accident. */\nconst MIN_KEY = 2\n\nconst SURNAMES = new Set('김이박최정강조윤장임한오서신권황안송류전홍고문양손배백허유남심노하곽성차주우구민진지엄채원천방공현함변염여추도소석선설마길연위표명기반왕금옥육인맹제모탁국어은편용예경봉사부가복태목형피두감음빈동온호범좌팽승간상갈단견당'.split(''))\n\nexport function looksLikePersonName(name: string): boolean {\n const trimmed = name.trim()\n return /^[가-힣]{2,4}$/.test(trimmed) && trimmed.length === 3 && SURNAMES.has(trimmed[0]) && !/(테크|솔루션|푸드|그룹|시스템|컴|넷|랩|소프트|산업|상사|물산|로지)/.test(trimmed)\n}\n\n/** The parties a list of names (contracts, documents) means, one per `nameKey`, spelled the way the ledger spells it most. */\nexport function partiesFromNames(names: readonly (string | null | undefined)[]): ProposedParty[] {\n const groups = new Map<string, Map<string, number>>()\n for (const raw of names) {\n const name = String(raw ?? '').trim()\n const key = nameKey(name)\n if (key.length < MIN_KEY) continue\n const spellings = groups.get(key) ?? new Map<string, number>()\n spellings.set(name, (spellings.get(name) ?? 0) + 1)\n groups.set(key, spellings)\n }\n\n return [...groups.values()]\n .map(spellings => {\n /* The spelling used most; on a tie the one without a corporate marker, then the shortest. */\n const variants = [...spellings.entries()].sort((a, b) => b[1] - a[1] || marker(a[0]) - marker(b[0]) || a[0].length - b[0].length).map(([one]) => one)\n const count = [...spellings.values()].reduce((sum, one) => sum + one, 0)\n return { name: variants[0], variants, count, maybePerson: looksLikePersonName(variants[0]) }\n })\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\nconst marker = (name: string) => (/\\(\\s*주\\s*\\)|㈜|주식회사/.test(name) ? 1 : 0)\n\nexport function aliasesOf(party: PartyForMatch): string[] {\n return String(party.aliases ?? '')\n .split(/[\\n,]/)\n .map(one => one.trim())\n .filter(Boolean)\n}\n\n/**\n * A function telling which party a name is — the party's name or one of its aliases, spelled any way\n * `nameKey` folds (§ a contract's 상호). An exact match, not a search.\n */\nexport function partyNamed(parties: readonly PartyForMatch[]): (name: string | null | undefined) => PartyForMatch | undefined {\n const byKey = new Map<string, PartyForMatch>()\n for (const party of parties) for (const name of [party.name, ...aliasesOf(party)]) byKey.set(nameKey(name), party)\n return name => (nameKey(name) ? byKey.get(nameKey(name)) : undefined)\n}\n\n/**\n * A function telling which party a text names. The longest matching name or alias wins; two\n * different parties matching equally long is ambiguous — no answer.\n */\nexport function matcherFor(parties: readonly PartyForMatch[]): (text: string | null | undefined) => PartyForMatch | undefined {\n const keys: { key: string; party: PartyForMatch }[] = []\n for (const party of parties) {\n for (const name of [party.name, ...aliasesOf(party)]) {\n const key = nameKey(name)\n if (key.length >= MIN_KEY) keys.push({ key, party })\n }\n }\n keys.sort((a, b) => b.key.length - a.key.length)\n\n return text => {\n const hay = nameKey(text)\n if (!hay) return undefined\n const hits = keys.filter(one => hay.includes(one.key))\n if (!hits.length) return undefined\n const longest = hits[0].key.length\n const top = new Set(hits.filter(one => one.key.length === longest).map(one => one.party.id))\n return top.size === 1 ? hits[0].party : undefined\n }\n}\n\n/** The texts left unmatched most often — what a person might add as aliases. */\nexport function frequentUnmatched(texts: readonly (string | null | undefined)[], limit = 30): { text: string; count: number }[] {\n const counts = new Map<string, number>()\n for (const raw of texts) {\n const text = String(raw ?? '').trim()\n if (!text) continue\n counts.set(text, (counts.get(text) ?? 0) + 1)\n }\n return [...counts.entries()]\n .map(([text, count]) => ({ text, count }))\n .sort((a, b) => b.count - a.count || a.text.localeCompare(b.text))\n .slice(0, limit)\n}\n"]}
1
+ {"version":3,"file":"counterparty-names.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty-names.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAaH,0BAYC;AAQD,0BAqBC;AAMD,0DAQC;AAUD,kCAiCC;AAGD,0CAaC;AAoBD,kDAGC;AAGD,4CAmBC;AAID,8BAKC;AAMD,gCASC;AAMD,gCAmBC;AAGD,8CAWC;AAzOD,iDAA+C;AAO/C;;;GAGG;AACH,SAAgB,OAAO,CAAC,IAA+B;IACrD,OAAO,CACL,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SACf,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,sCAAsC,EAAE,EAAE,CAAC;QACpD,+EAA+E;SAC9E,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;SACtB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;SACpB,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,WAAW,EAAE,CACjB,CAAA;AACH,CAAC;AAID;;;GAGG;AACH,SAAgB,OAAO,CACrB,KAAqF,EACrF,IAAmD,EACnD,MAAsD;IAEtD,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,EAAE,EAAE,CAAA;QACjC,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QAExH,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;QACtB,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,eAAO,CAAC,wBAAwB,EAAE,EAAE,EAAE,EAAE,EAAE,uDAAuD,CAAC,CAAA;QACxH,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,IAAI,EAAE,CAAA;IACrE,CAAC;IAED,IAAI,CAAC,CAAC,cAAc,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;IAC/C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;IAE5D,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACnC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;AACpH,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CAAC,KAAc;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,eAAO,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,IAAI,sDAAsD,CAAC,CAAA;IAEpJ,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CACzB,QAA6E,EAC7E,OAAyF,EACzF,UAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4C,CAAA;IACjE,KAAK,MAAM,GAAG,IAAI,QAAQ;QAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC,CAAA;IAElI,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAClC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,OAAO,KAAK,EAAE,EAAE,CAAA;QACxC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;QACzD,MAAM,IAAI,GAAG;YACX,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;YACjC,MAAM,EAAE,MAAM,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM;SAC3F,CAAA;QACD,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QACnB,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,cAAc,IAAI,EAAE,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0C,CAAA;IACjE,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,KAAK;QAAE,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IACpI,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,OAAO,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,SAAQ;QAChC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI;YAAE,SAAQ;QACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,CAAE,CAAA;QAChD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAA;IAC1D,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,4FAA4F;AAC5F,SAAgB,eAAe,CAAC,KAA6C,EAAE,KAAmC;IAChH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC1D,MAAM,GAAG,GAAa,EAAE,CAAA;IAExB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACzC,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;QAC5B,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QACpC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACd,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnB,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAeD,yDAAyD;AACzD,MAAM,OAAO,GAAG,CAAC,CAAA;AAEjB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,+GAA+G,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;AAEnJ,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,OAAO,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,0CAA0C,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AACtJ,CAAC;AAED,8HAA8H;AAC9H,SAAgB,gBAAgB,CAAC,KAA6C;IAC5E,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAA;IACrD,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QACzB,IAAI,GAAG,CAAC,MAAM,GAAG,OAAO;YAAE,SAAQ;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAA;QAC9D,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACnD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IAC5B,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;SACxB,GAAG,CAAC,SAAS,CAAC,EAAE;QACf,6FAA6F;QAC7F,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAA;QACrJ,MAAM,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,CAAA;QACxE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9F,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE1E,SAAgB,SAAS,CAAC,KAAoB;IAC5C,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;SAC/B,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;SACtB,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED;;;GAGG;AACH,SAAgB,UAAU,CAAC,OAAiC;IAC1D,sEAAsE;IACtE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgC,CAAA;IACrD,KAAK,MAAM,KAAK,IAAI,OAAO;QACzB,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;YACzB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QAC3E,CAAC;IACH,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AACpF,CAAC;AAED;;;GAGG;AACH,SAAgB,UAAU,CAAC,OAAiC;IAC1D,MAAM,IAAI,GAA4C,EAAE,CAAA;IACxD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;YACzB,IAAI,GAAG,CAAC,MAAM,IAAI,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;QACtD,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAEhD,OAAO,IAAI,CAAC,EAAE;QACZ,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QACzB,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAA;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAA;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAA;QAClC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;QAC5F,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IACnD,CAAC,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,SAAgB,iBAAiB,CAAC,KAA6C,EAAE,KAAK,GAAG,EAAE;IACzF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,IAAI,CAAC,IAAI;YAAE,SAAQ;QACnB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/C,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SACzB,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;SACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SACjE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;AACpB,CAAC","sourcesContent":["/**\n * How names point at counterparties — one place for every module that keeps a name and a link.\n *\n * A document (a contract, a bank line, an invoice) records **the name as it was** and, when the domain knows that\n * party, a link to it. The name stays even if the party is renamed later. Names are compared the way people mean\n * them (§`nameKey`), and a counterparty's aliases count as its names.\n *\n * Pure: the caller hands over what it looked up.\n */\n\nimport { Refusal } from '@things-factory/shell'\n\nexport interface KnownCounterparty {\n id: string\n name: string\n}\n\n/**\n * Compare names the way people mean them — case, spacing and `(주)` / `주식회사` do not make another party.\n * NFKC folds the compatibility forms Hometax writes (`(주)` in fullwidth, `㈜`) into the plain ones.\n */\nexport function nameKey(name: string | null | undefined): string {\n return (\n String(name ?? '')\n .normalize('NFKC')\n .replace(/\\(\\s*주\\s*\\)|㈜|주식회사|\\(\\s*유\\s*\\)|유한회사/g, '')\n /* The same company written in Hangul or in Latin — 씨제이올리브네트웍스 is CJ올리브네트웍스. */\n .replace(/씨제이/g, 'cj')\n .replace(/에스케이/g, 'sk')\n .replace(/엘지/g, 'lg')\n .replace(/[\\s\\-_.·,()]+/g, '')\n .toLowerCase()\n )\n}\n\nexport type LinkChange = { counterparty?: { id: string } | null; customerName?: string | null }\n\n/**\n * What to write for the link, given the row's patch. Returns only what changes; an update that\n * touches neither the name nor the counterparty returns `{}`.\n */\nexport function linkFor(\n patch: { counterparty?: { id?: string | null } | null; customerName?: string | null },\n byId: (id: string) => KnownCounterparty | undefined,\n byName: (key: string) => KnownCounterparty | undefined\n): LinkChange {\n if (patch.counterparty !== undefined) {\n const id = patch.counterparty?.id\n if (!id) return { counterparty: null, ...('customerName' in patch ? { customerName: patch.customerName ?? null } : {}) }\n\n const known = byId(id)\n if (!known) throw new Refusal('counterparty-not-found', { id }, 'the counterparty was not found. reload and try again.')\n return { counterparty: { id: known.id }, customerName: known.name }\n }\n\n if (!('customerName' in patch)) return {}\n\n const name = patch.customerName?.trim() || null\n if (!name) return { counterparty: null, customerName: null }\n\n const known = byName(nameKey(name))\n return known ? { counterparty: { id: known.id }, customerName: name } : { counterparty: null, customerName: name }\n}\n\n/**\n * 사업자등록번호 — ten digits, stored as `123-45-67890`. Empty stays empty; anything else that is\n * not ten digits is refused rather than stored half-right.\n */\nexport function normalizeBusinessNumber(value: unknown): string | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n\n const digits = text.replace(/[-\\s]/g, '')\n if (!/^\\d{10}$/.test(digits)) throw new Refusal('business-number-invalid', { text }, `'${text}' is not a business registration number (10 digits).`)\n\n return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`\n}\n\n/**\n * Two counterparties with one business registration number — after a batch would apply. Returns the clashing pair of\n * names and the number, or undefined. A number is one business: two counterparties holding it would split its\n * documents in two.\n *\n * Only a clash the batch makes counts: a number it registers or changes to. Names are not compared — two businesses\n * may trade under one name.\n */\nexport function numberClash(\n existing: readonly (KnownCounterparty & { businessNumber?: string | null })[],\n changes: readonly { id?: string; name?: string | null; businessNumber?: string | null }[],\n deleted: readonly string[] = []\n): { number: string; existing: string; name: string } | undefined {\n const before = new Map(existing.map(one => [one.id, one]))\n const after = new Map<string, { name: string; number: string }>()\n for (const one of existing) if (!deleted.includes(one.id)) after.set(one.id, { name: one.name, number: one.businessNumber ?? '' })\n\n const numbered = new Set<string>()\n let fresh = 0\n for (const change of changes) {\n const id = change.id ?? `new:${fresh++}`\n const current = after.get(id) ?? { name: '', number: '' }\n const next = {\n name: change.name ?? current.name,\n number: change.businessNumber !== undefined ? change.businessNumber ?? '' : current.number\n }\n after.set(id, next)\n if (next.number && next.number !== (before.get(id)?.businessNumber ?? '')) numbered.add(id)\n }\n\n const holders = new Map<string, { id: string; name: string }[]>()\n for (const [id, one] of after) if (one.number) holders.set(one.number, [...(holders.get(one.number) ?? []), { id, name: one.name }])\n for (const [number, parties] of holders) {\n if (parties.length < 2) continue\n const made = parties.find(one => numbered.has(one.id))\n if (!made) continue\n const other = parties.find(one => one !== made)!\n return { number, existing: other.name, name: made.name }\n }\n\n return undefined\n}\n\n/** Names to register: one spelling per party, skipping blanks and parties already known. */\nexport function namesToRegister(names: readonly (string | null | undefined)[], known: readonly KnownCounterparty[]): string[] {\n const taken = new Set(known.map(one => nameKey(one.name)))\n const out: string[] = []\n\n for (const name of names) {\n const trimmed = String(name ?? '').trim()\n const key = nameKey(trimmed)\n if (!key || taken.has(key)) continue\n taken.add(key)\n out.push(trimmed)\n }\n\n return out\n}\n\nexport interface PartyForMatch {\n id: string\n name: string\n aliases?: string | null\n}\n\nexport interface ProposedParty {\n name: string\n variants: string[]\n count: number\n maybePerson: boolean\n}\n\n/** Keys shorter than this match too much by accident. */\nconst MIN_KEY = 2\n\nconst SURNAMES = new Set('김이박최정강조윤장임한오서신권황안송류전홍고문양손배백허유남심노하곽성차주우구민진지엄채원천방공현함변염여추도소석선설마길연위표명기반왕금옥육인맹제모탁국어은편용예경봉사부가복태목형피두감음빈동온호범좌팽승간상갈단견당'.split(''))\n\nexport function looksLikePersonName(name: string): boolean {\n const trimmed = name.trim()\n return /^[가-힣]{2,4}$/.test(trimmed) && trimmed.length === 3 && SURNAMES.has(trimmed[0]) && !/(테크|솔루션|푸드|그룹|시스템|컴|넷|랩|소프트|산업|상사|물산|로지)/.test(trimmed)\n}\n\n/** The parties a list of names (contracts, documents) means, one per `nameKey`, spelled the way the ledger spells it most. */\nexport function partiesFromNames(names: readonly (string | null | undefined)[]): ProposedParty[] {\n const groups = new Map<string, Map<string, number>>()\n for (const raw of names) {\n const name = String(raw ?? '').trim()\n const key = nameKey(name)\n if (key.length < MIN_KEY) continue\n const spellings = groups.get(key) ?? new Map<string, number>()\n spellings.set(name, (spellings.get(name) ?? 0) + 1)\n groups.set(key, spellings)\n }\n\n return [...groups.values()]\n .map(spellings => {\n /* The spelling used most; on a tie the one without a corporate marker, then the shortest. */\n const variants = [...spellings.entries()].sort((a, b) => b[1] - a[1] || marker(a[0]) - marker(b[0]) || a[0].length - b[0].length).map(([one]) => one)\n const count = [...spellings.values()].reduce((sum, one) => sum + one, 0)\n return { name: variants[0], variants, count, maybePerson: looksLikePersonName(variants[0]) }\n })\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\nconst marker = (name: string) => (/\\(\\s*주\\s*\\)|㈜|주식회사/.test(name) ? 1 : 0)\n\nexport function aliasesOf(party: PartyForMatch): string[] {\n return String(party.aliases ?? '')\n .split(/[\\n,]/)\n .map(one => one.trim())\n .filter(Boolean)\n}\n\n/**\n * A function telling which party a name is — the party's name or one of its aliases, spelled any way\n * `nameKey` folds (§ a contract's 상호). An exact match, not a search.\n */\nexport function partyNamed(parties: readonly PartyForMatch[]): (name: string | null | undefined) => PartyForMatch | undefined {\n /* A name two parties go by answers neither — names are not unique. */\n const byKey = new Map<string, PartyForMatch | null>()\n for (const party of parties)\n for (const name of [party.name, ...aliasesOf(party)]) {\n const key = nameKey(name)\n byKey.set(key, byKey.has(key) && byKey.get(key) !== party ? null : party)\n }\n return name => (nameKey(name) ? byKey.get(nameKey(name)) ?? undefined : undefined)\n}\n\n/**\n * A function telling which party a text names. The longest matching name or alias wins; two\n * different parties matching equally long is ambiguous — no answer.\n */\nexport function matcherFor(parties: readonly PartyForMatch[]): (text: string | null | undefined) => PartyForMatch | undefined {\n const keys: { key: string; party: PartyForMatch }[] = []\n for (const party of parties) {\n for (const name of [party.name, ...aliasesOf(party)]) {\n const key = nameKey(name)\n if (key.length >= MIN_KEY) keys.push({ key, party })\n }\n }\n keys.sort((a, b) => b.key.length - a.key.length)\n\n return text => {\n const hay = nameKey(text)\n if (!hay) return undefined\n const hits = keys.filter(one => hay.includes(one.key))\n if (!hits.length) return undefined\n const longest = hits[0].key.length\n const top = new Set(hits.filter(one => one.key.length === longest).map(one => one.party.id))\n return top.size === 1 ? hits[0].party : undefined\n }\n}\n\n/** The texts left unmatched most often — what a person might add as aliases. */\nexport function frequentUnmatched(texts: readonly (string | null | undefined)[], limit = 30): { text: string; count: number }[] {\n const counts = new Map<string, number>()\n for (const raw of texts) {\n const text = String(raw ?? '').trim()\n if (!text) continue\n counts.set(text, (counts.get(text) ?? 0) + 1)\n }\n return [...counts.entries()]\n .map(([text, count]) => ({ text, count }))\n .sort((a, b) => b.count - a.count || a.text.localeCompare(b.text))\n .slice(0, limit)\n}\n"]}
@@ -14,7 +14,8 @@ type Saved = (tx: EntityManager, domain: {
14
14
  export declare function onCounterpartiesSaved(listener: Saved): void;
15
15
  /**
16
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 whose names mean the same party are refused (§`nameClash`).
17
+ * that name it keep the name). Two counterparties with one business registration number are refused (§`numberClash`);
18
+ * a name may be shared.
18
19
  */
19
20
  export declare function saveCounterparties(tx: EntityManager, domain: {
20
21
  id: string;
@@ -52,17 +52,18 @@ function rowOf(patch, isNew) {
52
52
  }
53
53
  /**
54
54
  * Apply grid rows to a domain's counterparties: `+` (or no id) registers, `M` changes, `-` deletes (softly — the rows
55
- * that name it keep the name). Two counterparties whose names mean the same party are refused (§`nameClash`).
55
+ * that name it keep the name). Two counterparties with one business registration number are refused (§`numberClash`);
56
+ * a name may be shared.
56
57
  */
57
58
  async function saveCounterparties(tx, domain, user, patches) {
58
59
  const repo = tx.getRepository(counterparty_1.Counterparty);
59
60
  const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id);
60
61
  const updates = patches.filter(one => one.id && one.cuFlag !== '-' && one.cuFlag !== '+').map(one => ({ id: one.id, ...rowOf(one, false) }));
61
62
  const creates = patches.filter(one => !one.id || one.cuFlag === '+').filter(one => one.cuFlag !== '-').map(one => rowOf(one, true));
62
- const existing = await repo.find({ where: { domain: { id: domain.id } }, select: ['id', 'name'] });
63
- const clash = (0, counterparty_names_1.nameClash)(existing, [...updates, ...creates], deletes);
63
+ const existing = await repo.find({ where: { domain: { id: domain.id } }, select: ['id', 'name', 'businessNumber'] });
64
+ const clash = (0, counterparty_names_1.numberClash)(existing, [...updates, ...creates], deletes);
64
65
  if (clash)
65
- throw new shell_1.Refusal('counterparty-name-clash', { name: clash[1], existing: clash[0] }, `'${clash[1]}' is the same counterparty as '${clash[0]}' - edit that one instead`);
66
+ throw new shell_1.Refusal('counterparty-number-clash', clash, `business number ${clash.number} is already '${clash.existing}' - edit that counterparty instead`);
66
67
  if (deletes.length) {
67
68
  const owned = await repo.find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(deletes) }, select: ['id'] });
68
69
  if (owned.length !== deletes.length)
@@ -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 { CounterpartyPatch } from './counterparty-type'\nimport { nameClash, normalizeBusinessNumber } 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 whose names mean the same party are refused (§`nameClash`).\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'] })\n const clash = nameClash(existing, [...updates, ...creates], deletes)\n if (clash) throw new Refusal('counterparty-name-clash', { name: clash[1], existing: clash[0] }, `'${clash[1]}' is the same counterparty as '${clash[0]}' - edit that one 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 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"]}
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 { 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 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"]}
@@ -12,6 +12,9 @@ export declare enum CounterpartyKind {
12
12
  * (`@things-factory/contact`), which point at the counterparty.
13
13
  *
14
14
  * For a person nothing beyond what identifies them to the business is kept — no resident registration number.
15
+ *
16
+ * A business registration number is one counterparty's in the domain; a name is not — two businesses may trade under
17
+ * one name, told apart by their numbers.
15
18
  */
16
19
  export declare class Counterparty {
17
20
  readonly id: string;
@@ -23,6 +23,9 @@ var CounterpartyKind;
23
23
  * (`@things-factory/contact`), which point at the counterparty.
24
24
  *
25
25
  * For a person nothing beyond what identifies them to the business is kept — no resident registration number.
26
+ *
27
+ * A business registration number is one counterparty's in the domain; a name is not — two businesses may trade under
28
+ * one name, told apart by their numbers.
26
29
  */
27
30
  let Counterparty = class Counterparty {
28
31
  };
@@ -43,7 +46,7 @@ tslib_1.__decorate([
43
46
  ], Counterparty.prototype, "domainId", void 0);
44
47
  tslib_1.__decorate([
45
48
  (0, typeorm_1.Column)(),
46
- (0, type_graphql_1.Field)({ description: 'The name the business knows it by, unique in the domain' }),
49
+ (0, type_graphql_1.Field)({ description: 'The name the business knows it by; two counterparties may share it' }),
47
50
  tslib_1.__metadata("design:type", String)
48
51
  ], Counterparty.prototype, "name", void 0);
49
52
  tslib_1.__decorate([
@@ -58,7 +61,7 @@ tslib_1.__decorate([
58
61
  ], Counterparty.prototype, "kind", void 0);
59
62
  tslib_1.__decorate([
60
63
  (0, typeorm_1.Column)({ nullable: true }),
61
- (0, type_graphql_1.Field)({ nullable: true, description: 'Business registration number, 123-45-67890' }),
64
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Business registration number, 123-45-67890 - one counterparty\'s in the domain' }),
62
65
  tslib_1.__metadata("design:type", String)
63
66
  ], Counterparty.prototype, "businessNumber", void 0);
64
67
  tslib_1.__decorate([
@@ -141,8 +144,8 @@ tslib_1.__decorate([
141
144
  ], Counterparty.prototype, "updaterId", void 0);
142
145
  exports.Counterparty = Counterparty = tslib_1.__decorate([
143
146
  (0, typeorm_1.Entity)(),
144
- (0, typeorm_1.Index)('ix_counterparty_0', (counterparty) => [counterparty.domain, counterparty.name], { unique: true, where: '"deleted_at" IS NULL' }),
145
- (0, typeorm_1.Index)('ix_counterparty_1', (counterparty) => [counterparty.domain, counterparty.businessNumber]),
147
+ (0, typeorm_1.Index)('ix_counterparty_0', (counterparty) => [counterparty.domain, counterparty.name], { where: '"deleted_at" IS NULL' }),
148
+ (0, typeorm_1.Index)('ix_counterparty_1', (counterparty) => [counterparty.domain, counterparty.businessNumber], { unique: true, where: '"deleted_at" IS NULL' }),
146
149
  (0, type_graphql_1.ObjectType)({ description: 'Entity for Counterparty — a company or person a domain deals with' })
147
150
  ], Counterparty);
148
151
  //# sourceMappingURL=counterparty.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"counterparty.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, ObjectType, registerEnumType } from 'type-graphql'\nimport { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain } from '@things-factory/shell'\n\nexport enum CounterpartyKind {\n COMPANY = 'COMPANY',\n PERSON = 'PERSON'\n}\n\nregisterEnumType(CounterpartyKind, {\n name: 'CounterpartyKind',\n description: 'Whether a counterparty is a company or a person'\n})\n\n/**\n * A company or person a domain signs with, bills, is billed by, or pays — its own list of them.\n *\n * Each domain keeps its counterparties itself: who they are to this business, the names it calls them by, whether\n * it still deals with them. Another domain's list is not shared or seen. The people to talk to there are contacts\n * (`@things-factory/contact`), which point at the counterparty.\n *\n * For a person nothing beyond what identifies them to the business is kept — no resident registration number.\n */\n@Entity()\n@Index('ix_counterparty_0', (counterparty: Counterparty) => [counterparty.domain, counterparty.name], { unique: true, where: '\"deleted_at\" IS NULL' })\n@Index('ix_counterparty_1', (counterparty: Counterparty) => [counterparty.domain, counterparty.businessNumber])\n@ObjectType({ description: 'Entity for Counterparty — a company or person a domain deals with' })\nexport class Counterparty {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain whose counterparty this is' })\n domain?: Domain\n\n @RelationId((counterparty: Counterparty) => counterparty.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The name the business knows it by, unique in the domain' })\n name: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Other names it appears under — in bank lines, old ledgers — one per line or comma' })\n aliases?: string\n\n @Column({ default: CounterpartyKind.COMPANY })\n @Field(type => CounterpartyKind, { description: 'A company or a person' })\n kind: CounterpartyKind\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business registration number, 123-45-67890' })\n businessNumber?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Sub-business place number, when tax invoices go to a branch' })\n branchNumber?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Representative' })\n representative?: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Address' })\n address?: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Address bills are sent to, when not the address' })\n billingAddress?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business type (업태)' })\n businessType?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business item (종목)' })\n businessItem?: string\n\n @Column({ default: false })\n @Field({ description: 'Whether its sales to it are zero-rated for VAT' })\n zeroRated: boolean\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Notes' })\n notes?: string\n\n @Column({ default: true })\n @Field({ description: 'An inactive counterparty stays on old rows but is not offered for new ones' })\n active: boolean\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When it was registered' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When it last changed' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'When it was deleted — the rows that name it are kept' })\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((counterparty: Counterparty) => counterparty.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((counterparty: Counterparty) => counterparty.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"counterparty.js","sourceRoot":"","sources":["../../../server/service/counterparty/counterparty.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, ObjectType, registerEnumType } from 'type-graphql'\nimport { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain } from '@things-factory/shell'\n\nexport enum CounterpartyKind {\n COMPANY = 'COMPANY',\n PERSON = 'PERSON'\n}\n\nregisterEnumType(CounterpartyKind, {\n name: 'CounterpartyKind',\n description: 'Whether a counterparty is a company or a person'\n})\n\n/**\n * A company or person a domain signs with, bills, is billed by, or pays — its own list of them.\n *\n * Each domain keeps its counterparties itself: who they are to this business, the names it calls them by, whether\n * it still deals with them. Another domain's list is not shared or seen. The people to talk to there are contacts\n * (`@things-factory/contact`), which point at the counterparty.\n *\n * For a person nothing beyond what identifies them to the business is kept — no resident registration number.\n *\n * A business registration number is one counterparty's in the domain; a name is not — two businesses may trade under\n * one name, told apart by their numbers.\n */\n@Entity()\n@Index('ix_counterparty_0', (counterparty: Counterparty) => [counterparty.domain, counterparty.name], { where: '\"deleted_at\" IS NULL' })\n@Index('ix_counterparty_1', (counterparty: Counterparty) => [counterparty.domain, counterparty.businessNumber], { unique: true, where: '\"deleted_at\" IS NULL' })\n@ObjectType({ description: 'Entity for Counterparty — a company or person a domain deals with' })\nexport class Counterparty {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain whose counterparty this is' })\n domain?: Domain\n\n @RelationId((counterparty: Counterparty) => counterparty.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The name the business knows it by; two counterparties may share it' })\n name: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Other names it appears under — in bank lines, old ledgers — one per line or comma' })\n aliases?: string\n\n @Column({ default: CounterpartyKind.COMPANY })\n @Field(type => CounterpartyKind, { description: 'A company or a person' })\n kind: CounterpartyKind\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business registration number, 123-45-67890 - one counterparty\\'s in the domain' })\n businessNumber?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Sub-business place number, when tax invoices go to a branch' })\n branchNumber?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Representative' })\n representative?: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Address' })\n address?: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Address bills are sent to, when not the address' })\n billingAddress?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business type (업태)' })\n businessType?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Business item (종목)' })\n businessItem?: string\n\n @Column({ default: false })\n @Field({ description: 'Whether its sales to it are zero-rated for VAT' })\n zeroRated: boolean\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Notes' })\n notes?: string\n\n @Column({ default: true })\n @Field({ description: 'An inactive counterparty stays on old rows but is not offered for new ones' })\n active: boolean\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When it was registered' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When it last changed' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'When it was deleted — the rows that name it are kept' })\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((counterparty: Counterparty) => counterparty.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((counterparty: Counterparty) => counterparty.updater)\n updaterId?: string\n}\n"]}