@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,6 +1,11 @@
|
|
|
1
1
|
import { currencyDigits } from '@things-factory/invoice/dist-shared/currency-digits.js';
|
|
2
2
|
export { currencyDigits };
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* An amount in its currency as the language writes money - `shared/document-money.ts`, in the screen's language
|
|
5
|
+
* unless another is named.
|
|
6
|
+
*/
|
|
7
|
+
export declare function documentAmount(value: number | null | undefined, currency: string, lng?: string): string;
|
|
8
|
+
/** A number without the currency (`shared/document-money.ts`), in the screen's language unless another is named. */
|
|
9
|
+
export declare function documentNumber(value: number | null | undefined, currency: string, lng?: string, exact?: boolean): string;
|
|
5
10
|
/** The currencies a document may be written in, most used first. */
|
|
6
11
|
export declare function currencyChoices(): string[];
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { i18next } from '@operato/i18n';
|
|
2
2
|
import { currencyDigits } from '@things-factory/invoice/dist-shared/currency-digits.js';
|
|
3
|
+
import { documentAmount as amountIn, documentNumber as numberIn } from '@things-factory/invoice/dist-shared/document-money.js';
|
|
3
4
|
export { currencyDigits };
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
5
|
+
/**
|
|
6
|
+
* An amount in its currency as the language writes money - `shared/document-money.ts`, in the screen's language
|
|
7
|
+
* unless another is named.
|
|
8
|
+
*/
|
|
9
|
+
export function documentAmount(value, currency, lng = i18next.language || 'ko') {
|
|
10
|
+
return amountIn(value, currency, lng);
|
|
11
|
+
}
|
|
12
|
+
/** A number without the currency (`shared/document-money.ts`), in the screen's language unless another is named. */
|
|
13
|
+
export function documentNumber(value, currency, lng = i18next.language || 'ko', exact = false) {
|
|
14
|
+
return numberIn(value, currency, lng, exact);
|
|
10
15
|
}
|
|
11
16
|
/** The currencies a document may be written in, most used first. */
|
|
12
17
|
export function currencyChoices() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-amount.js","sourceRoot":"","sources":["../../client/document/document-amount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAA;
|
|
1
|
+
{"version":3,"file":"document-amount.js","sourceRoot":"","sources":["../../client/document/document-amount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAA;AACvF,OAAO,EAAE,cAAc,IAAI,QAAQ,EAAE,cAAc,IAAI,QAAQ,EAAE,MAAM,uDAAuD,CAAA;AAE9H,OAAO,EAAE,cAAc,EAAE,CAAA;AAEzB;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAgC,EAAE,QAAgB,EAAE,MAAc,OAAO,CAAC,QAAQ,IAAI,IAAI;IACvH,OAAO,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;AACvC,CAAC;AAED,oHAAoH;AACpH,MAAM,UAAU,cAAc,CAAC,KAAgC,EAAE,QAAgB,EAAE,MAAc,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,KAAK,GAAG,KAAK;IACtI,OAAO,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;AAC9C,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAI,IAAY,CAAC,iBAAiB,CAAC,CAAC,CAAG,IAAY,CAAC,iBAAiB,CAAC,UAAU,CAAc,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5G,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IACjD,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC","sourcesContent":["import { i18next } from '@operato/i18n'\nimport { currencyDigits } from '@things-factory/invoice/dist-shared/currency-digits.js'\nimport { documentAmount as amountIn, documentNumber as numberIn } from '@things-factory/invoice/dist-shared/document-money.js'\n\nexport { currencyDigits }\n\n/**\n * An amount in its currency as the language writes money - `shared/document-money.ts`, in the screen's language\n * unless another is named.\n */\nexport function documentAmount(value: number | null | undefined, currency: string, lng: string = i18next.language || 'ko'): string {\n return amountIn(value, currency, lng)\n}\n\n/** A number without the currency (`shared/document-money.ts`), in the screen's language unless another is named. */\nexport function documentNumber(value: number | null | undefined, currency: string, lng: string = i18next.language || 'ko', exact = false): string {\n return numberIn(value, currency, lng, exact)\n}\n\n/** The currencies a document may be written in, most used first. */\nexport function currencyChoices(): string[] {\n const all = (Intl as any).supportedValuesOf ? ((Intl as any).supportedValuesOf('currency') as string[]) : []\n const first = ['KRW', 'USD', 'EUR', 'JPY', 'CNY']\n return [...first, ...all.filter(code => !first.includes(code))]\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The company's own words every new quotation and invoice starts with (ADR-0170) - as the Settings screen keeps them. */
|
|
2
|
+
export type DocumentDefaults = {
|
|
3
|
+
supplierName: string;
|
|
4
|
+
supplierRegistrationNumber: string;
|
|
5
|
+
supplierAddress: string;
|
|
6
|
+
bankDetails: string;
|
|
7
|
+
paymentTerms: string[];
|
|
8
|
+
notes: string;
|
|
9
|
+
validDays: number;
|
|
10
|
+
/** Days an invoice is due after its issue day. */
|
|
11
|
+
paymentDays: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const DOCUMENT_DEFAULTS_FIELDS = "supplierName supplierRegistrationNumber supplierAddress bankDetails paymentTerms notes validDays paymentDays";
|
|
14
|
+
/** The company's words, or null when this person may not read them or they could not be read - the editor then starts empty. */
|
|
15
|
+
export declare function documentDefaults(): Promise<DocumentDefaults | null>;
|
|
16
|
+
/** A new sales document's head from the company's words - only the fields still blank, so a person's typing stays. */
|
|
17
|
+
export declare function headFromDefaults(form: Record<string, any>, defaults: DocumentDefaults | null): Record<string, any>;
|
|
18
|
+
/** Our side of a sales document, as a person writes it once - our name, number and address, the account, the terms. */
|
|
19
|
+
export declare const OUR_SIDE_FIELDS: string[];
|
|
20
|
+
/**
|
|
21
|
+
* What `last` - the last sales document of the same kind - wrote of our side, for those fields still blank on `form`;
|
|
22
|
+
* nothing for a purchase, whose supplier is the other party.
|
|
23
|
+
*/
|
|
24
|
+
export declare function oursFromLast(form: Record<string, any>, last: Record<string, any> | null | undefined): Record<string, any>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import gql from 'graphql-tag';
|
|
2
|
+
import { client } from '@operato/graphql';
|
|
3
|
+
import { hasPrivilege } from '@things-factory/auth-base/dist-client';
|
|
4
|
+
export const DOCUMENT_DEFAULTS_FIELDS = 'supplierName supplierRegistrationNumber supplierAddress bankDetails paymentTerms notes validDays paymentDays';
|
|
5
|
+
/** The company's words, or null when this person may not read them or they could not be read - the editor then starts empty. */
|
|
6
|
+
export async function documentDefaults() {
|
|
7
|
+
/* Asked only of whoever may read them - a refusal would be shown to someone who never asked for it. */
|
|
8
|
+
if (!(await hasPrivilege({ category: 'invoice-published', privilege: 'query', domainOwnerGranted: true })))
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const response = await client.query({ query: gql(`query { documentDefaults { ${DOCUMENT_DEFAULTS_FIELDS} } }`), fetchPolicy: 'no-cache', errorPolicy: 'all' });
|
|
12
|
+
return response.data?.documentDefaults ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** A new sales document's head from the company's words - only the fields still blank, so a person's typing stays. */
|
|
19
|
+
export function headFromDefaults(form, defaults) {
|
|
20
|
+
if (!defaults || form.direction === 'PURCHASE')
|
|
21
|
+
return {};
|
|
22
|
+
const words = {
|
|
23
|
+
supplierName: defaults.supplierName,
|
|
24
|
+
supplierRegistrationNumber: defaults.supplierRegistrationNumber,
|
|
25
|
+
supplierAddress: defaults.supplierAddress,
|
|
26
|
+
bankDetails: defaults.bankDetails,
|
|
27
|
+
paymentTerms: defaults.paymentTerms[0] ?? '',
|
|
28
|
+
notes: defaults.notes
|
|
29
|
+
};
|
|
30
|
+
return Object.fromEntries(Object.entries(words).filter(([key, value]) => value && !String(form[key] ?? '').trim()));
|
|
31
|
+
}
|
|
32
|
+
/** Our side of a sales document, as a person writes it once - our name, number and address, the account, the terms. */
|
|
33
|
+
export const OUR_SIDE_FIELDS = ['supplierName', 'supplierAddress', 'supplierRegistrationNumber', 'bankDetails', 'paymentTerms'];
|
|
34
|
+
/**
|
|
35
|
+
* What `last` - the last sales document of the same kind - wrote of our side, for those fields still blank on `form`;
|
|
36
|
+
* nothing for a purchase, whose supplier is the other party.
|
|
37
|
+
*/
|
|
38
|
+
export function oursFromLast(form, last) {
|
|
39
|
+
if (!last || form.direction === 'PURCHASE')
|
|
40
|
+
return {};
|
|
41
|
+
return Object.fromEntries(OUR_SIDE_FIELDS.filter(key => last[key] && !String(form[key] ?? '').trim()).map(key => [key, last[key]]));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=document-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-defaults.js","sourceRoot":"","sources":["../../client/document/document-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AAepE,MAAM,CAAC,MAAM,wBAAwB,GAAG,8GAA8G,CAAA;AAEtJ,gIAAgI;AAChI,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,uGAAuG;IACvG,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,mBAAmB,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACvH,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,8BAA8B,wBAAwB,MAAM,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;QAC9J,OAAO,QAAQ,CAAC,IAAI,EAAE,gBAAgB,IAAI,IAAI,CAAA;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,gBAAgB,CAAC,IAAyB,EAAE,QAAiC;IAC3F,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU;QAAE,OAAO,EAAE,CAAA;IACzD,MAAM,KAAK,GAA2B;QACpC,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,0BAA0B,EAAE,QAAQ,CAAC,0BAA0B;QAC/D,eAAe,EAAE,QAAQ,CAAC,eAAe;QACzC,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE;QAC5C,KAAK,EAAE,QAAQ,CAAC,KAAK;KACtB,CAAA;IACD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AACrH,CAAC;AAED,uHAAuH;AACvH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,cAAc,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,aAAa,EAAE,cAAc,CAAC,CAAA;AAE/H;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAyB,EAAE,IAA4C;IAClG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU;QAAE,OAAO,EAAE,CAAA;IACrD,OAAO,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AACrI,CAAC","sourcesContent":["import gql from 'graphql-tag'\n\nimport { client } from '@operato/graphql'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n/** The company's own words every new quotation and invoice starts with (ADR-0170) - as the Settings screen keeps them. */\nexport type DocumentDefaults = {\n supplierName: string\n supplierRegistrationNumber: string\n supplierAddress: string\n bankDetails: string\n paymentTerms: string[]\n notes: string\n validDays: number\n /** Days an invoice is due after its issue day. */\n paymentDays: number\n}\n\nexport const DOCUMENT_DEFAULTS_FIELDS = 'supplierName supplierRegistrationNumber supplierAddress bankDetails paymentTerms notes validDays paymentDays'\n\n/** The company's words, or null when this person may not read them or they could not be read - the editor then starts empty. */\nexport async function documentDefaults(): Promise<DocumentDefaults | null> {\n /* Asked only of whoever may read them - a refusal would be shown to someone who never asked for it. */\n if (!(await hasPrivilege({ category: 'invoice-published', privilege: 'query', domainOwnerGranted: true }))) return null\n try {\n const response = await client.query({ query: gql(`query { documentDefaults { ${DOCUMENT_DEFAULTS_FIELDS} } }`), fetchPolicy: 'no-cache', errorPolicy: 'all' })\n return response.data?.documentDefaults ?? null\n } catch {\n return null\n }\n}\n\n/** A new sales document's head from the company's words - only the fields still blank, so a person's typing stays. */\nexport function headFromDefaults(form: Record<string, any>, defaults: DocumentDefaults | null): Record<string, any> {\n if (!defaults || form.direction === 'PURCHASE') return {}\n const words: Record<string, string> = {\n supplierName: defaults.supplierName,\n supplierRegistrationNumber: defaults.supplierRegistrationNumber,\n supplierAddress: defaults.supplierAddress,\n bankDetails: defaults.bankDetails,\n paymentTerms: defaults.paymentTerms[0] ?? '',\n notes: defaults.notes\n }\n return Object.fromEntries(Object.entries(words).filter(([key, value]) => value && !String(form[key] ?? '').trim()))\n}\n\n/** Our side of a sales document, as a person writes it once - our name, number and address, the account, the terms. */\nexport const OUR_SIDE_FIELDS = ['supplierName', 'supplierAddress', 'supplierRegistrationNumber', 'bankDetails', 'paymentTerms']\n\n/**\n * What `last` - the last sales document of the same kind - wrote of our side, for those fields still blank on `form`;\n * nothing for a purchase, whose supplier is the other party.\n */\nexport function oursFromLast(form: Record<string, any>, last: Record<string, any> | null | undefined): Record<string, any> {\n if (!last || form.direction === 'PURCHASE') return {}\n return Object.fromEntries(OUR_SIDE_FIELDS.filter(key => last[key] && !String(form[key] ?? '').trim()).map(key => [key, last[key]]))\n}\n"]}
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { nothing, type TemplateResult } from 'lit';
|
|
2
2
|
export type DocumentValue = Record<string, any>;
|
|
3
|
-
|
|
4
|
-
export declare const HEAD_FIELDS: {
|
|
3
|
+
type HeadField = {
|
|
5
4
|
name: string;
|
|
6
5
|
label: string;
|
|
7
6
|
wide?: boolean;
|
|
8
7
|
prose?: boolean;
|
|
9
|
-
}
|
|
8
|
+
};
|
|
9
|
+
/** The head fields in the order a person fills them: the document, each party as one group, then the terms. */
|
|
10
|
+
export declare const HEAD_FIELDS: HeadField[];
|
|
10
11
|
export declare const DocumentFormStyles: import("lit").CSSResult;
|
|
11
12
|
/** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */
|
|
12
13
|
/** `numberLabel` names the number as the kind does - 「청구서 번호」, 「견적 번호」. */
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** `terms` are the payment terms the company offers (ADR-0170) - one click puts one in. */
|
|
15
|
+
/**
|
|
16
|
+
* `party` is the counterparty picker, drawn inside the group it fills - the buyer's of a sale, the supplier's of a
|
|
17
|
+
* purchase (ADR-0170 decision 11).
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderDocumentHead(value: DocumentValue, editable: boolean, change: (patch: DocumentValue) => void, dates?: TemplateResult | typeof nothing, numberLabel?: string, { terms, party: picker }?: {
|
|
20
|
+
terms?: string[];
|
|
21
|
+
party?: TemplateResult | typeof nothing;
|
|
22
|
+
}): TemplateResult<1>;
|
|
23
|
+
/**
|
|
24
|
+
* Supply and tax of each tax kind and rate, then the document totals. With a discount of the whole document the lines'
|
|
25
|
+
* supply and the discount come first, and each group's supply is the one after its part (ADR-0170 decision 8).
|
|
26
|
+
* `discount` puts the discount's input over the table while a draft is written; `change` takes what is typed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderDocumentTotals(value: DocumentValue, discount?: {
|
|
29
|
+
value: unknown;
|
|
30
|
+
editable: boolean;
|
|
31
|
+
change: (value: string) => void;
|
|
32
|
+
}): TemplateResult<1>;
|
|
33
|
+
export {};
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import { i18next } from '@operato/i18n';
|
|
2
2
|
import { css, html, nothing } from 'lit';
|
|
3
3
|
import { currencyChoices, documentAmount } from './document-amount';
|
|
4
|
-
/** The head fields in the order a person fills them. */
|
|
4
|
+
/** The head fields in the order a person fills them: the document, each party as one group, then the terms. */
|
|
5
5
|
export const HEAD_FIELDS = [
|
|
6
6
|
{ name: 'number', label: 'field.invoice-number' },
|
|
7
7
|
{ name: 'issueDate', label: 'field.invoice-issue-date' },
|
|
8
|
-
{ name: 'supplierName', label: 'field.invoice-
|
|
9
|
-
{ name: '
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: 'buyerAddress', label: 'field.invoice-
|
|
8
|
+
{ name: 'supplierName', label: 'field.invoice-party-name' },
|
|
9
|
+
{ name: 'supplierRegistrationNumber', label: 'field.invoice-party-registration-number' },
|
|
10
|
+
{ name: 'supplierAddress', label: 'field.invoice-party-address', prose: true },
|
|
11
|
+
{ name: 'buyerName', label: 'field.invoice-party-name' },
|
|
12
|
+
{ name: 'buyerRegistrationNumber', label: 'field.invoice-party-registration-number' },
|
|
13
|
+
{ name: 'buyerAddress', label: 'field.invoice-party-address', prose: true },
|
|
14
14
|
{ name: 'purchaseOrder', label: 'field.invoice-purchase-order' },
|
|
15
15
|
{ name: 'sourceReference', label: 'field.invoice-source-reference' },
|
|
16
16
|
{ name: 'paymentTerms', label: 'field.invoice-payment-terms', wide: true, prose: true },
|
|
17
17
|
{ name: 'bankDetails', label: 'field.invoice-bank-details', wide: true, prose: true },
|
|
18
18
|
{ name: 'notes', label: 'field.invoice-notes', wide: true, prose: true }
|
|
19
19
|
];
|
|
20
|
+
const field = (name) => HEAD_FIELDS.find(one => one.name === name);
|
|
20
21
|
export const DocumentFormStyles = css `
|
|
22
|
+
/*
|
|
23
|
+
* The lines' grid is as wide as its columns. With the grid styles the page is a flex item, and a flex item is at least
|
|
24
|
+
* as wide as what it holds: the page grew past the window, the head spread into three cut-off columns and the
|
|
25
|
+
* page scrolled sideways. The page and its sections may be narrower than the grid; the grid scrolls inside.
|
|
26
|
+
*/
|
|
27
|
+
.page,
|
|
28
|
+
.page-section,
|
|
29
|
+
ox-grist {
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
21
32
|
.form {
|
|
22
33
|
display: grid;
|
|
23
34
|
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
|
|
@@ -33,6 +44,27 @@ export const DocumentFormStyles = css `
|
|
|
33
44
|
.form .wide {
|
|
34
45
|
grid-column: 1 / -1;
|
|
35
46
|
}
|
|
47
|
+
/* The two parties side by side, each its name, number and address together - not interleaved in one grid. */
|
|
48
|
+
.parties {
|
|
49
|
+
display: grid;
|
|
50
|
+
grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
|
|
51
|
+
gap: var(--spacing-large) var(--spacing-huge);
|
|
52
|
+
margin: var(--spacing-large) 0;
|
|
53
|
+
}
|
|
54
|
+
.parties fieldset {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: var(--spacing-large);
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding: var(--spacing-large) var(--spacing-large) var(--spacing-large);
|
|
59
|
+
border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
|
|
60
|
+
border-radius: var(--ui-r, 10px);
|
|
61
|
+
}
|
|
62
|
+
.parties legend {
|
|
63
|
+
padding: 0 var(--spacing-small);
|
|
64
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
color: var(--ui-ink);
|
|
67
|
+
}
|
|
36
68
|
.form input,
|
|
37
69
|
.form select,
|
|
38
70
|
.form textarea {
|
|
@@ -42,6 +74,33 @@ export const DocumentFormStyles = css `
|
|
|
42
74
|
font-size: var(--ui-t-body);
|
|
43
75
|
color: var(--ui-ink);
|
|
44
76
|
}
|
|
77
|
+
/* A searchable choice stands as the inputs beside it - the same box, not an underline. */
|
|
78
|
+
.form ox-select {
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
height: var(--form-element-height-medium);
|
|
83
|
+
border: var(--input-field-border, 1px solid var(--md-sys-color-outline));
|
|
84
|
+
border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));
|
|
85
|
+
background: var(--input-field-background, var(--md-sys-color-surface-container-highest));
|
|
86
|
+
font-size: var(--ui-t-body);
|
|
87
|
+
color: var(--ui-ink);
|
|
88
|
+
}
|
|
89
|
+
/* Whether the party is one of the counterparties - said quietly under the picker. */
|
|
90
|
+
.form .quiet {
|
|
91
|
+
font-size: var(--ui-t-micro);
|
|
92
|
+
color: var(--ui-muted);
|
|
93
|
+
}
|
|
94
|
+
/* The payment terms the company offers, a click away above the field. */
|
|
95
|
+
.form .terms {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-wrap: wrap;
|
|
98
|
+
gap: var(--spacing-small);
|
|
99
|
+
}
|
|
100
|
+
.form .terms button[selected] {
|
|
101
|
+
border-color: var(--md-sys-color-primary);
|
|
102
|
+
color: var(--md-sys-color-primary);
|
|
103
|
+
}
|
|
45
104
|
.form textarea {
|
|
46
105
|
height: auto;
|
|
47
106
|
min-height: calc(var(--form-element-height-medium) * 2);
|
|
@@ -69,12 +128,31 @@ export const DocumentFormStyles = css `
|
|
|
69
128
|
table.totals tfoot td {
|
|
70
129
|
font-weight: 600;
|
|
71
130
|
}
|
|
131
|
+
/* The discount of the whole document, typed over the totals it lowers. */
|
|
132
|
+
.discount {
|
|
133
|
+
margin-bottom: var(--spacing-medium);
|
|
134
|
+
}
|
|
135
|
+
.discount input {
|
|
136
|
+
max-width: 240px;
|
|
137
|
+
text-align: right;
|
|
138
|
+
}
|
|
139
|
+
.discount .problem {
|
|
140
|
+
color: var(--md-sys-color-error);
|
|
141
|
+
}
|
|
72
142
|
`;
|
|
73
143
|
/** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */
|
|
74
144
|
/** `numberLabel` names the number as the kind does - 「청구서 번호」, 「견적 번호」. */
|
|
75
|
-
|
|
145
|
+
/** `terms` are the payment terms the company offers (ADR-0170) - one click puts one in. */
|
|
146
|
+
/**
|
|
147
|
+
* `party` is the counterparty picker, drawn inside the group it fills - the buyer's of a sale, the supplier's of a
|
|
148
|
+
* purchase (ADR-0170 decision 11).
|
|
149
|
+
*/
|
|
150
|
+
export function renderDocumentHead(value, editable, change, dates = nothing, numberLabel = 'field.invoice-number', { terms = [], party: picker = nothing } = {}) {
|
|
151
|
+
const offered = (field) => field.name === 'paymentTerms' && editable && terms.length
|
|
152
|
+
? html `<span class="terms">${terms.map(term => html `<button small ?selected=${value.paymentTerms === term} @click=${() => change({ paymentTerms: term })}>${term.split('\n')[0]}</button>`)}</span>`
|
|
153
|
+
: nothing;
|
|
76
154
|
const input = (field) => html `<label class=${field.wide ? 'wide' : ''}
|
|
77
|
-
>${i18next.t(field.label)}
|
|
155
|
+
>${i18next.t(field.label)} ${offered(field)}
|
|
78
156
|
${field.prose
|
|
79
157
|
? html `<textarea .value=${value[field.name] ?? ''} ?disabled=${!editable} @input=${(e) => change({ [field.name]: e.target.value })}></textarea>`
|
|
80
158
|
: html `<input
|
|
@@ -84,8 +162,13 @@ export function renderDocumentHead(value, editable, change, dates = nothing, num
|
|
|
84
162
|
@input=${(e) => change({ [field.name]: e.target.value })}
|
|
85
163
|
/>`}</label
|
|
86
164
|
>`;
|
|
165
|
+
const other = value.direction === 'PURCHASE' ? 'supplier' : 'buyer';
|
|
166
|
+
const party = (side, legend) => html `<fieldset class="form">
|
|
167
|
+
<legend>${i18next.t(legend)}</legend>
|
|
168
|
+
${side === other ? picker : nothing} ${['Name', 'RegistrationNumber', 'Address'].map(part => input(field(`${side}${part}`)))}
|
|
169
|
+
</fieldset>`;
|
|
87
170
|
return html `<div class="form">
|
|
88
|
-
${
|
|
171
|
+
${input({ ...field('number'), label: numberLabel })} ${input(field('issueDate'))}
|
|
89
172
|
<label
|
|
90
173
|
>${i18next.t('field.invoice-direction')}
|
|
91
174
|
<select .value=${value.direction} ?disabled=${!editable} @change=${(e) => change({ direction: e.target.value })}>
|
|
@@ -106,14 +189,30 @@ export function renderDocumentHead(value, editable, change, dates = nothing, num
|
|
|
106
189
|
<option value="en" ?selected=${value.language === 'en'}>${i18next.t('text.invoice-language-en')}</option>
|
|
107
190
|
</select></label
|
|
108
191
|
>
|
|
109
|
-
${dates}
|
|
110
|
-
</div
|
|
192
|
+
${dates}
|
|
193
|
+
</div>
|
|
194
|
+
<div class="parties">${party('supplier', 'field.invoice-supplier-name')} ${party('buyer', 'field.invoice-buyer-name')}</div>
|
|
195
|
+
<div class="form">${['purchaseOrder', 'sourceReference', 'paymentTerms', 'bankDetails', 'notes'].map(name => input(field(name)))}</div>`;
|
|
111
196
|
}
|
|
112
|
-
/**
|
|
113
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Supply and tax of each tax kind and rate, then the document totals. With a discount of the whole document the lines'
|
|
199
|
+
* supply and the discount come first, and each group's supply is the one after its part (ADR-0170 decision 8).
|
|
200
|
+
* `discount` puts the discount's input over the table while a draft is written; `change` takes what is typed.
|
|
201
|
+
*/
|
|
202
|
+
export function renderDocumentTotals(value, discount) {
|
|
114
203
|
const currency = value.currency || 'KRW';
|
|
115
204
|
const kind = (taxKind) => taxKind === 'ZERO_RATED' ? i18next.t('text.invoice-tax-kind-zero-rated') : taxKind === 'EXEMPT' ? i18next.t('text.invoice-tax-kind-exempt') : i18next.t('text.invoice-tax-kind-taxable');
|
|
116
|
-
|
|
205
|
+
const discounted = Number(value.discountAmount) > 0;
|
|
206
|
+
return html `${discount?.editable
|
|
207
|
+
? html `<div class="form discount">
|
|
208
|
+
<label
|
|
209
|
+
>${i18next.t('field.invoice-document-discount')}
|
|
210
|
+
<input type="number" min="0" .value=${String(discount.value ?? '')} @input=${(e) => discount.change(e.target.value)} />
|
|
211
|
+
${value.discountProblem ? html `<span class="problem">${i18next.t('error.invoice-document-discount-invalid')}</span>` : nothing}
|
|
212
|
+
</label>
|
|
213
|
+
</div>`
|
|
214
|
+
: nothing}
|
|
215
|
+
<table class="totals">
|
|
117
216
|
<thead>
|
|
118
217
|
<tr>
|
|
119
218
|
<th>${i18next.t('field.invoice-tax-kind')}</th>
|
|
@@ -123,6 +222,18 @@ export function renderDocumentTotals(value) {
|
|
|
123
222
|
</tr>
|
|
124
223
|
</thead>
|
|
125
224
|
<tbody>
|
|
225
|
+
${discounted
|
|
226
|
+
? html `<tr>
|
|
227
|
+
<td colspan="2">${i18next.t('field.invoice-line-sum')}</td>
|
|
228
|
+
<td>${documentAmount(value.lineSupplyAmount, currency)}</td>
|
|
229
|
+
<td></td>
|
|
230
|
+
</tr>
|
|
231
|
+
<tr>
|
|
232
|
+
<td colspan="2">${i18next.t('field.invoice-discount')}</td>
|
|
233
|
+
<td>− ${documentAmount(value.discountAmount, currency)}</td>
|
|
234
|
+
<td></td>
|
|
235
|
+
</tr>`
|
|
236
|
+
: nothing}
|
|
126
237
|
${(value.taxGroups ?? []).map((group) => html `<tr>
|
|
127
238
|
<td>${kind(group.taxKind)}</td>
|
|
128
239
|
<td>${group.taxRate}%</td>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-form.js","sourceRoot":"","sources":["../../client/document/document-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAuB,MAAM,KAAK,CAAA;AAE7D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AASnE,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAuE;IAC7F,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,sBAAsB,EAAE;IACjD,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,0BAA0B,EAAE;IACxD,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,6BAA6B,EAAE;IAC9D,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,0BAA0B,EAAE;IACxD,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,4CAA4C,EAAE;IAC3F,EAAE,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE,yCAAyC,EAAE;IACrF,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,gCAAgC,EAAE,KAAK,EAAE,IAAI,EAAE;IACjF,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,6BAA6B,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3E,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,8BAA8B,EAAE;IAChE,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,gCAAgC,EAAE;IACpE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,6BAA6B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACvF,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,4BAA4B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACrF,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;CACzE,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDpC,CAAA;AAED,6GAA6G;AAC7G,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,KAAoB,EAAE,QAAiB,EAAE,MAAsC,EAAE,QAAyC,OAAO,EAAE,WAAW,GAAG,sBAAsB;IACxM,MAAM,KAAK,GAAG,CAAC,KAAuE,EAAE,EAAE,CACxF,IAAI,CAAA,gBAAgB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;SACvC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;QACvB,KAAK,CAAC,KAAK;QACX,CAAC,CAAC,IAAI,CAAA,oBAAoB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,QAAQ,WAAW,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,cAAc;QACrJ,CAAC,CAAC,IAAI,CAAA;mBACK,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;qBAC1C,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;wBACpB,CAAC,QAAQ;qBACZ,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;aAC5D;MACP,CAAA;IAEJ,OAAO,IAAI,CAAA;MACP,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;;SAE5G,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC;uBACtB,KAAK,CAAC,SAAS,cAAc,CAAC,QAAQ,YAAY,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;0CAChF,KAAK,CAAC,SAAS,KAAK,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;6CACrE,KAAK,CAAC,SAAS,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;;;;SAIlH,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;0BAClB,CAAC,QAAQ,YAAY,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;UACrF,eAAe,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA,iBAAiB,IAAI,cAAc,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,WAAW,CAAC;;;;SAIjH,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;0BAClB,CAAC,QAAQ,YAAY,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;uCACxD,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;uCAChE,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;;;MAGjG,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;SACrC,CAAA;AACT,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB,CAAC,KAAoB;IACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAA;IACxC,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,EAAE,CAC/B,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAA;IAC1L,OAAO,IAAI,CAAA;;;cAGC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;cACnC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;cACnC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;cACxC,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;;;;QAI3C,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAC3B,CAAC,KAAU,EAAE,EAAE,CACb,IAAI,CAAA;kBACI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;kBACnB,KAAK,CAAC,OAAO;kBACb,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC;kBAC5C,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC;gBAC3C,CACT;;;;0BAImB,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;cAC1C,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC;cAC5C,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC;;;0BAG7B,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC;cACnD,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC;;;WAG9C,CAAA;AACX,CAAC","sourcesContent":["import { i18next } from '@operato/i18n'\nimport { css, html, nothing, type TemplateResult } from 'lit'\n\nimport { currencyChoices, documentAmount } from './document-amount'\n\n/*\n * The parts every commercial document editor draws — the head, the totals by tax kind and rate. Invoice and quotation\n * editors put them in their own sections; the quotation module imports them from here (ADR-0120 decision 1).\n */\n\nexport type DocumentValue = Record<string, any>\n\n/** The head fields in the order a person fills them. */\nexport const HEAD_FIELDS: { name: string; label: string; wide?: boolean; prose?: boolean }[] = [\n { name: 'number', label: 'field.invoice-number' },\n { name: 'issueDate', label: 'field.invoice-issue-date' },\n { name: 'supplierName', label: 'field.invoice-supplier-name' },\n { name: 'buyerName', label: 'field.invoice-buyer-name' },\n { name: 'supplierRegistrationNumber', label: 'field.invoice-supplier-registration-number' },\n { name: 'buyerRegistrationNumber', label: 'field.invoice-buyer-registration-number' },\n { name: 'supplierAddress', label: 'field.invoice-supplier-address', prose: true },\n { name: 'buyerAddress', label: 'field.invoice-buyer-address', prose: true },\n { name: 'purchaseOrder', label: 'field.invoice-purchase-order' },\n { name: 'sourceReference', label: 'field.invoice-source-reference' },\n { name: 'paymentTerms', label: 'field.invoice-payment-terms', wide: true, prose: true },\n { name: 'bankDetails', label: 'field.invoice-bank-details', wide: true, prose: true },\n { name: 'notes', label: 'field.invoice-notes', wide: true, prose: true }\n]\n\nexport const DocumentFormStyles = css`\n .form {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));\n gap: var(--spacing-large) var(--spacing-huge);\n }\n .form label {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n font-size: var(--ui-t-micro);\n color: var(--ui-muted);\n }\n .form .wide {\n grid-column: 1 / -1;\n }\n .form input,\n .form select,\n .form textarea {\n box-sizing: border-box;\n width: 100%;\n height: var(--form-element-height-medium);\n font-size: var(--ui-t-body);\n color: var(--ui-ink);\n }\n .form textarea {\n height: auto;\n min-height: calc(var(--form-element-height-medium) * 2);\n }\n table.totals {\n width: 100%;\n border-collapse: collapse;\n font-variant-numeric: tabular-nums;\n }\n table.totals th,\n table.totals td {\n padding: var(--spacing-small) var(--spacing-medium);\n border-bottom: 1px solid var(--ui-line-soft);\n text-align: right;\n font-size: var(--ui-t-body);\n }\n table.totals th:first-child,\n table.totals td:first-child {\n text-align: left;\n }\n table.totals th {\n color: var(--ui-muted);\n font-weight: 400;\n }\n table.totals tfoot td {\n font-weight: 600;\n }\n`\n\n/** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */\n/** `numberLabel` names the number as the kind does - 「청구서 번호」, 「견적 번호」. */\nexport function renderDocumentHead(value: DocumentValue, editable: boolean, change: (patch: DocumentValue) => void, dates: TemplateResult | typeof nothing = nothing, numberLabel = 'field.invoice-number') {\n const input = (field: { name: string; label: string; wide?: boolean; prose?: boolean }) =>\n html`<label class=${field.wide ? 'wide' : ''}\n >${i18next.t(field.label)}\n ${field.prose\n ? html`<textarea .value=${value[field.name] ?? ''} ?disabled=${!editable} @input=${(e: any) => change({ [field.name]: e.target.value })}></textarea>`\n : html`<input\n type=${field.name === 'issueDate' ? 'date' : 'text'}\n .value=${value[field.name] ?? ''}\n ?disabled=${!editable}\n @input=${(e: any) => change({ [field.name]: e.target.value })}\n />`}</label\n >`\n\n return html`<div class=\"form\">\n ${HEAD_FIELDS.slice(0, 2).map(field => input(field.name === 'number' ? { ...field, label: numberLabel } : field))}\n <label\n >${i18next.t('field.invoice-direction')}\n <select .value=${value.direction} ?disabled=${!editable} @change=${(e: any) => change({ direction: e.target.value })}>\n <option value=\"SALES\" ?selected=${value.direction === 'SALES'}>${i18next.t('text.invoice-direction-sales')}</option>\n <option value=\"PURCHASE\" ?selected=${value.direction === 'PURCHASE'}>${i18next.t('text.invoice-direction-purchase')}</option>\n </select></label\n >\n <label\n >${i18next.t('field.invoice-currency')}\n <select ?disabled=${!editable} @change=${(e: any) => change({ currency: e.target.value })}>\n ${currencyChoices().map(code => html`<option value=${code} ?selected=${value.currency === code}>${code}</option>`)}\n </select></label\n >\n <label\n >${i18next.t('field.invoice-language')}\n <select ?disabled=${!editable} @change=${(e: any) => change({ language: e.target.value })}>\n <option value=\"ko\" ?selected=${value.language === 'ko'}>${i18next.t('text.invoice-language-ko')}</option>\n <option value=\"en\" ?selected=${value.language === 'en'}>${i18next.t('text.invoice-language-en')}</option>\n </select></label\n >\n ${dates} ${HEAD_FIELDS.slice(2).map(input)}\n </div>`\n}\n\n/** Supply and tax of each tax kind and rate, then the document totals. */\nexport function renderDocumentTotals(value: DocumentValue) {\n const currency = value.currency || 'KRW'\n const kind = (taxKind: string) =>\n taxKind === 'ZERO_RATED' ? i18next.t('text.invoice-tax-kind-zero-rated') : taxKind === 'EXEMPT' ? i18next.t('text.invoice-tax-kind-exempt') : i18next.t('text.invoice-tax-kind-taxable')\n return html`<table class=\"totals\">\n <thead>\n <tr>\n <th>${i18next.t('field.invoice-tax-kind')}</th>\n <th>${i18next.t('field.invoice-tax-rate')}</th>\n <th>${i18next.t('field.invoice-supply-amount')}</th>\n <th>${i18next.t('field.invoice-tax-amount')}</th>\n </tr>\n </thead>\n <tbody>\n ${(value.taxGroups ?? []).map(\n (group: any) =>\n html`<tr>\n <td>${kind(group.taxKind)}</td>\n <td>${group.taxRate}%</td>\n <td>${documentAmount(group.supplyAmount, currency)}</td>\n <td>${documentAmount(group.taxAmount, currency)}</td>\n </tr>`\n )}\n </tbody>\n <tfoot>\n <tr>\n <td colspan=\"2\">${i18next.t('field.invoice-sum')}</td>\n <td>${documentAmount(value.supplyAmount, currency)}</td>\n <td>${documentAmount(value.taxAmount, currency)}</td>\n </tr>\n <tr>\n <td colspan=\"3\">${i18next.t('field.invoice-total-amount')}</td>\n <td>${documentAmount(value.totalAmount, currency)}</td>\n </tr>\n </tfoot>\n </table>`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"document-form.js","sourceRoot":"","sources":["../../client/document/document-form.ts"],"names":[],"mappings":"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","sourcesContent":["import { i18next } from '@operato/i18n'\nimport { css, html, nothing, type TemplateResult } from 'lit'\n\nimport { currencyChoices, documentAmount } from './document-amount'\n\n/*\n * The parts every commercial document editor draws — the head, the totals by tax kind and rate. Invoice and quotation\n * editors put them in their own sections; the quotation module imports them from here (ADR-0120 decision 1).\n */\n\nexport type DocumentValue = Record<string, any>\n\ntype HeadField = { name: string; label: string; wide?: boolean; prose?: boolean }\n\n/** The head fields in the order a person fills them: the document, each party as one group, then the terms. */\nexport const HEAD_FIELDS: HeadField[] = [\n { name: 'number', label: 'field.invoice-number' },\n { name: 'issueDate', label: 'field.invoice-issue-date' },\n { name: 'supplierName', label: 'field.invoice-party-name' },\n { name: 'supplierRegistrationNumber', label: 'field.invoice-party-registration-number' },\n { name: 'supplierAddress', label: 'field.invoice-party-address', prose: true },\n { name: 'buyerName', label: 'field.invoice-party-name' },\n { name: 'buyerRegistrationNumber', label: 'field.invoice-party-registration-number' },\n { name: 'buyerAddress', label: 'field.invoice-party-address', prose: true },\n { name: 'purchaseOrder', label: 'field.invoice-purchase-order' },\n { name: 'sourceReference', label: 'field.invoice-source-reference' },\n { name: 'paymentTerms', label: 'field.invoice-payment-terms', wide: true, prose: true },\n { name: 'bankDetails', label: 'field.invoice-bank-details', wide: true, prose: true },\n { name: 'notes', label: 'field.invoice-notes', wide: true, prose: true }\n]\n\nconst field = (name: string) => HEAD_FIELDS.find(one => one.name === name)!\n\nexport const DocumentFormStyles = css`\n /*\n * The lines' grid is as wide as its columns. With the grid styles the page is a flex item, and a flex item is at least\n * as wide as what it holds: the page grew past the window, the head spread into three cut-off columns and the\n * page scrolled sideways. The page and its sections may be narrower than the grid; the grid scrolls inside.\n */\n .page,\n .page-section,\n ox-grist {\n min-width: 0;\n }\n .form {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));\n gap: var(--spacing-large) var(--spacing-huge);\n }\n .form label {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small);\n font-size: var(--ui-t-micro);\n color: var(--ui-muted);\n }\n .form .wide {\n grid-column: 1 / -1;\n }\n /* The two parties side by side, each its name, number and address together - not interleaved in one grid. */\n .parties {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));\n gap: var(--spacing-large) var(--spacing-huge);\n margin: var(--spacing-large) 0;\n }\n .parties fieldset {\n display: grid;\n gap: var(--spacing-large);\n margin: 0;\n padding: var(--spacing-large) var(--spacing-large) var(--spacing-large);\n border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n border-radius: var(--ui-r, 10px);\n }\n .parties legend {\n padding: 0 var(--spacing-small);\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 600;\n color: var(--ui-ink);\n }\n .form input,\n .form select,\n .form textarea {\n box-sizing: border-box;\n width: 100%;\n height: var(--form-element-height-medium);\n font-size: var(--ui-t-body);\n color: var(--ui-ink);\n }\n /* A searchable choice stands as the inputs beside it - the same box, not an underline. */\n .form ox-select {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n height: var(--form-element-height-medium);\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-size: var(--ui-t-body);\n color: var(--ui-ink);\n }\n /* Whether the party is one of the counterparties - said quietly under the picker. */\n .form .quiet {\n font-size: var(--ui-t-micro);\n color: var(--ui-muted);\n }\n /* The payment terms the company offers, a click away above the field. */\n .form .terms {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small);\n }\n .form .terms button[selected] {\n border-color: var(--md-sys-color-primary);\n color: var(--md-sys-color-primary);\n }\n .form textarea {\n height: auto;\n min-height: calc(var(--form-element-height-medium) * 2);\n }\n table.totals {\n width: 100%;\n border-collapse: collapse;\n font-variant-numeric: tabular-nums;\n }\n table.totals th,\n table.totals td {\n padding: var(--spacing-small) var(--spacing-medium);\n border-bottom: 1px solid var(--ui-line-soft);\n text-align: right;\n font-size: var(--ui-t-body);\n }\n table.totals th:first-child,\n table.totals td:first-child {\n text-align: left;\n }\n table.totals th {\n color: var(--ui-muted);\n font-weight: 400;\n }\n table.totals tfoot td {\n font-weight: 600;\n }\n /* The discount of the whole document, typed over the totals it lowers. */\n .discount {\n margin-bottom: var(--spacing-medium);\n }\n .discount input {\n max-width: 240px;\n text-align: right;\n }\n .discount .problem {\n color: var(--md-sys-color-error);\n }\n`\n\n/** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */\n/** `numberLabel` names the number as the kind does - 「청구서 번호」, 「견적 번호」. */\n/** `terms` are the payment terms the company offers (ADR-0170) - one click puts one in. */\n/**\n * `party` is the counterparty picker, drawn inside the group it fills - the buyer's of a sale, the supplier's of a\n * purchase (ADR-0170 decision 11).\n */\nexport function renderDocumentHead(\n value: DocumentValue,\n editable: boolean,\n change: (patch: DocumentValue) => void,\n dates: TemplateResult | typeof nothing = nothing,\n numberLabel = 'field.invoice-number',\n { terms = [], party: picker = nothing }: { terms?: string[]; party?: TemplateResult | typeof nothing } = {}\n) {\n const offered = (field: HeadField) =>\n field.name === 'paymentTerms' && editable && terms.length\n ? html`<span class=\"terms\">${terms.map(term => html`<button small ?selected=${value.paymentTerms === term} @click=${() => change({ paymentTerms: term })}>${term.split('\\n')[0]}</button>`)}</span>`\n : nothing\n const input = (field: HeadField) =>\n html`<label class=${field.wide ? 'wide' : ''}\n >${i18next.t(field.label)} ${offered(field)}\n ${field.prose\n ? html`<textarea .value=${value[field.name] ?? ''} ?disabled=${!editable} @input=${(e: any) => change({ [field.name]: e.target.value })}></textarea>`\n : html`<input\n type=${field.name === 'issueDate' ? 'date' : 'text'}\n .value=${value[field.name] ?? ''}\n ?disabled=${!editable}\n @input=${(e: any) => change({ [field.name]: e.target.value })}\n />`}</label\n >`\n\n const other = value.direction === 'PURCHASE' ? 'supplier' : 'buyer'\n const party = (side: 'supplier' | 'buyer', legend: string) =>\n html`<fieldset class=\"form\">\n <legend>${i18next.t(legend)}</legend>\n ${side === other ? picker : nothing} ${['Name', 'RegistrationNumber', 'Address'].map(part => input(field(`${side}${part}`)))}\n </fieldset>`\n\n return html`<div class=\"form\">\n ${input({ ...field('number'), label: numberLabel })} ${input(field('issueDate'))}\n <label\n >${i18next.t('field.invoice-direction')}\n <select .value=${value.direction} ?disabled=${!editable} @change=${(e: any) => change({ direction: e.target.value })}>\n <option value=\"SALES\" ?selected=${value.direction === 'SALES'}>${i18next.t('text.invoice-direction-sales')}</option>\n <option value=\"PURCHASE\" ?selected=${value.direction === 'PURCHASE'}>${i18next.t('text.invoice-direction-purchase')}</option>\n </select></label\n >\n <label\n >${i18next.t('field.invoice-currency')}\n <select ?disabled=${!editable} @change=${(e: any) => change({ currency: e.target.value })}>\n ${currencyChoices().map(code => html`<option value=${code} ?selected=${value.currency === code}>${code}</option>`)}\n </select></label\n >\n <label\n >${i18next.t('field.invoice-language')}\n <select ?disabled=${!editable} @change=${(e: any) => change({ language: e.target.value })}>\n <option value=\"ko\" ?selected=${value.language === 'ko'}>${i18next.t('text.invoice-language-ko')}</option>\n <option value=\"en\" ?selected=${value.language === 'en'}>${i18next.t('text.invoice-language-en')}</option>\n </select></label\n >\n ${dates}\n </div>\n <div class=\"parties\">${party('supplier', 'field.invoice-supplier-name')} ${party('buyer', 'field.invoice-buyer-name')}</div>\n <div class=\"form\">${['purchaseOrder', 'sourceReference', 'paymentTerms', 'bankDetails', 'notes'].map(name => input(field(name)))}</div>`\n}\n\n/**\n * Supply and tax of each tax kind and rate, then the document totals. With a discount of the whole document the lines'\n * supply and the discount come first, and each group's supply is the one after its part (ADR-0170 decision 8).\n * `discount` puts the discount's input over the table while a draft is written; `change` takes what is typed.\n */\nexport function renderDocumentTotals(value: DocumentValue, discount?: { value: unknown; editable: boolean; change: (value: string) => void }) {\n const currency = value.currency || 'KRW'\n const kind = (taxKind: string) =>\n taxKind === 'ZERO_RATED' ? i18next.t('text.invoice-tax-kind-zero-rated') : taxKind === 'EXEMPT' ? i18next.t('text.invoice-tax-kind-exempt') : i18next.t('text.invoice-tax-kind-taxable')\n const discounted = Number(value.discountAmount) > 0\n return html`${discount?.editable\n ? html`<div class=\"form discount\">\n <label\n >${i18next.t('field.invoice-document-discount')}\n <input type=\"number\" min=\"0\" .value=${String(discount.value ?? '')} @input=${(e: any) => discount.change(e.target.value)} />\n ${value.discountProblem ? html`<span class=\"problem\">${i18next.t('error.invoice-document-discount-invalid')}</span>` : nothing}\n </label>\n </div>`\n : nothing}\n <table class=\"totals\">\n <thead>\n <tr>\n <th>${i18next.t('field.invoice-tax-kind')}</th>\n <th>${i18next.t('field.invoice-tax-rate')}</th>\n <th>${i18next.t('field.invoice-supply-amount')}</th>\n <th>${i18next.t('field.invoice-tax-amount')}</th>\n </tr>\n </thead>\n <tbody>\n ${discounted\n ? html`<tr>\n <td colspan=\"2\">${i18next.t('field.invoice-line-sum')}</td>\n <td>${documentAmount(value.lineSupplyAmount, currency)}</td>\n <td></td>\n </tr>\n <tr>\n <td colspan=\"2\">${i18next.t('field.invoice-discount')}</td>\n <td>− ${documentAmount(value.discountAmount, currency)}</td>\n <td></td>\n </tr>`\n : nothing}\n ${(value.taxGroups ?? []).map(\n (group: any) =>\n html`<tr>\n <td>${kind(group.taxKind)}</td>\n <td>${group.taxRate}%</td>\n <td>${documentAmount(group.supplyAmount, currency)}</td>\n <td>${documentAmount(group.taxAmount, currency)}</td>\n </tr>`\n )}\n </tbody>\n <tfoot>\n <tr>\n <td colspan=\"2\">${i18next.t('field.invoice-sum')}</td>\n <td>${documentAmount(value.supplyAmount, currency)}</td>\n <td>${documentAmount(value.taxAmount, currency)}</td>\n </tr>\n <tr>\n <td colspan=\"3\">${i18next.t('field.invoice-total-amount')}</td>\n <td>${documentAmount(value.totalAmount, currency)}</td>\n </tr>\n </tfoot>\n </table>`\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DataGrist } from '@operato/data-grist';
|
|
2
|
+
import { type DocumentAmounts } from '@things-factory/invoice/dist-shared/document-amounts.js';
|
|
2
3
|
/** The grid of a document's lines — typed or pasted like a spreadsheet while the document is a draft. */
|
|
3
4
|
export declare function documentLinesConfig({ editable, received, currency }: {
|
|
4
5
|
editable: boolean;
|
|
@@ -26,6 +27,7 @@ export declare function documentLinesConfig({ editable, received, currency }: {
|
|
|
26
27
|
record: {
|
|
27
28
|
editable: boolean;
|
|
28
29
|
align: string;
|
|
30
|
+
renderer: (value: number) => string;
|
|
29
31
|
};
|
|
30
32
|
} | {
|
|
31
33
|
type: string;
|
|
@@ -52,8 +54,22 @@ export declare function documentLinesConfig({ editable, received, currency }: {
|
|
|
52
54
|
width: number;
|
|
53
55
|
record: {
|
|
54
56
|
editable: boolean;
|
|
55
|
-
options?: undefined;
|
|
56
57
|
align?: undefined;
|
|
58
|
+
options?: undefined;
|
|
59
|
+
renderer?: undefined;
|
|
60
|
+
};
|
|
61
|
+
gutterName?: undefined;
|
|
62
|
+
multiple?: undefined;
|
|
63
|
+
accumulator?: undefined;
|
|
64
|
+
} | {
|
|
65
|
+
type: string;
|
|
66
|
+
name: string;
|
|
67
|
+
header: string;
|
|
68
|
+
width: number;
|
|
69
|
+
record: {
|
|
70
|
+
editable: boolean;
|
|
71
|
+
align: string;
|
|
72
|
+
options?: undefined;
|
|
57
73
|
renderer?: undefined;
|
|
58
74
|
};
|
|
59
75
|
gutterName?: undefined;
|
|
@@ -97,8 +113,26 @@ export declare function linesOf(grist: DataGrist | null | undefined): {
|
|
|
97
113
|
receivedTaxAmount?: number;
|
|
98
114
|
description: string;
|
|
99
115
|
quantity: number;
|
|
116
|
+
unit: string;
|
|
100
117
|
unitPrice: number;
|
|
101
118
|
discount: number;
|
|
102
119
|
taxKind: any;
|
|
103
120
|
taxRate: number;
|
|
104
121
|
}[];
|
|
122
|
+
/**
|
|
123
|
+
* What a line typed into is missing, filled - a quantity of one, no discount, taxable at the standard rate - and its
|
|
124
|
+
* rate following its tax kind when the kind is what changed (taxable 10 %, zero-rated and exempt none). A select shows
|
|
125
|
+
* its first option for an empty value, so 「과세」 looked chosen and choosing it changed nothing; the line now holds it.
|
|
126
|
+
*/
|
|
127
|
+
export declare function settleLine(record: any, changed?: string): void;
|
|
128
|
+
/**
|
|
129
|
+
* The lines on the grid reckoned as the document will keep them (shared/document-amounts.ts): each row's supply is
|
|
130
|
+
* written onto it for the grid to show, and the document's tax groups and totals - after `discountAmount`, the
|
|
131
|
+
* discount of the whole document - are returned for the totals table.
|
|
132
|
+
*/
|
|
133
|
+
export declare function reckonLines(grist: DataGrist | null | undefined, currency: string, received?: boolean, discountAmount?: unknown): DocumentAmounts;
|
|
134
|
+
/**
|
|
135
|
+
* A change on the grid as an editor takes it: the changed line settled, every line reckoned, the grid redrawn with
|
|
136
|
+
* the supplies. Returns the totals to show.
|
|
137
|
+
*/
|
|
138
|
+
export declare function onLineChange(grist: DataGrist | null | undefined, event: CustomEvent, currency: string, received?: boolean, discountAmount?: unknown): DocumentAmounts;
|