@things-factory/biz-base 10.1.154 → 10.1.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist-server/index.d.ts +1 -0
  2. package/dist-server/index.js +1 -0
  3. package/dist-server/index.js.map +1 -1
  4. package/dist-server/service/counterparty/counterparty-bank-account-mutation.d.ts +9 -0
  5. package/dist-server/service/counterparty/counterparty-bank-account-mutation.js +70 -0
  6. package/dist-server/service/counterparty/counterparty-bank-account-mutation.js.map +1 -0
  7. package/dist-server/service/counterparty/counterparty-bank-account-query.d.ts +16 -0
  8. package/dist-server/service/counterparty/counterparty-bank-account-query.js +104 -0
  9. package/dist-server/service/counterparty/counterparty-bank-account-query.js.map +1 -0
  10. package/dist-server/service/counterparty/counterparty-bank-account-service.d.ts +27 -0
  11. package/dist-server/service/counterparty/counterparty-bank-account-service.js +87 -0
  12. package/dist-server/service/counterparty/counterparty-bank-account-service.js.map +1 -0
  13. package/dist-server/service/counterparty/counterparty-bank-account.d.ts +29 -0
  14. package/dist-server/service/counterparty/counterparty-bank-account.js +103 -0
  15. package/dist-server/service/counterparty/counterparty-bank-account.js.map +1 -0
  16. package/dist-server/service/counterparty/counterparty-document-readers.d.ts +7 -0
  17. package/dist-server/service/counterparty/counterparty-document-readers.js +18 -0
  18. package/dist-server/service/counterparty/counterparty-document-readers.js.map +1 -0
  19. package/dist-server/service/counterparty/counterparty-names.d.ts +22 -0
  20. package/dist-server/service/counterparty/counterparty-names.js +30 -14
  21. package/dist-server/service/counterparty/counterparty-names.js.map +1 -1
  22. package/dist-server/service/counterparty/counterparty-service.js +5 -0
  23. package/dist-server/service/counterparty/counterparty-service.js.map +1 -1
  24. package/dist-server/service/counterparty/counterparty-type.d.ts +9 -0
  25. package/dist-server/service/counterparty/counterparty-type.js +35 -1
  26. package/dist-server/service/counterparty/counterparty-type.js.map +1 -1
  27. package/dist-server/service/counterparty/index.d.ts +5 -2
  28. package/dist-server/service/counterparty/index.js +5 -2
  29. package/dist-server/service/counterparty/index.js.map +1 -1
  30. package/dist-server/service/index.d.ts +4 -2
  31. package/dist-server/service/index.js +2 -0
  32. package/dist-server/service/index.js.map +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +4 -3
  35. package/tests/counterparty-bank-account.test.mjs +117 -0
  36. package/translations/en.json +4 -1
  37. package/translations/es.json +4 -1
  38. package/translations/ja.json +4 -1
  39. package/translations/ko.json +4 -1
  40. package/translations/ms.json +4 -1
  41. package/translations/zh.json +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/biz-base",
3
- "version": "10.1.154",
3
+ "version": "10.1.156",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "exports": {
@@ -41,7 +41,8 @@
41
41
  "test": "npm run build && node --test tests/*.test.mjs tests/*.test.cjs"
42
42
  },
43
43
  "dependencies": {
44
- "@things-factory/auth-base": "^10.1.154"
44
+ "@things-factory/attachment-base": "^10.1.156",
45
+ "@things-factory/auth-base": "^10.1.155"
45
46
  },
46
- "gitHead": "f28579aa7131cbb282eb8813f8b285d9d29bb525"
47
+ "gitHead": "641146444c8ac4e510854c40824457ffad5cf3f1"
47
48
  }
