@design.estate/dees-document 3.1.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_bundle/bundle.js +528 -334
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_shared/demoletter.js +2 -2
- package/dist_ts_shared/index.d.ts +3 -0
- package/dist_ts_shared/index.js +4 -1
- package/dist_ts_shared/paymentcode.d.ts +43 -0
- package/dist_ts_shared/paymentcode.js +85 -0
- package/dist_ts_shared/paymentreminder.d.ts +28 -0
- package/dist_ts_shared/paymentreminder.js +33 -0
- package/dist_ts_shared/translation.d.ts +49 -0
- package/dist_ts_shared/translation.js +97 -3
- package/dist_ts_shared/unitnames.d.ts +44 -0
- package/dist_ts_shared/unitnames.js +103 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/contentinvoice.d.ts +89 -1
- package/dist_ts_web/elements/contentinvoice.js +580 -44
- package/dist_ts_web/elements/letterheader.d.ts +2 -0
- package/dist_ts_web/elements/letterheader.js +20 -1
- package/dist_ts_web/elements/paymentcode.d.ts +2 -0
- package/dist_ts_web/elements/paymentcode.js +23 -19
- package/package.json +5 -5
- package/readme.md +115 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/contentinvoice.ts +713 -49
- package/ts_web/elements/letterheader.ts +28 -0
- package/ts_web/elements/paymentcode.ts +27 -26
|
@@ -61,6 +61,14 @@ const accountingDocContents: Record<
|
|
|
61
61
|
requestsPayment: true,
|
|
62
62
|
printsRelatedDocuments: false,
|
|
63
63
|
},
|
|
64
|
+
// a complete invoice in place of one that lacked a detail or stated one
|
|
65
|
+
// incorrectly (§ 31 Abs. 5 UStDV); it names that invoice in its own block,
|
|
66
|
+
// renderCorrection(), not among the related documents
|
|
67
|
+
"corrected-invoice": {
|
|
68
|
+
introStatement: "invoice@@introStatement",
|
|
69
|
+
requestsPayment: true,
|
|
70
|
+
printsRelatedDocuments: false,
|
|
71
|
+
},
|
|
64
72
|
// reduces what the recipient owes: nothing to pay, and it names the document it corrects
|
|
65
73
|
creditnote: {
|
|
66
74
|
introStatement: "creditnote@@introStatement",
|
|
@@ -97,6 +105,160 @@ const relatedDocumentLabels: Record<
|
|
|
97
105
|
references: "relatedDocument.references",
|
|
98
106
|
};
|
|
99
107
|
|
|
108
|
+
/** A column that takes the width of its widest cell. */
|
|
109
|
+
interface IFittedColumn {
|
|
110
|
+
/** the cells of the column, the header included */
|
|
111
|
+
cellSelector: string;
|
|
112
|
+
/** the custom property the grid reads the column's width from */
|
|
113
|
+
widthProperty: string;
|
|
114
|
+
/** the width the column never goes below, in px */
|
|
115
|
+
minWidth: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The fitted columns of a table and the room the rest of a row takes. */
|
|
119
|
+
interface IColumnLayout {
|
|
120
|
+
columns: IFittedColumn[];
|
|
121
|
+
/** the columns of fixed width, together, in px */
|
|
122
|
+
fixedWidth: number;
|
|
123
|
+
/** the least room the flexible column (the description) keeps, in px */
|
|
124
|
+
flexibleMinWidth: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The positions: number 40 px and VAT rate 50 px fixed, the description
|
|
129
|
+
* flexible with at least 120 px (as the grid in the styles declares); the
|
|
130
|
+
* last column is the one the sums line up with.
|
|
131
|
+
*/
|
|
132
|
+
const positionColumns: IColumnLayout = {
|
|
133
|
+
columns: [
|
|
134
|
+
{ cellSelector: ".quantityCell", widthProperty: "--quantity-column-width", minWidth: 50 },
|
|
135
|
+
{ cellSelector: ".unitCell", widthProperty: "--unit-column-width", minWidth: 50 },
|
|
136
|
+
{ cellSelector: ".unitPriceCell", widthProperty: "--unit-price-column-width", minWidth: 100 },
|
|
137
|
+
{ cellSelector: ".amountCell", widthProperty: "--amount-column-width", minWidth: 100 },
|
|
138
|
+
],
|
|
139
|
+
fixedWidth: 90,
|
|
140
|
+
flexibleMinWidth: 120,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A reminder's claims, and its charges and sums, which share the last column;
|
|
145
|
+
* the document label is flexible with at least 150 px (as the grid in the
|
|
146
|
+
* styles declares).
|
|
147
|
+
*/
|
|
148
|
+
const reminderColumns: IColumnLayout = {
|
|
149
|
+
columns: [
|
|
150
|
+
{ cellSelector: ".issueDateCell", widthProperty: "--issue-date-column-width", minWidth: 80 },
|
|
151
|
+
{ cellSelector: ".dueDateCell", widthProperty: "--due-date-column-width", minWidth: 80 },
|
|
152
|
+
{ cellSelector: ".grossCell", widthProperty: "--gross-column-width", minWidth: 100 },
|
|
153
|
+
{ cellSelector: ".amountCell", widthProperty: "--amount-column-width", minWidth: 100 },
|
|
154
|
+
],
|
|
155
|
+
fixedWidth: 0,
|
|
156
|
+
flexibleMinWidth: 150,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** The smallest font a cell is set in to fit its column, in px. */
|
|
160
|
+
const MIN_FITTED_FONT_SIZE = 8;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The width a cell's text needs, its padding included, measured on the text
|
|
164
|
+
* itself, so it is the same whether the text fits its column or overflows it.
|
|
165
|
+
*/
|
|
166
|
+
const neededWidth = (cellArg: HTMLElement): number => {
|
|
167
|
+
const range = document.createRange();
|
|
168
|
+
range.selectNodeContents(cellArg);
|
|
169
|
+
const style = getComputedStyle(cellArg);
|
|
170
|
+
return Math.ceil(
|
|
171
|
+
range.getBoundingClientRect().width +
|
|
172
|
+
parseFloat(style.paddingLeft) +
|
|
173
|
+
parseFloat(style.paddingRight)
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Widths for columns that want `wantedArg` and get at least `minimumsArg`,
|
|
179
|
+
* within `availableArg`: what is left above the minimums is granted in equal
|
|
180
|
+
* shares, a column never getting more than it wants.
|
|
181
|
+
*/
|
|
182
|
+
const shareWidth = (
|
|
183
|
+
wantedArg: number[],
|
|
184
|
+
minimumsArg: number[],
|
|
185
|
+
availableArg: number
|
|
186
|
+
): number[] => {
|
|
187
|
+
const widths = [...minimumsArg];
|
|
188
|
+
let remaining =
|
|
189
|
+
availableArg - minimumsArg.reduce((sumArg, widthArg) => sumArg + widthArg, 0);
|
|
190
|
+
let open = widths.map((_, index) => index).filter((index) => wantedArg[index] > widths[index]);
|
|
191
|
+
while (open.length > 0 && remaining >= 1) {
|
|
192
|
+
const share = remaining / open.length;
|
|
193
|
+
for (const index of open) {
|
|
194
|
+
const grant = Math.min(share, wantedArg[index] - widths[index]);
|
|
195
|
+
widths[index] += grant;
|
|
196
|
+
remaining -= grant;
|
|
197
|
+
}
|
|
198
|
+
open = open.filter((index) => wantedArg[index] - widths[index] >= 1);
|
|
199
|
+
}
|
|
200
|
+
return widths.map((widthArg) => Math.floor(widthArg));
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sets a cell whose text is wider than its column in a smaller font, down to
|
|
205
|
+
* `MIN_FITTED_FONT_SIZE`, and lets it wrap within the column when even that
|
|
206
|
+
* is too wide.
|
|
207
|
+
*/
|
|
208
|
+
const fitCellText = (cellArg: HTMLElement, columnWidthArg: number): void => {
|
|
209
|
+
const needed = neededWidth(cellArg);
|
|
210
|
+
if (needed <= columnWidthArg) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const style = getComputedStyle(cellArg);
|
|
214
|
+
const padding = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
|
|
215
|
+
const fontSize =
|
|
216
|
+
(parseFloat(style.fontSize) * (columnWidthArg - padding)) / (needed - padding);
|
|
217
|
+
if (fontSize >= MIN_FITTED_FONT_SIZE) {
|
|
218
|
+
cellArg.style.fontSize = `${Math.floor(fontSize * 10) / 10}px`;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
cellArg.style.fontSize = `${MIN_FITTED_FONT_SIZE}px`;
|
|
222
|
+
cellArg.style.whiteSpace = "normal";
|
|
223
|
+
cellArg.style.overflowWrap = "anywhere";
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/** The kind of a reminded document, as the claims table names it. */
|
|
227
|
+
const claimDocumentLabels: Record<
|
|
228
|
+
plugins.tsclass.finance.TPaymentReminderClaim["documentType"],
|
|
229
|
+
plugins.shared.translation.TranslationKey
|
|
230
|
+
> = {
|
|
231
|
+
invoice: "paymentReminder@@claim.invoice",
|
|
232
|
+
"corrected-invoice": "paymentReminder@@claim.correctedInvoice",
|
|
233
|
+
debitnote: "paymentReminder@@claim.debitnote",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/** The words for a detail of § 14 Abs. 4 Satz 1 UStG a corrected invoice supplies or corrects. */
|
|
237
|
+
const correctionDetailLabels: Record<
|
|
238
|
+
plugins.tsclass.finance.TInvoiceMandatoryDetail,
|
|
239
|
+
plugins.shared.translation.TranslationKey
|
|
240
|
+
> = {
|
|
241
|
+
parties: "correctedInvoice.detail.parties",
|
|
242
|
+
"supplier-tax-number": "correctedInvoice.detail.supplierTaxNumber",
|
|
243
|
+
"issue-date": "correctedInvoice.detail.issueDate",
|
|
244
|
+
"invoice-number": "correctedInvoice.detail.invoiceNumber",
|
|
245
|
+
"supply-description": "correctedInvoice.detail.supplyDescription",
|
|
246
|
+
"supply-date": "correctedInvoice.detail.supplyDate",
|
|
247
|
+
"net-amount": "correctedInvoice.detail.netAmount",
|
|
248
|
+
tax: "correctedInvoice.detail.tax",
|
|
249
|
+
"retention-note": "correctedInvoice.detail.retentionNote",
|
|
250
|
+
"special-case-detail": "correctedInvoice.detail.specialCaseDetail",
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/** What was done with a detail: supplied when it was missing, corrected when it was incorrect. */
|
|
254
|
+
const correctionDefectLabels: Record<
|
|
255
|
+
plugins.tsclass.finance.TInvoiceCorrection["defect"],
|
|
256
|
+
plugins.shared.translation.TranslationKey
|
|
257
|
+
> = {
|
|
258
|
+
missing: "correctedInvoice.defect.missing",
|
|
259
|
+
incorrect: "correctedInvoice.defect.incorrect",
|
|
260
|
+
};
|
|
261
|
+
|
|
100
262
|
@customElement("dedocument-contentinvoice")
|
|
101
263
|
export class DeContentInvoice extends DeesElement {
|
|
102
264
|
public static demo = () => html`
|
|
@@ -135,6 +297,18 @@ export class DeContentInvoice extends DeesElement {
|
|
|
135
297
|
: null;
|
|
136
298
|
}
|
|
137
299
|
|
|
300
|
+
/** The payment reminder this content prints, or null. */
|
|
301
|
+
private get paymentReminder(): plugins.tsclass.finance.TPaymentReminder | null {
|
|
302
|
+
return plugins.shared.isPaymentReminder(this.letterData)
|
|
303
|
+
? this.letterData
|
|
304
|
+
: null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The currency of the letter's amounts: an accounting document's or a reminder's. */
|
|
308
|
+
private get currency(): string | undefined {
|
|
309
|
+
return this.accountingDoc?.currency ?? this.paymentReminder?.currency;
|
|
310
|
+
}
|
|
311
|
+
|
|
138
312
|
/**
|
|
139
313
|
* The document builds this element for every letter; one that is no
|
|
140
314
|
* accounting document, and one of a type this version does not know, keep
|
|
@@ -174,7 +348,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
174
348
|
|
|
175
349
|
.grid {
|
|
176
350
|
display: grid;
|
|
177
|
-
grid-template-columns: 40px
|
|
351
|
+
grid-template-columns: 40px minmax(120px, 1fr) var(--quantity-column-width, 50px) var(--unit-column-width, 50px) var(--unit-price-column-width, 100px) 50px var(--amount-column-width, 100px);
|
|
178
352
|
}
|
|
179
353
|
|
|
180
354
|
.topLine {
|
|
@@ -194,6 +368,16 @@ export class DeContentInvoice extends DeesElement {
|
|
|
194
368
|
border-right: none;
|
|
195
369
|
}
|
|
196
370
|
|
|
371
|
+
/*
|
|
372
|
+
* the description takes the room the other columns leave, at least its
|
|
373
|
+
* minimum, and wraps within it, so a long one never widens the table
|
|
374
|
+
* beyond the page
|
|
375
|
+
*/
|
|
376
|
+
.lineItem.descriptionCell {
|
|
377
|
+
white-space: normal;
|
|
378
|
+
overflow-wrap: anywhere;
|
|
379
|
+
}
|
|
380
|
+
|
|
197
381
|
.value.rightAlign,
|
|
198
382
|
.lineItem.rightAlign {
|
|
199
383
|
text-align: right;
|
|
@@ -211,6 +395,36 @@ export class DeContentInvoice extends DeesElement {
|
|
|
211
395
|
box-sizing: content-box;
|
|
212
396
|
}
|
|
213
397
|
|
|
398
|
+
.claimGrid {
|
|
399
|
+
display: grid;
|
|
400
|
+
grid-template-columns: minmax(150px, 1fr) var(--issue-date-column-width, 80px) var(--due-date-column-width, 80px) var(--gross-column-width, 100px) var(--amount-column-width, 100px);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.chargeGrid {
|
|
404
|
+
display: grid;
|
|
405
|
+
grid-template-columns: auto var(--amount-column-width, 100px);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.chargeGrid.dataHeader {
|
|
409
|
+
margin-top: 12px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* a charge's description and basis wrap: the basis of default interest is longer than a line */
|
|
413
|
+
.lineItem.chargeText {
|
|
414
|
+
white-space: normal;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.chargeBasis {
|
|
418
|
+
margin-top: 2px;
|
|
419
|
+
font-size: 11px;
|
|
420
|
+
color: #555;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/* a provision is not split across lines */
|
|
424
|
+
.citation {
|
|
425
|
+
white-space: nowrap;
|
|
426
|
+
}
|
|
427
|
+
|
|
214
428
|
.sums {
|
|
215
429
|
margin-top: 5px;
|
|
216
430
|
font-size: 12px;
|
|
@@ -220,7 +434,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
220
434
|
.sums .sumline {
|
|
221
435
|
margin-top: 3px;
|
|
222
436
|
display: grid;
|
|
223
|
-
grid-template-columns: auto 100px;
|
|
437
|
+
grid-template-columns: auto var(--amount-column-width, 100px);
|
|
224
438
|
}
|
|
225
439
|
|
|
226
440
|
.sums .sumline .label {
|
|
@@ -264,6 +478,19 @@ export class DeContentInvoice extends DeesElement {
|
|
|
264
478
|
margin-top: 4px;
|
|
265
479
|
}
|
|
266
480
|
|
|
481
|
+
.correction {
|
|
482
|
+
margin-bottom: 12px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.correctionTitle {
|
|
486
|
+
font-weight: bold;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.correctionDetails ul {
|
|
490
|
+
margin: 2px 0 0 0;
|
|
491
|
+
padding-left: 18px;
|
|
492
|
+
}
|
|
493
|
+
|
|
267
494
|
.relatedDocuments .label {
|
|
268
495
|
font-weight: bold;
|
|
269
496
|
}
|
|
@@ -294,7 +521,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
294
521
|
* an unknown or missing currency falls back to a plain decimal number.
|
|
295
522
|
*/
|
|
296
523
|
protected formatPrice(value: number): string {
|
|
297
|
-
const currency = this.
|
|
524
|
+
const currency = this.currency;
|
|
298
525
|
const locale = localeForLanguage(this.documentSettings?.languageCode);
|
|
299
526
|
if (currency) {
|
|
300
527
|
try {
|
|
@@ -306,6 +533,39 @@ export class DeContentInvoice extends DeesElement {
|
|
|
306
533
|
return new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
|
307
534
|
}
|
|
308
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Formats a quantity in the document's language with every decimal it
|
|
538
|
+
* carries: 12.5 prints as "12,5" in German and "12.5" in English, and no
|
|
539
|
+
* stated quantity is rounded. The decimals are those of the shortest
|
|
540
|
+
* representation of the number, the one `String()` gives, as the unit name
|
|
541
|
+
* counts them for its singular or plural.
|
|
542
|
+
*/
|
|
543
|
+
protected formatQuantity(quantityArg: number): string {
|
|
544
|
+
const fractionDigits = plugins.shared.quantityFractionDigits(quantityArg);
|
|
545
|
+
return new Intl.NumberFormat(
|
|
546
|
+
localeForLanguage(this.documentSettings?.languageCode),
|
|
547
|
+
{
|
|
548
|
+
minimumFractionDigits: fractionDigits,
|
|
549
|
+
maximumFractionDigits: fractionDigits,
|
|
550
|
+
}
|
|
551
|
+
).format(quantityArg);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Formats a rate in percent, with as many decimals as it states, up to four. */
|
|
555
|
+
protected formatPercent(percentArg: number): string {
|
|
556
|
+
return new Intl.NumberFormat(
|
|
557
|
+
localeForLanguage(this.documentSettings?.languageCode),
|
|
558
|
+
{ style: "percent", maximumFractionDigits: 4 }
|
|
559
|
+
).format(percentArg / 100);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Formats a day in the document's language and date style. */
|
|
563
|
+
protected formatDay(timestampArg: number): string {
|
|
564
|
+
return new Intl.DateTimeFormat(this.documentSettings.languageCode, {
|
|
565
|
+
dateStyle: this.documentSettings.dateStyle,
|
|
566
|
+
}).format(new Date(timestampArg));
|
|
567
|
+
}
|
|
568
|
+
|
|
309
569
|
public getTotalNet = (): number => {
|
|
310
570
|
let totalNet = 0;
|
|
311
571
|
|
|
@@ -485,24 +745,25 @@ export class DeContentInvoice extends DeesElement {
|
|
|
485
745
|
}
|
|
486
746
|
}
|
|
487
747
|
|
|
488
|
-
/**
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
748
|
+
/**
|
|
749
|
+
* The due date, which only an accounting document and a payment reminder
|
|
750
|
+
* have: it counts `dueInDays` from the letter's date.
|
|
751
|
+
*/
|
|
752
|
+
private renderPaymentTerms(optionsArg: {
|
|
753
|
+
date: number;
|
|
754
|
+
dueInDays: number;
|
|
755
|
+
labelKey: plugins.shared.translation.TranslationKey;
|
|
756
|
+
textKey: plugins.shared.translation.TranslationKey;
|
|
757
|
+
}): TemplateResult {
|
|
492
758
|
return html`<div class="infoBox">
|
|
493
759
|
<div>
|
|
494
760
|
<div>
|
|
495
|
-
<div class="label">
|
|
496
|
-
${this.translateKey("invoice@@payment.terms")}
|
|
497
|
-
</div>
|
|
761
|
+
<div class="label">${this.translateKey(optionsArg.labelKey)}</div>
|
|
498
762
|
<span>
|
|
499
|
-
${this.translateKey(
|
|
500
|
-
${
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
new Date(accountingDocArg.date).setDate(
|
|
504
|
-
new Date(accountingDocArg.date).getDate() +
|
|
505
|
-
accountingDocArg.dueInDays
|
|
763
|
+
${this.translateKey(optionsArg.textKey)}
|
|
764
|
+
${this.formatDay(
|
|
765
|
+
new Date(optionsArg.date).setDate(
|
|
766
|
+
new Date(optionsArg.date).getDate() + optionsArg.dueInDays
|
|
506
767
|
)
|
|
507
768
|
)}
|
|
508
769
|
</span>
|
|
@@ -511,15 +772,39 @@ export class DeContentInvoice extends DeesElement {
|
|
|
511
772
|
</div>`;
|
|
512
773
|
}
|
|
513
774
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
775
|
+
/**
|
|
776
|
+
* The QR pay box. It is printed only when the payment has a valid EPC QR
|
|
777
|
+
* code: a euro amount to the sender's IBAN, which a document in another
|
|
778
|
+
* currency or without the sender's IBAN does not have.
|
|
779
|
+
*/
|
|
780
|
+
private renderPaymentInfo(optionsArg: {
|
|
781
|
+
/** the sender, who is paid */
|
|
782
|
+
from: plugins.tsclass.business.TContact;
|
|
783
|
+
currency: string;
|
|
784
|
+
/** the amount to pay */
|
|
785
|
+
amount: number;
|
|
786
|
+
/** the number of the letter, the remittance information */
|
|
787
|
+
reference: string;
|
|
788
|
+
}): TemplateResult | null {
|
|
789
|
+
const bic = optionsArg.from.sepaConnection?.bic;
|
|
790
|
+
const name = optionsArg.from.name;
|
|
791
|
+
const iban = optionsArg.from.sepaConnection?.iban;
|
|
792
|
+
const currency = optionsArg.currency;
|
|
793
|
+
const totalGross = optionsArg.amount;
|
|
794
|
+
const reference = optionsArg.reference;
|
|
795
|
+
if (
|
|
796
|
+
typeof iban !== "string" ||
|
|
797
|
+
plugins.shared.buildEpcQrPayload({
|
|
798
|
+
bic,
|
|
799
|
+
name,
|
|
800
|
+
iban,
|
|
801
|
+
currency,
|
|
802
|
+
amount: totalGross,
|
|
803
|
+
remittanceInformation: reference,
|
|
804
|
+
}) === null
|
|
805
|
+
) {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
523
808
|
|
|
524
809
|
return html`<div class="infoBox">
|
|
525
810
|
<div>
|
|
@@ -539,6 +824,66 @@ export class DeContentInvoice extends DeesElement {
|
|
|
539
824
|
</div>`;
|
|
540
825
|
}
|
|
541
826
|
|
|
827
|
+
/**
|
|
828
|
+
* The reverse-charge statement. § 14a UStG requires the words
|
|
829
|
+
* "Steuerschuldnerschaft des Leistungsempfängers": Abs. 5 for a domestic
|
|
830
|
+
* supply whose recipient owes the tax under § 13b, Abs. 1 for a supply in
|
|
831
|
+
* another member state whose recipient owes the tax there, such as a
|
|
832
|
+
* business-to-business service under Art. 196 of the VAT Directive. They are
|
|
833
|
+
* printed on every reverse-charge document, and a document in another language adds its own
|
|
834
|
+
* line, in English "Reverse charge", the words of Art. 226 Nr. 11a of the VAT
|
|
835
|
+
* Directive (2006/112/EC).
|
|
836
|
+
*/
|
|
837
|
+
private renderReverseChargeNote(): TemplateResult {
|
|
838
|
+
const localizedLine = this.translateKey("invoice@@vat.reverseCharge.note");
|
|
839
|
+
return html`<div class="taxNote">
|
|
840
|
+
<div>${plugins.shared.translation.REVERSE_CHARGE_STATUTORY_PHRASE_DE}</div>
|
|
841
|
+
${localizedLine === plugins.shared.translation.REVERSE_CHARGE_STATUTORY_PHRASE_DE
|
|
842
|
+
? null
|
|
843
|
+
: html`<div>${localizedLine}</div>`}
|
|
844
|
+
</div>`;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* What makes a corrected invoice a correction (§ 31 Abs. 5 UStDV): its
|
|
849
|
+
* title, the invoice it corrects by number and issue date when the document
|
|
850
|
+
* states them ("spezifisch und eindeutig", Satz 2), and the details it
|
|
851
|
+
* supplies or corrects, in words. A received corrected invoice may name
|
|
852
|
+
* neither; then only the title is printed, and no reference is made up.
|
|
853
|
+
*/
|
|
854
|
+
private renderCorrection(): TemplateResult | null {
|
|
855
|
+
const accountingDoc = this.accountingDoc;
|
|
856
|
+
if (accountingDoc?.accountingDocType !== "corrected-invoice") {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
const reference = accountingDoc.correctedInvoice;
|
|
860
|
+
const corrections = accountingDoc.corrections ?? [];
|
|
861
|
+
return html`<div class="correction">
|
|
862
|
+
<div class="correctionTitle">
|
|
863
|
+
${this.translateKey("correctedInvoice.title")}${reference
|
|
864
|
+
? html` ${this.translateKey("correctedInvoice.reference")}
|
|
865
|
+
${reference.documentId}${reference.issueDate !== undefined
|
|
866
|
+
? html` ${this.translateKey("correctedInvoice.referenceDate")}
|
|
867
|
+
${this.formatDay(reference.issueDate)}`
|
|
868
|
+
: null}`
|
|
869
|
+
: null}
|
|
870
|
+
</div>
|
|
871
|
+
${corrections.length > 0
|
|
872
|
+
? html`<div class="correctionDetails">
|
|
873
|
+
<div>${this.translateKey("correctedInvoice.corrections")}:</div>
|
|
874
|
+
<ul>
|
|
875
|
+
${corrections.map(
|
|
876
|
+
(correctionArg) => html`<li>
|
|
877
|
+
${this.translateKey(correctionDetailLabels[correctionArg.detail])}:
|
|
878
|
+
${this.translateKey(correctionDefectLabels[correctionArg.defect])}
|
|
879
|
+
</li>`
|
|
880
|
+
)}
|
|
881
|
+
</ul>
|
|
882
|
+
</div>`
|
|
883
|
+
: null}
|
|
884
|
+
</div>`;
|
|
885
|
+
}
|
|
886
|
+
|
|
542
887
|
/** The earlier documents a correction answers, by number and, when known, by date. */
|
|
543
888
|
private renderRelatedDocuments(): TemplateResult | null {
|
|
544
889
|
const relatedDocuments = this.accountingDoc?.relatedDocuments;
|
|
@@ -572,8 +917,8 @@ export class DeContentInvoice extends DeesElement {
|
|
|
572
917
|
* pages and cannot split one, so a note is handed over token by token: a
|
|
573
918
|
* paragraph in one piece could be taller than a page and would never fit.
|
|
574
919
|
*/
|
|
575
|
-
private renderNotes(): TemplateResult | null {
|
|
576
|
-
const notes = (
|
|
920
|
+
private renderNotes(notesArg: string[] | undefined): TemplateResult | null {
|
|
921
|
+
const notes = (notesArg ?? [])
|
|
577
922
|
.filter((noteArg): noteArg is string => typeof noteArg === "string")
|
|
578
923
|
.map((noteArg) => noteArg.replace(/\r\n?/g, "\n").trim())
|
|
579
924
|
.filter((noteArg) => noteArg.length > 0);
|
|
@@ -588,6 +933,240 @@ export class DeContentInvoice extends DeesElement {
|
|
|
588
933
|
)}`;
|
|
589
934
|
}
|
|
590
935
|
|
|
936
|
+
/**
|
|
937
|
+
* The document a reminder claims, as the claims table and the charges name
|
|
938
|
+
* it: its kind and its number.
|
|
939
|
+
*/
|
|
940
|
+
private claimLabel(
|
|
941
|
+
reminderArg: plugins.tsclass.finance.TPaymentReminder,
|
|
942
|
+
documentIdArg: string
|
|
943
|
+
): string {
|
|
944
|
+
const claim = reminderArg.claims.find(
|
|
945
|
+
(claimArg) => claimArg.documentId === documentIdArg
|
|
946
|
+
);
|
|
947
|
+
if (!claim) {
|
|
948
|
+
return documentIdArg;
|
|
949
|
+
}
|
|
950
|
+
// a kind this version does not know is named as an invoice, as the letterhead and the content do
|
|
951
|
+
const label =
|
|
952
|
+
claimDocumentLabels[claim.documentType] ?? claimDocumentLabels.invoice;
|
|
953
|
+
return `${this.translateKey(label)} ${documentIdArg}`;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* The ground of the claim: every reminded document with its number, its
|
|
958
|
+
* date, the date it fell due when it stated one, its gross total and what is
|
|
959
|
+
* still open on it.
|
|
960
|
+
*/
|
|
961
|
+
private renderReminderClaims(
|
|
962
|
+
reminderArg: plugins.tsclass.finance.TPaymentReminder
|
|
963
|
+
): TemplateResult {
|
|
964
|
+
return html`<div class="claimGrid topLine dataHeader">
|
|
965
|
+
<div class="lineItem descriptionCell">
|
|
966
|
+
${this.translateKey("paymentReminder@@claim.document")}
|
|
967
|
+
</div>
|
|
968
|
+
<div class="lineItem rightAlign issueDateCell">
|
|
969
|
+
${this.translateKey("paymentReminder@@claim.issueDate")}
|
|
970
|
+
</div>
|
|
971
|
+
<div class="lineItem rightAlign dueDateCell">
|
|
972
|
+
${this.translateKey("paymentReminder@@claim.dueDate")}
|
|
973
|
+
</div>
|
|
974
|
+
<div class="lineItem rightAlign grossCell">
|
|
975
|
+
${this.translateKey("paymentReminder@@claim.totalGross")}
|
|
976
|
+
</div>
|
|
977
|
+
<div class="lineItem rightAlign amountCell">
|
|
978
|
+
${this.translateKey("paymentReminder@@claim.outstanding")}
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
${reminderArg.claims.map(
|
|
982
|
+
(claimArg) => html`<div class="claimGrid needsDataHeader">
|
|
983
|
+
<div class="lineItem descriptionCell">
|
|
984
|
+
${this.claimLabel(reminderArg, claimArg.documentId)}
|
|
985
|
+
</div>
|
|
986
|
+
<div class="lineItem rightAlign issueDateCell">
|
|
987
|
+
${this.formatDay(claimArg.issueDate)}
|
|
988
|
+
</div>
|
|
989
|
+
<div class="lineItem rightAlign dueDateCell">
|
|
990
|
+
${claimArg.dueDate !== undefined
|
|
991
|
+
? this.formatDay(claimArg.dueDate)
|
|
992
|
+
: "–"}
|
|
993
|
+
</div>
|
|
994
|
+
<div class="lineItem rightAlign grossCell">
|
|
995
|
+
${this.formatPrice(claimArg.totalGross)}
|
|
996
|
+
</div>
|
|
997
|
+
<div class="lineItem rightAlign amountCell">
|
|
998
|
+
${this.formatPrice(claimArg.outstandingAmount)}
|
|
999
|
+
</div>
|
|
1000
|
+
</div>`
|
|
1001
|
+
)}`;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* What a charge rests on, as the reminder states it: default interest its
|
|
1006
|
+
* computation (principal, rate a year with the base rate and the surcharge
|
|
1007
|
+
* points of § 288 Abs. 1 or 2 BGB, first and last day), the default lump sum
|
|
1008
|
+
* its provision, and every charge the claim it accrues on. A cost with lump
|
|
1009
|
+
* sums credited against it lists them (§ 288 Abs. 5 Satz 3 BGB); its amount
|
|
1010
|
+
* is already the cost less them.
|
|
1011
|
+
*/
|
|
1012
|
+
private renderChargeBasis(
|
|
1013
|
+
reminderArg: plugins.tsclass.finance.TPaymentReminder,
|
|
1014
|
+
chargeArg: plugins.tsclass.finance.TPaymentReminderCharge
|
|
1015
|
+
): TemplateResult | null {
|
|
1016
|
+
switch (chargeArg.chargeType) {
|
|
1017
|
+
case "default-interest": {
|
|
1018
|
+
const interest = chargeArg.interest;
|
|
1019
|
+
return html`<div class="chargeBasis">
|
|
1020
|
+
${this.claimLabel(reminderArg, chargeArg.claimDocumentId)}:
|
|
1021
|
+
${this.formatPrice(interest.principal)} ×
|
|
1022
|
+
${this.formatPercent(interest.ratePercent)}
|
|
1023
|
+
${this.translateKey("paymentReminder@@interest.perYear")}
|
|
1024
|
+
(${this.translateKey("paymentReminder@@interest.baseRate")}
|
|
1025
|
+
${this.formatPercent(interest.baseRatePercent)}
|
|
1026
|
+
${this.translateKey("paymentReminder@@interest.plus")}
|
|
1027
|
+
${interest.surchargePoints}
|
|
1028
|
+
${this.translateKey("paymentReminder@@interest.points")},
|
|
1029
|
+
<span class="citation"
|
|
1030
|
+
>${plugins.shared.defaultInterestCitations[interest.legalBasis]}</span
|
|
1031
|
+
>),
|
|
1032
|
+
${this.formatDay(interest.from)} – ${this.formatDay(interest.to)}
|
|
1033
|
+
</div>`;
|
|
1034
|
+
}
|
|
1035
|
+
case "default-lump-sum":
|
|
1036
|
+
return html`<div class="chargeBasis">
|
|
1037
|
+
${this.claimLabel(reminderArg, chargeArg.claimDocumentId)},
|
|
1038
|
+
<span class="citation"
|
|
1039
|
+
>${plugins.shared.DEFAULT_LUMP_SUM_CITATION}</span
|
|
1040
|
+
>
|
|
1041
|
+
</div>`;
|
|
1042
|
+
case "reminder-fee":
|
|
1043
|
+
case "other-cost": {
|
|
1044
|
+
const offsets = chargeArg.lumpSumOffsets ?? [];
|
|
1045
|
+
if (chargeArg.claimDocumentId === undefined && offsets.length === 0) {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
return html`${chargeArg.claimDocumentId !== undefined
|
|
1049
|
+
? html`<div class="chargeBasis">
|
|
1050
|
+
${this.claimLabel(reminderArg, chargeArg.claimDocumentId)}
|
|
1051
|
+
</div>`
|
|
1052
|
+
: null}
|
|
1053
|
+
${offsets.map(
|
|
1054
|
+
(offsetArg) => html`<div class="chargeBasis">
|
|
1055
|
+
${this.translateKey("paymentReminder@@charge.lumpSumOffset")}
|
|
1056
|
+
${this.claimLabel(reminderArg, offsetArg.claimDocumentId)}:
|
|
1057
|
+
${this.formatPrice(offsetArg.amount)}
|
|
1058
|
+
(<span class="citation"
|
|
1059
|
+
>${plugins.shared.LUMP_SUM_OFFSET_CITATION}</span
|
|
1060
|
+
>)
|
|
1061
|
+
</div>`
|
|
1062
|
+
)}`;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/** One row per charge: its description, what it rests on, and its amount. */
|
|
1068
|
+
private renderReminderCharges(
|
|
1069
|
+
reminderArg: plugins.tsclass.finance.TPaymentReminder
|
|
1070
|
+
): TemplateResult | null {
|
|
1071
|
+
if (reminderArg.charges.length === 0) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
return html`<div class="chargeGrid topLine dataHeader">
|
|
1075
|
+
<div class="lineItem">
|
|
1076
|
+
${this.translateKey("paymentReminder@@charge.description")}
|
|
1077
|
+
</div>
|
|
1078
|
+
<div class="lineItem rightAlign amountCell">
|
|
1079
|
+
${this.translateKey("paymentReminder@@charge.amount")}
|
|
1080
|
+
</div>
|
|
1081
|
+
</div>
|
|
1082
|
+
${reminderArg.charges.map(
|
|
1083
|
+
(chargeArg) => html`<div class="chargeGrid needsDataHeader">
|
|
1084
|
+
<div class="lineItem chargeText">
|
|
1085
|
+
<div>${chargeArg.description}</div>
|
|
1086
|
+
${this.renderChargeBasis(reminderArg, chargeArg)}
|
|
1087
|
+
</div>
|
|
1088
|
+
<div class="lineItem rightAlign amountCell">
|
|
1089
|
+
${this.formatPrice(chargeArg.amount)}
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>`
|
|
1092
|
+
)}`;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** The sums of a reminder: the open claims, the charges and the total payable, without VAT. */
|
|
1096
|
+
private renderReminderSums(
|
|
1097
|
+
totalsArg: plugins.shared.IPaymentReminderTotals,
|
|
1098
|
+
hasChargesArg: boolean
|
|
1099
|
+
): TemplateResult {
|
|
1100
|
+
return html`<div class="sums">
|
|
1101
|
+
<div class="sumline">
|
|
1102
|
+
<div class="label">
|
|
1103
|
+
${this.translateKey("paymentReminder@@paymentReminder.sum.claims")}
|
|
1104
|
+
</div>
|
|
1105
|
+
<div class="value rightAlign amountCell">
|
|
1106
|
+
${this.formatPrice(totalsArg.claimsOutstanding)}
|
|
1107
|
+
</div>
|
|
1108
|
+
</div>
|
|
1109
|
+
${hasChargesArg
|
|
1110
|
+
? html`<div class="sumline">
|
|
1111
|
+
<div class="label">
|
|
1112
|
+
${this.translateKey("paymentReminder@@paymentReminder.sum.charges")}
|
|
1113
|
+
</div>
|
|
1114
|
+
<div class="value rightAlign amountCell">
|
|
1115
|
+
${this.formatPrice(totalsArg.charges)}
|
|
1116
|
+
</div>
|
|
1117
|
+
</div>`
|
|
1118
|
+
: null}
|
|
1119
|
+
<div class="sumline">
|
|
1120
|
+
<div class="label">
|
|
1121
|
+
${this.translateKey("paymentReminder@@paymentReminder.sum.payable")}
|
|
1122
|
+
</div>
|
|
1123
|
+
<div class="value value--total rightAlign amountCell">
|
|
1124
|
+
${this.formatPrice(totalsArg.payable)}
|
|
1125
|
+
</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
</div>`;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* A payment reminder: the issuer's intro or the standard one, the reminded
|
|
1132
|
+
* documents, the charges, the sums, the notes, the new payment deadline and
|
|
1133
|
+
* the QR pay box for the total payable with the reminder's number.
|
|
1134
|
+
*/
|
|
1135
|
+
private renderPaymentReminder(
|
|
1136
|
+
reminderArg: plugins.tsclass.finance.TPaymentReminder
|
|
1137
|
+
): TemplateResult {
|
|
1138
|
+
const totals = plugins.shared.getPaymentReminderTotals(reminderArg);
|
|
1139
|
+
return html`
|
|
1140
|
+
<div>
|
|
1141
|
+
${reminderArg.topText ??
|
|
1142
|
+
this.translateKey("paymentReminder@@introStatement")}
|
|
1143
|
+
</div>
|
|
1144
|
+
${this.renderReminderClaims(reminderArg)}
|
|
1145
|
+
${this.renderReminderCharges(reminderArg)}
|
|
1146
|
+
${this.renderReminderSums(totals, reminderArg.charges.length > 0)}
|
|
1147
|
+
<div class="divider"></div>
|
|
1148
|
+
|
|
1149
|
+
<!-- NOTES -->
|
|
1150
|
+
${this.renderNotes(reminderArg.notes)}
|
|
1151
|
+
|
|
1152
|
+
<!-- PAYMENT DEADLINE -->
|
|
1153
|
+
${this.renderPaymentTerms({
|
|
1154
|
+
date: reminderArg.date,
|
|
1155
|
+
dueInDays: reminderArg.dueInDays,
|
|
1156
|
+
labelKey: "paymentReminder@@paymentReminder.deadline",
|
|
1157
|
+
textKey: "paymentReminder@@paymentReminder.deadline.text",
|
|
1158
|
+
})}
|
|
1159
|
+
|
|
1160
|
+
<!-- PAYMENT INFO -->
|
|
1161
|
+
${this.renderPaymentInfo({
|
|
1162
|
+
from: reminderArg.from,
|
|
1163
|
+
currency: reminderArg.currency,
|
|
1164
|
+
amount: totals.payable,
|
|
1165
|
+
reference: reminderArg.id,
|
|
1166
|
+
})}
|
|
1167
|
+
`;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
591
1170
|
private renderReferencedContract(): TemplateResult | null {
|
|
592
1171
|
return null;
|
|
593
1172
|
// return this.documentSettings.enableInvoiceContractRefSection &&
|
|
@@ -610,29 +1189,42 @@ export class DeContentInvoice extends DeesElement {
|
|
|
610
1189
|
|
|
611
1190
|
public async attachInvoiceDom() {
|
|
612
1191
|
const contentNodes = await this.getContentNodes();
|
|
1192
|
+
const paymentReminder = this.paymentReminder;
|
|
1193
|
+
if (paymentReminder) {
|
|
1194
|
+
render(this.renderPaymentReminder(paymentReminder), contentNodes.currentContent);
|
|
1195
|
+
this.fitColumns(
|
|
1196
|
+
contentNodes.currentContent,
|
|
1197
|
+
".claimGrid.dataHeader",
|
|
1198
|
+
reminderColumns
|
|
1199
|
+
);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
613
1202
|
const accountingDoc = this.accountingDoc;
|
|
614
1203
|
render(
|
|
615
1204
|
html`
|
|
1205
|
+
${this.renderCorrection()}
|
|
616
1206
|
<div>${this.translateKey(this.docContent.introStatement)}</div>
|
|
617
1207
|
${this.renderRelatedDocuments()}
|
|
618
1208
|
<div class="grid topLine dataHeader">
|
|
619
1209
|
<div class="lineItem rightAlign">
|
|
620
1210
|
${this.translateKey("invoice@@item.position")}
|
|
621
1211
|
</div>
|
|
622
|
-
<div class="lineItem">
|
|
1212
|
+
<div class="lineItem descriptionCell">
|
|
623
1213
|
${this.translateKey("invoice@@description")}
|
|
624
1214
|
</div>
|
|
625
|
-
<div class="lineItem rightAlign">
|
|
1215
|
+
<div class="lineItem rightAlign quantityCell">
|
|
626
1216
|
${this.translateKey("invoice@@quantity")}
|
|
627
1217
|
</div>
|
|
628
|
-
<div class="lineItem"
|
|
629
|
-
|
|
1218
|
+
<div class="lineItem unitCell">
|
|
1219
|
+
${this.translateKey("invoice@@unit.type")}
|
|
1220
|
+
</div>
|
|
1221
|
+
<div class="lineItem rightAlign unitPriceCell">
|
|
630
1222
|
${this.translateKey("invoice@@price.unit.net")}
|
|
631
1223
|
</div>
|
|
632
1224
|
<div class="lineItem rightAlign">
|
|
633
1225
|
${this.translateKey("invoice@@vat.short")}
|
|
634
1226
|
</div>
|
|
635
|
-
<div class="lineItem rightAlign">
|
|
1227
|
+
<div class="lineItem rightAlign amountCell">
|
|
636
1228
|
${this.translateKey("invoice@@price.total.net")}
|
|
637
1229
|
</div>
|
|
638
1230
|
</div>
|
|
@@ -640,16 +1232,24 @@ export class DeContentInvoice extends DeesElement {
|
|
|
640
1232
|
(invoiceItem, index) => html`
|
|
641
1233
|
<div class="grid needsDataHeader">
|
|
642
1234
|
<div class="lineItem rightAlign">${index + 1}</div>
|
|
643
|
-
<div class="lineItem">${invoiceItem.name}</div>
|
|
644
|
-
<div class="lineItem rightAlign"
|
|
645
|
-
|
|
646
|
-
|
|
1235
|
+
<div class="lineItem descriptionCell">${invoiceItem.name}</div>
|
|
1236
|
+
<div class="lineItem rightAlign quantityCell">
|
|
1237
|
+
${this.formatQuantity(invoiceItem.unitQuantity)}
|
|
1238
|
+
</div>
|
|
1239
|
+
<div class="lineItem unitCell">
|
|
1240
|
+
${plugins.shared.unitName(
|
|
1241
|
+
this.documentSettings.languageCode,
|
|
1242
|
+
invoiceItem.unitType,
|
|
1243
|
+
invoiceItem.unitQuantity
|
|
1244
|
+
)}
|
|
1245
|
+
</div>
|
|
1246
|
+
<div class="lineItem rightAlign unitPriceCell">
|
|
647
1247
|
${this.formatPrice(invoiceItem.unitNetPrice)}
|
|
648
1248
|
</div>
|
|
649
1249
|
<div class="lineItem rightAlign">
|
|
650
1250
|
${invoiceItem.vatPercentage}%
|
|
651
1251
|
</div>
|
|
652
|
-
<div class="lineItem rightAlign">
|
|
1252
|
+
<div class="lineItem rightAlign amountCell">
|
|
653
1253
|
${this.formatPrice(
|
|
654
1254
|
invoiceItem.unitQuantity * invoiceItem.unitNetPrice
|
|
655
1255
|
)}
|
|
@@ -662,7 +1262,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
662
1262
|
<div class="label">
|
|
663
1263
|
${this.translateKey("invoice@@sum.total.net")}
|
|
664
1264
|
</div>
|
|
665
|
-
<div class="value value--total rightAlign">
|
|
1265
|
+
<div class="value value--total rightAlign amountCell">
|
|
666
1266
|
${this.formatPrice(this.getTotalNet())}
|
|
667
1267
|
</div>
|
|
668
1268
|
</div>
|
|
@@ -684,7 +1284,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
684
1284
|
`
|
|
685
1285
|
: html``}
|
|
686
1286
|
</div>
|
|
687
|
-
<div class="value rightAlign">
|
|
1287
|
+
<div class="value rightAlign amountCell">
|
|
688
1288
|
${this.formatPrice(vatGroupArg.vatAmountSum)}
|
|
689
1289
|
</div>
|
|
690
1290
|
</div>
|
|
@@ -692,36 +1292,100 @@ export class DeContentInvoice extends DeesElement {
|
|
|
692
1292
|
})}
|
|
693
1293
|
<div class="sumline">
|
|
694
1294
|
<div class="label">${this.translateKey("invoice@@totalGross")}</div>
|
|
695
|
-
<div class="value value--total rightAlign">
|
|
1295
|
+
<div class="value value--total rightAlign amountCell">
|
|
696
1296
|
${this.formatPrice(this.getTotalGross())}
|
|
697
1297
|
</div>
|
|
698
1298
|
</div>
|
|
699
1299
|
</div>
|
|
700
1300
|
<div class="divider"></div>
|
|
701
1301
|
|
|
702
|
-
${accountingDoc?.reverseCharge
|
|
703
|
-
? html`<div class="taxNote">
|
|
704
|
-
${this.translateKey("invoice@@vat.reverseCharge.note")}
|
|
705
|
-
</div>`
|
|
706
|
-
: ``}
|
|
1302
|
+
${accountingDoc?.reverseCharge ? this.renderReverseChargeNote() : ``}
|
|
707
1303
|
|
|
708
1304
|
<!-- REFERENCED CONTRACT -->
|
|
709
1305
|
${this.renderReferencedContract()}
|
|
710
1306
|
|
|
711
1307
|
<!-- NOTES -->
|
|
712
|
-
${this.renderNotes()}
|
|
1308
|
+
${this.renderNotes(accountingDoc?.notes)}
|
|
713
1309
|
|
|
714
1310
|
<!-- PAYMENT TERMS -->
|
|
715
1311
|
${accountingDoc && this.docContent.requestsPayment
|
|
716
|
-
? this.renderPaymentTerms(
|
|
1312
|
+
? this.renderPaymentTerms({
|
|
1313
|
+
date: accountingDoc.date,
|
|
1314
|
+
dueInDays: accountingDoc.dueInDays,
|
|
1315
|
+
labelKey: "invoice@@payment.terms",
|
|
1316
|
+
textKey: "invoice@@payment.terms.direct",
|
|
1317
|
+
})
|
|
717
1318
|
: null}
|
|
718
1319
|
|
|
719
1320
|
<!-- PAYMENT INFO -->
|
|
720
1321
|
${accountingDoc && this.docContent.requestsPayment
|
|
721
|
-
? this.renderPaymentInfo(
|
|
1322
|
+
? this.renderPaymentInfo({
|
|
1323
|
+
from: accountingDoc.from,
|
|
1324
|
+
currency: accountingDoc.currency,
|
|
1325
|
+
amount: this.getTotalGross(),
|
|
1326
|
+
reference: accountingDoc.id,
|
|
1327
|
+
})
|
|
722
1328
|
: null}
|
|
723
1329
|
`,
|
|
724
1330
|
contentNodes.currentContent
|
|
725
1331
|
);
|
|
1332
|
+
this.fitColumns(
|
|
1333
|
+
contentNodes.currentContent,
|
|
1334
|
+
".grid.dataHeader",
|
|
1335
|
+
positionColumns
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Sizes the columns of a table to their widest cell. Every row is a grid of
|
|
1341
|
+
* its own, so the rows only line up when a column has one width for all of
|
|
1342
|
+
* them; each page renders every row before it is trimmed, so every page
|
|
1343
|
+
* measures the same widths.
|
|
1344
|
+
*
|
|
1345
|
+
* A column is never narrower than its minimum. The widths the columns ask
|
|
1346
|
+
* for beyond their minimums are granted out of the row's width less its
|
|
1347
|
+
* fixed columns and the minimum of its flexible column (the description),
|
|
1348
|
+
* in equal shares, so the table always fits the page. A cell whose text is
|
|
1349
|
+
* still wider than its column, such as an amount beyond hundreds of
|
|
1350
|
+
* billions, is set in a smaller font, down to 8 px, and wraps inside its
|
|
1351
|
+
* column once that is not enough, so it never runs into its neighbour.
|
|
1352
|
+
*/
|
|
1353
|
+
private fitColumns(
|
|
1354
|
+
containerArg: HTMLElement,
|
|
1355
|
+
headerRowSelectorArg: string,
|
|
1356
|
+
layoutArg: IColumnLayout
|
|
1357
|
+
): void {
|
|
1358
|
+
const cellsOf = (columnArg: IFittedColumn): HTMLElement[] =>
|
|
1359
|
+
Array.from(containerArg.querySelectorAll<HTMLElement>(columnArg.cellSelector));
|
|
1360
|
+
for (const column of layoutArg.columns) {
|
|
1361
|
+
this.style.removeProperty(column.widthProperty);
|
|
1362
|
+
for (const cell of cellsOf(column)) {
|
|
1363
|
+
cell.style.removeProperty("font-size");
|
|
1364
|
+
cell.style.removeProperty("white-space");
|
|
1365
|
+
cell.style.removeProperty("overflow-wrap");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
const headerRow = containerArg.querySelector<HTMLElement>(headerRowSelectorArg);
|
|
1369
|
+
if (!headerRow || headerRow.clientWidth === 0) {
|
|
1370
|
+
// not laid out: the columns keep their minimums
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const wanted = layoutArg.columns.map((columnArg) =>
|
|
1374
|
+
cellsOf(columnArg).reduce(
|
|
1375
|
+
(widestArg, cellArg) => Math.max(widestArg, neededWidth(cellArg)),
|
|
1376
|
+
columnArg.minWidth
|
|
1377
|
+
)
|
|
1378
|
+
);
|
|
1379
|
+
const widths = shareWidth(
|
|
1380
|
+
wanted,
|
|
1381
|
+
layoutArg.columns.map((columnArg) => columnArg.minWidth),
|
|
1382
|
+
headerRow.clientWidth - layoutArg.fixedWidth - layoutArg.flexibleMinWidth
|
|
1383
|
+
);
|
|
1384
|
+
layoutArg.columns.forEach((columnArg, index) => {
|
|
1385
|
+
this.style.setProperty(columnArg.widthProperty, `${widths[index]}px`);
|
|
1386
|
+
for (const cell of cellsOf(columnArg)) {
|
|
1387
|
+
fitCellText(cell, widths[index]);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
726
1390
|
}
|
|
727
1391
|
}
|