@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
@@ -11,17 +11,21 @@ import { notify } from '@operato/layout';
11
11
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
12
12
  import { navigate, PageView } from '@operato/shell';
13
13
  import { ButtonStyles, CommonGristStyles, DocumentPageStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles';
14
+ import { daysAfter, nextInvoiceNumber } from '@things-factory/invoice/dist-shared/document-start.js';
15
+ import { documentAmount } from '../document/document-amount';
14
16
  import { DocumentFormStyles, renderDocumentHead, renderDocumentTotals } from '../document/document-form';
15
- import { documentLinesConfig, linesOf } from '../document/document-lines';
16
- import { documentHtml, printDocument } from '../document/document-print';
17
+ import { documentLinesConfig, linesOf, onLineChange, reckonLines } from '../document/document-lines';
18
+ import { partyOfName } from '../document/document-party';
19
+ import { documentDefaults, headFromDefaults, OUR_SIDE_FIELDS, oursFromLast } from '../document/document-defaults';
20
+ import { openDocumentPreview } from '../document/document-preview';
17
21
  import { invoiceStatusWord } from '../entity360/invoice-360';
18
22
  import { invoiceLinkSection } from '../invoice-attach';
19
23
  import '../invoice-files';
20
24
  const FIELDS = `
21
25
  id number status direction currency language counterpartyId counterparty { id name } supplierName supplierAddress supplierRegistrationNumber buyerName buyerAddress
22
26
  buyerRegistrationNumber issueDate dueDate purchaseOrder paymentTerms bankDetails notes sourceReference taxBasis voidReason
23
- supplyAmount taxAmount totalAmount taxRounding currencyDigits
24
- items { position description quantity unitPrice discount taxKind taxRate supplyAmount receivedTaxAmount }
27
+ discountAmount lineSupplyAmount supplyAmount taxAmount totalAmount taxRounding currencyDigits
28
+ items { position description quantity unit unitPrice discount taxKind taxRate supplyAmount receivedTaxAmount }
25
29
  taxGroups { taxKind taxRate supplyAmount taxAmount }
26
30
  `;
27
31
  const HEAD = [
@@ -43,9 +47,12 @@ const HEAD = [
43
47
  'bankDetails',
44
48
  'notes',
45
49
  'sourceReference',
46
- 'taxBasis'
50
+ 'taxBasis',
51
+ 'discountAmount'
47
52
  ];
48
53
  const today = () => new Date().toLocaleDateString('en-CA');
54
+ /** Days an invoice is due after its issue unless the company says otherwise (ADR-0170 decision 11). */
55
+ const PAYMENT_DAYS = 30;
49
56
  /**
50
57
  * 청구서 편집 — the head, the lines, the totals by tax kind and rate, and what an application links it to.
51
58
  *
@@ -61,9 +68,12 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
61
68
  this.error = null;
62
69
  this.busy = false;
63
70
  this.voidReason = '';
64
- /** The counterparty search: what was typed, and what it found when more than one. */
65
- this.partySearch = '';
66
- this.partiesFound = [];
71
+ /** The totals of the lines as typed - reckoned as the invoice will keep them, shown before it is saved. */
72
+ this.amounts = {};
73
+ /** The company's words a new invoice starts with, and the payment terms it offers (ADR-0170). */
74
+ this.defaults = null;
75
+ /** The number a new invoice was offered - it follows the direction while the person leaves it. */
76
+ this.suggestedNumber = '';
67
77
  }
68
78
  static { this.styles = [
69
79
  FormFieldStyles,
@@ -76,16 +86,11 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
76
86
  ox-grist {
77
87
  height: calc(var(--form-element-height-medium) * 10);
78
88
  }
79
- .party {
80
- display: flex;
81
- gap: var(--spacing-small);
82
- align-items: center;
83
- }
84
- .party input {
85
- flex: 1;
86
- }
87
89
  `
88
90
  ]; }
91
+ get received() {
92
+ return this.form.taxBasis === 'RECEIVED';
93
+ }
89
94
  get editable() {
90
95
  return !this.invoice || this.invoice.status === 'DRAFT';
91
96
  }
@@ -96,7 +101,7 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
96
101
  actions.push({ title: i18next.t('button.invoice-issue'), icon: 'send', emphasis: { raised: true }, action: () => this.issue() });
97
102
  }
98
103
  if (this.invoice)
99
- actions.push({ title: i18next.t('button.invoice-print'), icon: 'print', action: () => this.print() });
104
+ actions.push({ title: i18next.t('button.invoice-preview'), icon: 'preview', action: () => this.preview() });
100
105
  return {
101
106
  title: this.invoice ? `${i18next.t('title.invoice')} ${this.invoice.number}` : i18next.t('title.invoice-new'),
102
107
  ...(this.invoice ? { description: invoiceStatusWord(this.invoice.status) } : {}),
@@ -110,11 +115,16 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
110
115
  if (!this.active || !(changes.active || changes.params))
111
116
  return;
112
117
  const id = lifecycle?.params?.id;
118
+ if (!this.defaults)
119
+ this.defaults = await documentDefaults();
113
120
  if (!id) {
114
121
  this.invoice = null;
115
122
  this.form = blank();
123
+ this.amounts = {};
116
124
  this.configure();
117
125
  this.updateContext();
126
+ /* Started, not waited for - the page's title stands meanwhile. */
127
+ this.prefill();
118
128
  return;
119
129
  }
120
130
  if (id !== this.invoice?.id)
@@ -126,18 +136,26 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
126
136
  <div class="page">
127
137
  <section class="page-section">
128
138
  <header class="page-section-header"><h3>${i18next.t('text.invoice-section-head')}</h3></header>
129
- ${renderDocumentHead(this.form, this.editable && !this.busy, patch => (this.form = { ...this.form, ...patch }), this.renderTerms(), 'field.invoice-number')}
139
+ ${renderDocumentHead(this.form, this.editable && !this.busy, patch => this.edit(patch), this.renderTerms(), 'field.invoice-number', {
140
+ terms: this.defaults?.paymentTerms,
141
+ party: this.renderParty(this.editable && !this.busy)
142
+ })}
130
143
  </section>
131
144
  <section class="page-section">
132
145
  <header class="page-section-header"><h3>${i18next.t('text.invoice-section-lines')}</h3></header>
133
- <ox-grist mode="GRID" .config=${this.gristConfig} .fetchHandler=${() => ({ total: this.form.items.length, records: this.form.items })}></ox-grist>
146
+ <ox-grist
147
+ mode="GRID"
148
+ .config=${this.gristConfig}
149
+ .fetchHandler=${() => ({ total: this.form.items.length, records: this.form.items })}
150
+ @record-change=${(e) => (this.amounts = onLineChange(this.grist, e, this.form.currency, this.received, this.form.discountAmount))}
151
+ ></ox-grist>
152
+ </section>
153
+ <section class="page-section">
154
+ <header class="page-section-header"><h3>${i18next.t('text.invoice-section-totals')}</h3></header>
155
+ ${renderDocumentTotals({ ...this.amounts, currency: this.form.currency }, { value: this.form.discountAmount, editable: this.editable && !this.busy, change: value => this.discount(value) })}
134
156
  </section>
135
157
  ${this.invoice
136
158
  ? html `<section class="page-section">
137
- <header class="page-section-header"><h3>${i18next.t('text.invoice-section-totals')}</h3></header>
138
- ${renderDocumentTotals(this.invoice)}
139
- </section>
140
- <section class="page-section">
141
159
  <header class="page-section-header"><h3>${i18next.t('text.invoice-section-files')}</h3></header>
142
160
  <invoice-files .invoiceId=${this.invoice.id}></invoice-files>
143
161
  </section>`
@@ -154,7 +172,7 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
154
172
  }
155
173
  renderTerms() {
156
174
  const editable = this.editable && !this.busy;
157
- return html `${this.renderParty(editable)}<label
175
+ return html `<label
158
176
  >${i18next.t('field.invoice-due-date')}
159
177
  <input type="date" .value=${this.form.dueDate ?? ''} ?disabled=${!editable} @input=${(e) => (this.form = { ...this.form, dueDate: e.target.value })}
160
178
  /></label>
@@ -170,31 +188,66 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
170
188
  }
171
189
  /**
172
190
  * Who the other party is among the domain's counterparties - the buyer of a sale, the supplier of a purchase. Found by
173
- * name or business number; choosing one writes its name and number onto the printed side, which stays editable.
191
+ * name or business number; choosing one writes its name and number onto the printed side, which stays editable. A
192
+ * saved invoice whose party is none of them says so under the picker (ADR-0170 decision 11).
174
193
  */
175
194
  renderParty(editable) {
176
- const chosen = this.form.counterparty;
195
+ const name = this.form.direction === 'PURCHASE' ? this.form.supplierName : this.form.buyerName;
177
196
  return html `<label
178
197
  >${i18next.t('field.invoice-counterparty')}
179
- <span class="party">
180
- <input
181
- .value=${this.partySearch}
182
- placeholder=${chosen?.name ?? i18next.t('text.invoice-counterparty-search')}
183
- ?disabled=${!editable}
184
- @input=${(e) => (this.partySearch = e.target.value)}
185
- @keydown=${(e) => e.key === 'Enter' && this.findParties()}
186
- />
187
- <button ?disabled=${!editable} @click=${() => this.findParties()}>${i18next.t('button.invoice-counterparty-find')}</button>
188
- ${chosen ? html `<button ?disabled=${!editable} @click=${() => this.chooseParty(null)}>${i18next.t('button.invoice-counterparty-clear')}</button>` : nothing}
189
- </span>
190
- ${this.partiesFound.length
191
- ? html `<select ?disabled=${!editable} @change=${(e) => this.chooseParty(this.partiesFound.find(one => one.id === e.target.value) ?? null)}>
192
- <option value="">${chosen?.name ?? ''}</option>
193
- ${this.partiesFound.map(one => html `<option value=${one.id}>${one.name}${one.businessNumber ? ` (${one.businessNumber})` : ''}</option>`)}
194
- </select>`
195
- : nothing}
198
+ <document-party-select .party=${this.form.counterparty ?? null} ?disabled=${!editable} @party=${(e) => this.chooseParty(e.detail)}></document-party-select>
199
+ ${this.invoice && !this.form.counterpartyId && String(name ?? '').trim() ? html `<span class="quiet">${i18next.t('text.invoice-counterparty-unlinked')}</span>` : nothing}
196
200
  </label>`;
197
201
  }
202
+ /**
203
+ * A party typed by name and not picked is the counterparty of that name when exactly one is - case and spaces aside;
204
+ * otherwise it stays unlinked and the editor says so.
205
+ */
206
+ async linkByName() {
207
+ if (this.form.counterpartyId)
208
+ return;
209
+ const party = await partyOfName(this.form.direction === 'PURCHASE' ? this.form.supplierName : this.form.buyerName);
210
+ if (party)
211
+ this.form = { ...this.form, counterpartyId: party.id, counterparty: party };
212
+ }
213
+ /**
214
+ * What a new invoice starts with, so a person writes only what is new: the number after the last sales invoice's, the
215
+ * due date the company's days after the issue, the company's own words (ADR-0170 decision 6), and what they leave
216
+ * blank of our side as the last sales invoice wrote it.
217
+ */
218
+ async prefill() {
219
+ let last;
220
+ try {
221
+ const response = await client.query({
222
+ query: gql `
223
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
224
+ invoices(filters: $filters, pagination: $pagination, sortings: $sortings) { items { number ${OUR_SIDE_FIELDS.join(' ')} } }
225
+ }
226
+ `,
227
+ variables: { filters: [{ name: 'direction', operator: 'eq', value: 'SALES' }], pagination: { page: 1, limit: 1 }, sortings: [{ name: 'createdAt', desc: true }] },
228
+ fetchPolicy: 'no-cache'
229
+ });
230
+ last = response.data?.invoices?.items?.[0];
231
+ }
232
+ catch {
233
+ /* Nothing to follow - the first number of the year, and our side from the company's words alone. */
234
+ }
235
+ /* An invoice opened meanwhile is not a new one. */
236
+ if (this.invoice)
237
+ return;
238
+ this.suggestedNumber = nextInvoiceNumber(last?.number, Number(this.form.issueDate.slice(0, 4)));
239
+ this.form = { ...this.form, ...headFromDefaults(this.form, this.defaults) };
240
+ this.form = {
241
+ ...this.form,
242
+ ...oursFromLast(this.form, last),
243
+ number: this.form.number || this.suggestedNumber,
244
+ dueDate: this.form.dueDate || daysAfter(this.form.issueDate, this.paymentDays)
245
+ };
246
+ }
247
+ /** Days an invoice is due - the company's, or thirty. */
248
+ get paymentDays() {
249
+ return this.defaults?.paymentDays ?? PAYMENT_DAYS;
250
+ }
198
251
  chooseParty(party) {
199
252
  const side = this.form.direction === 'PURCHASE' ? 'supplier' : 'buyer';
200
253
  this.form = {
@@ -203,30 +256,6 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
203
256
  counterparty: party,
204
257
  ...(party ? { [`${side}Name`]: party.name, [`${side}RegistrationNumber`]: party.businessNumber ?? this.form[`${side}RegistrationNumber`] } : {})
205
258
  };
206
- this.partySearch = '';
207
- this.partiesFound = [];
208
- }
209
- async findParties() {
210
- await this.run(async () => {
211
- const response = await client.query({
212
- query: gql `
213
- query ($search: String, $pagination: Pagination) {
214
- counterparties: counterpartyChoices(search: $search, pagination: $pagination) {
215
- items { id name businessNumber }
216
- }
217
- }
218
- `,
219
- variables: { search: this.partySearch.trim() || undefined, pagination: { page: 1, limit: 30 } },
220
- fetchPolicy: 'no-cache'
221
- });
222
- if (response.errors?.length)
223
- throw response;
224
- const found = response.data.counterparties.items ?? [];
225
- if (found.length === 1)
226
- this.chooseParty(found[0]);
227
- else
228
- this.partiesFound = found;
229
- });
230
259
  }
231
260
  renderVoid() {
232
261
  return html `<section class="page-section">
@@ -247,6 +276,33 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
247
276
  changeTaxBasis(taxBasis) {
248
277
  this.form = { ...this.form, items: linesOf(this.grist), taxBasis };
249
278
  this.configure();
279
+ this.updateComplete.then(() => (this.amounts = reckonLines(this.grist, this.form.currency, this.received, this.form.discountAmount)));
280
+ }
281
+ /** The discount of the whole invoice as typed, and the totals after it. */
282
+ discount(value) {
283
+ this.form = { ...this.form, discountAmount: value };
284
+ this.amounts = reckonLines(this.grist, this.form.currency, this.received, value);
285
+ }
286
+ /**
287
+ * A change of the head; a new currency reckons the lines in its minor unit; the due date follows a moved issue date
288
+ * while it is the one offered. A new invoice's offered number is ours - a purchase carries its sender's, so it goes
289
+ * while the person leaves it, and comes back with a sale.
290
+ */
291
+ edit(patch) {
292
+ const currency = this.form.currency;
293
+ const following = patch.issueDate && this.form.dueDate === daysAfter(this.form.issueDate, this.paymentDays);
294
+ const number = this.invoice || !patch.direction
295
+ ? {}
296
+ : patch.direction === 'PURCHASE' && this.form.number === this.suggestedNumber
297
+ ? { number: '' }
298
+ : patch.direction === 'SALES' && !String(this.form.number ?? '').trim()
299
+ ? { number: this.suggestedNumber }
300
+ : {};
301
+ this.form = { ...this.form, ...patch, ...number, ...(following ? { dueDate: daysAfter(patch.issueDate, this.paymentDays) } : {}) };
302
+ if (patch.currency && patch.currency !== currency) {
303
+ this.configure();
304
+ this.amounts = reckonLines(this.grist, this.form.currency, this.received, this.form.discountAmount);
305
+ }
250
306
  }
251
307
  configure() {
252
308
  this.gristConfig = documentLinesConfig({ editable: this.editable, received: this.form.taxBasis === 'RECEIVED', currency: this.form.currency || 'KRW' });
@@ -273,6 +329,7 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
273
329
  show(invoice) {
274
330
  this.invoice = invoice;
275
331
  this.form = { ...invoice, items: invoice.items.map((item) => ({ ...item })) };
332
+ this.amounts = invoice;
276
333
  this.voidReason = '';
277
334
  this.configure();
278
335
  this.grist?.fetch();
@@ -297,10 +354,14 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
297
354
  const input = Object.fromEntries(HEAD.map(key => [key, this.form[key] === '' ? null : this.form[key] ?? null]));
298
355
  input.taxBasis = input.direction === 'PURCHASE' ? input.taxBasis || 'CALCULATED' : 'CALCULATED';
299
356
  input.items = linesOf(this.grist);
357
+ input.discountAmount = input.discountAmount === null ? 0 : Number(input.discountAmount);
300
358
  return input;
301
359
  }
302
360
  async save() {
303
361
  const id = this.invoice?.id;
362
+ if (this.busy)
363
+ return undefined;
364
+ await this.linkByName();
304
365
  const saved = await this.run(async () => {
305
366
  const response = await client.mutate({
306
367
  mutation: gql(id
@@ -320,8 +381,21 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
320
381
  navigate(`invoice-editor?id=${saved.id}`);
321
382
  return saved;
322
383
  }
323
- /** Save what is on screen, then issue it — its amounts are fixed from here on. */
384
+ /** Asked first - to whom and how much, fixed after - then save what is on screen and issue it. */
324
385
  async issue() {
386
+ const purchase = this.form.direction === 'PURCHASE';
387
+ const words = {
388
+ party: (purchase ? this.form.supplierName : this.form.buyerName) || '—',
389
+ total: documentAmount(this.amounts.totalAmount ?? 0, this.form.currency || 'KRW')
390
+ };
391
+ const confirmed = await OxPrompt.open({
392
+ title: i18next.t('button.invoice-issue'),
393
+ text: purchase ? i18next.t('text.invoice-issue-confirm-purchase', words) : i18next.t('text.invoice-issue-confirm', words),
394
+ confirmButton: { text: i18next.t('button.invoice-issue') },
395
+ cancelButton: { text: i18next.t('button.cancel') }
396
+ });
397
+ if (!confirmed)
398
+ return;
325
399
  const saved = await this.save();
326
400
  if (!saved)
327
401
  return;
@@ -356,13 +430,26 @@ let InvoiceEditor = class InvoiceEditor extends localize(i18next)(PageView) {
356
430
  }
357
431
  return changed;
358
432
  }
359
- print() {
360
- const invoice = this.invoice;
361
- const lng = invoice.language === 'en' ? 'en' : 'ko';
362
- printDocument(documentHtml(invoice, {
363
- title: i18next.t('title.invoice-print', { lng }),
364
- terms: [{ label: i18next.t('field.invoice-due-date', { lng }), value: invoice.dueDate }]
365
- }));
433
+ /**
434
+ * The saved invoice as it goes out (ADR-0170 decision 10) - the server's page in the language chosen there, the
435
+ * invoice's own first, to download as PDF or print. What is on screen and not saved is not in it.
436
+ */
437
+ preview() {
438
+ const { id, number, language } = this.invoice;
439
+ openDocumentPreview(async (format, lng) => {
440
+ const response = await client.query({
441
+ query: gql `
442
+ query ($id: String!, $format: DocumentFileFormat!, $language: String) {
443
+ invoiceDocumentFile(id: $id, format: $format, language: $language) { fileName mimeType content }
444
+ }
445
+ `,
446
+ variables: { id, format, language: lng },
447
+ fetchPolicy: 'no-cache'
448
+ });
449
+ if (!response.data?.invoiceDocumentFile)
450
+ throw response.errors?.[0] ?? new Error(i18next.t('text.invoice-not-found'));
451
+ return response.data.invoiceDocumentFile;
452
+ }, language, `${i18next.t('title.invoice-preview')} · ${number}`);
366
453
  }
367
454
  };
368
455
  __decorate([
@@ -392,15 +479,19 @@ __decorate([
392
479
  __decorate([
393
480
  state(),
394
481
  __metadata("design:type", Object)
395
- ], InvoiceEditor.prototype, "partySearch", void 0);
482
+ ], InvoiceEditor.prototype, "gristConfig", void 0);
483
+ __decorate([
484
+ state(),
485
+ __metadata("design:type", Object)
486
+ ], InvoiceEditor.prototype, "amounts", void 0);
396
487
  __decorate([
397
488
  state(),
398
- __metadata("design:type", Array)
399
- ], InvoiceEditor.prototype, "partiesFound", void 0);
489
+ __metadata("design:type", Object)
490
+ ], InvoiceEditor.prototype, "defaults", void 0);
400
491
  __decorate([
401
492
  state(),
402
493
  __metadata("design:type", Object)
403
- ], InvoiceEditor.prototype, "gristConfig", void 0);
494
+ ], InvoiceEditor.prototype, "suggestedNumber", void 0);
404
495
  __decorate([
405
496
  query('ox-grist'),
406
497
  __metadata("design:type", DataGrist)
@@ -420,6 +511,7 @@ function blank() {
420
511
  issueDate: today(),
421
512
  dueDate: '',
422
513
  taxBasis: 'CALCULATED',
514
+ discountAmount: 0,
423
515
  items: []
424
516
  };
425
517
  }
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-editor.js","sourceRoot":"","sources":["../../client/pages/invoice-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { navigate, PageView } from '@operato/shell'\nimport { ButtonStyles, CommonGristStyles, DocumentPageStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { DocumentFormStyles, renderDocumentHead, renderDocumentTotals, type DocumentValue } from '../document/document-form'\nimport { documentLinesConfig, linesOf } from '../document/document-lines'\nimport { documentHtml, printDocument } from '../document/document-print'\nimport { invoiceStatusWord } from '../entity360/invoice-360'\nimport { invoiceLinkSection } from '../invoice-attach'\nimport '../invoice-files'\n\nconst FIELDS = `\n id number status direction currency language counterpartyId counterparty { id name } supplierName supplierAddress supplierRegistrationNumber buyerName buyerAddress\n buyerRegistrationNumber issueDate dueDate purchaseOrder paymentTerms bankDetails notes sourceReference taxBasis voidReason\n supplyAmount taxAmount totalAmount taxRounding currencyDigits\n items { position description quantity unitPrice discount taxKind taxRate supplyAmount receivedTaxAmount }\n taxGroups { taxKind taxRate supplyAmount taxAmount }\n`\n\nconst HEAD = [\n 'number',\n 'counterpartyId',\n 'direction',\n 'currency',\n 'language',\n 'supplierName',\n 'supplierAddress',\n 'supplierRegistrationNumber',\n 'buyerName',\n 'buyerAddress',\n 'buyerRegistrationNumber',\n 'issueDate',\n 'dueDate',\n 'purchaseOrder',\n 'paymentTerms',\n 'bankDetails',\n 'notes',\n 'sourceReference',\n 'taxBasis'\n]\n\nconst today = () => new Date().toLocaleDateString('en-CA')\n\ntype Party = { id: string; name: string; businessNumber?: string | null }\n\n/**\n * 청구서 편집 — the head, the lines, the totals by tax kind and rate, and what an application links it to.\n *\n * A draft is written and saved; 「발행」 saves what is on screen and issues it, after which its amounts never change.\n * An issued invoice is voided with a reason, in its own section. While a request runs only the inputs wait.\n */\n@customElement('invoice-editor')\nexport class InvoiceEditor extends localize(i18next)(PageView) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n DocumentPageStyles,\n CommonGristStyles,\n ScrollbarStyles,\n DocumentFormStyles,\n css`\n ox-grist {\n height: calc(var(--form-element-height-medium) * 10);\n }\n .party {\n display: flex;\n gap: var(--spacing-small);\n align-items: center;\n }\n .party input {\n flex: 1;\n }\n `\n ]\n\n @state() invoice: DocumentValue | null = null\n @state() form: DocumentValue = blank()\n @state() loading = false\n @state() error: unknown = null\n @state() busy = false\n @state() voidReason = ''\n /** The counterparty search: what was typed, and what it found when more than one. */\n @state() partySearch = ''\n @state() partiesFound: Party[] = []\n @state() gristConfig: any\n\n @query('ox-grist') private grist?: DataGrist\n\n get editable() {\n return !this.invoice || this.invoice.status === 'DRAFT'\n }\n\n get context() {\n const actions: any[] = []\n if (this.editable) {\n actions.push({ title: i18next.t('button.save'), icon: 'save', action: () => this.save() })\n actions.push({ title: i18next.t('button.invoice-issue'), icon: 'send', emphasis: { raised: true }, action: () => this.issue() })\n }\n if (this.invoice) actions.push({ title: i18next.t('button.invoice-print'), icon: 'print', action: () => this.print() })\n return {\n title: this.invoice ? `${i18next.t('title.invoice')} ${this.invoice.number}` : i18next.t('title.invoice-new'),\n ...(this.invoice ? { description: invoiceStatusWord(this.invoice.status) } : {}),\n frame: 'document',\n /* Opened from the invoice list; the menu marks it. */\n menu: 'invoice-list',\n actions: this.error ? [] : actions\n }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active || !(changes.active || changes.params)) return\n const id = lifecycle?.params?.id\n if (!id) {\n this.invoice = null\n this.form = blank()\n this.configure()\n this.updateContext()\n return\n }\n if (id !== this.invoice?.id) await this.load(id)\n }\n\n render() {\n const links = this.invoice ? invoiceLinkSection() : undefined\n return html`<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.invoice && this.load(this.invoice.id)}>\n <div class=\"page\">\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-head')}</h3></header>\n ${renderDocumentHead(this.form, this.editable && !this.busy, patch => (this.form = { ...this.form, ...patch }), this.renderTerms(), 'field.invoice-number')}\n </section>\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-lines')}</h3></header>\n <ox-grist mode=\"GRID\" .config=${this.gristConfig} .fetchHandler=${() => ({ total: this.form.items.length, records: this.form.items })}></ox-grist>\n </section>\n ${this.invoice\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-totals')}</h3></header>\n ${renderDocumentTotals(this.invoice)}\n </section>\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-files')}</h3></header>\n <invoice-files .invoiceId=${this.invoice.id}></invoice-files>\n </section>`\n : nothing}\n ${links && this.invoice\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${links.heading()}</h3></header>\n ${links.render(this.invoice as any)}\n </section>`\n : nothing}\n ${this.invoice?.status === 'ISSUED' ? this.renderVoid() : nothing}\n </div>\n </ox-page-state>`\n }\n\n private renderTerms() {\n const editable = this.editable && !this.busy\n return html`${this.renderParty(editable)}<label\n >${i18next.t('field.invoice-due-date')}\n <input type=\"date\" .value=${this.form.dueDate ?? ''} ?disabled=${!editable} @input=${(e: any) => (this.form = { ...this.form, dueDate: e.target.value })}\n /></label>\n ${this.form.direction === 'PURCHASE'\n ? html`<label\n >${i18next.t('field.invoice-tax-basis')}\n <select ?disabled=${!editable} @change=${(e: any) => this.changeTaxBasis(e.target.value)}>\n <option value=\"CALCULATED\" ?selected=${this.form.taxBasis !== 'RECEIVED'}>${i18next.t('text.invoice-tax-basis-calculated')}</option>\n <option value=\"RECEIVED\" ?selected=${this.form.taxBasis === 'RECEIVED'}>${i18next.t('text.invoice-tax-basis-received')}</option>\n </select></label\n >`\n : nothing}`\n }\n\n /**\n * Who the other party is among the domain's counterparties - the buyer of a sale, the supplier of a purchase. Found by\n * name or business number; choosing one writes its name and number onto the printed side, which stays editable.\n */\n private renderParty(editable: boolean) {\n const chosen = this.form.counterparty as Party | null | undefined\n return html`<label\n >${i18next.t('field.invoice-counterparty')}\n <span class=\"party\">\n <input\n .value=${this.partySearch}\n placeholder=${chosen?.name ?? i18next.t('text.invoice-counterparty-search')}\n ?disabled=${!editable}\n @input=${(e: any) => (this.partySearch = e.target.value)}\n @keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.findParties()}\n />\n <button ?disabled=${!editable} @click=${() => this.findParties()}>${i18next.t('button.invoice-counterparty-find')}</button>\n ${chosen ? html`<button ?disabled=${!editable} @click=${() => this.chooseParty(null)}>${i18next.t('button.invoice-counterparty-clear')}</button>` : nothing}\n </span>\n ${this.partiesFound.length\n ? html`<select ?disabled=${!editable} @change=${(e: any) => this.chooseParty(this.partiesFound.find(one => one.id === e.target.value) ?? null)}>\n <option value=\"\">${chosen?.name ?? ''}</option>\n ${this.partiesFound.map(one => html`<option value=${one.id}>${one.name}${one.businessNumber ? ` (${one.businessNumber})` : ''}</option>`)}\n </select>`\n : nothing}\n </label>`\n }\n\n private chooseParty(party: Party | null) {\n const side = this.form.direction === 'PURCHASE' ? 'supplier' : 'buyer'\n this.form = {\n ...this.form,\n counterpartyId: party?.id ?? null,\n counterparty: party,\n ...(party ? { [`${side}Name`]: party.name, [`${side}RegistrationNumber`]: party.businessNumber ?? this.form[`${side}RegistrationNumber`] } : {})\n }\n this.partySearch = ''\n this.partiesFound = []\n }\n\n private async findParties() {\n await this.run(async () => {\n const response = await client.query({\n query: gql`\n query ($search: String, $pagination: Pagination) {\n counterparties: counterpartyChoices(search: $search, pagination: $pagination) {\n items { id name businessNumber }\n }\n }\n `,\n variables: { search: this.partySearch.trim() || undefined, pagination: { page: 1, limit: 30 } },\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) throw response\n const found: Party[] = response.data.counterparties.items ?? []\n if (found.length === 1) this.chooseParty(found[0])\n else this.partiesFound = found\n })\n }\n\n private renderVoid() {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\">\n <h3>${i18next.t('text.invoice-section-void')}</h3>\n <div class=\"page-section-actions\">\n <button danger ?disabled=${this.busy || !this.voidReason.trim()} @click=${() => this.void()}>${i18next.t('button.invoice-void')}</button>\n </div>\n </header>\n <div class=\"form\">\n <label class=\"wide\"\n >${i18next.t('field.invoice-void-reason')}\n <input .value=${this.voidReason} ?disabled=${this.busy} @input=${(e: any) => (this.voidReason = e.target.value)}\n /></label>\n </div>\n </section>`\n }\n\n private changeTaxBasis(taxBasis: string) {\n this.form = { ...this.form, items: linesOf(this.grist), taxBasis }\n this.configure()\n }\n\n private configure() {\n this.gristConfig = documentLinesConfig({ editable: this.editable, received: this.form.taxBasis === 'RECEIVED', currency: this.form.currency || 'KRW' })\n }\n\n private async load(id: string) {\n this.loading = true\n this.error = null\n try {\n const response = await client.query({ query: gql(`query ($id: String!) { invoice(id: $id) { ${FIELDS} } }`), variables: { id }, fetchPolicy: 'no-cache' })\n if (response.errors?.length) throw response\n if (!response.data.invoice) throw new Error(i18next.t('text.invoice-not-found'))\n this.show(response.data.invoice)\n } catch (error) {\n this.error = error\n } finally {\n this.loading = false\n this.updateContext()\n }\n }\n\n private show(invoice: DocumentValue) {\n this.invoice = invoice\n this.form = { ...invoice, items: invoice.items.map((item: any) => ({ ...item })) }\n this.voidReason = ''\n this.configure()\n this.grist?.fetch()\n }\n\n /** Runs one request at a time; a refusal is said in the server's words. */\n private async run<T>(work: () => Promise<T>): Promise<T | undefined> {\n if (this.busy) return undefined\n this.busy = true\n try {\n return await work()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) })\n return undefined\n } finally {\n this.busy = false\n }\n }\n\n private input() {\n const input: DocumentValue = Object.fromEntries(HEAD.map(key => [key, this.form[key] === '' ? null : this.form[key] ?? null]))\n input.taxBasis = input.direction === 'PURCHASE' ? input.taxBasis || 'CALCULATED' : 'CALCULATED'\n input.items = linesOf(this.grist)\n return input\n }\n\n private async save(): Promise<DocumentValue | undefined> {\n const id = this.invoice?.id\n const saved = await this.run(async () => {\n const response = await client.mutate({\n mutation: gql(\n id\n ? `mutation ($id: String!, $invoice: NewInvoice!) { invoice: updateInvoice(id: $id, invoice: $invoice) { ${FIELDS} } }`\n : `mutation ($invoice: NewInvoice!) { invoice: createInvoice(invoice: $invoice) { ${FIELDS} } }`\n ),\n variables: { ...(id ? { id } : {}), invoice: this.input() }\n })\n if (response.errors?.length) throw response\n return response.data.invoice as DocumentValue\n })\n if (!saved) return undefined\n this.show(saved)\n this.updateContext()\n if (!id) navigate(`invoice-editor?id=${saved.id}`)\n return saved\n }\n\n /** Save what is on screen, then issue it — its amounts are fixed from here on. */\n private async issue() {\n const saved = await this.save()\n if (!saved) return\n const issued = await this.mutate(`mutation ($id: String!) { invoice: issueInvoice(id: $id) { ${FIELDS} } }`, { id: saved.id })\n if (issued) notify({ level: 'info', message: i18next.t('text.invoice-issued') })\n }\n\n private async void() {\n const confirmed = await OxPrompt.open({\n title: i18next.t('button.invoice-void'),\n text: i18next.t('text.invoice-void-confirm'),\n confirmButton: { text: i18next.t('button.invoice-void'), destructive: true },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n if (!confirmed) return\n await this.mutate(`mutation ($id: String!, $reason: String!) { invoice: voidInvoice(id: $id, reason: $reason) { ${FIELDS} } }`, {\n id: this.invoice!.id,\n reason: this.voidReason.trim()\n })\n }\n\n private async mutate(mutation: string, variables: any) {\n const changed = await this.run(async () => {\n const response = await client.mutate({ mutation: gql(mutation), variables })\n if (response.errors?.length) throw response\n return response.data.invoice as DocumentValue\n })\n if (changed) {\n this.show(changed)\n this.updateContext()\n }\n return changed\n }\n\n private print() {\n const invoice = this.invoice!\n const lng = invoice.language === 'en' ? 'en' : 'ko'\n printDocument(\n documentHtml(invoice, {\n title: i18next.t('title.invoice-print', { lng }),\n terms: [{ label: i18next.t('field.invoice-due-date', { lng }), value: invoice.dueDate }]\n })\n )\n }\n}\n\nfunction blank(): DocumentValue {\n return {\n number: '',\n direction: 'SALES',\n currency: 'KRW',\n language: 'ko',\n supplierName: '',\n buyerName: '',\n issueDate: today(),\n dueDate: '',\n taxBasis: 'CALCULATED',\n items: []\n }\n}\n"]}
1
+ {"version":3,"file":"invoice-editor.js","sourceRoot":"","sources":["../../client/pages/invoice-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { navigate, PageView } from '@operato/shell'\nimport { ButtonStyles, CommonGristStyles, DocumentPageStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { daysAfter, nextInvoiceNumber } from '@things-factory/invoice/dist-shared/document-start.js'\n\nimport { documentAmount } from '../document/document-amount'\nimport { DocumentFormStyles, renderDocumentHead, renderDocumentTotals, type DocumentValue } from '../document/document-form'\nimport { documentLinesConfig, linesOf, onLineChange, reckonLines } from '../document/document-lines'\nimport { partyOfName, type DocumentParty } from '../document/document-party'\nimport { documentDefaults, headFromDefaults, OUR_SIDE_FIELDS, oursFromLast, type DocumentDefaults } from '../document/document-defaults'\nimport { openDocumentPreview, type DocumentFileFormat } from '../document/document-preview'\nimport { invoiceStatusWord } from '../entity360/invoice-360'\nimport { invoiceLinkSection } from '../invoice-attach'\nimport '../invoice-files'\n\nconst FIELDS = `\n id number status direction currency language counterpartyId counterparty { id name } supplierName supplierAddress supplierRegistrationNumber buyerName buyerAddress\n buyerRegistrationNumber issueDate dueDate purchaseOrder paymentTerms bankDetails notes sourceReference taxBasis voidReason\n discountAmount lineSupplyAmount supplyAmount taxAmount totalAmount taxRounding currencyDigits\n items { position description quantity unit unitPrice discount taxKind taxRate supplyAmount receivedTaxAmount }\n taxGroups { taxKind taxRate supplyAmount taxAmount }\n`\n\nconst HEAD = [\n 'number',\n 'counterpartyId',\n 'direction',\n 'currency',\n 'language',\n 'supplierName',\n 'supplierAddress',\n 'supplierRegistrationNumber',\n 'buyerName',\n 'buyerAddress',\n 'buyerRegistrationNumber',\n 'issueDate',\n 'dueDate',\n 'purchaseOrder',\n 'paymentTerms',\n 'bankDetails',\n 'notes',\n 'sourceReference',\n 'taxBasis',\n 'discountAmount'\n]\n\nconst today = () => new Date().toLocaleDateString('en-CA')\n\n/** Days an invoice is due after its issue unless the company says otherwise (ADR-0170 decision 11). */\nconst PAYMENT_DAYS = 30\n\ntype Party = DocumentParty\n\n/**\n * 청구서 편집 — the head, the lines, the totals by tax kind and rate, and what an application links it to.\n *\n * A draft is written and saved; 「발행」 saves what is on screen and issues it, after which its amounts never change.\n * An issued invoice is voided with a reason, in its own section. While a request runs only the inputs wait.\n */\n@customElement('invoice-editor')\nexport class InvoiceEditor extends localize(i18next)(PageView) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n DocumentPageStyles,\n CommonGristStyles,\n ScrollbarStyles,\n DocumentFormStyles,\n css`\n ox-grist {\n height: calc(var(--form-element-height-medium) * 10);\n }\n `\n ]\n\n @state() invoice: DocumentValue | null = null\n @state() form: DocumentValue = blank()\n @state() loading = false\n @state() error: unknown = null\n @state() busy = false\n @state() voidReason = ''\n @state() gristConfig: any\n /** The totals of the lines as typed - reckoned as the invoice will keep them, shown before it is saved. */\n @state() amounts: Partial<DocumentValue> = {}\n /** The company's words a new invoice starts with, and the payment terms it offers (ADR-0170). */\n @state() defaults: DocumentDefaults | null = null\n /** The number a new invoice was offered - it follows the direction while the person leaves it. */\n @state() suggestedNumber = ''\n\n @query('ox-grist') private grist?: DataGrist\n\n private get received() {\n return this.form.taxBasis === 'RECEIVED'\n }\n\n get editable() {\n return !this.invoice || this.invoice.status === 'DRAFT'\n }\n\n get context() {\n const actions: any[] = []\n if (this.editable) {\n actions.push({ title: i18next.t('button.save'), icon: 'save', action: () => this.save() })\n actions.push({ title: i18next.t('button.invoice-issue'), icon: 'send', emphasis: { raised: true }, action: () => this.issue() })\n }\n if (this.invoice) actions.push({ title: i18next.t('button.invoice-preview'), icon: 'preview', action: () => this.preview() })\n return {\n title: this.invoice ? `${i18next.t('title.invoice')} ${this.invoice.number}` : i18next.t('title.invoice-new'),\n ...(this.invoice ? { description: invoiceStatusWord(this.invoice.status) } : {}),\n frame: 'document',\n /* Opened from the invoice list; the menu marks it. */\n menu: 'invoice-list',\n actions: this.error ? [] : actions\n }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active || !(changes.active || changes.params)) return\n const id = lifecycle?.params?.id\n if (!this.defaults) this.defaults = await documentDefaults()\n if (!id) {\n this.invoice = null\n this.form = blank()\n this.amounts = {}\n this.configure()\n this.updateContext()\n /* Started, not waited for - the page's title stands meanwhile. */\n this.prefill()\n return\n }\n if (id !== this.invoice?.id) await this.load(id)\n }\n\n render() {\n const links = this.invoice ? invoiceLinkSection() : undefined\n return html`<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.invoice && this.load(this.invoice.id)}>\n <div class=\"page\">\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-head')}</h3></header>\n ${renderDocumentHead(this.form, this.editable && !this.busy, patch => this.edit(patch), this.renderTerms(), 'field.invoice-number', {\n terms: this.defaults?.paymentTerms,\n party: this.renderParty(this.editable && !this.busy)\n })}\n </section>\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-lines')}</h3></header>\n <ox-grist\n mode=\"GRID\"\n .config=${this.gristConfig}\n .fetchHandler=${() => ({ total: this.form.items.length, records: this.form.items })}\n @record-change=${(e: CustomEvent) => (this.amounts = onLineChange(this.grist, e, this.form.currency, this.received, this.form.discountAmount))}\n ></ox-grist>\n </section>\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-totals')}</h3></header>\n ${renderDocumentTotals(\n { ...this.amounts, currency: this.form.currency },\n { value: this.form.discountAmount, editable: this.editable && !this.busy, change: value => this.discount(value) }\n )}\n </section>\n ${this.invoice\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.invoice-section-files')}</h3></header>\n <invoice-files .invoiceId=${this.invoice.id}></invoice-files>\n </section>`\n : nothing}\n ${links && this.invoice\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${links.heading()}</h3></header>\n ${links.render(this.invoice as any)}\n </section>`\n : nothing}\n ${this.invoice?.status === 'ISSUED' ? this.renderVoid() : nothing}\n </div>\n </ox-page-state>`\n }\n\n private renderTerms() {\n const editable = this.editable && !this.busy\n return html`<label\n >${i18next.t('field.invoice-due-date')}\n <input type=\"date\" .value=${this.form.dueDate ?? ''} ?disabled=${!editable} @input=${(e: any) => (this.form = { ...this.form, dueDate: e.target.value })}\n /></label>\n ${this.form.direction === 'PURCHASE'\n ? html`<label\n >${i18next.t('field.invoice-tax-basis')}\n <select ?disabled=${!editable} @change=${(e: any) => this.changeTaxBasis(e.target.value)}>\n <option value=\"CALCULATED\" ?selected=${this.form.taxBasis !== 'RECEIVED'}>${i18next.t('text.invoice-tax-basis-calculated')}</option>\n <option value=\"RECEIVED\" ?selected=${this.form.taxBasis === 'RECEIVED'}>${i18next.t('text.invoice-tax-basis-received')}</option>\n </select></label\n >`\n : nothing}`\n }\n\n /**\n * Who the other party is among the domain's counterparties - the buyer of a sale, the supplier of a purchase. Found by\n * name or business number; choosing one writes its name and number onto the printed side, which stays editable. A\n * saved invoice whose party is none of them says so under the picker (ADR-0170 decision 11).\n */\n private renderParty(editable: boolean) {\n const name = this.form.direction === 'PURCHASE' ? this.form.supplierName : this.form.buyerName\n return html`<label\n >${i18next.t('field.invoice-counterparty')}\n <document-party-select .party=${this.form.counterparty ?? null} ?disabled=${!editable} @party=${(e: CustomEvent) => this.chooseParty(e.detail)}></document-party-select>\n ${this.invoice && !this.form.counterpartyId && String(name ?? '').trim() ? html`<span class=\"quiet\">${i18next.t('text.invoice-counterparty-unlinked')}</span>` : nothing}\n </label>`\n }\n\n /**\n * A party typed by name and not picked is the counterparty of that name when exactly one is - case and spaces aside;\n * otherwise it stays unlinked and the editor says so.\n */\n private async linkByName() {\n if (this.form.counterpartyId) return\n const party = await partyOfName(this.form.direction === 'PURCHASE' ? this.form.supplierName : this.form.buyerName)\n if (party) this.form = { ...this.form, counterpartyId: party.id, counterparty: party }\n }\n\n /**\n * What a new invoice starts with, so a person writes only what is new: the number after the last sales invoice's, the\n * due date the company's days after the issue, the company's own words (ADR-0170 decision 6), and what they leave\n * blank of our side as the last sales invoice wrote it.\n */\n private async prefill() {\n let last: DocumentValue | undefined\n try {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n invoices(filters: $filters, pagination: $pagination, sortings: $sortings) { items { number ${OUR_SIDE_FIELDS.join(' ')} } }\n }\n `,\n variables: { filters: [{ name: 'direction', operator: 'eq', value: 'SALES' }], pagination: { page: 1, limit: 1 }, sortings: [{ name: 'createdAt', desc: true }] },\n fetchPolicy: 'no-cache'\n })\n last = response.data?.invoices?.items?.[0]\n } catch {\n /* Nothing to follow - the first number of the year, and our side from the company's words alone. */\n }\n /* An invoice opened meanwhile is not a new one. */\n if (this.invoice) return\n this.suggestedNumber = nextInvoiceNumber(last?.number, Number(this.form.issueDate.slice(0, 4)))\n this.form = { ...this.form, ...headFromDefaults(this.form, this.defaults) }\n this.form = {\n ...this.form,\n ...oursFromLast(this.form, last),\n number: this.form.number || this.suggestedNumber,\n dueDate: this.form.dueDate || daysAfter(this.form.issueDate, this.paymentDays)\n }\n }\n\n /** Days an invoice is due - the company's, or thirty. */\n private get paymentDays() {\n return this.defaults?.paymentDays ?? PAYMENT_DAYS\n }\n\n private chooseParty(party: Party | null) {\n const side = this.form.direction === 'PURCHASE' ? 'supplier' : 'buyer'\n this.form = {\n ...this.form,\n counterpartyId: party?.id ?? null,\n counterparty: party,\n ...(party ? { [`${side}Name`]: party.name, [`${side}RegistrationNumber`]: party.businessNumber ?? this.form[`${side}RegistrationNumber`] } : {})\n }\n }\n\n private renderVoid() {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\">\n <h3>${i18next.t('text.invoice-section-void')}</h3>\n <div class=\"page-section-actions\">\n <button danger ?disabled=${this.busy || !this.voidReason.trim()} @click=${() => this.void()}>${i18next.t('button.invoice-void')}</button>\n </div>\n </header>\n <div class=\"form\">\n <label class=\"wide\"\n >${i18next.t('field.invoice-void-reason')}\n <input .value=${this.voidReason} ?disabled=${this.busy} @input=${(e: any) => (this.voidReason = e.target.value)}\n /></label>\n </div>\n </section>`\n }\n\n private changeTaxBasis(taxBasis: string) {\n this.form = { ...this.form, items: linesOf(this.grist), taxBasis }\n this.configure()\n this.updateComplete.then(() => (this.amounts = reckonLines(this.grist, this.form.currency, this.received, this.form.discountAmount)))\n }\n\n /** The discount of the whole invoice as typed, and the totals after it. */\n private discount(value: string) {\n this.form = { ...this.form, discountAmount: value }\n this.amounts = reckonLines(this.grist, this.form.currency, this.received, value)\n }\n\n /**\n * A change of the head; a new currency reckons the lines in its minor unit; the due date follows a moved issue date\n * while it is the one offered. A new invoice's offered number is ours - a purchase carries its sender's, so it goes\n * while the person leaves it, and comes back with a sale.\n */\n private edit(patch: DocumentValue) {\n const currency = this.form.currency\n const following = patch.issueDate && this.form.dueDate === daysAfter(this.form.issueDate, this.paymentDays)\n const number =\n this.invoice || !patch.direction\n ? {}\n : patch.direction === 'PURCHASE' && this.form.number === this.suggestedNumber\n ? { number: '' }\n : patch.direction === 'SALES' && !String(this.form.number ?? '').trim()\n ? { number: this.suggestedNumber }\n : {}\n this.form = { ...this.form, ...patch, ...number, ...(following ? { dueDate: daysAfter(patch.issueDate, this.paymentDays) } : {}) }\n if (patch.currency && patch.currency !== currency) {\n this.configure()\n this.amounts = reckonLines(this.grist, this.form.currency, this.received, this.form.discountAmount)\n }\n }\n\n private configure() {\n this.gristConfig = documentLinesConfig({ editable: this.editable, received: this.form.taxBasis === 'RECEIVED', currency: this.form.currency || 'KRW' })\n }\n\n private async load(id: string) {\n this.loading = true\n this.error = null\n try {\n const response = await client.query({ query: gql(`query ($id: String!) { invoice(id: $id) { ${FIELDS} } }`), variables: { id }, fetchPolicy: 'no-cache' })\n if (response.errors?.length) throw response\n if (!response.data.invoice) throw new Error(i18next.t('text.invoice-not-found'))\n this.show(response.data.invoice)\n } catch (error) {\n this.error = error\n } finally {\n this.loading = false\n this.updateContext()\n }\n }\n\n private show(invoice: DocumentValue) {\n this.invoice = invoice\n this.form = { ...invoice, items: invoice.items.map((item: any) => ({ ...item })) }\n this.amounts = invoice\n this.voidReason = ''\n this.configure()\n this.grist?.fetch()\n }\n\n /** Runs one request at a time; a refusal is said in the server's words. */\n private async run<T>(work: () => Promise<T>): Promise<T | undefined> {\n if (this.busy) return undefined\n this.busy = true\n try {\n return await work()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) })\n return undefined\n } finally {\n this.busy = false\n }\n }\n\n private input() {\n const input: DocumentValue = Object.fromEntries(HEAD.map(key => [key, this.form[key] === '' ? null : this.form[key] ?? null]))\n input.taxBasis = input.direction === 'PURCHASE' ? input.taxBasis || 'CALCULATED' : 'CALCULATED'\n input.items = linesOf(this.grist)\n input.discountAmount = input.discountAmount === null ? 0 : Number(input.discountAmount)\n return input\n }\n\n private async save(): Promise<DocumentValue | undefined> {\n const id = this.invoice?.id\n if (this.busy) return undefined\n await this.linkByName()\n const saved = await this.run(async () => {\n const response = await client.mutate({\n mutation: gql(\n id\n ? `mutation ($id: String!, $invoice: NewInvoice!) { invoice: updateInvoice(id: $id, invoice: $invoice) { ${FIELDS} } }`\n : `mutation ($invoice: NewInvoice!) { invoice: createInvoice(invoice: $invoice) { ${FIELDS} } }`\n ),\n variables: { ...(id ? { id } : {}), invoice: this.input() }\n })\n if (response.errors?.length) throw response\n return response.data.invoice as DocumentValue\n })\n if (!saved) return undefined\n this.show(saved)\n this.updateContext()\n if (!id) navigate(`invoice-editor?id=${saved.id}`)\n return saved\n }\n\n /** Asked first - to whom and how much, fixed after - then save what is on screen and issue it. */\n private async issue() {\n const purchase = this.form.direction === 'PURCHASE'\n const words = {\n party: (purchase ? this.form.supplierName : this.form.buyerName) || '—',\n total: documentAmount(this.amounts.totalAmount ?? 0, this.form.currency || 'KRW')\n }\n const confirmed = await OxPrompt.open({\n title: i18next.t('button.invoice-issue'),\n text: purchase ? i18next.t('text.invoice-issue-confirm-purchase', words) : i18next.t('text.invoice-issue-confirm', words),\n confirmButton: { text: i18next.t('button.invoice-issue') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n if (!confirmed) return\n const saved = await this.save()\n if (!saved) return\n const issued = await this.mutate(`mutation ($id: String!) { invoice: issueInvoice(id: $id) { ${FIELDS} } }`, { id: saved.id })\n if (issued) notify({ level: 'info', message: i18next.t('text.invoice-issued') })\n }\n\n private async void() {\n const confirmed = await OxPrompt.open({\n title: i18next.t('button.invoice-void'),\n text: i18next.t('text.invoice-void-confirm'),\n confirmButton: { text: i18next.t('button.invoice-void'), destructive: true },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n if (!confirmed) return\n await this.mutate(`mutation ($id: String!, $reason: String!) { invoice: voidInvoice(id: $id, reason: $reason) { ${FIELDS} } }`, {\n id: this.invoice!.id,\n reason: this.voidReason.trim()\n })\n }\n\n private async mutate(mutation: string, variables: any) {\n const changed = await this.run(async () => {\n const response = await client.mutate({ mutation: gql(mutation), variables })\n if (response.errors?.length) throw response\n return response.data.invoice as DocumentValue\n })\n if (changed) {\n this.show(changed)\n this.updateContext()\n }\n return changed\n }\n\n /**\n * The saved invoice as it goes out (ADR-0170 decision 10) - the server's page in the language chosen there, the\n * invoice's own first, to download as PDF or print. What is on screen and not saved is not in it.\n */\n private preview() {\n const { id, number, language } = this.invoice!\n openDocumentPreview(\n async (format: DocumentFileFormat, lng: string) => {\n const response = await client.query({\n query: gql`\n query ($id: String!, $format: DocumentFileFormat!, $language: String) {\n invoiceDocumentFile(id: $id, format: $format, language: $language) { fileName mimeType content }\n }\n `,\n variables: { id, format, language: lng },\n fetchPolicy: 'no-cache'\n })\n if (!response.data?.invoiceDocumentFile) throw response.errors?.[0] ?? new Error(i18next.t('text.invoice-not-found'))\n return response.data.invoiceDocumentFile\n },\n language,\n `${i18next.t('title.invoice-preview')} · ${number}`\n )\n }\n}\n\nfunction blank(): DocumentValue {\n return {\n number: '',\n direction: 'SALES',\n currency: 'KRW',\n language: 'ko',\n supplierName: '',\n buyerName: '',\n issueDate: today(),\n dueDate: '',\n taxBasis: 'CALCULATED',\n discountAmount: 0,\n items: []\n }\n}\n"]}
@@ -1,15 +1,27 @@
1
1
  import '@operato/data-grist/ox-grist.js';
2
+ import '@operato/input/ox-select.js';
3
+ import '@operato/popup/ox-popup-list.js';
2
4
  import { FetchOption } from '@operato/data-grist';
3
5
  import { PageView } from '@operato/shell';
4
6
  declare const InvoiceList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
5
7
  /**
6
8
  * 청구서 — every invoice the domain sent or received. The other party's name opens the invoice's 360; the edit gutter
7
9
  * opens the editor. Search is the server's: number, parties, purchase order and notes. Rows picked go to the assistant
8
- * (「AI와 작업」) as `invoice:<uuid>`, read by the invoice list's read (server invoice-read.ts).
10
+ * (「AI와 작업」) as `invoice:<uuid>`, read by the invoice list's read (server invoice-read.ts). The headroom narrows it by
11
+ * status, direction and issue period; the totals of the rows shown stand over it (ADR-0170 decision 11).
9
12
  */
10
13
  export declare class InvoiceList extends InvoiceList_base {
11
14
  static styles: import("lit").CSSResult[];
12
15
  gristConfig: any;
16
+ status: string;
17
+ direction: string;
18
+ period: string;
19
+ /** The totals of the rows on the page, by direction and currency. */
20
+ totals: {
21
+ direction: string;
22
+ currency: string;
23
+ amount: number;
24
+ }[];
13
25
  private grist;
14
26
  get context(): {
15
27
  title: string;
@@ -17,6 +29,9 @@ export declare class InvoiceList extends InvoiceList_base {
17
29
  handler: (search: string) => void;
18
30
  value: string;
19
31
  };
32
+ filter: {
33
+ handler: () => void;
34
+ };
20
35
  actions: {
21
36
  title: string;
22
37
  icon: string;
@@ -36,5 +51,10 @@ export declare class InvoiceList extends InvoiceList_base {
36
51
  total: any;
37
52
  records: any;
38
53
  }>;
54
+ /**
55
+ * The headroom's choices as the list's filters. Overdue is issued and due before today - the invoice module keeps no
56
+ * payments, so the list reads it from the status and the due date alone (invoice-standing.ts).
57
+ */
58
+ private headroomFilters;
39
59
  }
40
60
  export {};