@things-factory/invoice 10.1.223 → 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.
Files changed (95) hide show
  1. package/dist-client/document/document-amount.d.ts +7 -2
  2. package/dist-client/document/document-amount.js +11 -6
  3. package/dist-client/document/document-amount.js.map +1 -1
  4. package/dist-client/document/document-defaults.d.ts +24 -0
  5. package/dist-client/document/document-defaults.js +43 -0
  6. package/dist-client/document/document-defaults.js.map +1 -0
  7. package/dist-client/document/document-form.d.ts +24 -6
  8. package/dist-client/document/document-form.js +126 -15
  9. package/dist-client/document/document-form.js.map +1 -1
  10. package/dist-client/document/document-lines.d.ts +35 -1
  11. package/dist-client/document/document-lines.js +58 -9
  12. package/dist-client/document/document-lines.js.map +1 -1
  13. package/dist-client/document/document-party.d.ts +37 -0
  14. package/dist-client/document/document-party.js +217 -0
  15. package/dist-client/document/document-party.js.map +1 -0
  16. package/dist-client/document/document-preview.d.ts +37 -0
  17. package/dist-client/document/document-preview.js +181 -0
  18. package/dist-client/document/document-preview.js.map +1 -0
  19. package/dist-client/document/document-print.d.ts +3 -13
  20. package/dist-client/document/document-print.js +3 -45
  21. package/dist-client/document/document-print.js.map +1 -1
  22. package/dist-client/index.d.ts +9 -3
  23. package/dist-client/index.js +9 -3
  24. package/dist-client/index.js.map +1 -1
  25. package/dist-client/pages/invoice-editor.d.ts +37 -12
  26. package/dist-client/pages/invoice-editor.js +171 -79
  27. package/dist-client/pages/invoice-editor.js.map +1 -1
  28. package/dist-client/pages/invoice-list.d.ts +21 -1
  29. package/dist-client/pages/invoice-list.js +145 -14
  30. package/dist-client/pages/invoice-list.js.map +1 -1
  31. package/dist-client/tsconfig.tsbuildinfo +1 -1
  32. package/dist-client/viewparts/document-defaults-setting-let.d.ts +19 -0
  33. package/dist-client/viewparts/document-defaults-setting-let.js +139 -0
  34. package/dist-client/viewparts/document-defaults-setting-let.js.map +1 -0
  35. package/dist-server/role-tasks.js +5 -2
  36. package/dist-server/role-tasks.js.map +1 -1
  37. package/dist-server/service/document-defaults/document-defaults-resolver.d.ts +28 -0
  38. package/dist-server/service/document-defaults/document-defaults-resolver.js +136 -0
  39. package/dist-server/service/document-defaults/document-defaults-resolver.js.map +1 -0
  40. package/dist-server/service/document-defaults/document-defaults-rules.d.ts +25 -0
  41. package/dist-server/service/document-defaults/document-defaults-rules.js +50 -0
  42. package/dist-server/service/document-defaults/document-defaults-rules.js.map +1 -0
  43. package/dist-server/service/document-defaults/index.d.ts +6 -0
  44. package/dist-server/service/document-defaults/index.js +11 -0
  45. package/dist-server/service/document-defaults/index.js.map +1 -0
  46. package/dist-server/service/index.d.ts +2 -0
  47. package/dist-server/service/index.js +4 -1
  48. package/dist-server/service/index.js.map +1 -1
  49. package/dist-server/service/invoice/commercial-document.d.ts +2 -0
  50. package/dist-server/service/invoice/commercial-document.js +11 -1
  51. package/dist-server/service/invoice/commercial-document.js.map +1 -1
  52. package/dist-server/service/invoice/invoice-document-file.d.ts +20 -0
  53. package/dist-server/service/invoice/invoice-document-file.js +38 -0
  54. package/dist-server/service/invoice/invoice-document-file.js.map +1 -0
  55. package/dist-server/service/invoice/invoice-query.d.ts +3 -1
  56. package/dist-server/service/invoice/invoice-query.js +33 -0
  57. package/dist-server/service/invoice/invoice-query.js.map +1 -1
  58. package/dist-server/service/invoice/invoice-rules.d.ts +18 -11
  59. package/dist-server/service/invoice/invoice-rules.js +49 -96
  60. package/dist-server/service/invoice/invoice-rules.js.map +1 -1
  61. package/dist-server/service/invoice/invoice-service.d.ts +1 -0
  62. package/dist-server/service/invoice/invoice-service.js +5 -2
  63. package/dist-server/service/invoice/invoice-service.js.map +1 -1
  64. package/dist-server/service/invoice/invoice-type.d.ts +12 -0
  65. package/dist-server/service/invoice/invoice-type.js +37 -1
  66. package/dist-server/service/invoice/invoice-type.js.map +1 -1
  67. package/dist-server/tsconfig.tsbuildinfo +1 -1
  68. package/dist-shared/document-amounts.d.ts +56 -0
  69. package/dist-shared/document-amounts.js +134 -0
  70. package/dist-shared/document-amounts.js.map +1 -0
  71. package/dist-shared/document-money.d.ts +12 -0
  72. package/dist-shared/document-money.js +38 -0
  73. package/dist-shared/document-money.js.map +1 -0
  74. package/dist-shared/document-print.d.ts +37 -0
  75. package/dist-shared/document-print.js +87 -0
  76. package/dist-shared/document-print.js.map +1 -0
  77. package/dist-shared/document-start.d.ts +15 -0
  78. package/dist-shared/document-start.js +51 -0
  79. package/dist-shared/document-start.js.map +1 -0
  80. package/dist-shared/invoice-standing.d.ts +25 -0
  81. package/dist-shared/invoice-standing.js +51 -0
  82. package/dist-shared/invoice-standing.js.map +1 -0
  83. package/package.json +9 -7
  84. package/shared/document-amounts.ts +209 -0
  85. package/shared/document-money.ts +33 -0
  86. package/shared/document-print.ts +131 -0
  87. package/shared/document-start.ts +46 -0
  88. package/shared/invoice-standing.ts +49 -0
  89. package/translations/en.json +32 -6
  90. package/translations/es.json +22 -1
  91. package/translations/ja.json +22 -1
  92. package/translations/ko.json +32 -6
  93. package/translations/ms.json +22 -1
  94. package/translations/zh.json +22 -1
  95. 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
