@design.estate/dees-document 3.3.0 → 3.4.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.
@@ -452,6 +452,21 @@ export class DeContentInvoice extends DeesElement {
452
452
  font-weight: bold;
453
453
  }
454
454
 
455
+ /* the tax an advance payment contains is stated, not added up */
456
+ .sums .sumline--contained .label,
457
+ .sums .sumline--contained .value {
458
+ font-weight: normal;
459
+ font-size: 11px;
460
+ }
461
+
462
+ .advancePayment {
463
+ margin-top: 2px;
464
+ padding-left: 20%;
465
+ font-size: 11px;
466
+ text-align: right;
467
+ color: #555;
468
+ }
469
+
455
470
  .divider {
456
471
  margin-top: 8px;
457
472
  border-top: 1px dotted #ccc;
@@ -747,20 +762,28 @@ export class DeContentInvoice extends DeesElement {
747
762
 
748
763
  /**
749
764
  * The due date, which only an accounting document and a payment reminder
750
- * have: it counts `dueInDays` from the letter's date.
765
+ * have: it counts `dueInDays` from the letter's date. A document that
766
+ * states a paid amount names the amount due, since the total is not what
767
+ * remains to be paid.
751
768
  */
752
769
  private renderPaymentTerms(optionsArg: {
753
770
  date: number;
754
771
  dueInDays: number;
755
772
  labelKey: plugins.shared.translation.TranslationKey;
756
773
  textKey: plugins.shared.translation.TranslationKey;
774
+ /** the amount due, when the document states a paid amount */
775
+ amountDue?: number;
757
776
  }): TemplateResult {
758
777
  return html`<div class="infoBox">
759
778
  <div>
760
779
  <div>
761
780
  <div class="label">${this.translateKey(optionsArg.labelKey)}</div>
762
781
  <span>
763
- ${this.translateKey(optionsArg.textKey)}
782
+ ${optionsArg.amountDue === undefined
783
+ ? this.translateKey(optionsArg.textKey)
784
+ : html`${this.translateKey("payment.amountDue")}:
785
+ ${this.formatPrice(optionsArg.amountDue)}
786
+ ${this.translateKey("payment.terms.amountDue")}`}
764
787
  ${this.formatDay(
765
788
  new Date(optionsArg.date).setDate(
766
789
  new Date(optionsArg.date).getDate() + optionsArg.dueInDays
@@ -824,6 +847,115 @@ export class DeContentInvoice extends DeesElement {
824
847
  </div>`;
825
848
  }
826
849
 
850
+ /**
851
+ * The paid amount and the amount due, below the gross total, when the
852
+ * document states a paid amount (EN 16931 BT-113 and BT-115). The advance
853
+ * payments a final invoice deducts are deducted as the gross total received,
854
+ * with the tax it contains stated per rate, as Abschnitt 14.8 Abs. 7 Satz 3
855
+ * UStAE allows: "Statt der vorausgezahlten Entgelte oder Teilentgelte und
856
+ * der Steuerbeträge können auch die Gesamtbeträge der Voraus- oder
857
+ * Anzahlungen abgesetzt und die darin enthaltenen Steuerbeträge zusätzlich
858
+ * angegeben werden." More paid than the total prints as a credit balance.
859
+ */
860
+ private renderPaidAmountSums(
861
+ summaryArg: plugins.shared.IPaidAmountSummary
862
+ ): TemplateResult {
863
+ return html`<div class="sumline">
864
+ <div class="label">${this.translateKey("payment.paid")}</div>
865
+ <div class="value rightAlign amountCell">
866
+ ${this.formatPrice(summaryArg.paidAmount)}
867
+ </div>
868
+ </div>
869
+ ${summaryArg.advancePaymentTaxes.map(
870
+ (taxArg) => html`<div class="sumline sumline--contained">
871
+ <div class="label">
872
+ ${this.translateKey("advancePayment.vat")} ${taxArg.percentage}%
873
+ </div>
874
+ <div class="value rightAlign amountCell">
875
+ ${this.formatPrice(taxArg.vat)}
876
+ </div>
877
+ </div>`
878
+ )}
879
+ <div class="sumline">
880
+ <div class="label">
881
+ ${this.translateKey(
882
+ summaryArg.amountDue < 0 ? "payment.creditBalance" : "payment.amountDue"
883
+ )}
884
+ </div>
885
+ <div class="value value--total rightAlign amountCell">
886
+ ${this.formatPrice(Math.abs(summaryArg.amountDue))}
887
+ </div>
888
+ </div>`;
889
+ }
890
+
891
+ /**
892
+ * The advance payments the paid amount deducts, one line each, by the
893
+ * advance invoice's number and date and the day the payment was received,
894
+ * as far as the document states them. Each line is its own block, so a long
895
+ * list flows across pages.
896
+ */
897
+ private renderAdvancePayments(
898
+ summaryArg: plugins.shared.IPaidAmountSummary
899
+ ): TemplateResult[] {
900
+ return summaryArg.advancePayments.map(
901
+ (paymentArg) => html`<div class="advancePayment">
902
+ ${this.translateKey("advancePayment.title")}${paymentArg.invoice
903
+ ? html` ${this.translateKey("advancePayment.invoice")}
904
+ ${paymentArg.invoice.documentId}${paymentArg.invoice.issueDate !== undefined
905
+ ? html` ${this.translateKey("advancePayment.invoiceDate")}
906
+ ${this.formatDay(paymentArg.invoice.issueDate)}`
907
+ : null}`
908
+ : null}${paymentArg.receivedOn !== undefined
909
+ ? html`, ${this.translateKey("advancePayment.received")}
910
+ ${this.formatDay(paymentArg.receivedOn)}`
911
+ : null}
912
+ </div>`
913
+ );
914
+ }
915
+
916
+ /**
917
+ * What the document asks to be paid: the payment terms and the QR pay box
918
+ * for the gross total, or for the amount due when the document states a
919
+ * paid amount. When nothing is due, it says so, and names the credit
920
+ * balance when more was paid than the total; there are no terms and no QR
921
+ * pay box then.
922
+ */
923
+ private renderPaymentRequest(
924
+ accountingDocArg: plugins.tsclass.finance.TAccountingDoc,
925
+ summaryArg: plugins.shared.IPaidAmountSummary | null
926
+ ): TemplateResult {
927
+ if (summaryArg && summaryArg.amountDue <= 0) {
928
+ return html`<div class="infoBox">
929
+ <div>
930
+ <div>
931
+ <div class="label">${this.translateKey("invoice@@payment.terms")}</div>
932
+ <span>
933
+ ${summaryArg.amountDue === 0
934
+ ? this.translateKey("payment.nothingDue")
935
+ : html`${this.translateKey("payment.nothingDue.creditBalance")}
936
+ ${this.formatPrice(-summaryArg.amountDue)}.`}
937
+ </span>
938
+ </div>
939
+ </div>
940
+ </div>`;
941
+ }
942
+ return html`
943
+ ${this.renderPaymentTerms({
944
+ date: accountingDocArg.date,
945
+ dueInDays: accountingDocArg.dueInDays,
946
+ labelKey: "invoice@@payment.terms",
947
+ textKey: "invoice@@payment.terms.direct",
948
+ amountDue: summaryArg?.amountDue,
949
+ })}
950
+ ${this.renderPaymentInfo({
951
+ from: accountingDocArg.from,
952
+ currency: accountingDocArg.currency,
953
+ amount: summaryArg ? summaryArg.amountDue : this.getTotalGross(),
954
+ reference: accountingDocArg.id,
955
+ })}
956
+ `;
957
+ }
958
+
827
959
  /**
828
960
  * The reverse-charge statement. § 14a UStG requires the words
829
961
  * "Steuerschuldnerschaft des Leistungsempfängers": Abs. 5 for a domestic
@@ -1200,6 +1332,10 @@ export class DeContentInvoice extends DeesElement {
1200
1332
  return;
1201
1333
  }
1202
1334
  const accountingDoc = this.accountingDoc;
1335
+ // throws when the advance payments do not add up to the paid amount
1336
+ const paidAmountSummary = accountingDoc
1337
+ ? plugins.shared.getPaidAmountSummary(accountingDoc, this.getTotalGross())
1338
+ : null;
1203
1339
  render(
1204
1340
  html`
1205
1341
  ${this.renderCorrection()}
@@ -1296,7 +1432,9 @@ export class DeContentInvoice extends DeesElement {
1296
1432
  ${this.formatPrice(this.getTotalGross())}
1297
1433
  </div>
1298
1434
  </div>
1435
+ ${paidAmountSummary ? this.renderPaidAmountSums(paidAmountSummary) : null}
1299
1436
  </div>
1437
+ ${paidAmountSummary ? this.renderAdvancePayments(paidAmountSummary) : null}
1300
1438
  <div class="divider"></div>
1301
1439
 
1302
1440
  ${accountingDoc?.reverseCharge ? this.renderReverseChargeNote() : ``}
@@ -1307,24 +1445,9 @@ export class DeContentInvoice extends DeesElement {
1307
1445
  <!-- NOTES -->
1308
1446
  ${this.renderNotes(accountingDoc?.notes)}
1309
1447
 
1310
- <!-- PAYMENT TERMS -->
1311
- ${accountingDoc && this.docContent.requestsPayment
1312
- ? this.renderPaymentTerms({
1313
- date: accountingDoc.date,
1314
- dueInDays: accountingDoc.dueInDays,
1315
- labelKey: "invoice@@payment.terms",
1316
- textKey: "invoice@@payment.terms.direct",
1317
- })
1318
- : null}
1319
-
1320
- <!-- PAYMENT INFO -->
1448
+ <!-- PAYMENT TERMS AND PAYMENT INFO -->
1321
1449
  ${accountingDoc && this.docContent.requestsPayment
1322
- ? this.renderPaymentInfo({
1323
- from: accountingDoc.from,
1324
- currency: accountingDoc.currency,
1325
- amount: this.getTotalGross(),
1326
- reference: accountingDoc.id,
1327
- })
1450
+ ? this.renderPaymentRequest(accountingDoc, paidAmountSummary)
1328
1451
  : null}
1329
1452
  `,
1330
1453
  contentNodes.currentContent
@@ -151,6 +151,17 @@ export class DeDocument extends DeesElement {
151
151
  public paginationInfo: IPagePaginationInfo[] = [];
152
152
 
153
153
  public async renderDocument() {
154
+ // a document whose advance payments do not add up to its paid amount is
155
+ // refused: no page of it, and none of the letter before it, stays on screen
156
+ try {
157
+ plugins.shared.assertPaidAmountMatchesAdvancePayments(this.letterData);
158
+ } catch (error) {
159
+ this.latestDocumentSettings = this.documentSettings;
160
+ this.latestRenderedLetterData = this.letterData;
161
+ this.paginationInfo = [];
162
+ this.shadowRoot?.querySelector(".documentContainer")?.replaceChildren();
163
+ throw error;
164
+ }
154
165
  this.latestDocumentSettings = this.documentSettings;
155
166
  this.latestRenderedLetterData = this.letterData;
156
167
  this.paginationInfo = [];