@@ -0,0 +1,117 @@
1
+ import { test, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { createRequire } from 'node:module'
4
+
5
+ /*
6
+ * The accounts counterparties are paid into (ADR-0134): kept as digits, one counterparty's in the domain, gone with
7
+ * their counterparty, heard by the module that links bank lines, and their files read only by who may read
8
+ * counterparties. An in-memory SQLite source; no application database is opened.
9
+ */
10
+
11
+ const require = createRequire(import.meta.url)
12
+ const { DataSource } = require('typeorm')
13
+ const shell = require('@things-factory/shell')
14
+ const auth = require('@things-factory/auth-base')
15
+ const biz = require('../dist-server/index.js')
16
+ const { counterpartyDocumentReader } = require('../dist-server/service/counterparty/counterparty-document-readers.js')
17
+
18
+ let db, domain, other, user, hanbit, nuri, theirs
19
+ before(async () => {
20
+ db = await new DataSource({
21
+ type: 'sqlite',
22
+ database: ':memory:',
23
+ synchronize: true,
24
+ entities: [...new Set([...shell.entities, ...auth.entities, ...biz.entities])],
25
+ namingStrategy: new shell.NamingStrategy()
26
+ }).initialize()
27
+ domain = await db.getRepository(shell.Domain).save({ name: 'one', subdomain: 'account-one' })
28
+ other = await db.getRepository(shell.Domain).save({ name: 'two', subdomain: 'account-two' })
29
+ user = await db.getRepository(auth.User).save({ name: 'clerk', email: 'clerk@example.test', status: 'activated', domains: [domain] })
30
+ ;[hanbit, nuri] = await db.transaction(tx => biz.saveCounterparties(tx, domain, user, [{ name: '(주)한빛' }, { name: '누리' }]))
31
+ ;[theirs] = await db.transaction(tx => biz.saveCounterparties(tx, other, user, [{ name: '남의 거래처' }]))
32
+ })
33
+ after(async () => {
34
+ await db?.destroy()
35
+ })
36
+
37
+ const save = (patches, into = domain) => db.transaction(tx => biz.saveCounterpartyBankAccounts(tx, into, user, patches))
38
+ const accountsOf = party => db.getRepository(biz.CounterpartyBankAccount).find({ where: { counterparty: { id: party.id } } })
39
+
40
+ test('an account number is its digits - the hyphens a bank prints do not make another account', () => {
41
+ assert.equal(biz.accountKey('110-123-456789'), '110123456789')
42
+ assert.equal(biz.accountKey(' 110 123 456789 '), '110123456789')
43
+ assert.equal(biz.accountKey(null), '')
44
+ assert.equal(biz.accountKey('없음'), '')
45
+ })
46
+
47
+ test('an account is registered with its bank, number and holder - the number kept as digits', async () => {
48
+ const [one] = await save([{ counterpartyId: hanbit.id, bankName: ' 기업은행 ', accountNumber: '026-123456-01-017', holderName: '(주)한빛' }])
49
+ assert.equal(one.accountNumber, '02612345601017')
50
+ assert.equal(one.bankName, '기업은행')
51
+ assert.equal(one.holderName, '(주)한빛')
52
+ assert.equal(one.counterpartyId, hanbit.id)
53
+ await assert.rejects(save([{ counterpartyId: hanbit.id, accountNumber: '-' }]), { code: 'counterparty-account-number-required' })
54
+ await assert.rejects(save([{ accountNumber: '1234' }]), { code: 'counterparty-not-found' })
55
+ })
56
+
57
+ test('a number is one counterparty account in the domain - the refusal names who holds it', async () => {
58
+ await assert.rejects(save([{ counterpartyId: nuri.id, accountNumber: '02612345601017' }]), error => {
59
+ assert.equal(error.code, 'counterparty-account-clash')
60
+ assert.equal(error.params.existing, '(주)한빛')
61
+ return true
62
+ })
63
+ await assert.rejects(save([{ counterpartyId: hanbit.id, accountNumber: '026-1234-5601-017' }]), { code: 'counterparty-account-clash' }, 'the same digits, written another way')
64
+ const [two] = await save([{ counterpartyId: nuri.id, accountNumber: '333-3333-3333' }])
65
+ await assert.rejects(save([{ id: two.id, cuFlag: 'M', accountNumber: '02612345601017' }]), { code: 'counterparty-account-clash' })
66
+ })
67
+
68
+ test('the database holds a number to one live account, whatever writes it', async () => {
69
+ const repo = db.getRepository(biz.CounterpartyBankAccount)
70
+ await assert.rejects(repo.save({ domain, counterparty: { id: nuri.id }, accountNumber: '02612345601017' }), /UNIQUE/)
71
+ })
72
+
73
+ test('another domain cannot use my counterparty, nor change or delete my accounts', async () => {
74
+ const [mine] = await accountsOf(hanbit)
75
+ await assert.rejects(save([{ counterpartyId: hanbit.id, accountNumber: '999' }], other), { code: 'counterparty-not-found' })
76
+ await assert.rejects(save([{ counterpartyId: theirs.id, accountNumber: '998' }]), { code: 'counterparty-not-found' })
77
+ await assert.rejects(save([{ id: mine.id, cuFlag: 'M', notes: 'x' }], other), { code: 'counterparty-account-not-found' })
78
+ await assert.rejects(save([{ id: mine.id, cuFlag: '-' }], other), { code: 'counterparty-account-not-found' })
79
+ const [same] = await save([{ counterpartyId: theirs.id, accountNumber: '02612345601017' }], other)
80
+ assert.equal(same.accountNumber, '02612345601017', 'another domain keeps its own list')
81
+ })
82
+
83
+ test('a deleted account frees its number; a deleted counterparty takes its accounts with it', async () => {
84
+ const [mine] = await accountsOf(nuri)
85
+ await save([{ id: mine.id, cuFlag: '-' }])
86
+ const [again] = await save([{ counterpartyId: hanbit.id, accountNumber: '333-3333-3333' }])
87
+ assert.equal(again.counterpartyId, hanbit.id)
88
+
89
+ const [gone] = await db.transaction(tx => biz.saveCounterparties(tx, domain, user, [{ name: '곧 지울 곳' }]))
90
+ await save([{ counterpartyId: gone.id, accountNumber: '777-7777' }])
91
+ await db.transaction(tx => biz.saveCounterparties(tx, domain, user, [{ id: gone.id, cuFlag: '-' }]))
92
+ assert.deepEqual(await accountsOf(gone), [])
93
+ const [taken] = await save([{ counterpartyId: nuri.id, accountNumber: '7777777' }])
94
+ assert.equal(taken.counterpartyId, nuri.id, 'the number is free for whoever holds it next')
95
+ })
96
+
97
+ test('the module that links bank lines hears every save, with each account\'s counterparty, in the same transaction', async () => {
98
+ const heard = []
99
+ biz.onCounterpartyBankAccountsSaved(async (tx, into, saved, by) => {
100
+ heard.push(...saved.map(one => [one.accountNumber, one.counterpartyId, by.id]))
101
+ assert.ok(tx.queryRunner, 'the transaction of the save')
102
+ })
103
+ await save([{ counterpartyId: nuri.id, accountNumber: '555-55' }])
104
+ assert.deepEqual(heard, [['55555', nuri.id, user.id]])
105
+ })
106
+
107
+ test('a business registration and a passbook copy are read only by who may query counterparties', async () => {
108
+ const asked = []
109
+ const check = async (privilege, ...rest) => (asked.push(privilege), privilege.category === 'counterparty' && rest[0]?.id === 'reader')
110
+ const context = id => ({ state: { domain, user: { id } } })
111
+ assert.equal(await counterpartyDocumentReader({}, context('reader'), check), true)
112
+ assert.equal(await counterpartyDocumentReader({}, context('stranger'), check), false)
113
+ assert.deepEqual(asked[0], { category: 'counterparty', privilege: 'query', owner: true })
114
+
115
+ const access = require('@things-factory/attachment-base')
116
+ for (const refType of [biz.BUSINESS_REGISTRATION_REF_TYPE, biz.PASSBOOK_REF_TYPE]) assert.ok(access.attachmentReaderOf(refType), `${refType} has its reader`)
117
+ })
@@ -14,5 +14,8 @@
14
14
  "error.counterparty-number-clash": "Business number {number} already belongs to '{existing}'. Edit that counterparty instead.",
15
15
  "error.counterparty-not-found": "The counterparty was not found. Reload it.",
16
16
  "error.counterparty-kind-invalid": "'{text}' is neither a company nor a person.",
17
- "error.business-number-invalid": "'{text}' is not a business registration number. It has 10 digits."
17
+ "error.business-number-invalid": "'{text}' is not a business registration number. It has 10 digits.",
18
+ "error.counterparty-account-number-required": "Give the account number.",
19
+ "error.counterparty-account-clash": "Account {number} is already registered to '{existing}'.",
20
+ "error.counterparty-account-not-found": "The account was not found. Reload it."
18
21
  }
