@things-factory/invoice 10.1.208 → 10.1.209

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 (48) hide show
  1. package/dist-client/document/document-form.d.ts +2 -1
  2. package/dist-client/document/document-form.js +3 -2
  3. package/dist-client/document/document-form.js.map +1 -1
  4. package/dist-client/document/document-print.d.ts +2 -1
  5. package/dist-client/document/document-print.js +2 -2
  6. package/dist-client/document/document-print.js.map +1 -1
  7. package/dist-client/entity360/invoice-360.d.ts +8 -0
  8. package/dist-client/entity360/invoice-360.js +12 -2
  9. package/dist-client/entity360/invoice-360.js.map +1 -1
  10. package/dist-client/invoice-files.d.ts +32 -0
  11. package/dist-client/invoice-files.js +249 -0
  12. package/dist-client/invoice-files.js.map +1 -0
  13. package/dist-client/pages/invoice-editor.d.ts +16 -0
  14. package/dist-client/pages/invoice-editor.js +88 -3
  15. package/dist-client/pages/invoice-editor.js.map +1 -1
  16. package/dist-client/pages/invoice-list.js +8 -4
  17. package/dist-client/pages/invoice-list.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/service/index.d.ts +1 -0
  20. package/dist-server/service/index.js +1 -0
  21. package/dist-server/service/index.js.map +1 -1
  22. package/dist-server/service/invoice/index.d.ts +1 -1
  23. package/dist-server/service/invoice/invoice-files.d.ts +14 -0
  24. package/dist-server/service/invoice/invoice-files.js +43 -0
  25. package/dist-server/service/invoice/invoice-files.js.map +1 -0
  26. package/dist-server/service/invoice/invoice-mutation.d.ts +2 -0
  27. package/dist-server/service/invoice/invoice-mutation.js +18 -0
  28. package/dist-server/service/invoice/invoice-mutation.js.map +1 -1
  29. package/dist-server/service/invoice/invoice-query.d.ts +4 -0
  30. package/dist-server/service/invoice/invoice-query.js +23 -0
  31. package/dist-server/service/invoice/invoice-query.js.map +1 -1
  32. package/dist-server/service/invoice/invoice-rules.d.ts +2 -2
  33. package/dist-server/service/invoice/invoice-service.js +5 -0
  34. package/dist-server/service/invoice/invoice-service.js.map +1 -1
  35. package/dist-server/service/invoice/invoice-type.d.ts +1 -0
  36. package/dist-server/service/invoice/invoice-type.js +4 -0
  37. package/dist-server/service/invoice/invoice-type.js.map +1 -1
  38. package/dist-server/service/invoice/invoice.d.ts +3 -0
  39. package/dist-server/service/invoice/invoice.js +11 -0
  40. package/dist-server/service/invoice/invoice.js.map +1 -1
  41. package/dist-server/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +8 -3
  43. package/translations/en.json +13 -1
  44. package/translations/es.json +13 -1
  45. package/translations/ja.json +13 -1
  46. package/translations/ko.json +13 -1
  47. package/translations/ms.json +13 -1
  48. package/translations/zh.json +13 -1
@@ -9,6 +9,7 @@ export declare const HEAD_FIELDS: {
9
9
  }[];
10
10
  export declare const DocumentFormStyles: import("lit").CSSResult;
11
11
  /** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */
12
- export declare function renderDocumentHead(value: DocumentValue, editable: boolean, change: (patch: DocumentValue) => void, dates?: TemplateResult | typeof nothing): TemplateResult<1>;
12
+ /** `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>;
13
14
  /** Supply and tax of each tax kind and rate, then the document totals. */
14
15
  export declare function renderDocumentTotals(value: DocumentValue): TemplateResult<1>;
@@ -71,7 +71,8 @@ export const DocumentFormStyles = css `
71
71
  }
72
72
  `;
73
73
  /** The head of a document. `dates` holds the fields a kind adds — the due date, the last day of an offer. */
