@design.estate/dees-document 4.0.2 → 4.1.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 +272 -268
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_shared/translation.d.ts +11 -0
- package/dist_ts_shared/translation.js +25 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/contentinvoice.d.ts +32 -0
- package/dist_ts_web/elements/contentinvoice.js +99 -21
- package/package.json +2 -2
- package/readme.md +2 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/contentinvoice.ts +114 -22
|
@@ -124,6 +124,9 @@ interface IColumnLayout {
|
|
|
124
124
|
flexibleMinWidth: number;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/** the order of the VAT groups of one rate: taxed categories first, then the ones without tax */
|
|
128
|
+
const VAT_CATEGORY_ORDER: plugins.tsclass.finance.TVatCategory[] = ["S", "L", "M", "Z", "E", "AE", "K", "G", "O"];
|
|
129
|
+
|
|
127
130
|
/**
|
|
128
131
|
* The positions: number 40 px and VAT rate 50 px fixed, the description
|
|
129
132
|
* flexible with at least 120 px (as the grid in the styles declares); the
|
|
@@ -608,9 +611,25 @@ export class DeContentInvoice extends DeesElement {
|
|
|
608
611
|
return this.getTotalNet() + totalVat;
|
|
609
612
|
};
|
|
610
613
|
|
|
614
|
+
/**
|
|
615
|
+
* The VAT category of an item (EN 16931 BT-151): the one it states, else
|
|
616
|
+
* reverse charge (AE) on a document that states reverse charge, else
|
|
617
|
+
* standard rated (S)
|
|
618
|
+
*/
|
|
619
|
+
public vatCategoryOf = (
|
|
620
|
+
itemArg: plugins.tsclass.finance.TAccountingDocItem
|
|
621
|
+
): plugins.tsclass.finance.TVatCategory =>
|
|
622
|
+
itemArg.vatCategory ?? (this.accountingDoc?.reverseCharge ? "AE" : "S");
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The VAT groups of the document: one per VAT category and rate, so items
|
|
626
|
+
* that are zero rated, exempt or an intra-community supply at 0 % are not
|
|
627
|
+
* one line; the highest rate first
|
|
628
|
+
*/
|
|
611
629
|
public getVatGroups = () => {
|
|
612
630
|
const vatGroups: {
|
|
613
631
|
vatPercentage: number;
|
|
632
|
+
vatCategory: plugins.tsclass.finance.TVatCategory;
|
|
614
633
|
items: plugins.tsclass.finance.TAccountingDocItem[];
|
|
615
634
|
vatAmountSum: number;
|
|
616
635
|
}[] = [];
|
|
@@ -620,33 +639,106 @@ export class DeContentInvoice extends DeesElement {
|
|
|
620
639
|
return vatGroups;
|
|
621
640
|
}
|
|
622
641
|
|
|
623
|
-
const taxAmounts: number[] = [];
|
|
624
642
|
for (const item of accountingDoc.items) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
643
|
+
const vatCategory = this.vatCategoryOf(item);
|
|
644
|
+
const group = vatGroups.find(
|
|
645
|
+
(groupArg) => groupArg.vatCategory === vatCategory && groupArg.vatPercentage === item.vatPercentage
|
|
646
|
+
);
|
|
647
|
+
if (group) {
|
|
648
|
+
group.items.push(item);
|
|
649
|
+
} else {
|
|
650
|
+
vatGroups.push({ vatPercentage: item.vatPercentage, vatCategory, items: [item], vatAmountSum: 0 });
|
|
651
|
+
}
|
|
628
652
|
}
|
|
629
653
|
|
|
630
|
-
for (const
|
|
631
|
-
const matchingItems = accountingDoc.items.filter(
|
|
632
|
-
(itemArg) => itemArg.vatPercentage === taxAmount
|
|
633
|
-
);
|
|
654
|
+
for (const group of vatGroups) {
|
|
634
655
|
let sum = 0;
|
|
635
|
-
for (const matchingItem of
|
|
636
|
-
sum +=
|
|
637
|
-
matchingItem.unitNetPrice *
|
|
638
|
-
matchingItem.unitQuantity *
|
|
639
|
-
(taxAmount / 100);
|
|
656
|
+
for (const matchingItem of group.items) {
|
|
657
|
+
sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (group.vatPercentage / 100);
|
|
640
658
|
}
|
|
641
|
-
|
|
642
|
-
items: matchingItems,
|
|
643
|
-
vatPercentage: taxAmount,
|
|
644
|
-
vatAmountSum: Math.round(sum * 100) / 100,
|
|
645
|
-
});
|
|
659
|
+
group.vatAmountSum = Math.round(sum * 100) / 100;
|
|
646
660
|
}
|
|
647
|
-
return vatGroups.sort(
|
|
661
|
+
return vatGroups.sort(
|
|
662
|
+
(a, b) =>
|
|
663
|
+
b.vatPercentage - a.vatPercentage ||
|
|
664
|
+
VAT_CATEGORY_ORDER.indexOf(a.vatCategory) - VAT_CATEGORY_ORDER.indexOf(b.vatCategory)
|
|
665
|
+
);
|
|
648
666
|
};
|
|
649
667
|
|
|
668
|
+
/**
|
|
669
|
+
* The label of a VAT group: the tax and its rate, and the category's name
|
|
670
|
+
* for a category other than standard rated and reverse charge (which has its
|
|
671
|
+
* statutory note below the sums); IGIC and IPSI are named instead of VAT
|
|
672
|
+
*/
|
|
673
|
+
private vatGroupLabel(groupArg: { vatPercentage: number; vatCategory: plugins.tsclass.finance.TVatCategory }): string {
|
|
674
|
+
const category = groupArg.vatCategory;
|
|
675
|
+
if (category === "L" || category === "M") {
|
|
676
|
+
return `${this.translateKey(`vat.category.${category}`)} ${groupArg.vatPercentage}%`;
|
|
677
|
+
}
|
|
678
|
+
const rate = `${this.translateKey("vat.short")} ${groupArg.vatPercentage}%`;
|
|
679
|
+
return category === "S" || category === "AE" ? rate : `${rate} – ${this.translateKey(`vat.category.${category}`)}`;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* The notes on the exemptions of the items (§ 14 Abs. 4 Satz 1 Nr. 8 UStG):
|
|
684
|
+
* the reason an item states (`vatExemptionReason`, EN 16931 BT-120), else,
|
|
685
|
+
* for an exempt item, an intra-community supply, an export or an item not
|
|
686
|
+
* subject to VAT, the words of its category. Items with the same note share
|
|
687
|
+
* it, with their positions. A reverse charge item is covered by the
|
|
688
|
+
* reverse-charge statement; a reason of its own that says something else is
|
|
689
|
+
* printed as well. The VATEX code (BT-121) is for machines and not printed.
|
|
690
|
+
*/
|
|
691
|
+
private getExemptionNotes(): { note: string; positions: number[] }[] {
|
|
692
|
+
const notes: { note: string; positions: number[] }[] = [];
|
|
693
|
+
const statutory = new Set([
|
|
694
|
+
plugins.shared.translation.REVERSE_CHARGE_STATUTORY_PHRASE_DE,
|
|
695
|
+
this.translateKey("invoice@@vat.reverseCharge.note"),
|
|
696
|
+
]);
|
|
697
|
+
(this.accountingDoc?.items ?? []).forEach((itemArg, index) => {
|
|
698
|
+
const category = this.vatCategoryOf(itemArg);
|
|
699
|
+
const stated = itemArg.vatExemptionReason?.trim();
|
|
700
|
+
const note =
|
|
701
|
+
stated ||
|
|
702
|
+
(category === "E" || category === "K" || category === "G" || category === "O"
|
|
703
|
+
? this.translateKey(`vat.category.${category}.note`)
|
|
704
|
+
: undefined);
|
|
705
|
+
if (!note || (category === "AE" && statutory.has(note))) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const existing = notes.find((noteArg) => noteArg.note === note);
|
|
709
|
+
if (existing) {
|
|
710
|
+
existing.positions.push(index + 1);
|
|
711
|
+
} else {
|
|
712
|
+
notes.push({ note, positions: [index + 1] });
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
return notes;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** The notes on the exemptions, each with its positions unless it applies to every item */
|
|
719
|
+
private renderExemptionNotes(): TemplateResult | null {
|
|
720
|
+
const notes = this.getExemptionNotes();
|
|
721
|
+
if (notes.length === 0) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
const itemCount = this.accountingDoc?.items.length ?? 0;
|
|
725
|
+
return html`<div class="taxNote">
|
|
726
|
+
${notes.map(
|
|
727
|
+
(noteArg) => html`<div>
|
|
728
|
+
${noteArg.note}${noteArg.positions.length < itemCount
|
|
729
|
+
? html` (${this.translateKey("invoice@@vat.position")}: ${noteArg.positions.join(", ")})`
|
|
730
|
+
: null}
|
|
731
|
+
</div>`
|
|
732
|
+
)}
|
|
733
|
+
</div>`;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Whether the document or one of its items is in reverse charge */
|
|
737
|
+
private hasReverseCharge(): boolean {
|
|
738
|
+
const accountingDoc = this.accountingDoc;
|
|
739
|
+
return !!accountingDoc && (accountingDoc.reverseCharge || accountingDoc.items.some((itemArg) => itemArg.vatCategory === "AE"));
|
|
740
|
+
}
|
|
741
|
+
|
|
650
742
|
/** the rendered shadow root; the element is always attached when the content is paginated */
|
|
651
743
|
private get root(): ShadowRoot {
|
|
652
744
|
const root = this.shadowRoot;
|
|
@@ -1409,8 +1501,7 @@ export class DeContentInvoice extends DeesElement {
|
|
|
1409
1501
|
return html`
|
|
1410
1502
|
<div class="sumline">
|
|
1411
1503
|
<div class="label">
|
|
1412
|
-
${this.
|
|
1413
|
-
${vatGroupArg.vatPercentage}%
|
|
1504
|
+
${this.vatGroupLabel(vatGroupArg)}
|
|
1414
1505
|
${this.documentSettings.vatGroupPositions
|
|
1415
1506
|
? html`
|
|
1416
1507
|
<br /><span style="font-weight: normal"
|
|
@@ -1437,7 +1528,8 @@ export class DeContentInvoice extends DeesElement {
|
|
|
1437
1528
|
${paidAmountSummary ? this.renderAdvancePayments(paidAmountSummary) : null}
|
|
1438
1529
|
<div class="divider"></div>
|
|
1439
1530
|
|
|
1440
|
-
${
|
|
1531
|
+
${this.hasReverseCharge() ? this.renderReverseChargeNote() : ``}
|
|
1532
|
+
${this.renderExemptionNotes()}
|
|
1441
1533
|
|
|
1442
1534
|
<!-- REFERENCED CONTRACT -->
|
|
1443
1535
|
${this.renderReferencedContract()}
|