@@ -14,5 +14,8 @@
14
14
  "error.counterparty-number-clash": "El número de empresa {number} ya pertenece a '{existing}'. Edite esa contraparte en su lugar.",
15
15
  "error.counterparty-not-found": "No se encontró la contraparte. Vuelva a cargarla.",
16
16
  "error.counterparty-kind-invalid": "'{text}' no es ni una empresa ni una persona.",
17
- "error.business-number-invalid": "'{text}' no es un número de registro empresarial. Debe tener 10 dígitos."
17
+ "error.business-number-invalid": "'{text}' no es un número de registro empresarial. Debe tener 10 dígitos.",
18
+ "error.counterparty-account-number-required": "Indique el número de cuenta.",
19
+ "error.counterparty-account-clash": "La cuenta {number} ya está registrada a '{existing}'.",
20
+ "error.counterparty-account-not-found": "No se encontró la cuenta. Vuelva a cargarla."
18
21
  }
@@ -10,5 +10,8 @@
10
10
  "field.latlng": "緯度/経度",
11
11
  "field.company": "会社",
12
12
  "error.counterparty-number-clash": "事業者番号 {number} はすでに取引先 '{existing}' の番号です。その取引先を編集してください。",
13
- "error.no x found": "{x}が見つかりません"
13
+ "error.no x found": "{x}が見つかりません",
14
+ "error.counterparty-account-number-required": "口座番号を入力してください。",
15
+ "error.counterparty-account-clash": "口座 {number} はすでに取引先 '{existing}' の口座です。",
16
+ "error.counterparty-account-not-found": "口座が見つかりません。再読み込みしてください。"
14
17
  }