74
- export function renderDocumentHead(value, editable, change, dates = nothing) {
74
+ /** `numberLabel` names the number as the kind does - 「청구서 번호」, 「견적 번호」. */
75
+ export function renderDocumentHead(value, editable, change, dates = nothing, numberLabel = 'field.invoice-number') {
75
76
  const input = (field) => html `<label class=${field.wide ? 'wide' : ''}
76
77
  >${i18next.t(field.label)}
77
78
  ${field.prose
@@ -84,7 +85,7 @@ export function renderDocumentHead(value, editable, change, dates = nothing) {
84
85
  />`}</label
85
86
  >`;
86
87
  return html `<div class="form">
87
- ${HEAD_FIELDS.slice(0, 2).map(input)}
88
+ ${HEAD_FIELDS.slice(0, 2).map(field => input(field.name === 'number' ? { ...field, label: numberLabel } : field))}
88
89
  <label
89
90
  >${i18next.t('field.invoice-direction')}
90
91
  <select .value=${value.direction} ?disabled=${!editable} @change=${(e) => change({ direction: e.target.value })}>
@@ -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. */\nexport function renderDocumentHead(value: DocumentValue, editable: boolean, change: (patch: DocumentValue) => void, dates: TemplateResult | typeof nothing = nothing) {\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(input)}\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\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"]}
@@ -4,12 +4,13 @@ export type PrintedDocument = Record<string, any>;
4
4
  * words (「청구서」 and its due date, 「견적서」 and its last day); everything else is the shared head and lines.
5
5
  * Only escaped business data enters the page; the browser prints it or saves it as PDF.
6
6
  */
7
- export declare function documentHtml(document: PrintedDocument, { title, terms }: {
7
+ export declare function documentHtml(document: PrintedDocument, { title, terms, numberLabel }: {
8
8
  title: string;
9
9
  terms: {
10
10
  label: string;
11
11
  value?: string | null;
12
12
  }[];
13
+ numberLabel?: string;
13
14
  }): string;
14
15
  /** Print through a hidden frame; the browser's dialog saves it as PDF too. */
15
16
  export declare function printDocument(html: string): void;
@@ -6,7 +6,7 @@ const escape = (text) => String(text ?? '').replace(/[&<>"']/g, c => ({ '&': '&a
6
6
  * words (「청구서」 and its due date, 「견적서」 and its last day); everything else is the shared head and lines.
7
7
  * Only escaped business data enters the page; the browser prints it or saves it as PDF.
8
8
  */
9
- export function documentHtml(document, { title, terms }) {
9
+ export function documentHtml(document, { title, terms, numberLabel = 'field.invoice-number' }) {
10
10
  const lng = document.language === 'en' ? 'en' : 'ko';
11
11
  const t = (key) => escape(i18next.t(key, { lng }));
12
12
  const digits = currencyDigits(document.currency);
@@ -27,7 +27,7 @@ export function documentHtml(document, { title, terms }) {
27
27
  .totals{width:50%;margin-left:auto}
28
28
  .grand td{font-weight:600;font-size:15px}
29
29
  </style></head><body>
30
- <header><h1>${escape(title)}</h1><div><p>${t('field.invoice-number')}: ${escape(document.number)}</p><p>${t('field.invoice-issue-date')}: ${escape(document.issueDate)}</p>${terms
30
+ <header><h1>${escape(title)}</h1><div><p>${t(numberLabel)}: ${escape(document.number)}</p><p>${t('field.invoice-issue-date')}: ${escape(document.issueDate)}</p>${terms
31
31
  .map(one => `<p>${escape(one.label)}: ${escape(one.value)}</p>`)
32
32
  .join('')}</div></header>
33
33
  <div class="parties">${party(t('field.invoice-supplier-name'), document.supplierName, document.supplierAddress, document.supplierRegistrationNumber)}${party(t('field.invoice-buyer-name'), document.buyerName, document.buyerAddress, document.buyerRegistrationNumber)}</div>
@@ -1 +1 @@
1
- {"version":3,"file":"document-print.js","sourceRoot":"","sources":["../../client/document/document-print.ts"],"names":[],"mappings":"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","sourcesContent":["import { i18next } from '@operato/i18n'\n\nimport { currencyDigits } from './document-amount'\n\nconst escape = (text: unknown) => String(text ?? '').replace(/[&<>\"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!)\n\nexport type PrintedDocument = Record<string, any>\n\n/**\n * A commercial document as the buyer reads it, in the document's own language. `title` and `terms` are the kind's\n * words (「청구서」 and its due date, 「견적서」 and its last day); everything else is the shared head and lines.\n * Only escaped business data enters the page; the browser prints it or saves it as PDF.\n */\nexport function documentHtml(document: PrintedDocument, { title, terms }: { title: string; terms: { label: string; value?: string | null }[] }) {\n const lng = document.language === 'en' ? 'en' : 'ko'\n const t = (key: string) => escape(i18next.t(key, { lng }))\n const digits = currencyDigits(document.currency)\n const amount = (value: number) => escape(`${Number(value ?? 0).toLocaleString(lng, { minimumFractionDigits: digits, maximumFractionDigits: digits })}`)\n const kind = (taxKind: string) =>\n taxKind === 'ZERO_RATED' ? t('text.invoice-tax-kind-zero-rated') : taxKind === 'EXEMPT' ? t('text.invoice-tax-kind-exempt') : t('text.invoice-tax-kind-taxable')\n const party = (label: string, name: string, address: string, number: string) =>\n `<section><h2>${label}</h2><strong>${escape(name)}</strong><p class=\"text\">${escape(address)}</p><p>${escape(number)}</p></section>`\n\n return `<!doctype html><html lang=\"${lng}\"><head><meta charset=\"utf-8\"><title>${escape(document.number)}</title><style>\n @page{size:A4;margin:18mm}\n body{font:13px/1.5 system-ui,sans-serif;color:#000}\n h1{font-size:26px;margin:0}\n h2{font-size:13px;color:#555;margin:0 0 4px}\n header,.parties{display:flex;justify-content:space-between;gap:32px}\n .parties section{flex:1}\n table{width:100%;border-collapse:collapse;margin:24px 0}\n th,td{padding:8px;border-bottom:1px solid #999;text-align:right;font-variant-numeric:tabular-nums}\n th:first-child,td:first-child{text-align:left}\n .text{white-space:pre-wrap}\n .totals{width:50%;margin-left:auto}\n .grand td{font-weight:600;font-size:15px}\n </style></head><body>\n <header><h1>${escape(title)}</h1><div><p>${t('field.invoice-number')}: ${escape(document.number)}</p><p>${t('field.invoice-issue-date')}: ${escape(document.issueDate)}</p>${terms\n .map(one => `<p>${escape(one.label)}: ${escape(one.value)}</p>`)\n .join('')}</div></header>\n <div class=\"parties\">${party(t('field.invoice-supplier-name'), document.supplierName, document.supplierAddress, document.supplierRegistrationNumber)}${party(\n t('field.invoice-buyer-name'),\n document.buyerName,\n document.buyerAddress,\n document.buyerRegistrationNumber\n )}</div>\n ${document.purchaseOrder ? `<p>${t('field.invoice-purchase-order')}: ${escape(document.purchaseOrder)}</p>` : ''}\n <table><thead><tr><th>${t('field.invoice-line-description')}</th><th>${t('field.invoice-quantity')}</th><th>${t('field.invoice-unit-price')}</th><th>${t(\n 'field.invoice-discount'\n )}</th><th>${t('field.invoice-tax-kind')}</th><th>${t('field.invoice-supply-amount')}</th></tr></thead><tbody>${(document.items ?? [])\n .map(\n (item: any) =>\n `<tr><td>${escape(item.description)}</td><td>${escape(item.quantity)}</td><td>${amount(item.unitPrice)}</td><td>${amount(item.discount)}</td><td>${kind(item.taxKind)} ${escape(\n item.taxRate\n )}%</td><td>${amount(item.supplyAmount)}</td></tr>`\n )\n .join('')}</tbody></table>\n <table class=\"totals\"><tbody>${(document.taxGroups ?? [])\n .map((group: any) => `<tr><td>${kind(group.taxKind)} ${escape(group.taxRate)}%</td><td>${amount(group.supplyAmount)}</td><td>${amount(group.taxAmount)}</td></tr>`)\n .join('')}<tr><td>${t('field.invoice-sum')}</td><td>${amount(document.supplyAmount)}</td><td>${amount(document.taxAmount)}</td></tr><tr class=\"grand\"><td colspan=\"2\">${t(\n 'field.invoice-total-amount'\n )} (${escape(document.currency)})</td><td>${amount(document.totalAmount)}</td></tr></tbody></table>\n ${['paymentTerms', 'bankDetails', 'notes']\n .filter(key => document[key])\n .map(key => `<p class=\"text\">${escape(document[key])}</p>`)\n .join('')}\n </body></html>`\n}\n\n/** Print through a hidden frame; the browser's dialog saves it as PDF too. */\nexport function printDocument(html: string) {\n const frame = window.document.createElement('iframe')\n frame.style.cssText = 'position:fixed;width:0;height:0;border:0;'\n frame.onload = () => {\n const view = frame.contentWindow!\n view.addEventListener('afterprint', () => frame.remove(), { once: true })\n view.focus()\n view.print()\n }\n frame.srcdoc = html\n window.document.body.append(frame)\n}\n"]}
1
+ {"version":3,"file":"document-print.js","sourceRoot":"","sources":["../../client/document/document-print.ts"],"names":[],"mappings":"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","sourcesContent":["import { i18next } from '@operato/i18n'\n\nimport { currencyDigits } from './document-amount'\n\nconst escape = (text: unknown) => String(text ?? '').replace(/[&<>\"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!)\n\nexport type PrintedDocument = Record<string, any>\n\n/**\n * A commercial document as the buyer reads it, in the document's own language. `title` and `terms` are the kind's\n * words (「청구서」 and its due date, 「견적서」 and its last day); everything else is the shared head and lines.\n * Only escaped business data enters the page; the browser prints it or saves it as PDF.\n */\nexport function documentHtml(\n document: PrintedDocument,\n { title, terms, numberLabel = 'field.invoice-number' }: { title: string; terms: { label: string; value?: string | null }[]; numberLabel?: string }\n) {\n const lng = document.language === 'en' ? 'en' : 'ko'\n const t = (key: string) => escape(i18next.t(key, { lng }))\n const digits = currencyDigits(document.currency)\n const amount = (value: number) => escape(`${Number(value ?? 0).toLocaleString(lng, { minimumFractionDigits: digits, maximumFractionDigits: digits })}`)\n const kind = (taxKind: string) =>\n taxKind === 'ZERO_RATED' ? t('text.invoice-tax-kind-zero-rated') : taxKind === 'EXEMPT' ? t('text.invoice-tax-kind-exempt') : t('text.invoice-tax-kind-taxable')\n const party = (label: string, name: string, address: string, number: string) =>\n `<section><h2>${label}</h2><strong>${escape(name)}</strong><p class=\"text\">${escape(address)}</p><p>${escape(number)}</p></section>`\n\n return `<!doctype html><html lang=\"${lng}\"><head><meta charset=\"utf-8\"><title>${escape(document.number)}</title><style>\n @page{size:A4;margin:18mm}\n body{font:13px/1.5 system-ui,sans-serif;color:#000}\n h1{font-size:26px;margin:0}\n h2{font-size:13px;color:#555;margin:0 0 4px}\n header,.parties{display:flex;justify-content:space-between;gap:32px}\n .parties section{flex:1}\n table{width:100%;border-collapse:collapse;margin:24px 0}\n th,td{padding:8px;border-bottom:1px solid #999;text-align:right;font-variant-numeric:tabular-nums}\n th:first-child,td:first-child{text-align:left}\n .text{white-space:pre-wrap}\n .totals{width:50%;margin-left:auto}\n .grand td{font-weight:600;font-size:15px}\n </style></head><body>\n <header><h1>${escape(title)}</h1><div><p>${t(numberLabel)}: ${escape(document.number)}</p><p>${t('field.invoice-issue-date')}: ${escape(document.issueDate)}</p>${terms\n .map(one => `<p>${escape(one.label)}: ${escape(one.value)}</p>`)\n .join('')}</div></header>\n <div class=\"parties\">${party(t('field.invoice-supplier-name'), document.supplierName, document.supplierAddress, document.supplierRegistrationNumber)}${party(\n t('field.invoice-buyer-name'),\n document.buyerName,\n document.buyerAddress,\n document.buyerRegistrationNumber\n )}</div>\n ${document.purchaseOrder ? `<p>${t('field.invoice-purchase-order')}: ${escape(document.purchaseOrder)}</p>` : ''}\n <table><thead><tr><th>${t('field.invoice-line-description')}</th><th>${t('field.invoice-quantity')}</th><th>${t('field.invoice-unit-price')}</th><th>${t(\n 'field.invoice-discount'\n )}</th><th>${t('field.invoice-tax-kind')}</th><th>${t('field.invoice-supply-amount')}</th></tr></thead><tbody>${(document.items ?? [])\n .map(\n (item: any) =>\n `<tr><td>${escape(item.description)}</td><td>${escape(item.quantity)}</td><td>${amount(item.unitPrice)}</td><td>${amount(item.discount)}</td><td>${kind(item.taxKind)} ${escape(\n item.taxRate\n )}%</td><td>${amount(item.supplyAmount)}</td></tr>`\n )\n .join('')}</tbody></table>\n <table class=\"totals\"><tbody>${(document.taxGroups ?? [])\n .map((group: any) => `<tr><td>${kind(group.taxKind)} ${escape(group.taxRate)}%</td><td>${amount(group.supplyAmount)}</td><td>${amount(group.taxAmount)}</td></tr>`)\n .join('')}<tr><td>${t('field.invoice-sum')}</td><td>${amount(document.supplyAmount)}</td><td>${amount(document.taxAmount)}</td></tr><tr class=\"grand\"><td colspan=\"2\">${t(\n 'field.invoice-total-amount'\n )} (${escape(document.currency)})</td><td>${amount(document.totalAmount)}</td></tr></tbody></table>\n ${['paymentTerms', 'bankDetails', 'notes']\n .filter(key => document[key])\n .map(key => `<p class=\"text\">${escape(document[key])}</p>`)\n .join('')}\n </body></html>`\n}\n\n/** Print through a hidden frame; the browser's dialog saves it as PDF too. */\nexport function printDocument(html: string) {\n const frame = window.document.createElement('iframe')\n frame.style.cssText = 'position:fixed;width:0;height:0;border:0;'\n frame.onload = () => {\n const view = frame.contentWindow!\n view.addEventListener('afterprint', () => frame.remove(), { once: true })\n view.focus()\n view.print()\n }\n frame.srcdoc = html\n window.document.body.append(frame)\n}\n"]}
@@ -1,11 +1,19 @@
1
1
  import { LitElement } from 'lit';
2
2
  import { type Entity360DoorAnswer, type Entity360Subject } from '@operato/layout';
3
+ import '../invoice-files';
3
4
  /** The other party: the buyer of a sale, the supplier of a purchase. */
4
5
  export declare const counterpartOf: (value: {
5
6
  direction?: string;
6
7
  supplierName?: string;
7
8
  buyerName?: string;
8
9
  }) => string;
10
+ /** The other party as printed - a link to its counterparty's 360 when the invoice names one, else the printed name. */
11
+ export declare const counterpartCell: (value: {
12
+ counterpartyId?: string | null;
13
+ direction?: string;
14
+ supplierName?: string;
15
+ buyerName?: string;
16
+ }) => string | import("lit-html").TemplateResult;
9
17
  export declare const invoiceStatusWord: (status?: string) => string;
10
18
  /** An invoice's 360 (ADR-0067). The module draws the invoice; an application hangs its own sections on 'invoice'. */
11
19
  export declare function registerInvoiceKind(): void;
@@ -4,11 +4,14 @@ import { css, html, LitElement } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { client } from '@operato/graphql';
6
6
  import { i18next } from '@operato/i18n';
7
- import { readEntity360Door, registerEntity360Kind, registerEntity360Section } from '@operato/layout';
7
+ import { entity360Name, readEntity360Door, registerEntity360Kind, registerEntity360Section } from '@operato/layout';
8
8
  import { DocumentFormStyles, renderDocumentTotals } from '../document/document-form';
9
9
  import { invoice360Query } from '../invoice-attach';
10
+ import '../invoice-files';
10
11
  /** The other party: the buyer of a sale, the supplier of a purchase. */
11
12
  export const counterpartOf = (value) => (value.direction === 'PURCHASE' ? value.supplierName : value.buyerName) || '';
13
+ /** The other party as printed - a link to its counterparty's 360 when the invoice names one, else the printed name. */
14
+ export const counterpartCell = (value) => value.counterpartyId ? entity360Name({ __typename: 'Counterparty', id: value.counterpartyId, name: counterpartOf(value) }, counterpartOf(value)) : counterpartOf(value);
12
15
  export const invoiceStatusWord = (status) => status === 'ISSUED' ? i18next.t('text.invoice-status-issued') : status === 'VOID' ? i18next.t('text.invoice-status-void') : i18next.t('text.invoice-status-draft');
13
16
  /** An invoice's 360 (ADR-0067). The module draws the invoice; an application hangs its own sections on 'invoice'. */
14
17
  export function registerInvoiceKind() {
@@ -23,6 +26,11 @@ export function registerInvoiceKind() {
23
26
  order: 10,
24
27
  render: subject => html `<invoice-overview-section .subject=${subject}></invoice-overview-section>`
25
28
  });
29
+ registerEntity360Section('invoice', {
30
+ name: 'files',
31
+ order: 20,
32
+ render: subject => html `<ox-entity360-section .heading=${i18next.t('text.invoice-section-files')} .state=${'ready'}><invoice-files .invoiceId=${subject.id}></invoice-files></ox-entity360-section>`
33
+ });
26
34
  }
27
35
  let InvoiceOverviewSection = class InvoiceOverviewSection extends LitElement {
28
36
  static { this.styles = [
@@ -63,7 +71,7 @@ let InvoiceOverviewSection = class InvoiceOverviewSection extends LitElement {
63
71
  query: gql(`
64
72
  query ($id: String!) {
65
73
  invoice: ${invoice360Query()}(id: $id) {
66
- id number status direction currency issueDate dueDate supplierName buyerName purchaseOrder
74
+ id number status direction currency issueDate dueDate counterpartyId supplierName buyerName purchaseOrder
67
75
  supplyAmount taxAmount totalAmount taxGroups { taxKind taxRate supplyAmount taxAmount }
68
76
  }
69
77
  }
@@ -87,6 +95,8 @@ let InvoiceOverviewSection = class InvoiceOverviewSection extends LitElement {
87
95
  ? html `<dl>
88
96
  <dt>${i18next.t('field.invoice-number')}</dt>
89
97
  <dd>${one.number} · ${invoiceStatusWord(one.status)}</dd>
98
+ <dt>${i18next.t('field.invoice-counterparty')}</dt>
99
+ <dd>${one.counterpartyId ? counterpartCell(one) : '—'}</dd>
90
100
  <dt>${i18next.t('field.invoice-supplier-name')}</dt>
91
101
  <dd>${one.supplierName}</dd>
92
102
  <dt>${i18next.t('field.invoice-buyer-name')}</dt>
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-360.js","sourceRoot":"","sources":["../../client/entity360/invoice-360.ts"],"names":[],"mappings":";AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,wBAAwB,EAAmD,MAAM,iBAAiB,CAAA;AAErJ,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAwE,EAAE,EAAE,CACxG,CAAC,KAAK,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;AAE/E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAe,EAAE,EAAE,CACnD,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAA;AAEpK,qHAAqH;AACrH,MAAM,UAAU,mBAAmB;IACjC,qBAAqB,CAAC;QACpB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,CAAC,SAAS,CAAC;QACtB,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE;QACrG,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;KACzI,CAAC,CAAA;IACF,wBAAwB,CAAC,SAAS,EAAE;QAClC,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,CAAA,sCAAsC,OAAO,8BAA8B;KACnG,CAAC,CAAA;AACJ,CAAC;AAGM,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,UAAU;aAC7C,WAAM,GAAG;QACd,kBAAkB;QAClB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;KAmBF;KACF,AAtBY,CAsBZ;IAKD,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,IAAI,CAAC,IAAI,EAAE,CAAA;IACzC,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACvB,IAAI,CAAC,EAAE;YAAE,OAAM;QACf,IAAI,QAAa,CAAA;QACjB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;gBAC5B,KAAK,EAAE,GAAG,CAAC;;uBAEI,eAAe,EAAE;;;;;SAK/B,CAAC;gBACF,SAAS,EAAE,EAAE,EAAE,EAAE;gBACjB,WAAW,EAAE,UAAU;gBACvB,WAAW,EAAE,KAAK;aACZ,CAAC,CAAA;QACX,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QACxH,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE;YAAE,OAAM;QACnC,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAClE,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAA;QAC7B,OAAO,IAAI,CAAA,kCAAkC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,WAAW,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,SAAS,aAAa,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE;QACpJ,GAAG;YACH,CAAC,CAAC,IAAI,CAAA;oBACM,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;oBACjC,GAAG,CAAC,MAAM,MAAM,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;oBAC7C,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;oBACxC,GAAG,CAAC,YAAY;oBAChB,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;oBACrC,GAAG,CAAC,SAAS;oBACb,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;oBACrC,GAAG,CAAC,SAAS,IAAI,GAAG;oBACpB,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;oBACnC,GAAG,CAAC,OAAO,IAAI,GAAG;;cAExB,oBAAoB,CAAC,GAAG,CAAC,EAAE;YACjC,CAAC,CAAC,EAAE;4BACgB,CAAA;IAC1B,CAAC;;AApD+B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;;uDAA2B;AACjD;IAAR,KAAK,EAAE;;sDAAkC;AA1B/B,sBAAsB;IADlC,aAAa,CAAC,0BAA0B,CAAC;GAC7B,sBAAsB,CA8ElC","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { readEntity360Door, registerEntity360Kind, registerEntity360Section, type Entity360DoorAnswer, type Entity360Subject } from '@operato/layout'\n\nimport { DocumentFormStyles, renderDocumentTotals } from '../document/document-form'\nimport { invoice360Query } from '../invoice-attach'\n\n/** The other party: the buyer of a sale, the supplier of a purchase. */\nexport const counterpartOf = (value: { direction?: string; supplierName?: string; buyerName?: string }) =>\n (value.direction === 'PURCHASE' ? value.supplierName : value.buyerName) || ''\n\nexport const invoiceStatusWord = (status?: string) =>\n status === 'ISSUED' ? i18next.t('text.invoice-status-issued') : status === 'VOID' ? i18next.t('text.invoice-status-void') : i18next.t('text.invoice-status-draft')\n\n/** An invoice's 360 (ADR-0067). The module draws the invoice; an application hangs its own sections on 'invoice'. */\nexport function registerInvoiceKind() {\n registerEntity360Kind({\n kind: 'invoice',\n typenames: ['Invoice'],\n title: ({ value }) => `${i18next.t('title.invoice')} · ${counterpartOf(value) || value.number || ''}`,\n context: ({ value }) => [value.number, value.issueDate, value.status ? invoiceStatusWord(value.status) : ''].filter(Boolean).join(' · ')\n })\n registerEntity360Section('invoice', {\n name: 'overview',\n order: 10,\n render: subject => html`<invoice-overview-section .subject=${subject}></invoice-overview-section>`\n })\n}\n\n@customElement('invoice-overview-section')\nexport class InvoiceOverviewSection extends LitElement {\n static styles = [\n DocumentFormStyles,\n css`\n :host {\n font-size: var(--ui-t-body);\n line-height: 1.5;\n }\n dl {\n margin: 0 0 var(--spacing-large);\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: var(--spacing-small) var(--spacing-large);\n }\n dt {\n color: var(--ui-muted);\n }\n dd {\n margin: 0;\n overflow-wrap: anywhere;\n font-variant-numeric: tabular-nums;\n }\n `\n ]\n\n @property({ attribute: false }) subject?: Entity360Subject\n @state() answer?: Entity360DoorAnswer<any>\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('subject')) this.read()\n }\n\n private async read() {\n const id = this.subject?.id\n this.answer = undefined\n if (!id) return\n let response: any\n try {\n response = await client.query({\n query: gql(`\n query ($id: String!) {\n invoice: ${invoice360Query()}(id: $id) {\n id number status direction currency issueDate dueDate supplierName buyerName purchaseOrder\n supplyAmount taxAmount totalAmount taxGroups { taxKind taxRate supplyAmount taxAmount }\n }\n }\n `),\n variables: { id },\n fetchPolicy: 'no-cache',\n errorPolicy: 'all'\n } as any)\n } catch (e: any) {\n response = { data: null, errors: e?.graphQLErrors?.length ? e.graphQLErrors : [{ message: e?.message ?? String(e) }] }\n }\n if (this.subject?.id !== id) return\n this.answer = readEntity360Door(response, data => data?.invoice)\n }\n\n render() {\n const one = this.answer?.data\n return html`<ox-entity360-section .heading=${i18next.t('title.invoice')} .state=${this.answer?.state ?? 'loading'} .message=${this.answer?.message ?? ''}>\n ${one\n ? html`<dl>\n <dt>${i18next.t('field.invoice-number')}</dt>\n <dd>${one.number} · ${invoiceStatusWord(one.status)}</dd>\n <dt>${i18next.t('field.invoice-supplier-name')}</dt>\n <dd>${one.supplierName}</dd>\n <dt>${i18next.t('field.invoice-buyer-name')}</dt>\n <dd>${one.buyerName}</dd>\n <dt>${i18next.t('field.invoice-issue-date')}</dt>\n <dd>${one.issueDate ?? '—'}</dd>\n <dt>${i18next.t('field.invoice-due-date')}</dt>\n <dd>${one.dueDate ?? '—'}</dd>\n </dl>\n ${renderDocumentTotals(one)}`\n : ''}\n </ox-entity360-section>`\n }\n}\n"]}
1
+ {"version":3,"file":"invoice-360.js","sourceRoot":"","sources":["../../client/entity360/invoice-360.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { entity360Name, readEntity360Door, registerEntity360Kind, registerEntity360Section, type Entity360DoorAnswer, type Entity360Subject } from '@operato/layout'\n\nimport { DocumentFormStyles, renderDocumentTotals } from '../document/document-form'\nimport { invoice360Query } from '../invoice-attach'\nimport '../invoice-files'\n\n/** The other party: the buyer of a sale, the supplier of a purchase. */\nexport const counterpartOf = (value: { direction?: string; supplierName?: string; buyerName?: string }) =>\n (value.direction === 'PURCHASE' ? value.supplierName : value.buyerName) || ''\n\n/** The other party as printed - a link to its counterparty's 360 when the invoice names one, else the printed name. */\nexport const counterpartCell = (value: { counterpartyId?: string | null; direction?: string; supplierName?: string; buyerName?: string }) =>\n value.counterpartyId ? entity360Name({ __typename: 'Counterparty', id: value.counterpartyId, name: counterpartOf(value) }, counterpartOf(value)) : counterpartOf(value)\n\nexport const invoiceStatusWord = (status?: string) =>\n status === 'ISSUED' ? i18next.t('text.invoice-status-issued') : status === 'VOID' ? i18next.t('text.invoice-status-void') : i18next.t('text.invoice-status-draft')\n\n/** An invoice's 360 (ADR-0067). The module draws the invoice; an application hangs its own sections on 'invoice'. */\nexport function registerInvoiceKind() {\n registerEntity360Kind({\n kind: 'invoice',\n typenames: ['Invoice'],\n title: ({ value }) => `${i18next.t('title.invoice')} · ${counterpartOf(value) || value.number || ''}`,\n context: ({ value }) => [value.number, value.issueDate, value.status ? invoiceStatusWord(value.status) : ''].filter(Boolean).join(' · ')\n })\n registerEntity360Section('invoice', {\n name: 'overview',\n order: 10,\n render: subject => html`<invoice-overview-section .subject=${subject}></invoice-overview-section>`\n })\n registerEntity360Section('invoice', {\n name: 'files',\n order: 20,\n render: subject => html`<ox-entity360-section .heading=${i18next.t('text.invoice-section-files')} .state=${'ready'}><invoice-files .invoiceId=${subject.id}></invoice-files></ox-entity360-section>`\n })\n}\n\n@customElement('invoice-overview-section')\nexport class InvoiceOverviewSection extends LitElement {\n static styles = [\n DocumentFormStyles,\n css`\n :host {\n font-size: var(--ui-t-body);\n line-height: 1.5;\n }\n dl {\n margin: 0 0 var(--spacing-large);\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: var(--spacing-small) var(--spacing-large);\n }\n dt {\n color: var(--ui-muted);\n }\n dd {\n margin: 0;\n overflow-wrap: anywhere;\n font-variant-numeric: tabular-nums;\n }\n `\n ]\n\n @property({ attribute: false }) subject?: Entity360Subject\n @state() answer?: Entity360DoorAnswer<any>\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('subject')) this.read()\n }\n\n private async read() {\n const id = this.subject?.id\n this.answer = undefined\n if (!id) return\n let response: any\n try {\n response = await client.query({\n query: gql(`\n query ($id: String!) {\n invoice: ${invoice360Query()}(id: $id) {\n id number status direction currency issueDate dueDate counterpartyId supplierName buyerName purchaseOrder\n supplyAmount taxAmount totalAmount taxGroups { taxKind taxRate supplyAmount taxAmount }\n }\n }\n `),\n variables: { id },\n fetchPolicy: 'no-cache',\n errorPolicy: 'all'\n } as any)\n } catch (e: any) {\n response = { data: null, errors: e?.graphQLErrors?.length ? e.graphQLErrors : [{ message: e?.message ?? String(e) }] }\n }\n if (this.subject?.id !== id) return\n this.answer = readEntity360Door(response, data => data?.invoice)\n }\n\n render() {\n const one = this.answer?.data\n return html`<ox-entity360-section .heading=${i18next.t('title.invoice')} .state=${this.answer?.state ?? 'loading'} .message=${this.answer?.message ?? ''}>\n ${one\n ? html`<dl>\n <dt>${i18next.t('field.invoice-number')}</dt>\n <dd>${one.number} · ${invoiceStatusWord(one.status)}</dd>\n <dt>${i18next.t('field.invoice-counterparty')}</dt>\n <dd>${one.counterpartyId ? counterpartCell(one) : '—'}</dd>\n <dt>${i18next.t('field.invoice-supplier-name')}</dt>\n <dd>${one.supplierName}</dd>\n <dt>${i18next.t('field.invoice-buyer-name')}</dt>\n <dd>${one.buyerName}</dd>\n <dt>${i18next.t('field.invoice-issue-date')}</dt>\n <dd>${one.issueDate ?? '—'}</dd>\n <dt>${i18next.t('field.invoice-due-date')}</dt>\n <dd>${one.dueDate ?? '—'}</dd>\n </dl>\n ${renderDocumentTotals(one)}`\n : ''}\n </ox-entity360-section>`\n }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@operato/attachment';
3
+ import '@operato/input/ox-input-file.js';
4
+ import { LitElement } from 'lit';
5
+ type InvoiceFile = {
6
+ id: string;
7
+ name: string;
8
+ mimetype?: string;
9
+ fullpath: string;
10
+ };
11
+ declare const InvoiceFiles_base: (new (...args: any[]) => LitElement) & typeof LitElement;
12
+ /**
13
+ * The files an invoice keeps - the signed copy, the order, the delivery note. Each opens in its own tab. Whoever may
14
+ * change invoices attaches (a photo taken on the spot, or any file) and takes one off; it is saved at once.
15
+ */
16
+ export declare class InvoiceFiles extends InvoiceFiles_base {
17
+ static styles: import("lit").CSSResult[];
18
+ invoiceId?: string;
19
+ files: InvoiceFile[];
20
+ mayChange: boolean;
21
+ busy: boolean;
22
+ progress?: number;
23
+ connectedCallback(): void;
24
+ updated(changed: Map<string, unknown>): void;
25
+ render(): import("lit-html").TemplateResult<1>;
26
+ private tile;
27
+ private load;
28
+ private pick;
29
+ private takeOff;
30
+ private change;
31
+ }
32
+ export {};
@@ -0,0 +1,249 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ /* The tile is not a subpath of its own; the package root registers it. */
4
+ import '@operato/attachment';
5
+ import '@operato/input/ox-input-file.js';
6
+ import gql from 'graphql-tag';
7
+ import { css, html, LitElement, nothing } from 'lit';
8
+ import { customElement, property, state } from 'lit/decorators.js';
9
+ import { client, uploadContext } from '@operato/graphql';
10
+ import { i18next, localize } from '@operato/i18n';
11
+ import { notify } from '@operato/layout';
12
+ import { OxPrompt } from '@operato/popup/ox-prompt.js';
13
+ import { hasPrivilege } from '@things-factory/auth-base/dist-client/index.js';
14
+ import { invoice360Query } from './invoice-attach';
15
+ /** The phone's camera is offered where a finger taps - on a desktop the file button is the only way in. */
16
+ const touch = () => window.matchMedia('(pointer: coarse)').matches;
17
+ /**
18
+ * The files an invoice keeps - the signed copy, the order, the delivery note. Each opens in its own tab. Whoever may
19
+ * change invoices attaches (a photo taken on the spot, or any file) and takes one off; it is saved at once.
20
+ */
21
+ let InvoiceFiles = class InvoiceFiles extends localize(i18next)(LitElement) {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.files = [];
25
+ this.mayChange = false;
26
+ this.busy = false;
27
+ }
28
+ static { this.styles = [
29
+ css `
30
+ :host {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--spacing-medium, 8px);
34
+ color: var(--ui-ink);
35
+ }
36
+ .tiles {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ gap: var(--spacing-medium, 8px);
40
+ padding-top: 6px;
41
+ }
42
+ .tile {
43
+ position: relative;
44
+ display: flex;
45
+ flex-direction: column;
46
+ width: 72px;
47
+ gap: var(--spacing-tiny, 2px);
48
+ }
49
+ .tile a {
50
+ display: block;
51
+ width: 100%;
52
+ aspect-ratio: 1;
53
+ border-radius: var(--ui-r-small, 6px);
54
+ border: 1px solid var(--ui-line);
55
+ background: var(--ui-panel-2);
56
+ overflow: hidden;
57
+ }
58
+ .tile ox-attachment-tile,
59
+ .tile img {
60
+ width: 100%;
61
+ height: 100%;
62
+ }
63
+ .tile img {
64
+ object-fit: cover;
65
+ }
66
+ .name {
67
+ font-size: var(--ui-t-small, 11.5px);
68
+ color: var(--ui-muted);
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ }
73
+ .tile button {
74
+ position: absolute;
75
+ top: -6px;
76
+ right: -6px;
77
+ display: grid;
78
+ place-items: center;
79
+ width: 20px;
80
+ height: 20px;
81
+ padding: 0;
82
+ border: 1px solid var(--ui-line);
83
+ border-radius: 50%;
84
+ background: var(--ui-panel);
85
+ color: var(--ui-muted);
86
+ cursor: pointer;
87
+ }
88
+ .tile button md-icon {
89
+ --md-icon-size: 14px;
90
+ }
91
+ .pickers {
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ gap: var(--spacing-small, 4px);
95
+ }
96
+ .none {
97
+ font-size: var(--ui-t-small, 11.5px);
98
+ color: var(--ui-muted);
99
+ }
100
+ `
101
+ ]; }
102
+ connectedCallback() {
103
+ super.connectedCallback();
104
+ hasPrivilege({ privilege: 'mutation', category: 'invoice', domainOwnerGranted: true }).then(may => (this.mayChange = !!may));
105
+ }
106
+ updated(changed) {
107
+ if (changed.has('invoiceId'))
108
+ this.load();
109
+ }
110
+ render() {
111
+ const editable = this.mayChange && !this.busy;
112
+ return html `
113
+ ${this.files.length
114
+ ? html `<div class="tiles">${this.files.map(one => this.tile(one, editable))}</div>`
115
+ : html `<span class="none">${i18next.t('text.invoice-files-none')}</span>`}
116
+ ${this.mayChange
117
+ ? html `<div class="pickers">
118
+ ${touch()
119
+ ? html `<ox-input-file
120
+ quiet
121
+ hide-filelist
122
+ accept="image/*"
123
+ capture="environment"
124
+ icon="photo_camera"
125
+ .label=${i18next.t('button.invoice-file-photo')}
126
+ ?disabled=${!editable}
127
+ .progress=${this.progress}
128
+ @change=${(e) => this.pick(e)}
129
+ ></ox-input-file>`
130
+ : nothing}
131
+ <ox-input-file
132
+ quiet
133
+ hide-filelist
134
+ multiple
135
+ icon="attach_file"
136
+ .label=${i18next.t('button.invoice-file-attach')}
137
+ ?disabled=${!editable}
138
+ .progress=${touch() ? undefined : this.progress}
139
+ @change=${(e) => this.pick(e)}
140
+ ></ox-input-file>
141
+ </div>`
142
+ : nothing}
143
+ `;
144
+ }
145
+ tile(one, editable) {
146
+ return html `<span class="tile">
147
+ <a href=${one.fullpath} target="_blank" rel="noopener" title=${one.name}
148
+ >${one.mimetype?.startsWith('image/')
149
+ ? html `<img src=${one.fullpath} alt=${one.name} />`
150
+ : html `<ox-attachment-tile .src=${one.fullpath} .path=${one.name} .mimetype=${one.mimetype ?? ''}></ox-attachment-tile>`}</a
151
+ >
152
+ <span class="name">${one.name}</span>
153
+ ${this.mayChange
154
+ ? html `<button type="button" ?disabled=${!editable} title=${i18next.t('button.invoice-file-remove')} aria-label=${i18next.t('button.invoice-file-remove')} @click=${() => this.takeOff(one)}>
155
+ <md-icon>close</md-icon>
156
+ </button>`
157
+ : nothing}
158
+ </span>`;
159
+ }
160
+ async load() {
161
+ const id = this.invoiceId;
162
+ this.files = [];
163
+ if (!id)
164
+ return;
165
+ try {
166
+ const response = await client.query({
167
+ query: gql(`query ($id: String!) { invoice: ${invoice360Query()}(id: $id) { id files { id name mimetype fullpath } } }`),
168
+ variables: { id },
169
+ fetchPolicy: 'no-cache'
170
+ });
171
+ if (this.invoiceId === id)
172
+ this.files = response.data?.invoice?.files ?? [];
173
+ }
174
+ catch (error) {
175
+ notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) });
176
+ }
177
+ }
178
+ async pick(e) {
179
+ const picked = [...(e.detail ?? [])];
180
+ /* The picker's own reset says `change` again, with nothing - that one is let go. */
181
+ if (!picked.length)
182
+ return;
183
+ e.target.reset?.();
184
+ await this.change({ attach: picked });
185
+ }
186
+ async takeOff(one) {
187
+ const sure = await OxPrompt.open({
188
+ title: i18next.t('button.invoice-file-remove'),
189
+ text: i18next.t('text.invoice-file-remove-confirm', { name: one.name }),
190
+ confirmButton: { text: i18next.t('button.invoice-file-remove'), destructive: true },
191
+ cancelButton: { text: i18next.t('button.cancel') }
192
+ });
193
+ if (sure)
194
+ await this.change({ remove: [one.id] });
195
+ }
196
+ async change({ attach = [], remove = [] }) {
197
+ if (!this.invoiceId || this.busy)
198
+ return;
199
+ this.busy = true;
200
+ try {
201
+ const response = await client.mutate({
202
+ mutation: gql `
203
+ mutation ($id: String!, $attach: [Upload!], $remove: [String!]) {
204
+ changeInvoiceFiles(id: $id, attach: $attach, remove: $remove) {
205
+ id
206
+ }
207
+ }
208
+ `,
209
+ variables: { id: this.invoiceId, attach, remove },
210
+ context: attach.length ? uploadContext(fraction => (this.progress = fraction)) : {}
211
+ });
212
+ if (response.errors?.length)
213
+ throw new Error(response.errors.map(one => one.message).join(' · '));
214
+ await this.load();
215
+ }
216
+ catch (error) {
217
+ notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) });
218
+ }
219
+ finally {
220
+ this.busy = false;
221
+ this.progress = undefined;
222
+ }
223
+ }
224
+ };
225
+ __decorate([
226
+ property(),
227
+ __metadata("design:type", String)
228
+ ], InvoiceFiles.prototype, "invoiceId", void 0);
229
+ __decorate([
230
+ state(),
231
+ __metadata("design:type", Array)
232
+ ], InvoiceFiles.prototype, "files", void 0);
233
+ __decorate([
234
+ state(),
235
+ __metadata("design:type", Object)
236
+ ], InvoiceFiles.prototype, "mayChange", void 0);
237
+ __decorate([
238
+ state(),
239
+ __metadata("design:type", Object)
240
+ ], InvoiceFiles.prototype, "busy", void 0);
241
+ __decorate([
242
+ state(),
243
+ __metadata("design:type", Number)
244
+ ], InvoiceFiles.prototype, "progress", void 0);
245
+ InvoiceFiles = __decorate([
246
+ customElement('invoice-files')
247
+ ], InvoiceFiles);
248
+ export { InvoiceFiles };
249
+ //# sourceMappingURL=invoice-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-files.js","sourceRoot":"","sources":["../client/invoice-files.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n/* The tile is not a subpath of its own; the package root registers it. */\nimport '@operato/attachment'\nimport '@operato/input/ox-input-file.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client, uploadContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client/index.js'\n\nimport { invoice360Query } from './invoice-attach'\n\ntype InvoiceFile = { id: string; name: string; mimetype?: string; fullpath: string }\n\n/** The phone's camera is offered where a finger taps - on a desktop the file button is the only way in. */\nconst touch = () => window.matchMedia('(pointer: coarse)').matches\n\n/**\n * The files an invoice keeps - the signed copy, the order, the delivery note. Each opens in its own tab. Whoever may\n * change invoices attaches (a photo taken on the spot, or any file) and takes one off; it is saved at once.\n */\n@customElement('invoice-files')\nexport class InvoiceFiles extends localize(i18next)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n color: var(--ui-ink);\n }\n .tiles {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-medium, 8px);\n padding-top: 6px;\n }\n .tile {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 72px;\n gap: var(--spacing-tiny, 2px);\n }\n .tile a {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n border-radius: var(--ui-r-small, 6px);\n border: 1px solid var(--ui-line);\n background: var(--ui-panel-2);\n overflow: hidden;\n }\n .tile ox-attachment-tile,\n .tile img {\n width: 100%;\n height: 100%;\n }\n .tile img {\n object-fit: cover;\n }\n .name {\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .tile button {\n position: absolute;\n top: -6px;\n right: -6px;\n display: grid;\n place-items: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 1px solid var(--ui-line);\n border-radius: 50%;\n background: var(--ui-panel);\n color: var(--ui-muted);\n cursor: pointer;\n }\n .tile button md-icon {\n --md-icon-size: 14px;\n }\n .pickers {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n }\n .none {\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted);\n }\n `\n ]\n\n @property() invoiceId?: string\n @state() files: InvoiceFile[] = []\n @state() mayChange = false\n @state() busy = false\n @state() progress?: number\n\n connectedCallback() {\n super.connectedCallback()\n hasPrivilege({ privilege: 'mutation', category: 'invoice', domainOwnerGranted: true }).then(may => (this.mayChange = !!may))\n }\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('invoiceId')) this.load()\n }\n\n render() {\n const editable = this.mayChange && !this.busy\n return html`\n ${this.files.length\n ? html`<div class=\"tiles\">${this.files.map(one => this.tile(one, editable))}</div>`\n : html`<span class=\"none\">${i18next.t('text.invoice-files-none')}</span>`}\n ${this.mayChange\n ? html`<div class=\"pickers\">\n ${touch()\n ? html`<ox-input-file\n quiet\n hide-filelist\n accept=\"image/*\"\n capture=\"environment\"\n icon=\"photo_camera\"\n .label=${i18next.t('button.invoice-file-photo')}\n ?disabled=${!editable}\n .progress=${this.progress}\n @change=${(e: CustomEvent) => this.pick(e)}\n ></ox-input-file>`\n : nothing}\n <ox-input-file\n quiet\n hide-filelist\n multiple\n icon=\"attach_file\"\n .label=${i18next.t('button.invoice-file-attach')}\n ?disabled=${!editable}\n .progress=${touch() ? undefined : this.progress}\n @change=${(e: CustomEvent) => this.pick(e)}\n ></ox-input-file>\n </div>`\n : nothing}\n `\n }\n\n private tile(one: InvoiceFile, editable: boolean) {\n return html`<span class=\"tile\">\n <a href=${one.fullpath} target=\"_blank\" rel=\"noopener\" title=${one.name}\n >${one.mimetype?.startsWith('image/')\n ? html`<img src=${one.fullpath} alt=${one.name} />`\n : html`<ox-attachment-tile .src=${one.fullpath} .path=${one.name} .mimetype=${one.mimetype ?? ''}></ox-attachment-tile>`}</a\n >\n <span class=\"name\">${one.name}</span>\n ${this.mayChange\n ? html`<button type=\"button\" ?disabled=${!editable} title=${i18next.t('button.invoice-file-remove')} aria-label=${i18next.t('button.invoice-file-remove')} @click=${() => this.takeOff(one)}>\n <md-icon>close</md-icon>\n </button>`\n : nothing}\n </span>`\n }\n\n private async load() {\n const id = this.invoiceId\n this.files = []\n if (!id) return\n try {\n const response = await client.query({\n query: gql(`query ($id: String!) { invoice: ${invoice360Query()}(id: $id) { id files { id name mimetype fullpath } } }`),\n variables: { id },\n fetchPolicy: 'no-cache'\n })\n if (this.invoiceId === id) this.files = response.data?.invoice?.files ?? []\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) })\n }\n }\n\n private async pick(e: CustomEvent) {\n const picked = [...((e.detail as File[]) ?? [])]\n /* The picker's own reset says `change` again, with nothing - that one is let go. */\n if (!picked.length) return\n ;(e.target as any).reset?.()\n await this.change({ attach: picked })\n }\n\n private async takeOff(one: InvoiceFile) {\n const sure = await OxPrompt.open({\n title: i18next.t('button.invoice-file-remove'),\n text: i18next.t('text.invoice-file-remove-confirm', { name: one.name }),\n confirmButton: { text: i18next.t('button.invoice-file-remove'), destructive: true } as any,\n cancelButton: { text: i18next.t('button.cancel') }\n } as any)\n if (sure) await this.change({ remove: [one.id] })\n }\n\n private async change({ attach = [], remove = [] }: { attach?: File[]; remove?: string[] }) {\n if (!this.invoiceId || this.busy) return\n this.busy = true\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $attach: [Upload!], $remove: [String!]) {\n changeInvoiceFiles(id: $id, attach: $attach, remove: $remove) {\n id\n }\n }\n `,\n variables: { id: this.invoiceId, attach, remove },\n context: attach.length ? uploadContext(fraction => (this.progress = fraction)) : {}\n })\n if (response.errors?.length) throw new Error(response.errors.map(one => one.message).join(' · '))\n await this.load()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.message ?? String(error) })\n } finally {\n this.busy = false\n this.progress = undefined\n }\n }\n}\n"]}