@things-factory/invoice 10.1.226 → 10.1.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/document/document-amount.d.ts +7 -2
- package/dist-client/document/document-amount.js +11 -6
- package/dist-client/document/document-amount.js.map +1 -1
- package/dist-client/document/document-defaults.d.ts +24 -0
- package/dist-client/document/document-defaults.js +43 -0
- package/dist-client/document/document-defaults.js.map +1 -0
- package/dist-client/document/document-form.d.ts +24 -6
- package/dist-client/document/document-form.js +126 -15
- package/dist-client/document/document-form.js.map +1 -1
- package/dist-client/document/document-lines.d.ts +35 -1
- package/dist-client/document/document-lines.js +58 -9
- package/dist-client/document/document-lines.js.map +1 -1
- package/dist-client/document/document-party.d.ts +37 -0
- package/dist-client/document/document-party.js +217 -0
- package/dist-client/document/document-party.js.map +1 -0
- package/dist-client/document/document-preview.d.ts +37 -0
- package/dist-client/document/document-preview.js +181 -0
- package/dist-client/document/document-preview.js.map +1 -0
- package/dist-client/document/document-print.d.ts +3 -13
- package/dist-client/document/document-print.js +3 -45
- package/dist-client/document/document-print.js.map +1 -1
- package/dist-client/index.d.ts +9 -3
- package/dist-client/index.js +9 -3
- package/dist-client/index.js.map +1 -1
- package/dist-client/pages/invoice-editor.d.ts +37 -12
- package/dist-client/pages/invoice-editor.js +171 -79
- package/dist-client/pages/invoice-editor.js.map +1 -1
- package/dist-client/pages/invoice-list.d.ts +21 -1
- package/dist-client/pages/invoice-list.js +145 -14
- package/dist-client/pages/invoice-list.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/document-defaults-setting-let.d.ts +19 -0
- package/dist-client/viewparts/document-defaults-setting-let.js +139 -0
- package/dist-client/viewparts/document-defaults-setting-let.js.map +1 -0
- package/dist-server/role-tasks.js +5 -2
- package/dist-server/role-tasks.js.map +1 -1
- package/dist-server/service/document-defaults/document-defaults-resolver.d.ts +28 -0
- package/dist-server/service/document-defaults/document-defaults-resolver.js +136 -0
- package/dist-server/service/document-defaults/document-defaults-resolver.js.map +1 -0
- package/dist-server/service/document-defaults/document-defaults-rules.d.ts +25 -0
- package/dist-server/service/document-defaults/document-defaults-rules.js +50 -0
- package/dist-server/service/document-defaults/document-defaults-rules.js.map +1 -0
- package/dist-server/service/document-defaults/index.d.ts +6 -0
- package/dist-server/service/document-defaults/index.js +11 -0
- package/dist-server/service/document-defaults/index.js.map +1 -0
- package/dist-server/service/index.d.ts +2 -0
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/invoice/commercial-document.d.ts +2 -0
- package/dist-server/service/invoice/commercial-document.js +11 -1
- package/dist-server/service/invoice/commercial-document.js.map +1 -1
- package/dist-server/service/invoice/invoice-document-file.d.ts +20 -0
- package/dist-server/service/invoice/invoice-document-file.js +38 -0
- package/dist-server/service/invoice/invoice-document-file.js.map +1 -0
- package/dist-server/service/invoice/invoice-query.d.ts +3 -1
- package/dist-server/service/invoice/invoice-query.js +33 -0
- package/dist-server/service/invoice/invoice-query.js.map +1 -1
- package/dist-server/service/invoice/invoice-rules.d.ts +18 -11
- package/dist-server/service/invoice/invoice-rules.js +49 -96
- package/dist-server/service/invoice/invoice-rules.js.map +1 -1
- package/dist-server/service/invoice/invoice-service.d.ts +1 -0
- package/dist-server/service/invoice/invoice-service.js +5 -2
- package/dist-server/service/invoice/invoice-service.js.map +1 -1
- package/dist-server/service/invoice/invoice-type.d.ts +12 -0
- package/dist-server/service/invoice/invoice-type.js +37 -1
- package/dist-server/service/invoice/invoice-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-shared/document-amounts.d.ts +56 -0
- package/dist-shared/document-amounts.js +134 -0
- package/dist-shared/document-amounts.js.map +1 -0
- package/dist-shared/document-money.d.ts +12 -0
- package/dist-shared/document-money.js +38 -0
- package/dist-shared/document-money.js.map +1 -0
- package/dist-shared/document-print.d.ts +37 -0
- package/dist-shared/document-print.js +87 -0
- package/dist-shared/document-print.js.map +1 -0
- package/dist-shared/document-start.d.ts +15 -0
- package/dist-shared/document-start.js +51 -0
- package/dist-shared/document-start.js.map +1 -0
- package/dist-shared/invoice-standing.d.ts +25 -0
- package/dist-shared/invoice-standing.js +51 -0
- package/dist-shared/invoice-standing.js.map +1 -0
- package/package.json +9 -7
- package/shared/document-amounts.ts +209 -0
- package/shared/document-money.ts +33 -0
- package/shared/document-print.ts +131 -0
- package/shared/document-start.ts +46 -0
- package/shared/invoice-standing.ts +49 -0
- package/translations/en.json +32 -6
- package/translations/es.json +22 -1
- package/translations/ja.json +22 -1
- package/translations/ko.json +32 -6
- package/translations/ms.json +22 -1
- package/translations/zh.json +22 -1
- package/tsconfig.shared.tsbuildinfo +1 -1
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { i18next } from '@operato/i18n';
|
|
2
|
-
import {
|
|
2
|
+
import { defaultTaxRate, documentAmounts } from '@things-factory/invoice/dist-shared/document-amounts.js';
|
|
3
|
+
import { documentAmount, documentNumber } from './document-amount';
|
|
3
4
|
/** The grid of a document's lines — typed or pasted like a spreadsheet while the document is a draft. */
|
|
4
5
|
export function documentLinesConfig({ editable, received, currency }) {
|
|
5
|
-
|
|
6
|
+
/* Prices and discounts read with thousands marks; the widths keep the supply in view at about 900px, the unit's column too. */
|
|
7
|
+
const number = (name, header, width = 100) => ({
|
|
8
|
+
type: 'float',
|
|
9
|
+
name,
|
|
10
|
+
header,
|
|
11
|
+
width,
|
|
12
|
+
record: { editable, align: 'right', renderer: (value) => documentNumber(value, currency) }
|
|
13
|
+
});
|
|
6
14
|
return {
|
|
7
15
|
rows: { appendable: editable, selectable: { multiple: true }, accumulator: true },
|
|
8
16
|
pagination: { infinite: true },
|
|
@@ -10,15 +18,16 @@ export function documentLinesConfig({ editable, received, currency }) {
|
|
|
10
18
|
columns: [
|
|
11
19
|
{ type: 'gutter', gutterName: 'sequence' },
|
|
12
20
|
...(editable ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),
|
|
13
|
-
{ type: 'string', name: 'description', header: i18next.t('field.invoice-line-description'), width:
|
|
14
|
-
number('quantity', i18next.t('field.invoice-quantity'),
|
|
15
|
-
|
|
16
|
-
number('
|
|
21
|
+
{ type: 'string', name: 'description', header: i18next.t('field.invoice-line-description'), width: 220, record: { editable } },
|
|
22
|
+
number('quantity', i18next.t('field.invoice-quantity'), 60),
|
|
23
|
+
{ type: 'string', name: 'unit', header: i18next.t('field.invoice-unit'), width: 60, record: { editable, align: 'center' } },
|
|
24
|
+
number('unitPrice', i18next.t('field.invoice-unit-price'), 110),
|
|
25
|
+
number('discount', i18next.t('field.invoice-discount'), 80),
|
|
17
26
|
{
|
|
18
27
|
type: 'select',
|
|
19
28
|
name: 'taxKind',
|
|
20
29
|
header: i18next.t('field.invoice-tax-kind'),
|
|
21
|
-
width:
|
|
30
|
+
width: 70,
|
|
22
31
|
record: {
|
|
23
32
|
editable,
|
|
24
33
|
options: [
|
|
@@ -28,13 +37,13 @@ export function documentLinesConfig({ editable, received, currency }) {
|
|
|
28
37
|
]
|
|
29
38
|
}
|
|
30
39
|
},
|
|
31
|
-
number('taxRate', i18next.t('field.invoice-tax-rate-percent'),
|
|
40
|
+
number('taxRate', i18next.t('field.invoice-tax-rate-percent'), 56),
|
|
32
41
|
...(received ? [number('receivedTaxAmount', i18next.t('field.invoice-received-tax-amount'), 120)] : []),
|
|
33
42
|
{
|
|
34
43
|
type: 'float',
|
|
35
44
|
name: 'supplyAmount',
|
|
36
45
|
header: i18next.t('field.invoice-supply-amount'),
|
|
37
|
-
width:
|
|
46
|
+
width: 130,
|
|
38
47
|
record: { editable: false, align: 'right', renderer: (value) => documentAmount(value, currency) },
|
|
39
48
|
accumulator: 'sum'
|
|
40
49
|
}
|
|
@@ -49,6 +58,7 @@ export function linesOf(grist) {
|
|
|
49
58
|
.map(record => ({
|
|
50
59
|
description: String(record.description).trim(),
|
|
51
60
|
quantity: Number(record.quantity ?? 1),
|
|
61
|
+
unit: String(record.unit ?? '').trim() || null,
|
|
52
62
|
unitPrice: Number(record.unitPrice ?? 0),
|
|
53
63
|
discount: Number(record.discount ?? 0),
|
|
54
64
|
taxKind: record.taxKind || 'TAXABLE',
|
|
@@ -58,4 +68,43 @@ export function linesOf(grist) {
|
|
|
58
68
|
: { receivedTaxAmount: Number(record.receivedTaxAmount) })
|
|
59
69
|
}));
|
|
60
70
|
}
|
|
71
|
+
const blank = (value) => value === undefined || value === null || value === '';
|
|
72
|
+
/**
|
|
73
|
+
* What a line typed into is missing, filled - a quantity of one, no discount, taxable at the standard rate - and its
|
|
74
|
+
* rate following its tax kind when the kind is what changed (taxable 10 %, zero-rated and exempt none). A select shows
|
|
75
|
+
* its first option for an empty value, so 「과세」 looked chosen and choosing it changed nothing; the line now holds it.
|
|
76
|
+
*/
|
|
77
|
+
export function settleLine(record, changed) {
|
|
78
|
+
if (!record || record.__dirty__ === '-')
|
|
79
|
+
return;
|
|
80
|
+
if (blank(record.quantity))
|
|
81
|
+
record.quantity = 1;
|
|
82
|
+
if (blank(record.discount))
|
|
83
|
+
record.discount = 0;
|
|
84
|
+
if (!record.taxKind)
|
|
85
|
+
record.taxKind = 'TAXABLE';
|
|
86
|
+
if (changed === 'taxKind' || blank(record.taxRate))
|
|
87
|
+
record.taxRate = defaultTaxRate(record.taxKind);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The lines on the grid reckoned as the document will keep them (shared/document-amounts.ts): each row's supply is
|
|
91
|
+
* written onto it for the grid to show, and the document's tax groups and totals - after `discountAmount`, the
|
|
92
|
+
* discount of the whole document - are returned for the totals table.
|
|
93
|
+
*/
|
|
94
|
+
export function reckonLines(grist, currency, received = false, discountAmount = 0) {
|
|
95
|
+
const records = (grist?.dirtyData?.records ?? []).filter(record => record.__dirty__ !== '-' && String(record.description ?? '').trim());
|
|
96
|
+
const amounts = documentAmounts(records, currency || 'KRW', received, discountAmount);
|
|
97
|
+
records.forEach((record, index) => (record.supplyAmount = amounts.lines[index].supplyAmount ?? null));
|
|
98
|
+
return amounts;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A change on the grid as an editor takes it: the changed line settled, every line reckoned, the grid redrawn with
|
|
102
|
+
* the supplies. Returns the totals to show.
|
|
103
|
+
*/
|
|
104
|
+
export function onLineChange(grist, event, currency, received = false, discountAmount = 0) {
|
|
105
|
+
settleLine(event.detail?.after, event.detail?.column?.name);
|
|
106
|
+
const amounts = reckonLines(grist, currency, received, discountAmount);
|
|
107
|
+
grist?.refresh();
|
|
108
|
+
return amounts;
|
|
109
|
+
}
|
|
61
110
|
//# sourceMappingURL=document-lines.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-lines.js","sourceRoot":"","sources":["../../client/document/document-lines.ts"],"names":[],"mappings":"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","sourcesContent":["import { DataGrist } from '@operato/data-grist'\nimport { i18next } from '@operato/i18n'\n\nimport { documentAmount } from './document-amount'\n\n/** The grid of a document's lines — typed or pasted like a spreadsheet while the document is a draft. */\nexport function documentLinesConfig({ editable, received, currency }: { editable: boolean; received?: boolean; currency: string }) {\n const number = (name: string, header: string, width = 110) => ({ type: 'float', name, header, width, record: { editable, align: 'right' } })\n return {\n rows: { appendable: editable, selectable: { multiple: true }, accumulator: true },\n pagination: { infinite: true },\n emptyNote: { title: i18next.t('text.invoice-lines-empty') },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n ...(editable ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),\n { type: 'string', name: 'description', header: i18next.t('field.invoice-line-description'), width: 280, record: { editable } },\n number('quantity', i18next.t('field.invoice-quantity'), 90),\n number('unitPrice', i18next.t('field.invoice-unit-price'), 120),\n number('discount', i18next.t('field.invoice-discount')),\n {\n type: 'select',\n name: 'taxKind',\n header: i18next.t('field.invoice-tax-kind'),\n width: 100,\n record: {\n editable,\n options: [\n { value: 'TAXABLE', display: i18next.t('text.invoice-tax-kind-taxable') },\n { value: 'ZERO_RATED', display: i18next.t('text.invoice-tax-kind-zero-rated') },\n { value: 'EXEMPT', display: i18next.t('text.invoice-tax-kind-exempt') }\n ]\n }\n },\n number('taxRate', i18next.t('field.invoice-tax-rate-percent'), 90),\n ...(received ? [number('receivedTaxAmount', i18next.t('field.invoice-received-tax-amount'), 120)] : []),\n {\n type: 'float',\n name: 'supplyAmount',\n header: i18next.t('field.invoice-supply-amount'),\n width: 140,\n record: { editable: false, align: 'right', renderer: (value: number) => documentAmount(value, currency) },\n accumulator: 'sum'\n }\n ]\n }\n}\n\n/** The lines on the grid, as the save mutation takes them; blank rows are left out. */\nexport function linesOf(grist: DataGrist | null | undefined) {\n const records = (grist?.dirtyData?.records ?? []) as any[]\n return records\n .filter(record => record.__dirty__ !== '-' && String(record.description ?? '').trim())\n .map(record => ({\n description: String(record.description).trim(),\n quantity: Number(record.quantity ?? 1),\n unitPrice: Number(record.unitPrice ?? 0),\n discount: Number(record.discount ?? 0),\n taxKind: record.taxKind || 'TAXABLE',\n taxRate: Number(record.taxRate ?? 0),\n ...(record.receivedTaxAmount === undefined || record.receivedTaxAmount === null || record.receivedTaxAmount === ''\n ? {}\n : { receivedTaxAmount: Number(record.receivedTaxAmount) })\n }))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"document-lines.js","sourceRoot":"","sources":["../../client/document/document-lines.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAwB,MAAM,yDAAyD,CAAA;AAE/H,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElE,yGAAyG;AACzG,MAAM,UAAU,mBAAmB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAA+D;IAC/H,+HAA+H;IAC/H,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,KAAK,GAAG,GAAG,EAAE,EAAE,CAAC,CAAC;QAC7D,IAAI,EAAE,OAAO;QACb,IAAI;QACJ,MAAM;QACN,KAAK;QACL,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE;KACnG,CAAC,CAAA;IACF,OAAO;QACL,IAAI,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;QACjF,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;QAC9B,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE;QAC3D,OAAO,EAAE;YACP,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;YAC1C,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE;YAC9H,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,EAAE,EAAE,CAAC;YAC3D,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAC3H,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,GAAG,CAAC;YAC/D,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,EAAE,EAAE,CAAC;YAC3D;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;gBAC3C,KAAK,EAAE,EAAE;gBACT,MAAM,EAAE;oBACN,QAAQ;oBACR,OAAO,EAAE;wBACP,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC,EAAE;wBACzE,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAAC,EAAE;wBAC/E,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,EAAE;qBACxE;iBACF;aACF;YACD,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC,EAAE,EAAE,CAAC;YAClE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC,CAAC,mCAAmC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvG;gBACE,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;gBAChD,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE;gBACzG,WAAW,EAAE,KAAK;aACnB;SACF;KACF,CAAA;AACH,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,OAAO,CAAC,KAAmC;IACzD,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,IAAI,EAAE,CAAU,CAAA;IAC1D,OAAO,OAAO;SACX,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;SACrF,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACd,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE;QAC9C,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI;QAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;QACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,SAAS;QACpC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;QACpC,GAAG,CAAC,MAAM,CAAC,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,iBAAiB,KAAK,IAAI,IAAI,MAAM,CAAC,iBAAiB,KAAK,EAAE;YAChH,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC;KAC7D,CAAC,CAAC,CAAA;AACP,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAA;AAEvF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAW,EAAE,OAAgB;IACtD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,KAAK,GAAG;QAAE,OAAM;IAC/C,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAA;IAC/C,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAA;IAC/C,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IAC/C,IAAI,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,MAAM,CAAC,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACrG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAmC,EAAE,QAAgB,EAAE,QAAQ,GAAG,KAAK,EAAE,iBAA0B,CAAC;IAC9H,MAAM,OAAO,GAAI,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,IAAI,EAAE,CAAW,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAClJ,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAA;IACrF,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAA;IACrG,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmC,EAAE,KAAkB,EAAE,QAAgB,EAAE,QAAQ,GAAG,KAAK,EAAE,iBAA0B,CAAC;IACnJ,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAA;IACtE,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import { DataGrist } from '@operato/data-grist'\nimport { i18next } from '@operato/i18n'\n\nimport { defaultTaxRate, documentAmounts, type DocumentAmounts } from '@things-factory/invoice/dist-shared/document-amounts.js'\n\nimport { documentAmount, documentNumber } from './document-amount'\n\n/** The grid of a document's lines — typed or pasted like a spreadsheet while the document is a draft. */\nexport function documentLinesConfig({ editable, received, currency }: { editable: boolean; received?: boolean; currency: string }) {\n /* Prices and discounts read with thousands marks; the widths keep the supply in view at about 900px, the unit's column too. */\n const number = (name: string, header: string, width = 100) => ({\n type: 'float',\n name,\n header,\n width,\n record: { editable, align: 'right', renderer: (value: number) => documentNumber(value, currency) }\n })\n return {\n rows: { appendable: editable, selectable: { multiple: true }, accumulator: true },\n pagination: { infinite: true },\n emptyNote: { title: i18next.t('text.invoice-lines-empty') },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n ...(editable ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),\n { type: 'string', name: 'description', header: i18next.t('field.invoice-line-description'), width: 220, record: { editable } },\n number('quantity', i18next.t('field.invoice-quantity'), 60),\n { type: 'string', name: 'unit', header: i18next.t('field.invoice-unit'), width: 60, record: { editable, align: 'center' } },\n number('unitPrice', i18next.t('field.invoice-unit-price'), 110),\n number('discount', i18next.t('field.invoice-discount'), 80),\n {\n type: 'select',\n name: 'taxKind',\n header: i18next.t('field.invoice-tax-kind'),\n width: 70,\n record: {\n editable,\n options: [\n { value: 'TAXABLE', display: i18next.t('text.invoice-tax-kind-taxable') },\n { value: 'ZERO_RATED', display: i18next.t('text.invoice-tax-kind-zero-rated') },\n { value: 'EXEMPT', display: i18next.t('text.invoice-tax-kind-exempt') }\n ]\n }\n },\n number('taxRate', i18next.t('field.invoice-tax-rate-percent'), 56),\n ...(received ? [number('receivedTaxAmount', i18next.t('field.invoice-received-tax-amount'), 120)] : []),\n {\n type: 'float',\n name: 'supplyAmount',\n header: i18next.t('field.invoice-supply-amount'),\n width: 130,\n record: { editable: false, align: 'right', renderer: (value: number) => documentAmount(value, currency) },\n accumulator: 'sum'\n }\n ]\n }\n}\n\n/** The lines on the grid, as the save mutation takes them; blank rows are left out. */\nexport function linesOf(grist: DataGrist | null | undefined) {\n const records = (grist?.dirtyData?.records ?? []) as any[]\n return records\n .filter(record => record.__dirty__ !== '-' && String(record.description ?? '').trim())\n .map(record => ({\n description: String(record.description).trim(),\n quantity: Number(record.quantity ?? 1),\n unit: String(record.unit ?? '').trim() || null,\n unitPrice: Number(record.unitPrice ?? 0),\n discount: Number(record.discount ?? 0),\n taxKind: record.taxKind || 'TAXABLE',\n taxRate: Number(record.taxRate ?? 0),\n ...(record.receivedTaxAmount === undefined || record.receivedTaxAmount === null || record.receivedTaxAmount === ''\n ? {}\n : { receivedTaxAmount: Number(record.receivedTaxAmount) })\n }))\n}\n\nconst blank = (value: unknown) => value === undefined || value === null || value === ''\n\n/**\n * What a line typed into is missing, filled - a quantity of one, no discount, taxable at the standard rate - and its\n * rate following its tax kind when the kind is what changed (taxable 10 %, zero-rated and exempt none). A select shows\n * its first option for an empty value, so 「과세」 looked chosen and choosing it changed nothing; the line now holds it.\n */\nexport function settleLine(record: any, changed?: string) {\n if (!record || record.__dirty__ === '-') return\n if (blank(record.quantity)) record.quantity = 1\n if (blank(record.discount)) record.discount = 0\n if (!record.taxKind) record.taxKind = 'TAXABLE'\n if (changed === 'taxKind' || blank(record.taxRate)) record.taxRate = defaultTaxRate(record.taxKind)\n}\n\n/**\n * The lines on the grid reckoned as the document will keep them (shared/document-amounts.ts): each row's supply is\n * written onto it for the grid to show, and the document's tax groups and totals - after `discountAmount`, the\n * discount of the whole document - are returned for the totals table.\n */\nexport function reckonLines(grist: DataGrist | null | undefined, currency: string, received = false, discountAmount: unknown = 0): DocumentAmounts {\n const records = ((grist?.dirtyData?.records ?? []) as any[]).filter(record => record.__dirty__ !== '-' && String(record.description ?? '').trim())\n const amounts = documentAmounts(records, currency || 'KRW', received, discountAmount)\n records.forEach((record, index) => (record.supplyAmount = amounts.lines[index].supplyAmount ?? null))\n return amounts\n}\n\n/**\n * A change on the grid as an editor takes it: the changed line settled, every line reckoned, the grid redrawn with\n * the supplies. Returns the totals to show.\n */\nexport function onLineChange(grist: DataGrist | null | undefined, event: CustomEvent, currency: string, received = false, discountAmount: unknown = 0): DocumentAmounts {\n settleLine(event.detail?.after, event.detail?.column?.name)\n const amounts = reckonLines(grist, currency, received, discountAmount)\n grist?.refresh()\n return amounts\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
/** A counterparty as a document's party - which one, its name and registration number (`counterpartyChoices`). */
|
|
4
|
+
export type DocumentParty = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
businessNumber?: string | null;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The counterparty a party's name is - the one choice of that name, case and spaces aside (ADR-0170 decision 11); null
|
|
11
|
+
* when none or more than one is, or the choices cannot be read. Searched by the name's longest word, so 「(주) 하티오랩」
|
|
12
|
+
* finds 「(주)하티오랩」.
|
|
13
|
+
*/
|
|
14
|
+
export declare function partyOfName(name: unknown): Promise<DocumentParty | null>;
|
|
15
|
+
declare const DocumentPartySelect_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
16
|
+
/**
|
|
17
|
+
* Picking a counterparty for a document - one field that finds as it is typed and lists what it found under it; a
|
|
18
|
+
* click takes one and fires `party` with it (or with null when cleared). Every editor of a commercial document picks
|
|
19
|
+
* its party here - invoice and quotation (ADR-0120 decision 1). Reads `counterpartyChoices`, which every seat may.
|
|
20
|
+
*/
|
|
21
|
+
export declare class DocumentPartySelect extends DocumentPartySelect_base {
|
|
22
|
+
static styles: import("lit").CSSResult[];
|
|
23
|
+
/** The party chosen, shown as the field's placeholder until something is typed. */
|
|
24
|
+
party: DocumentParty | null;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
private search;
|
|
27
|
+
private found;
|
|
28
|
+
private active;
|
|
29
|
+
private timer?;
|
|
30
|
+
private asked;
|
|
31
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
32
|
+
private findSoon;
|
|
33
|
+
private find;
|
|
34
|
+
private key;
|
|
35
|
+
private choose;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
3
|
+
import gql from 'graphql-tag';
|
|
4
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
5
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
6
|
+
import { client } from '@operato/graphql';
|
|
7
|
+
import { i18next, localize } from '@operato/i18n';
|
|
8
|
+
import { ButtonStyles } from '@operato/styles';
|
|
9
|
+
import { partyNamed } from '@things-factory/invoice/dist-shared/document-start.js';
|
|
10
|
+
const CHOICES = gql `
|
|
11
|
+
query ($search: String, $pagination: Pagination) {
|
|
12
|
+
counterparties: counterpartyChoices(search: $search, pagination: $pagination) {
|
|
13
|
+
items { id name businessNumber }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
/**
|
|
18
|
+
* The counterparty a party's name is - the one choice of that name, case and spaces aside (ADR-0170 decision 11); null
|
|
19
|
+
* when none or more than one is, or the choices cannot be read. Searched by the name's longest word, so 「(주) 하티오랩」
|
|
20
|
+
* finds 「(주)하티오랩」.
|
|
21
|
+
*/
|
|
22
|
+
export async function partyOfName(name) {
|
|
23
|
+
const word = String(name ?? '')
|
|
24
|
+
.split(/\s+/)
|
|
25
|
+
.reduce((longest, one) => (one.length > longest.length ? one : longest), '');
|
|
26
|
+
if (!word)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
const response = await client.query({ query: CHOICES, variables: { search: word, pagination: { page: 1, limit: 100 } }, fetchPolicy: 'no-cache' });
|
|
30
|
+
return partyNamed(name, (response.data?.counterparties?.items ?? [])) ?? null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Picking a counterparty for a document - one field that finds as it is typed and lists what it found under it; a
|
|
38
|
+
* click takes one and fires `party` with it (or with null when cleared). Every editor of a commercial document picks
|
|
39
|
+
* its party here - invoice and quotation (ADR-0120 decision 1). Reads `counterpartyChoices`, which every seat may.
|
|
40
|
+
*/
|
|
41
|
+
let DocumentPartySelect = class DocumentPartySelect extends localize(i18next)(LitElement) {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
/** The party chosen, shown as the field's placeholder until something is typed. */
|
|
45
|
+
this.party = null;
|
|
46
|
+
this.disabled = false;
|
|
47
|
+
this.search = '';
|
|
48
|
+
this.found = null;
|
|
49
|
+
this.active = 0;
|
|
50
|
+
this.asked = 0;
|
|
51
|
+
}
|
|
52
|
+
static { this.styles = [
|
|
53
|
+
ButtonStyles,
|
|
54
|
+
css `
|
|
55
|
+
:host {
|
|
56
|
+
position: relative;
|
|
57
|
+
display: block;
|
|
58
|
+
}
|
|
59
|
+
.field {
|
|
60
|
+
display: flex;
|
|
61
|
+
gap: var(--spacing-small, 4px);
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
input {
|
|
65
|
+
flex: 1;
|
|
66
|
+
min-width: 0;
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
height: var(--form-element-height-medium, 32px);
|
|
69
|
+
padding: 0 var(--spacing-medium, 8px);
|
|
70
|
+
border: var(--input-field-border, 1px solid var(--md-sys-color-outline));
|
|
71
|
+
border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));
|
|
72
|
+
background: var(--input-field-background, var(--md-sys-color-surface-container-highest));
|
|
73
|
+
font: inherit;
|
|
74
|
+
font-size: var(--ui-t-body, 13px);
|
|
75
|
+
color: var(--ui-ink, var(--md-sys-color-on-surface));
|
|
76
|
+
}
|
|
77
|
+
input:focus-visible {
|
|
78
|
+
outline: none;
|
|
79
|
+
border-color: var(--md-sys-color-primary);
|
|
80
|
+
}
|
|
81
|
+
ul {
|
|
82
|
+
position: absolute;
|
|
83
|
+
z-index: 10;
|
|
84
|
+
left: 0;
|
|
85
|
+
right: 0;
|
|
86
|
+
top: calc(100% + 2px);
|
|
87
|
+
max-height: 260px;
|
|
88
|
+
overflow-y: auto;
|
|
89
|
+
margin: 0;
|
|
90
|
+
padding: var(--spacing-small, 4px) 0;
|
|
91
|
+
list-style: none;
|
|
92
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
93
|
+
border-radius: var(--ui-r, 10px);
|
|
94
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
95
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
|
96
|
+
}
|
|
97
|
+
li {
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
100
|
+
gap: var(--spacing-large, 12px);
|
|
101
|
+
padding: 6px var(--spacing-large, 12px);
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
font-size: var(--ui-t-body, 13px);
|
|
104
|
+
color: var(--ui-ink, var(--md-sys-color-on-surface));
|
|
105
|
+
}
|
|
106
|
+
li:hover,
|
|
107
|
+
li[active] {
|
|
108
|
+
background: var(--md-sys-color-surface-container-high);
|
|
109
|
+
}
|
|
110
|
+
li small {
|
|
111
|
+
white-space: nowrap;
|
|
112
|
+
}
|
|
113
|
+
li small,
|
|
114
|
+
li.none {
|
|
115
|
+
color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
|
|
116
|
+
}
|
|
117
|
+
li.none {
|
|
118
|
+
cursor: default;
|
|
119
|
+
}
|
|
120
|
+
`
|
|
121
|
+
]; }
|
|
122
|
+
render() {
|
|
123
|
+
return html `
|
|
124
|
+
<div class="field">
|
|
125
|
+
<input
|
|
126
|
+
.value=${this.search}
|
|
127
|
+
placeholder=${this.party?.name ?? i18next.t('text.invoice-counterparty-search')}
|
|
128
|
+
aria-label=${i18next.t('field.invoice-counterparty')}
|
|
129
|
+
?disabled=${this.disabled}
|
|
130
|
+
@focus=${() => this.find()}
|
|
131
|
+
@input=${(e) => ((this.search = e.target.value), this.findSoon())}
|
|
132
|
+
@keydown=${(e) => this.key(e)}
|
|
133
|
+
@blur=${() => setTimeout(() => (this.found = null), 150)}
|
|
134
|
+
/>
|
|
135
|
+
${this.party && !this.disabled
|
|
136
|
+
? html `<button quiet icon-only title=${i18next.t('button.invoice-counterparty-clear')} aria-label=${i18next.t('button.invoice-counterparty-clear')} @click=${() => this.choose(null)}>
|
|
137
|
+
<md-icon>close</md-icon>
|
|
138
|
+
</button>`
|
|
139
|
+
: nothing}
|
|
140
|
+
</div>
|
|
141
|
+
${this.found
|
|
142
|
+
? html `<ul role="listbox">
|
|
143
|
+
${this.found.length
|
|
144
|
+
? this.found.map((one, index) => html `<li role="option" ?active=${index === this.active} @mousedown=${(e) => (e.preventDefault(), this.choose(one))}>
|
|
145
|
+
<span>${one.name}</span>${one.businessNumber ? html `<small>${one.businessNumber}</small>` : nothing}
|
|
146
|
+
</li>`)
|
|
147
|
+
: html `<li class="none">${i18next.t('text.invoice-counterparty-none')}</li>`}
|
|
148
|
+
</ul>`
|
|
149
|
+
: nothing}
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
findSoon() {
|
|
153
|
+
clearTimeout(this.timer);
|
|
154
|
+
this.timer = setTimeout(() => this.find(), 250);
|
|
155
|
+
}
|
|
156
|
+
async find() {
|
|
157
|
+
if (this.disabled)
|
|
158
|
+
return;
|
|
159
|
+
const asked = ++this.asked;
|
|
160
|
+
try {
|
|
161
|
+
const response = await client.query({ query: CHOICES, variables: { search: this.search.trim() || undefined, pagination: { page: 1, limit: 30 } }, fetchPolicy: 'no-cache' });
|
|
162
|
+
/* Only the answer to the last thing typed is shown. */
|
|
163
|
+
if (asked !== this.asked)
|
|
164
|
+
return;
|
|
165
|
+
this.found = response.data?.counterparties?.items ?? [];
|
|
166
|
+
this.active = 0;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
this.found = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
key(e) {
|
|
173
|
+
if (!this.found?.length || e.isComposing)
|
|
174
|
+
return;
|
|
175
|
+
if (e.key === 'ArrowDown')
|
|
176
|
+
this.active = Math.min(this.active + 1, this.found.length - 1);
|
|
177
|
+
else if (e.key === 'ArrowUp')
|
|
178
|
+
this.active = Math.max(this.active - 1, 0);
|
|
179
|
+
else if (e.key === 'Enter')
|
|
180
|
+
this.choose(this.found[this.active]);
|
|
181
|
+
else if (e.key === 'Escape')
|
|
182
|
+
this.found = null;
|
|
183
|
+
else
|
|
184
|
+
return;
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
}
|
|
187
|
+
choose(party) {
|
|
188
|
+
this.search = '';
|
|
189
|
+
this.found = null;
|
|
190
|
+
this.dispatchEvent(new CustomEvent('party', { detail: party, bubbles: true, composed: true }));
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
__decorate([
|
|
194
|
+
property({ attribute: false }),
|
|
195
|
+
__metadata("design:type", Object)
|
|
196
|
+
], DocumentPartySelect.prototype, "party", void 0);
|
|
197
|
+
__decorate([
|
|
198
|
+
property({ type: Boolean }),
|
|
199
|
+
__metadata("design:type", Object)
|
|
200
|
+
], DocumentPartySelect.prototype, "disabled", void 0);
|
|
201
|
+
__decorate([
|
|
202
|
+
state(),
|
|
203
|
+
__metadata("design:type", Object)
|
|
204
|
+
], DocumentPartySelect.prototype, "search", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
state(),
|
|
207
|
+
__metadata("design:type", Array)
|
|
208
|
+
], DocumentPartySelect.prototype, "found", void 0);
|
|
209
|
+
__decorate([
|
|
210
|
+
state(),
|
|
211
|
+
__metadata("design:type", Object)
|
|
212
|
+
], DocumentPartySelect.prototype, "active", void 0);
|
|
213
|
+
DocumentPartySelect = __decorate([
|
|
214
|
+
customElement('document-party-select')
|
|
215
|
+
], DocumentPartySelect);
|
|
216
|
+
export { DocumentPartySelect };
|
|
217
|
+
//# sourceMappingURL=document-party.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-party.js","sourceRoot":"","sources":["../../client/document/document-party.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { ButtonStyles } from '@operato/styles'\nimport { partyNamed } from '@things-factory/invoice/dist-shared/document-start.js'\n\n/** A counterparty as a document's party - which one, its name and registration number (`counterpartyChoices`). */\nexport type DocumentParty = { id: string; name: string; businessNumber?: string | null }\n\nconst CHOICES = gql`\n query ($search: String, $pagination: Pagination) {\n counterparties: counterpartyChoices(search: $search, pagination: $pagination) {\n items { id name businessNumber }\n }\n }\n`\n\n/**\n * The counterparty a party's name is - the one choice of that name, case and spaces aside (ADR-0170 decision 11); null\n * when none or more than one is, or the choices cannot be read. Searched by the name's longest word, so 「(주) 하티오랩」\n * finds 「(주)하티오랩」.\n */\nexport async function partyOfName(name: unknown): Promise<DocumentParty | null> {\n const word = String(name ?? '')\n .split(/\\s+/)\n .reduce((longest, one) => (one.length > longest.length ? one : longest), '')\n if (!word) return null\n try {\n const response = await client.query({ query: CHOICES, variables: { search: word, pagination: { page: 1, limit: 100 } }, fetchPolicy: 'no-cache' })\n return partyNamed(name, (response.data?.counterparties?.items ?? []) as DocumentParty[]) ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Picking a counterparty for a document - one field that finds as it is typed and lists what it found under it; a\n * click takes one and fires `party` with it (or with null when cleared). Every editor of a commercial document picks\n * its party here - invoice and quotation (ADR-0120 decision 1). Reads `counterpartyChoices`, which every seat may.\n */\n@customElement('document-party-select')\nexport class DocumentPartySelect extends localize(i18next)(LitElement) {\n static styles = [\n ButtonStyles,\n css`\n :host {\n position: relative;\n display: block;\n }\n .field {\n display: flex;\n gap: var(--spacing-small, 4px);\n align-items: center;\n }\n input {\n flex: 1;\n min-width: 0;\n box-sizing: border-box;\n height: var(--form-element-height-medium, 32px);\n padding: 0 var(--spacing-medium, 8px);\n border: var(--input-field-border, 1px solid var(--md-sys-color-outline));\n border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));\n background: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n font: inherit;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n input:focus-visible {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n ul {\n position: absolute;\n z-index: 10;\n left: 0;\n right: 0;\n top: calc(100% + 2px);\n max-height: 260px;\n overflow-y: auto;\n margin: 0;\n padding: var(--spacing-small, 4px) 0;\n list-style: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: var(--ui-r, 10px);\n background: var(--md-sys-color-surface-container-lowest);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);\n }\n li {\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-large, 12px);\n padding: 6px var(--spacing-large, 12px);\n cursor: pointer;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n li:hover,\n li[active] {\n background: var(--md-sys-color-surface-container-high);\n }\n li small {\n white-space: nowrap;\n }\n li small,\n li.none {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n li.none {\n cursor: default;\n }\n `\n ]\n\n /** The party chosen, shown as the field's placeholder until something is typed. */\n @property({ attribute: false }) party: DocumentParty | null = null\n @property({ type: Boolean }) disabled = false\n\n @state() private search = ''\n @state() private found: DocumentParty[] | null = null\n @state() private active = 0\n\n private timer?: ReturnType<typeof setTimeout>\n private asked = 0\n\n render() {\n return html`\n <div class=\"field\">\n <input\n .value=${this.search}\n placeholder=${this.party?.name ?? i18next.t('text.invoice-counterparty-search')}\n aria-label=${i18next.t('field.invoice-counterparty')}\n ?disabled=${this.disabled}\n @focus=${() => this.find()}\n @input=${(e: any) => ((this.search = e.target.value), this.findSoon())}\n @keydown=${(e: KeyboardEvent) => this.key(e)}\n @blur=${() => setTimeout(() => (this.found = null), 150)}\n />\n ${this.party && !this.disabled\n ? html`<button quiet icon-only title=${i18next.t('button.invoice-counterparty-clear')} aria-label=${i18next.t('button.invoice-counterparty-clear')} @click=${() => this.choose(null)}>\n <md-icon>close</md-icon>\n </button>`\n : nothing}\n </div>\n ${this.found\n ? html`<ul role=\"listbox\">\n ${this.found.length\n ? this.found.map(\n (one, index) =>\n html`<li role=\"option\" ?active=${index === this.active} @mousedown=${(e: Event) => (e.preventDefault(), this.choose(one))}>\n <span>${one.name}</span>${one.businessNumber ? html`<small>${one.businessNumber}</small>` : nothing}\n </li>`\n )\n : html`<li class=\"none\">${i18next.t('text.invoice-counterparty-none')}</li>`}\n </ul>`\n : nothing}\n `\n }\n\n private findSoon() {\n clearTimeout(this.timer)\n this.timer = setTimeout(() => this.find(), 250)\n }\n\n private async find() {\n if (this.disabled) return\n const asked = ++this.asked\n try {\n const response = await client.query({ query: CHOICES, variables: { search: this.search.trim() || undefined, pagination: { page: 1, limit: 30 } }, fetchPolicy: 'no-cache' })\n /* Only the answer to the last thing typed is shown. */\n if (asked !== this.asked) return\n this.found = response.data?.counterparties?.items ?? []\n this.active = 0\n } catch {\n this.found = null\n }\n }\n\n private key(e: KeyboardEvent) {\n if (!this.found?.length || e.isComposing) return\n if (e.key === 'ArrowDown') this.active = Math.min(this.active + 1, this.found.length - 1)\n else if (e.key === 'ArrowUp') this.active = Math.max(this.active - 1, 0)\n else if (e.key === 'Enter') this.choose(this.found[this.active])\n else if (e.key === 'Escape') this.found = null\n else return\n e.preventDefault()\n }\n\n private choose(party: DocumentParty | null) {\n this.search = ''\n this.found = null\n this.dispatchEvent(new CustomEvent('party', { detail: party, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@operato/input/ox-select.js';
|
|
3
|
+
import '@operato/popup/ox-popup-list.js';
|
|
4
|
+
import { LitElement } from 'lit';
|
|
5
|
+
import { type PrintLanguage } from './document-print';
|
|
6
|
+
/** The two forms the server makes a document in (`DocumentFileFormat`): the page to preview and print, and the PDF. */
|
|
7
|
+
export type DocumentFileFormat = 'HTML' | 'PDF';
|
|
8
|
+
/** A file the server made - its name, media type and bytes in base64 (`DocumentFile`). */
|
|
9
|
+
export type DocumentFile = {
|
|
10
|
+
fileName: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
content: string;
|
|
13
|
+
};
|
|
14
|
+
/** How a kind's editor asks the server for its document in a form and a language - the invoice's door, the quotation's. */
|
|
15
|
+
export type DocumentFileLoader = (format: DocumentFileFormat, language: PrintLanguage) => Promise<DocumentFile>;
|
|
16
|
+
declare const DocumentPreview_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
17
|
+
/**
|
|
18
|
+
* A saved invoice or quotation as it goes out, before it does (ADR-0170 decision 10): the server's page of A4 in a
|
|
19
|
+
* frame, the language of the print to change - the document's own first - and the PDF to download or the same page to
|
|
20
|
+
* print, in that language. Every commercial document's editor opens it with its own loader.
|
|
21
|
+
*/
|
|
22
|
+
export declare class DocumentPreview extends DocumentPreview_base {
|
|
23
|
+
static styles: import("lit").CSSResult[];
|
|
24
|
+
loader: DocumentFileLoader;
|
|
25
|
+
language: PrintLanguage;
|
|
26
|
+
private page;
|
|
27
|
+
private busy;
|
|
28
|
+
firstUpdated(): void;
|
|
29
|
+
private show;
|
|
30
|
+
private download;
|
|
31
|
+
/** As tall as its page - read from its document, which is ours (same origin, no script). */
|
|
32
|
+
private fit;
|
|
33
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
34
|
+
}
|
|
35
|
+
/** Open the preview of a saved document, in its own language when the print has words for it. */
|
|
36
|
+
export declare function openDocumentPreview(loader: DocumentFileLoader, language: string | null | undefined, title: string): import("@operato/layout").PopupHandle<unknown>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@operato/input/ox-select.js';
|
|
4
|
+
import '@operato/popup/ox-popup-list.js';
|
|
5
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
6
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
|
+
import { i18next, localize } from '@operato/i18n';
|
|
8
|
+
import { notify, openPopup } from '@operato/layout';
|
|
9
|
+
import { ButtonStyles, ScrollbarStyles } from '@operato/styles';
|
|
10
|
+
import { PRINT_LANGUAGES, printDocument } from './document-print';
|
|
11
|
+
const bytesOf = (content) => Uint8Array.from(atob(content), char => char.charCodeAt(0));
|
|
12
|
+
/** Hand a file the server made to the browser to save. */
|
|
13
|
+
function saveDocumentFile({ fileName, mimeType, content }) {
|
|
14
|
+
const url = URL.createObjectURL(new Blob([bytesOf(content)], { type: mimeType }));
|
|
15
|
+
const link = Object.assign(document.createElement('a'), { href: url, download: fileName });
|
|
16
|
+
link.click();
|
|
17
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A saved invoice or quotation as it goes out, before it does (ADR-0170 decision 10): the server's page of A4 in a
|
|
21
|
+
* frame, the language of the print to change - the document's own first - and the PDF to download or the same page to
|
|
22
|
+
* print, in that language. Every commercial document's editor opens it with its own loader.
|
|
23
|
+
*/
|
|
24
|
+
let DocumentPreview = class DocumentPreview extends localize(i18next)(LitElement) {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(...arguments);
|
|
27
|
+
this.language = PRINT_LANGUAGES[0];
|
|
28
|
+
this.page = '';
|
|
29
|
+
this.busy = false;
|
|
30
|
+
}
|
|
31
|
+
static { this.styles = [
|
|
32
|
+
ButtonStyles,
|
|
33
|
+
ScrollbarStyles,
|
|
34
|
+
css `
|
|
35
|
+
:host {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
height: 100%;
|
|
39
|
+
min-height: 0;
|
|
40
|
+
background: var(--ui-bg, var(--md-sys-color-surface));
|
|
41
|
+
}
|
|
42
|
+
.bar {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--spacing-medium, 8px);
|
|
47
|
+
padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
|
|
48
|
+
border-bottom: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
|
|
49
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
50
|
+
color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
|
|
51
|
+
}
|
|
52
|
+
.bar .spacer {
|
|
53
|
+
flex: 1;
|
|
54
|
+
}
|
|
55
|
+
ox-select {
|
|
56
|
+
min-width: 120px;
|
|
57
|
+
}
|
|
58
|
+
/* The frame grows to its page and this box scrolls it - with the house scrollbar, not the frame's own. */
|
|
59
|
+
.frame {
|
|
60
|
+
position: relative;
|
|
61
|
+
flex: 1;
|
|
62
|
+
min-height: 0;
|
|
63
|
+
overflow: auto;
|
|
64
|
+
background: #e9edf1;
|
|
65
|
+
}
|
|
66
|
+
iframe {
|
|
67
|
+
display: block;
|
|
68
|
+
width: 100%;
|
|
69
|
+
border: 0;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
}
|
|
72
|
+
.loading {
|
|
73
|
+
position: absolute;
|
|
74
|
+
inset: 0;
|
|
75
|
+
display: grid;
|
|
76
|
+
place-items: center;
|
|
77
|
+
background: rgba(233, 237, 241, 0.6);
|
|
78
|
+
font-size: var(--ui-t-body, 13px);
|
|
79
|
+
}
|
|
80
|
+
`
|
|
81
|
+
]; }
|
|
82
|
+
firstUpdated() {
|
|
83
|
+
this.show();
|
|
84
|
+
}
|
|
85
|
+
async show() {
|
|
86
|
+
this.busy = true;
|
|
87
|
+
try {
|
|
88
|
+
this.page = new TextDecoder().decode(bytesOf((await this.loader('HTML', this.language)).content));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) });
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
this.busy = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async download() {
|
|
98
|
+
this.busy = true;
|
|
99
|
+
try {
|
|
100
|
+
saveDocumentFile(await this.loader('PDF', this.language));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) });
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
this.busy = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** As tall as its page - read from its document, which is ours (same origin, no script). */
|
|
110
|
+
fit(frame) {
|
|
111
|
+
const height = frame.contentDocument?.documentElement?.scrollHeight;
|
|
112
|
+
if (height)
|
|
113
|
+
frame.style.height = `${height}px`;
|
|
114
|
+
}
|
|
115
|
+
render() {
|
|
116
|
+
const word = (language) => (language === 'en' ? i18next.t('text.invoice-language-en') : i18next.t('text.invoice-language-ko'));
|
|
117
|
+
return html `
|
|
118
|
+
<div class="bar">
|
|
119
|
+
<span>${i18next.t('title.invoice-print-language')}</span>
|
|
120
|
+
<ox-select
|
|
121
|
+
.value=${this.language}
|
|
122
|
+
aria-label=${i18next.t('title.invoice-print-language')}
|
|
123
|
+
@change=${(e) => {
|
|
124
|
+
const next = PRINT_LANGUAGES.find(one => one === e.detail);
|
|
125
|
+
if (!next || next === this.language)
|
|
126
|
+
return;
|
|
127
|
+
this.language = next;
|
|
128
|
+
this.show();
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<span slot="label">${word(this.language)}</span>
|
|
132
|
+
<ox-popup-list align-left nowrap>${PRINT_LANGUAGES.map(one => html `<div option value=${one}>${word(one)}</div>`)}</ox-popup-list>
|
|
133
|
+
</ox-select>
|
|
134
|
+
<span class="spacer"></span>
|
|
135
|
+
<button ?disabled=${this.busy || !this.page} @click=${() => printDocument(this.page)}><md-icon>print</md-icon>${i18next.t('button.invoice-print')}</button>
|
|
136
|
+
<button raised ?disabled=${this.busy} @click=${() => this.download()}><md-icon>picture_as_pdf</md-icon>${i18next.t('button.invoice-download-pdf')}</button>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="frame">
|
|
139
|
+
<!-- No script runs in the frame; an empty sandbox also kept the page from drawing in the app's browser. -->
|
|
140
|
+
<iframe
|
|
141
|
+
title=${i18next.t('title.invoice-preview')}
|
|
142
|
+
sandbox="allow-same-origin"
|
|
143
|
+
scrolling="no"
|
|
144
|
+
.srcdoc=${this.page}
|
|
145
|
+
@load=${(e) => this.fit(e.target)}
|
|
146
|
+
></iframe>
|
|
147
|
+
${this.busy ? html `<div class="loading">${i18next.t('text.loading')}</div>` : nothing}
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
__decorate([
|
|
153
|
+
property({ attribute: false }),
|
|
154
|
+
__metadata("design:type", Function)
|
|
155
|
+
], DocumentPreview.prototype, "loader", void 0);
|
|
156
|
+
__decorate([
|
|
157
|
+
property(),
|
|
158
|
+
__metadata("design:type", String)
|
|
159
|
+
], DocumentPreview.prototype, "language", void 0);
|
|
160
|
+
__decorate([
|
|
161
|
+
state(),
|
|
162
|
+
__metadata("design:type", Object)
|
|
163
|
+
], DocumentPreview.prototype, "page", void 0);
|
|
164
|
+
__decorate([
|
|
165
|
+
state(),
|
|
166
|
+
__metadata("design:type", Object)
|
|
167
|
+
], DocumentPreview.prototype, "busy", void 0);
|
|
168
|
+
DocumentPreview = __decorate([
|
|
169
|
+
customElement('document-preview')
|
|
170
|
+
], DocumentPreview);
|
|
171
|
+
export { DocumentPreview };
|
|
172
|
+
/** Open the preview of a saved document, in its own language when the print has words for it. */
|
|
173
|
+
export function openDocumentPreview(loader, language, title) {
|
|
174
|
+
const own = PRINT_LANGUAGES.find(one => one === language) ?? PRINT_LANGUAGES[0];
|
|
175
|
+
return openPopup(html `<document-preview .loader=${loader} .language=${own}></document-preview>`, {
|
|
176
|
+
backdrop: true,
|
|
177
|
+
size: 'large',
|
|
178
|
+
title
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=document-preview.js.map
|