@@ -14,5 +14,8 @@
14
14
  "error.counterparty-number-clash": "사업자번호 {number}은(는) 이미 거래처 '{existing}'의 번호입니다. 그 거래처를 고치세요.",
15
15
  "error.counterparty-not-found": "거래처를 찾을 수 없습니다. 다시 불러오세요.",
16
16
  "error.counterparty-kind-invalid": "'{text}'은(는) 회사도 개인도 아닙니다.",
17
- "error.business-number-invalid": "'{text}'은(는) 사업자등록번호가 아닙니다. 사업자등록번호는 숫자 10자리입니다."
17
+ "error.business-number-invalid": "'{text}'은(는) 사업자등록번호가 아닙니다. 사업자등록번호는 숫자 10자리입니다.",
18
+ "error.counterparty-account-number-required": "계좌번호를 넣으세요.",
19
+ "error.counterparty-account-clash": "계좌 {number}은(는) 이미 거래처 '{existing}'의 계좌입니다.",
20
+ "error.counterparty-account-not-found": "계좌를 찾을 수 없습니다. 다시 불러오세요."
18
21
  }
@@ -10,5 +10,8 @@
10
10
  "field.latlng": "latitud/longitud",
11
11
  "field.company": "syarikat",
12
12
  "error.counterparty-number-clash": "Nombor pendaftaran perniagaan {number} sudah milik '{existing}'. Sunting rakan niaga itu sahaja.",
13
- "error.no x found": "tiada {x} yang dijumpai"
13
+ "error.no x found": "tiada {x} yang dijumpai",
14
+ "error.counterparty-account-number-required": "Masukkan nombor akaun.",
15
+ "error.counterparty-account-clash": "Akaun {number} sudah didaftarkan kepada '{existing}'.",
16
+ "error.counterparty-account-not-found": "Akaun tidak dijumpai. Muat semula."
14
17
  }
@@ -10,5 +10,8 @@
10
10
  "field.latlng": "纬度/经度",
11
11
  "field.company": "公司",
12
12
  "error.counterparty-number-clash": "营业登记号 {number} 已属于往来单位 '{existing}'。请改为编辑该往来单位。",
13
- "error.no x found": "没有找到{x}"
13
+ "error.no x found": "没有找到{x}",
14
+ "error.counterparty-account-number-required": "请输入账号。",
15
+ "error.counterparty-account-clash": "账户 {number} 已登记在往来单位 '{existing}' 名下。",
16
+ "error.counterparty-account-not-found": "找不到该账户。请重新加载。"
14
17
  }