diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFInvoiceLineBuilder.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFInvoiceLineBuilder.java index 3c9ce7f..af42f52 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFInvoiceLineBuilder.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFInvoiceLineBuilder.java @@ -286,34 +286,49 @@ public class KSeFInvoiceLineBuilder { Map aggregates, BigDecimal grossTotal) { - Element elementAdnotacje = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, "Adnotacje"); + // Aggregate by target XML field name, because multiple TaxCodes + // can map to the same summary field (e.g. CARGO_0, CARGO_0MR, + // CARGO_023 all target P_13_6_1). + Map netByField = new java.util.LinkedHashMap<>(); + Map vatByField = new java.util.LinkedHashMap<>(); + boolean reverseCharge = false; for (Map.Entry entry : aggregates.entrySet()) { TaxCode taxCode = entry.getKey(); NetVatPair pair = entry.getValue(); - // Net summary field (always present) - String netField = taxCode.getNetSummaryField(); - Element netElement = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, netField); - netElement.setTextContent(format(pair.net)); - logger.info("│ ├── set " + netField + " = " + format(pair.net)); + netByField.merge(taxCode.getNetSummaryField(), pair.net, BigDecimal::add); - // VAT summary field (only for taxable codes) if (taxCode.hasVat()) { - String vatField = taxCode.getVatSummaryField(); - Element vatElement = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, vatField); - vatElement.setTextContent(format(pair.vat)); - logger.info("│ ├── set " + vatField + " = " + format(pair.vat)); + vatByField.merge(taxCode.getVatSummaryField(), pair.vat, BigDecimal::add); } - // Set P_18 - Element p18Element = model.findOrCreateChildNode(elementAdnotacje, EInvoiceNS.KSEF, "P_18"); - p18Element.setTextContent(taxCode.getP18Value()); - logger.info("│ ├── set P_18 = " + taxCode.getP18Value()); - + if ("1".equals(taxCode.getP18Value())) { + reverseCharge = true; + } } - // P_15 - gross total of the entire invoice (always present) + // Write net summary fields (P_13_x) + for (Map.Entry entry : netByField.entrySet()) { + Element netElement = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, entry.getKey()); + netElement.setTextContent(format(entry.getValue())); + logger.info("│ ├── set " + entry.getKey() + " = " + format(entry.getValue())); + } + + // Write VAT summary fields (P_14_x) + for (Map.Entry entry : vatByField.entrySet()) { + Element vatElement = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, entry.getKey()); + vatElement.setTextContent(format(entry.getValue())); + logger.info("│ ├── set " + entry.getKey() + " = " + format(entry.getValue())); + } + + // P_18 - reverse charge flag + Element elementAdnotacje = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, "Adnotacje"); + Element p18Element = model.findOrCreateChildNode(elementAdnotacje, EInvoiceNS.KSEF, "P_18"); + p18Element.setTextContent(reverseCharge ? "1" : "2"); + logger.info("│ ├── set P_18 = " + (reverseCharge ? "1" : "2")); + + // P_15 - gross total Element p15 = model.findOrCreateChildNode(elementFa, EInvoiceNS.KSEF, "P_15"); p15.setTextContent(format(grossTotal)); logger.info("│ ├── set P_15 = " + format(grossTotal)); diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/api/KSeFAdapterTest.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/api/KSeFAdapterTest.java index 5a8d850..a34bdb8 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/api/KSeFAdapterTest.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/api/KSeFAdapterTest.java @@ -491,6 +491,86 @@ public class KSeFAdapterTest { "Unknown CargoSoft VAT code must raise a PluginException"); } + /** + * Multiple 0% KR codes (023, 0, 0MR) on the same invoice in USD. + *