- /** An amount in its currency, with exactly the currency's decimal places; a code the platform does not know, plainly. */
4
- export declare function documentAmount(value: number | null | undefined, currency: string): string;
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
- /** An amount in its currency, with exactly the currency's decimal places; a code the platform does not know, plainly. */
5
- export function documentAmount(value, currency) {
6
- if (value === null || value === undefined || !Number.isFinite(Number(value)))
7
- return '';
8
- const digits = currencyDigits(currency);
9
- return `${currency} ${Number(value).toLocaleString(i18next.language || 'ko', { minimumFractionDigits: digits, maximumFractionDigits: digits })}`;
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;AAEvF,OAAO,EAAE,cAAc,EAAE,CAAA;AAEzB,yHAAyH;AACzH,MAAM,UAAU,cAAc,CAAC,KAAgC,EAAE,QAAgB;IAC/E,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACvF,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;IACvC,OAAO,GAAG,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,EAAE,qBAAqB,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;AAClJ,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'\n\nexport { currencyDigits }\n\n/** An amount in its currency, with exactly the currency's decimal places; a code the platform does not know, plainly. */\nexport function documentAmount(value: number | null | undefined, currency: string): string {\n if (value === null || value === undefined || !Number.isFinite(Number(value))) return ''\n const digits = currencyDigits(currency)\n return `${currency} ${Number(value).toLocaleString(i18next.language || 'ko', { minimumFractionDigits: digits, maximumFractionDigits: digits })}`\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"]}
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
- /** The head fields in the order a person fills them. */
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
- export declare function renderDocumentHead(value: DocumentValue, editable: boolean, change: (patch: DocumentValue) => void, dates?: TemplateResult | typeof nothing, numberLabel?: string): TemplateResult<1>;
14
- /** Supply and tax of each tax kind and rate, then the document totals. */
15
- export declare function renderDocumentTotals(value: DocumentValue): TemplateResult<1>;
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-supplier-name' },
9
- { name: 'buyerName', label: 'field.invoice-buyer-name' },
10
- { name: 'supplierRegistrationNumber', label: 'field.invoice-supplier-registration-number' },
11
- { name: 'buyerRegistrationNumber', label: 'field.invoice-buyer-registration-number' },
12
- { name: 'supplierAddress', label: 'field.invoice-supplier-address', prose: true },
13
- { name: 'buyerAddress', label: 'field.invoice-buyer-address', prose: true },
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
- export function renderDocumentHead(value, editable, change, dates = nothing, numberLabel = 'field.invoice-number') {
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
- ${HEAD_FIELDS.slice(0, 2).map(field => input(field.name === 'number' ? { ...field, label: numberLabel } : field))}
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} ${HEAD_FIELDS.slice(2).map(input)}
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
- /** Supply and tax of each tax kind and rate, then the document totals. */
113
- export function renderDocumentTotals(value) {
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
- return html `<table class="totals">
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":"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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\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;