+ * Regression test for the summary aggregation bug: all three CargoSoft + * codes map to {@code P_13_6_1}. Before the fix, only the last code's + * amount survived because {@code setTextContent} overwrote the previous + * value instead of summing. Expected: {@code P_13_6_1 = 2640.00} + * (1515 + 340 + 785). + */ + @Test + @DisplayName("Test Multiple 0% KR Codes aggregate into P_13_6_1 (USD)") + public void testMultipleZeroRateCodes() throws Exception { + logger.info("==> Test: Multiple 0% KR Codes"); + + ItemCollection workitem = new ItemCollection(); + workitem.setItemValue("invoice.number", "FV/2026/7795"); + workitem.setItemValue("invoice.date", LocalDate.of(2026, 5, 22)); + workitem.setItemValue("invoice.duedate", LocalDate.of(2026, 6, 21)); + workitem.setItemValue("invoice.performancedate", LocalDate.of(2026, 5, 21)); + workitem.setItemValue("invoice.currency", "USD"); + workitem.setItemValue("invoice.rate", 0.2724); // CargoSoft format + workitem.setItemValue("invoice.correction", "false"); + workitem.setItemValue("invoice.CorrectionInvoiceNumber", ""); + workitem.setItemValue("partner.id", "BP-001"); + workitem.setItemValue("partner.vat", "PL1234567890"); + + List childItems = new ArrayList<>(); + + // Position 1: code "023" -> 0 KR -> P_13_6_1 + ItemCollection lineItem1 = new ItemCollection() + .setItemValue("numpos", "1") + .setItemValue("datev.text", "Fracht morski") + .setItemValue("billingtext", "Usługa spedycyjna: Fracht morski, + ubezpieczenie") + .setItemValue("datev.umsatz", 1515.00) + .setItemValue("datev.vatrate", 0.0) + .setItemValue("cargosoft.vat.code", "023"); + childItems.add(lineItem1.getAllItems()); + + // Position 2: code "0" -> 0 KR -> P_13_6_1 + ItemCollection lineItem2 = new ItemCollection() + .setItemValue("numpos", "2") + .setItemValue("datev.text", "THC") + .setItemValue("billingtext", "Usługa spedycyjna: THC") + .setItemValue("datev.umsatz", 340.00) + .setItemValue("datev.vatrate", 0.0) + .setItemValue("cargosoft.vat.code", "0"); + childItems.add(lineItem2.getAllItems()); + + // Position 3: code "0MR" -> 0 KR -> P_13_6_1 + ItemCollection lineItem3 = new ItemCollection() + .setItemValue("numpos", "3") + .setItemValue("datev.text", "Transport intermodalny") + .setItemValue("billingtext", "Usługa spedycyjna:, transport intermodalny") + .setItemValue("datev.umsatz", 785.00) + .setItemValue("datev.vatrate", 0.0) + .setItemValue("cargosoft.vat.code", "0MR"); + childItems.add(lineItem3.getAllItems()); + + workitem.setItemValue("_childitems", childItems); + + when(businessPartnerService.getBusinessPartnerByID("BP-001")) + .thenReturn(businessPartner); + + FileData xmlTemplate = loadTemplateFromResources(TEMPLATE_FILE); + + adapter.updateEInvoice(xmlTemplate, workitem); + + assertNotNull(xmlTemplate.getContent(), "XML content should not be null after update"); + assertTrue(xmlTemplate.getContent().length > 0, "XML content should not be empty"); + + // Verify that P_13_6_1 contains the SUM of all three positions + String xml = new String(xmlTemplate.getContent(), "UTF-8"); + assertTrue(xml.contains("2640.00"), + "P_13_6_1 must be the sum of all 0%KR positions (1515 + 340 + 785 = 2640.00) but was not"); + assertTrue(xml.contains("2640.00"), + "P_15 must equal the gross total (2640.00) but was not"); + + writeOutputToResources(xmlTemplate, "invoice-multiple-zero-rate-codes.xml"); + } + // ── Helper methods ────────────────────────────────────────────── /** diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-eu-service-np.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-eu-service-np.xml index 703d868..bf5d589 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-eu-service-np.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-eu-service-np.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.122365080Z + 2026-05-28T19:15:59.290135Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/7470 - 2026-05-14 + 2026-05-28 4620.00 4620.00 0.2326 diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-mixed-rates.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-mixed-rates.xml index d7364b1..863a3eb 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-mixed-rates.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-mixed-rates.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.516800880Z + 2026-05-28T19:15:59.433548Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/7471 - 2026-05-14 + 2026-05-28 3960.00 910.80 14040.00 diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-multiple-zero-rate-codes.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-multiple-zero-rate-codes.xml new file mode 100644 index 0000000..0aa21e4 --- /dev/null +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-multiple-zero-rate-codes.xml @@ -0,0 +1,124 @@ + + + + FA + 3 + 2026-05-28T19:15:59.040792Z + Imixs eInvoice + + + PL + + 9552521552 + ALEXANDER GLOBAL LOGISTICS POLAND sp. z o.o. + + + PL + ul. Gdańska 36, 70-660 Szczecin + + + MBudas@alexander-logistics.com + + + + + 1234567890 + Test Sp. z o.o. + + + PL + ul. Testowa 1 + 00-001 Warszawa + + D-12345 + 2 + 2 + + + USD + 2026-05-22 + Szczecin + FV/2026/7795 + 2026-05-21 + 2640.00 + 2640.00 + 3.6711 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + Usługa spedycyjna: Fracht morski, + ubezpieczenie + szt. + 1.00 + 1515.00 + 1515.00 + 1515.00 + 0.00 + 0 KR + + + 2 + Usługa spedycyjna: THC + szt. + 1.00 + 340.00 + 340.00 + 340.00 + 0.00 + 0 KR + + + 3 + Usługa spedycyjna:, transport intermodalny + szt. + 1.00 + 785.00 + 785.00 + 785.00 + 0.00 + 0 KR + + + + 2026-06-21 + + 6 + + PL79116022020000000654306674 + BIGBPLPWXXX + Bank Millennium S.A. + Konto PLN + + + PL16116022020000000654910469 + BIGBPLPWXXX + Bank Millennium S.A. + Konto EUR + + + PL72116022020000000654911595 + BIGBPLPWXXX + Bank Millennium S.A. + Konto USD + + + + diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-np.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-np.xml index a0dcd88..7342d97 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-np.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-np.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.478749958Z + 2026-05-28T19:15:59.394284Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/001 - 2026-05-14 + 2026-05-28 10000.00 10000.00 diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-npt.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-npt.xml index 2a86850..c56d1ad 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-npt.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-npt.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.408813197Z + 2026-05-28T19:15:59.342086Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/001 - 2026-05-14 + 2026-05-28 10000.00 10000.00 diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-simple.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-simple.xml index 00f7f7f..6d61c21 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-simple.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/invoice-simple.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.613224263Z + 2026-05-28T19:15:59.512152Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/001 - 2026-05-14 + 2026-05-28 10000.00 2300.00 12300.00 diff --git a/office-alexander-logistics-app/src/test/resources/ksef/output/test-korrekturrechnung.xml b/office-alexander-logistics-app/src/test/resources/ksef/output/test-korrekturrechnung.xml index b71fafd..2507d5b 100644 --- a/office-alexander-logistics-app/src/test/resources/ksef/output/test-korrekturrechnung.xml +++ b/office-alexander-logistics-app/src/test/resources/ksef/output/test-korrekturrechnung.xml @@ -6,7 +6,7 @@ FA 3 - 2026-05-14T09:50:45.565082350Z + 2026-05-28T19:15:59.464195Z Imixs eInvoice @@ -42,7 +42,7 @@ 2025-02-10 Szczecin FV/2025/001 - 2026-05-14 + 2026-05-28 -900.00 -207.00 -48.15