From eb05c257190192d2e54ac61d04ee9f2968d681dc Mon Sep 17 00:00:00 2001 From: Jonathan Hamberger Date: Tue, 19 Aug 2025 14:51:41 +0200 Subject: [PATCH 01/18] Update SubjectCode.java Added subject code 'PMT' --- library/src/main/java/org/mustangproject/SubjectCode.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/SubjectCode.java b/library/src/main/java/org/mustangproject/SubjectCode.java index 808c09d4..539314d1 100644 --- a/library/src/main/java/org/mustangproject/SubjectCode.java +++ b/library/src/main/java/org/mustangproject/SubjectCode.java @@ -40,5 +40,9 @@ public enum SubjectCode { /** * Vehicle licence number */ - ABZ + ABZ, + /** + * Payment information + */ + PMT } From 7813f2db59ebd028b32a82ad224f50bb993a1c54 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 19 Aug 2025 18:46:42 +0200 Subject: [PATCH 02/18] json ignoring some attributes --- History.md | 2 ++ library/src/main/java/org/mustangproject/Invoice.java | 2 ++ library/src/main/java/org/mustangproject/Product.java | 3 +++ 3 files changed, 7 insertions(+) diff --git a/History.md b/History.md index 1efe1d53..917531d5 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,5 @@ +- added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way) + 2.19.0 ======= 2025-08-12 diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 72d71a6c..b1ec632f 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -23,6 +23,7 @@ package org.mustangproject; import java.math.BigDecimal; import java.util.*; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.*; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; @@ -754,6 +755,7 @@ public class Invoice implements IExportableTransaction { * checks if all required items are set in order to be able to export it * @return true if all required items are set */ + @JsonIgnore public boolean isValid() { return (dueDate != null) && (sender != null) && (sender.getTaxID() != null) && (sender.getVATID() != null) && (recipient != null); //contact diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 467fbb60..6aca773a 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -214,11 +214,14 @@ public class Product implements IZUGFeRDExportableProduct { return this; } + @JsonIgnore @Override public boolean isReverseCharge() { return isReverseCharge; } + + @JsonIgnore @Override public boolean isIntraCommunitySupply() { return isIntraCommunitySupply; From 4876c3180a51e54143cad2e906864193b7e76cd3 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 20 Aug 2025 16:32:30 +0200 Subject: [PATCH 03/18] json ignore more elements --- History.md | 1 + library/src/main/java/org/mustangproject/Invoice.java | 5 +++++ .../org/mustangproject/ZUGFeRD/IExportableTransaction.java | 3 +++ 3 files changed, 9 insertions(+) diff --git a/History.md b/History.md index 917531d5..8a51971b 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,5 @@ - added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way) +- 917? 2.19.0 ======= diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index b1ec632f..d1a31537 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -394,24 +394,29 @@ public class Invoice implements IExportableTransaction { return this; } + + @JsonIgnore @Override public String getOwnStreet() { return sender.getStreet(); } + @JsonIgnore @Override public String getOwnZIP() { return sender.getZIP(); } + @JsonIgnore @Override public String getOwnLocation() { return sender.getLocation(); } + @JsonIgnore @Override public String getOwnCountry() { return sender.getCountry(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index 9ce55ef1..c7a6d903 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.mustangproject.FileAttachment; import org.mustangproject.IncludedNote; import org.mustangproject.ReferencedDocument; @@ -181,6 +182,7 @@ public interface IExportableTransaction { * * @return Tax ID (not VAT ID) of the sender */ + @JsonIgnore default String getOwnTaxID() { if (getSender() != null) { return getSender().getTaxID(); @@ -194,6 +196,7 @@ public interface IExportableTransaction { * * @return VAT ID (Umsatzsteueridentifikationsnummer) of the sender */ + @JsonIgnore default String getOwnVATID() { if (getSender() != null) { return getSender().getVATID(); From 3a3993cfdfcac90be16cf1cb2fc9c3e01fe8b880 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 20 Aug 2025 16:40:27 +0200 Subject: [PATCH 04/18] added a test for #917 --- .../mustangproject/ZUGFeRD/CalculationTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index f386d655..80bf4b35 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -5,12 +5,15 @@ import static java.math.BigDecimal.valueOf; import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.xmlunit.assertj.XmlAssert.assertThat; import org.junit.Test; import org.mustangproject.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xmlunit.builder.Input; +import javax.xml.transform.Source; import javax.xml.xpath.XPathExpressionException; import java.io.*; import java.math.BigDecimal; @@ -283,6 +286,18 @@ public class CalculationTest extends ResourceCase { item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); invoice.addItem(item); + + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(invoice); + + + String theXML = new String(zf2p.getXML()); + assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']") + .asString() + .isEqualTo("0.55");// test for issue #917 + + TransactionCalculator calculator = new TransactionCalculator(invoice); assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros()); } From 3f1634588f1330caac311b0fbcdc7772f1faf7ef Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 20 Aug 2025 16:57:25 +0200 Subject: [PATCH 05/18] updated tests for new jsonignore --- .../org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 47ac24cb..ad1998d7 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -407,7 +407,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { ObjectMapper mapper = new ObjectMapper(); String jsonArray = mapper.writeValueAsString(i); - JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false); + JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}]}",jsonArray,false); } catch (IOException e) { fail("IOException not expected"); @@ -439,7 +439,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); String expectedPaymentTermDesciption="Please remit until "+german.format(now); - JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); + JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00,}, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00}, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00 }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ]}",jsonArray,true); } catch (IOException e) { fail("IOException not expected"); } catch (XPathExpressionException e) { From 2e4ab561af85ab2cc310fc2c00d9c2096cf3f411 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 20 Aug 2025 17:45:51 +0200 Subject: [PATCH 06/18] closes #917 --- History.md | 2 +- .../java/org/mustangproject/Allowance.java | 3 +- .../main/java/org/mustangproject/Charge.java | 7 +-- .../main/java/org/mustangproject/Item.java | 3 -- .../ZUGFeRD/IAbsoluteValueProvider.java | 3 ++ .../ZUGFeRD/LineCalculator.java | 13 +---- .../ZUGFeRD/CalculationTest.java | 54 +++++++++++++++++++ 7 files changed, 65 insertions(+), 20 deletions(-) diff --git a/History.md b/History.md index 8a51971b..49adceb6 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,5 @@ - added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way) -- 917? +- #917 2.19.0 ======= diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java index cca2033a..ab8bc8e9 100644 --- a/library/src/main/java/org/mustangproject/Allowance.java +++ b/library/src/main/java/org/mustangproject/Allowance.java @@ -39,9 +39,8 @@ public class Allowance extends Charge { return totalAmount; } else if (percent!=null) { BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); -// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); - return singlePriceDiff; + return singlePriceDiff.multiply(currentItem.getQuantity()); } else { throw new RuntimeException("percent must be set"); } diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index 3d647757..524c8ce0 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -145,9 +145,10 @@ public class Charge implements IZUGFeRDAllowanceCharge { if(totalAmount != null) { return totalAmount; } else if (percent!=null) { - BigDecimal factor=getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP); - BigDecimal singlePrice=currentItem.getValue().multiply(factor); - return singlePrice; + BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP))); + BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); + return singlePriceDiff.multiply(currentItem.getQuantity()); + } else { throw new RuntimeException("percent must be set"); } diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index b8afa6be..fabc88de 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -203,9 +203,6 @@ public class Item implements IZUGFeRDExportableItem { } if (amountString != null) { izac.setTotalAmount(new BigDecimal(amountString)); - if (percentString != null && (!percentString.equals("0"))) { - izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity())); - } } if (basisAmountString != null) { izac.setBasisAmount(new BigDecimal(basisAmountString)); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java index 3c4be8b5..cf3e50dc 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java @@ -24,4 +24,7 @@ public interface IAbsoluteValueProvider { public BigDecimal getValue(); + default BigDecimal getQuantity() { + return BigDecimal.ONE; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index 50ea54ae..33f883f4 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -24,26 +24,17 @@ public class LineCalculator { if (currentItem.getItemAllowances() != null) { for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { - BigDecimal factor=BigDecimal.ONE; BigDecimal singleAllowance=allowance.getTotalAmount(currentItem); addItemAllowance(singleAllowance); - - if ((allowance.getPercent()!=null)&&(allowance.getPercent().compareTo(BigDecimal.ZERO)!=0)) { - factor=currentItem.getQuantity(); - } - addAllowanceItemTotal(singleAllowance.multiply(factor)); + addAllowanceItemTotal(singleAllowance); } } if (currentItem.getItemCharges() != null) { for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { - BigDecimal factor=BigDecimal.ONE; BigDecimal singleCharge=charge.getTotalAmount(currentItem); addItemCharge(singleCharge); - if ((charge.getPercent()!=null)&&(charge.getPercent().compareTo(BigDecimal.ZERO)!=0)) { - factor=currentItem.getQuantity(); - } - subtractAllowanceItemTotal(singleCharge.multiply(factor)); + subtractAllowanceItemTotal(singleCharge); } } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 80bf4b35..680b312e 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -302,6 +302,60 @@ public class CalculationTest extends ResourceCase { assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros()); } + public void testSimpleItemPercentCharge() { + /*** + * a product with net 1.10 and qty 5 and relative item allowance of 10% should return 5 as line and grand total + */ + SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); + + Invoice invoice = new Invoice(); + invoice.setDocumentName("Rechnung"); + invoice.setNumber("777777"); + try { + invoice.setIssueDate(sqlDate.parse("2020-12-31")); + invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1])); + invoice.setDeliveryDate(sqlDate.parse("2020-12-31")); + invoice.setDueDate(sqlDate.parse("2021-01-15")); + } catch (Exception e) { + LOGGER.error("Failed to set dates", e); + + } + TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE"); + sender.addVATID("DE2222222222"); + invoice.setSender(sender); + + /* trade party (recipient) */ + TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE"); + recipient.setID("111111"); + recipient.addVATID("DE111111111"); + invoice.setRecipient(recipient); + + /* item */ + Product product; + Item item; + + product = new Product("AAA", "", "H87", BigDecimal.ZERO); + item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00)); + + item.addCharge(new Charge().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); + invoice.addItem(item); + + + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(invoice); + + + String theXML = new String(zf2p.getXML()); + assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']") + .asString() + .isEqualTo("0.55");// test for issue #917 + + + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(new BigDecimal("6.05"), calculator.getGrandTotal().stripTrailingZeros()); + } + public void testSimpleDocumentPercentCharge() { String orgname = "Test company"; From 6bdb493929fff047d8f9bdb24aaae6b03699727e Mon Sep 17 00:00:00 2001 From: Matthias Kilian Date: Mon, 25 Aug 2025 16:09:35 +0200 Subject: [PATCH 07/18] Treat schematron rule flag "information" as notice. XRechnung 3.0.3 Summer 2025 edition introduced a rule with flag="information", breaking the Mustangproject validator which doesn't expect such a flag value. See https://projekte.kosit.org/xrechnung/xrechnung-schematron/-/commit/06be5b2c75f79a5f5e2f5156a34117f60f1fe099 Explicitly mapping "information" to ESeverity.notice hopefully fixes this and any future additions of rules with flag="information". --- .../validator/XMLValidator.java | 7 +++-- .../validator/ZUGFeRDValidatorTest.java | 6 ++--- validator/src/test/resources/validXRV30.xml | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 5e8ce312..e7615f33 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -536,12 +536,15 @@ public class XMLValidator extends Validator { } ESeverity severity; + Node failNode = currentFailNode.getAttributes().getNamedItem("flag"); + String failVal = failNode == null ? null : failNode.getNodeValue(); if (defaultSeverity == ESeverity.notice) { severity = defaultSeverity; - } else if (currentFailNode.getAttributes().getNamedItem("flag") != null - && "warning".equals(currentFailNode.getAttributes().getNamedItem("flag").getNodeValue())) { + } else if ("warning".equals(failVal)) { // the XR issues warnings with flag=warning severity = ESeverity.warning; + } else if ("information".equals(failVal)) { + severity = ESeverity.notice; } else { severity = ESeverity.error; } diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java index 4adb787b..5e28bdec 100644 --- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java @@ -202,7 +202,7 @@ public class ZUGFeRDValidatorTest extends ResourceCase { assertThat(res).valueByXPath("count(//notice)") .asInt() - .isEqualTo(0); + .isEqualTo(1); assertThat(res).valueByXPath("/validation/summary/@status") .asString() .isEqualTo("valid");// expect to be valid because XR notices are, well, only notices @@ -216,14 +216,14 @@ public class ZUGFeRDValidatorTest extends ResourceCase { assertThat(res).valueByXPath("count(//error)") .asInt() - .isEqualTo(5); + .isEqualTo(4); assertThat(res).valueByXPath("count(//warning)") .asInt() .isEqualTo(1); assertThat(res).valueByXPath("count(//notice)") .asInt() - .isEqualTo(0); // 12 notices RE XRechnung 3.0 + .isEqualTo(1); // 12 notices RE XRechnung 3.0 assertThat(res).valueByXPath("/validation/summary/@status") .asString() .isEqualTo("invalid");// expect to be valid diff --git a/validator/src/test/resources/validXRV30.xml b/validator/src/test/resources/validXRV30.xml index adffb992..5a78754a 100644 --- a/validator/src/test/resources/validXRV30.xml +++ b/validator/src/test/resources/validXRV30.xml @@ -102,6 +102,32 @@ + + + Zeitlose Diensleistung + + + Zeitlose Diensleistung + + + + 0.00 + + + + 1 + + + + VAT + S + 7 + + + 0.00 + + + 04011000-12345-03 From dadf0de4b0c160bad2d1ad6de87cf989ed0bcefb Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 1 Sep 2025 10:38:54 +0200 Subject: [PATCH 08/18] closes #926 --- History.md | 5 ++++- .../org/mustangproject/validator/ValidationContext.java | 9 +++++++++ .../java/org/mustangproject/validator/XMLValidator.java | 8 ++++++++ .../org/mustangproject/validator/ZUGFeRDValidator.java | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 49adceb6..d676ce27 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,9 @@ - added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way) - #917 - +- #915 +- #921 +- #926 +- 2.19.0 ======= 2025-08-12 diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java index 4c5edb88..ba939eaa 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java @@ -14,6 +14,7 @@ public class ValidationContext { private String profile = null; private String signature = null; private boolean isValid = true; + private boolean hasPDF = false; protected Logger logger; private String filename; @@ -22,6 +23,14 @@ public class ValidationContext { results = new Vector<>(); } + public void setHasPDF() { + hasPDF=true; + } + + public boolean hasPDF() { + return hasPDF; + } + public void addResultItem(ValidationResultItem vr) throws IrrecoverableValidationError { results.add(vr); diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index e7615f33..8d827418 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -374,6 +374,14 @@ public class XMLValidator extends Validator { addUnsupportedProfileResultItem(); } } + } else { + // no CII -> has to be UBL + if (context.hasPDF()) { + final ValidationResultItem vri = new ValidationResultItem(ESeverity.error, "Factur-X/ZUGFeRD and Order-X are always strictly CII only, no UBL allowed.").setSection(17) + .setPart(EPart.fx); + context.addResultItem(vri); + + } } if (xsltFilename != null) { diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index bb5e9576..e162edd9 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -121,6 +121,7 @@ public class ZUGFeRDValidator { // Avoid reading again from file pdfv.setFilenameAndContents(contextFilename, content); + context.setHasPDF(); optionsRecognized = true; finalStringResult.append(""); try { From 37e4e43ed2f95c8d95d869503d7866d121f5a50e Mon Sep 17 00:00:00 2001 From: langfr Date: Sat, 6 Sep 2025 12:04:08 +0100 Subject: [PATCH 09/18] Squashed commit of the following: commit f0a44d757de2693e5baa89ac68192ee92632e0a2 Author: langfr Date: Sat Sep 6 12:00:42 2025 +0100 Enable option setting Additional Document Description 2. commit 15524a2ed661fb44f782863da5d7e9fcf4f68e78 Author: langfr Date: Tue Sep 2 21:30:30 2025 +0100 Enable option setting Additional Document Description. Enable setting and reading Additional Document Description. --- .../main/java/org/mustangproject/FileAttachment.java | 12 +++++++++--- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 6 ++++++ .../test/java/org/mustangproject/ZUGFeRD/XRTest.java | 5 +++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/org/mustangproject/FileAttachment.java b/library/src/main/java/org/mustangproject/FileAttachment.java index 99a60419..791db6d4 100644 --- a/library/src/main/java/org/mustangproject/FileAttachment.java +++ b/library/src/main/java/org/mustangproject/FileAttachment.java @@ -10,7 +10,7 @@ public class FileAttachment { protected String filename; protected String mimetype; protected String relation = "Unspecified"; - protected String description; + protected String description = "Additional file attachment"; protected byte[] data; @@ -21,19 +21,25 @@ public class FileAttachment { } + public FileAttachment(String filename, String mimetype, String relation, byte[] data, String description) { + this.filename = filename; + this.mimetype = mimetype; + this.relation = relation; + this.data = data; + this.description = description; + } + public FileAttachment(String filename, String mimetype, String relation, byte[] data) { this.filename = filename; this.mimetype = mimetype; this.relation = relation; this.data = data; - this.description = "Additional file attachment"; } public FileAttachment(String filename, String mimetype, byte[] data) { this.filename = filename; this.mimetype = mimetype; this.data = data; - this.description = "Additional file attachment"; } public String getDescription() { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index fce06f01..cdfe1d29 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -994,6 +994,12 @@ public class ZUGFeRDInvoiceImporter { NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); for (int i = 0; i < attachmentNodes.getLength(); i++) { FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i)))); + NodeList nl = attachmentNodes.item(i).getParentNode().getChildNodes(); + for (int j = 0; j < nl.getLength(); j++) { + if (nl.item(j).getLocalName() != null && nl.item(j).getLocalName().equals("Name")) { + fa.setDescription(nl.item(j).getTextContent()); + } + } zpp.embedFileInXML(fa); // filename = "Aufmass.png" mimeCode = "image/png" //EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png" diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index 42fd653c..cba6aeca 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -102,7 +102,7 @@ public class XRTest extends TestCase { BigDecimal amount = new BigDecimal(amountStr); byte[] b = {12, 13}; - FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b); + FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b,"Beschreibung"); Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX").setAccountName("kontoInhaber"))) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setEmail("recipient@sample.org")) @@ -121,6 +121,7 @@ public class XRTest extends TestCase { zf2p.setProfile(Profiles.getByName("XRechnung")); zf2p.generateXML(i); String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8); + System.out.println(theXML); assertTrue(theXML.contains(" Date: Mon, 8 Sep 2025 09:56:07 +0200 Subject: [PATCH 10/18] Fix typo --- validator/src/test/resources/validXRV30.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validator/src/test/resources/validXRV30.xml b/validator/src/test/resources/validXRV30.xml index 5a78754a..93caadfb 100644 --- a/validator/src/test/resources/validXRV30.xml +++ b/validator/src/test/resources/validXRV30.xml @@ -104,10 +104,10 @@ - Zeitlose Diensleistung + Zeitlose Dienstleistung - Zeitlose Diensleistung + Zeitlose Dienstleistung From 2e96ea6048cc0433a270c950365c3731bc03dac1 Mon Sep 17 00:00:00 2001 From: Felix Eckhofer Date: Mon, 8 Sep 2025 21:38:33 +0200 Subject: [PATCH 11/18] Format list of dates individually This is already done for the HTML visualisation in library/src/main/resources/stylesheets/xrechnung-html.de.ids.xsl:1584 ff Fixes #413 Fixes #557 Fixes #765 --- .../stylesheets/xr-pdf/lib/structure/content-templates.xsl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl index de3c25aa..5255ab52 100644 --- a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl +++ b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl @@ -194,7 +194,12 @@ - + + + + ; + + From fca0a85c7a4a6c58fd87b8e74a76eaca0093e4f1 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 18 Sep 2025 10:14:56 +0200 Subject: [PATCH 12/18] upgrade fop --- History.md | 3 ++- library/pom.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index d676ce27..2ea63183 100644 --- a/History.md +++ b/History.md @@ -3,7 +3,8 @@ - #915 - #921 - #926 -- +- upgrade apache fop 2.10 to 2.11 + 2.19.0 ======= 2025-08-12 diff --git a/library/pom.xml b/library/pom.xml index 37454501..bc81cf7f 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -81,7 +81,7 @@ org.apache.xmlgraphics fop - 2.10 + 2.11 xml-apis From cae963a1ca5c5769bf898cfd17b6a1e1d9f069d0 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 23 Sep 2025 11:01:11 +0200 Subject: [PATCH 13/18] added history for merged PRs --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index 2ea63183..39907279 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,9 @@ - #921 - #926 - upgrade apache fop 2.10 to 2.11 +- #931 +- #932 +- #933, #413, #557, #765 2.19.0 ======= From ec5bbfdc5c47727706392707f4e14498b12a8e62 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 30 Sep 2025 19:44:02 +0200 Subject: [PATCH 14/18] - make Line Calculation, e.g. total line net amount, accessible via JSON using getCalculation --- History.md | 1 + .../main/java/org/mustangproject/Item.java | 1 + .../ZUGFeRD/DAPullProvider.java | 2 +- .../ZUGFeRD/IZUGFeRDExportableItem.java | 2 + .../ZUGFeRD/OXPullProvider.java | 2 +- .../ZUGFeRD/TransactionCalculator.java | 4 +- .../ZUGFeRD/ZUGFeRD1PullProvider.java | 2 +- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 2 +- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 2 +- .../ZUGFeRD/CalculationTest.java | 12 +- .../ZUGFeRD/DeSerializationTest.java | 14 ++ .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 150 +++++++++++++++++- 12 files changed, 179 insertions(+), 15 deletions(-) diff --git a/History.md b/History.md index 39907279..4f0f485e 100644 --- a/History.md +++ b/History.md @@ -7,6 +7,7 @@ - #931 - #932 - #933, #413, #557, #765 +- make Line Calculation, e.g. total line net amount, accessible via JSON using getCalculation 2.19.0 ======= diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index fabc88de..5a3853a0 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; +import org.mustangproject.ZUGFeRD.LineCalculator; import org.mustangproject.util.NodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java index 9bac1b07..7caa3e52 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java @@ -79,7 +79,7 @@ public class DAPullProvider extends ZUGFeRD2PullProvider { if (currentItem.getProduct().getTaxExemptionReason() != null) { // exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 04cadc22..05d86170 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -205,4 +205,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ default String getAccountingReference() { return null; } + + default LineCalculator getCalculation() {return new LineCalculator(this); }; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java index f2a032f2..7659b526 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java @@ -107,7 +107,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { // exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 02ae965d..f8c80127 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -195,7 +195,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { percent = currentItem.getProduct().getVATPercent(); } if (percent != null) { - LineCalculator lc = new LineCalculator(currentItem); + LineCalculator lc = currentItem.getCalculation(); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); String reasonText = currentItem.getProduct().getTaxExemptionReason(); @@ -265,7 +265,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } if (percent != null) { - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); final VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode, percent); final String reasonText = currentItem.getProduct().getTaxExemptionReason(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 87e6f479..8c74e361 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -344,7 +344,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider { } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); xml += "" + "" + "" + lineID + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 87ffb274..c51cb60c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -415,7 +415,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getId() != null) { lineIDStr = currentItem.getId(); } - final LineCalculator lc = new LineCalculator(currentItem); + final LineCalculator lc = currentItem.getCalculation(); if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) { xml += "" + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index cdfe1d29..2e105fc1 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -1146,7 +1146,7 @@ public class ZUGFeRDInvoiceImporter { try { moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = " + Stream.of(tc.trans.getZFItems()) - .map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString()) + .map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString()) .collect(Collectors.joining(" + ")); } catch (Exception ignored) { } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 680b312e..30448728 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -35,7 +35,7 @@ public class CalculationTest extends ResourceCase { .setQuantity(TEN) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(100).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1000).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -54,7 +54,7 @@ public class CalculationTest extends ResourceCase { .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1769.89).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -74,7 +74,7 @@ public class CalculationTest extends ResourceCase { .setItemCharges(new IZUGFeRDAllowanceCharge[]{charge}) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); assertEquals(valueOf(1799.63).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); @@ -113,13 +113,13 @@ public class CalculationTest extends ResourceCase { product.addAllowance(new Allowance(new BigDecimal(1))); item = new Item(product, new BigDecimal("9.50"), new BigDecimal(25)); item.addCharge(new Charge(new BigDecimal(10)).setReasonCode("ZZZ").setReason("Zuschlag")); - LineCalculator lc = new LineCalculator(item); + LineCalculator lc = item.getCalculation(); assertEquals(new BigDecimal("222.50"), lc.getItemTotalNetAmount()); invoice.addItem(item); product = new Product("Paper", "", "H87", new BigDecimal(25)); item = new Item(product, new BigDecimal("4.50"), new BigDecimal(15)); item.addAllowance(new Allowance().setPercent(new BigDecimal(5)).setReasonCode("ZZZ").setReason("Zuschlag")); - lc = new LineCalculator(item); + lc = item.getCalculation(); assertEquals(new BigDecimal("64.12"), lc.getItemTotalNetAmount()); invoice.addItem(item); invoice.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(new BigDecimal(25)).setReasonCode("ZZZ").setReason("Mengenrabatt")); @@ -459,7 +459,7 @@ public class CalculationTest extends ResourceCase { .setQuantity(BigDecimal.valueOf(31)) .setBasisQuantity(BigDecimal.valueOf(366)) .setProduct(product); - final LineCalculator calculator = new LineCalculator(currentItem); + final LineCalculator calculator = currentItem.getCalculation(); assertEquals(BigDecimal.valueOf(32.74), calculator.getItemTotalNetAmount()); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index 6f0a910c..8c8dbd7d 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -479,6 +479,20 @@ public class DeSerializationTest extends ResourceCase { assertEquals("sender@test.org", fromJSON.getSender().getEmail()); } + public void testItemAbsoluteChargeFromJSON() throws JsonProcessingException { + String globalID = "4000001123452"; + String globalIDScheme = "0088"; + + String json="{\"number\":\"471102\",\"currency\":\"EUR\",\"issueDate\":\"2018-03-04T00:00:00.000+01:00\",\"dueDate\":\"2018-03-04T00:00:00.000+01:00\",\"deliveryDate\":\"2018-03-04T00:00:00.000+01:00\",\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"globalID\":\"4000001123452\",\"globalIDScheme\":\"0088\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\"},\"zfitems\":[{\"price\":9.9,\"quantity\":20,\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"description\":\"\",\"vatpercent\":19,\"taxCategoryCode\":\"S\"},\"itemCharges\":[{\"totalAmount\":1,\"taxPercent\":19,\"reason\":\"Invoice line charge reason\",\"categoryCode\":\"S\"}]},{\"price\":5.5,\"quantity\":50,\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"description\":\"\",\"vatpercent\":7,\"taxCategoryCode\":\"S\"}}]}"; + + ObjectMapper mapper = new ObjectMapper(); + Invoice fromJSON = mapper.readValue(json, Invoice.class); + assertEquals(globalID, fromJSON.getSender().getGlobalID()); + assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme()); + TransactionCalculator tc=new TransactionCalculator(fromJSON); + assertEquals(new BigDecimal("531.06"), tc.getDuePayable()); + } + public void testGrossFromJSON() throws JsonProcessingException { String json="{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 28.07.2025\", \"issueDate\": 1753653600000, \"dueDate\": 1753653600000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"lineTotalAmount\": 29.00, \"duePayable\": 34.51, \"grandTotal\": 34.51, \"taxBasis\": 29.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 10.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"allowances\": [ { \"totalAmount\": 0.1000, \"categoryCode\": \"S\" } ], \"vatpercent\": 19.00, \"intraCommunitySupply\": false, \"reverseCharge\": false }, \"value\": 3.0000 } ], \"ownVATID\": \"DE0815\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\", \"ownCountry\": \"DE\", \"ownStreet\": \"teststr\"}"; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index ad1998d7..b793184d 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -350,7 +350,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { assertEquals(version,2); assertTrue(new BigDecimal("1").compareTo(invoice.getZFItems()[0].getQuantity()) == 0); - LineCalculator lc=new LineCalculator(invoice.getZFItems()[0]); + LineCalculator lc=invoice.getZFItems()[0].getCalculation(); assertTrue(new BigDecimal("1").compareTo(lc.getItemTotalNetAmount()) == 0); assertEquals("Z", invoice.getZFItems()[0].getProduct().getTaxCategoryCode()); @@ -439,7 +439,153 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); String expectedPaymentTermDesciption="Please remit until "+german.format(now); - JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00,}, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00}, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00 }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ]}",jsonArray,true); + JSONAssert.assertEquals("{\n" + + " \"documentCode\" : \"380\",\n" + + " \"number\" : \"123\",\n" + + " \"currency\" : \"EUR\",\n" + + " \"paymentTermDescription\" : \"Please remit until 30.09.2025\",\n" + + " \"issueDate\" : 1759190400000,\n" + + " \"dueDate\" : 1759190400000,\n" + + " \"sender\" : {\n" + + " \"name\" : \"Test company\",\n" + + " \"zip\" : \"55232\",\n" + + " \"street\" : \"teststr\",\n" + + " \"location\" : \"teststadt\",\n" + + " \"country\" : \"DE\",\n" + + " \"taxID\" : \"4711\",\n" + + " \"vatID\" : \"DE0815\",\n" + + " \"vatid\" : \"DE0815\"\n" + + " },\n" + + " \"recipient\" : {\n" + + " \"name\" : \"Franz Müller\",\n" + + " \"zip\" : \"55232\",\n" + + " \"street\" : \"teststr.12\",\n" + + " \"location\" : \"Entenhausen\",\n" + + " \"country\" : \"DE\",\n" + + " \"contact\" : {\n" + + " \"name\" : \"contact testname\",\n" + + " \"phone\" : \"123456\",\n" + + " \"email\" : \"contact.testemail@example.org\",\n" + + " \"fax\" : \"0911623562\"\n" + + " }\n" + + " },\n" + + " \"totalPrepaidAmount\" : 0.0,\n" + + " \"zfitems\" : [ {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"1\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"totalAmount\" : 0.1,\n" + + " \"taxPercent\" : 0,\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 2.9,\n" + + " \"itemTotalVATAmount\" : 0.551,\n" + + " \"itemTotalGrossAmount\" : 2.9\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"2\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"percent\" : 50.0,\n" + + " \"totalAmount\" : 1.5,\n" + + " \"basisAmount\" : 3.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"In love with salesperson\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 1.5,\n" + + " \"itemTotalVATAmount\" : 0.285,\n" + + " \"itemTotalGrossAmount\" : 1.5\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 2.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"3\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemCharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"AnotherReason\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 7.0,\n" + + " \"itemTotalVATAmount\" : 1.33,\n" + + " \"itemTotalGrossAmount\" : 7.0\n" + + " }\n" + + " }, {\n" + + " \"price\" : 3.0,\n" + + " \"quantity\" : 1.0,\n" + + " \"basisQuantity\" : 1.0,\n" + + " \"id\" : \"4\",\n" + + " \"product\" : {\n" + + " \"unit\" : \"H87\",\n" + + " \"name\" : \"Testprodukt\",\n" + + " \"taxCategoryCode\" : \"S\",\n" + + " \"vatpercent\" : 19.0\n" + + " },\n" + + " \"itemAllowances\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"Something completely strange\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"itemCharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 0,\n" + + " \"reason\" : \"Yet another reason\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ],\n" + + " \"value\" : 3.0,\n" + + " \"calculation\" : {\n" + + " \"price\" : 3.0,\n" + + " \"priceGross\" : 3.0,\n" + + " \"itemTotalNetAmount\" : 3.0,\n" + + " \"itemTotalVATAmount\" : 0.57,\n" + + " \"itemTotalGrossAmount\" : 3.0\n" + + " }\n" + + " } ],\n" + + " \"zfcharges\" : [ {\n" + + " \"totalAmount\" : 1.0,\n" + + " \"taxPercent\" : 19.0,\n" + + " \"reason\" : \"AReason\",\n" + + " \"reasonCode\" : \"ABK\",\n" + + " \"categoryCode\" : \"S\"\n" + + " } ]\n" + + "}",jsonArray,true); } catch (IOException e) { fail("IOException not expected"); } catch (XPathExpressionException e) { From 0ef4b2ba905b68392371a39f13ba85060ee23e68 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 1 Oct 2025 13:04:20 +0200 Subject: [PATCH 15/18] parametrizing the test again --- .../org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index b793184d..7397a941 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -443,9 +443,9 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { " \"documentCode\" : \"380\",\n" + " \"number\" : \"123\",\n" + " \"currency\" : \"EUR\",\n" + - " \"paymentTermDescription\" : \"Please remit until 30.09.2025\",\n" + - " \"issueDate\" : 1759190400000,\n" + - " \"dueDate\" : 1759190400000,\n" + + " \"paymentTermDescription\" : "+expectedPaymentTermDesciption+",\n" + + " \"issueDate\" : "+expectedIssueDate+",\n" + + " \"dueDate\" : "+expectedDueDate+",\n" + " \"sender\" : {\n" + " \"name\" : \"Test company\",\n" + " \"zip\" : \"55232\",\n" + From cfc028f87bf1506417817866f9ab87c2b1d3547c Mon Sep 17 00:00:00 2001 From: langfr Date: Thu, 2 Oct 2025 16:07:25 +0100 Subject: [PATCH 16/18] Remove System.out.println from XRTest.java. --- library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index cba6aeca..095bafa0 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -121,7 +121,6 @@ public class XRTest extends TestCase { zf2p.setProfile(Profiles.getByName("XRechnung")); zf2p.generateXML(i); String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8); - System.out.println(theXML); assertTrue(theXML.contains(" Date: Mon, 6 Oct 2025 13:31:05 +0200 Subject: [PATCH 17/18] working on potential wrong calculation --- .../ZUGFeRD/CalculationTest.java | 34 ++ .../EN16931_1_Teilrechnung_corrected.xml | 379 ++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 30448728..6f90dc6b 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -165,6 +165,40 @@ public class CalculationTest extends ResourceCase { } + @Test + public void testRounding() { +/*** xml of official fx sample with allowances and charges + * 10x100 with 10% and 50€ item discount =850€ + * +8,75 charges on document level=858,75, +19%VAT=1021,91 + * prepaid 500->due payable=521,91 + */ + File inputCII = getResourceAsFile("EN16931_1_Teilrechnung_corrected.xml"); + + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + Invoice invoice = null; + zii.doIgnoreCalculationErrors(); + boolean hasExceptions = false; + try { + zii.setInputStream(new FileInputStream(inputCII)); + + invoice = zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { +// handle Exceptions + hasExceptions = true; + } catch (FileNotFoundException e) { + hasExceptions = true; + } + assertFalse(hasExceptions); + // Reading ZUGFeRD + + final TransactionCalculator calculator = new TransactionCalculator(invoice); + + assertEquals(valueOf(147.59).stripTrailingZeros(), calculator.getDuePayable().stripTrailingZeros()); + + + } + + @Test public void testTotalCalculatorGrandTotalRounding() { SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); diff --git a/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml new file mode 100644 index 00000000..8ffdde89 --- /dev/null +++ b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml @@ -0,0 +1,379 @@ + + + + + + + + + + + urn:cen.eu:en16931:2017 + + + + 471102 + 380 + + 20180605 + + + Rechnung gemäß Bestellung Nr. 2018-471331 vom 01.03.2018. + + + Es bestehen Rabatt- und Bonusvereinbarungen. + AAK + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + REG + + + + + + 1 + + Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2018-51112 in Rechnung zu stellen: + + + + 4012345001235 + KR3M + + Kunstrasen grün 3m breit + 300cm x 100 cm + + + + 4.0000 + + + false + + 0.6667 + + + + 3.3333 + + + + 3.0000 + + + + VAT + S + 19.00 + + + 10.00 + + + + + + 2 + + Bestellt wurden 5 kg Schweinesteak. Mit dieser Rechnung werden nur die bereits gelieferten Steaks berechnet. Die noch offenen 4 kg Schweinesteak werden separat geliefert und berechnet. + + + + 4000050986428 + SFK5 + + Schweinesteak + Schweinesteak aus Deutschland + + + + 5.5000 + + + 5.5000 + + + + 1.0000 + + + + VAT + S + 7.00 + + + 5.50 + + + + + + 3 + + + 4000001234561 + GTRWA5 + + Mineralwasser Medium +12 x 1,0l PET + + + + + 5.4900 + + + 5.4900 + + + + 20.0000 + + + + VAT + S + 7.00 + + + 109.80 + + + + + + 4 + + + 4000001234578 + PFA5 + + Pfand + + + + 2.7700 + + + 2.7700 + + + + 20.0000 + + + + VAT + S + 19.00 + + + 55.46 + + + + + + 549910 + 4000001123452 + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + GE2020211 + Kunden AG Mitte + + 69876 + Kundenstraße 15 + Frankfurt + DE + + + + 2018-471331 + + + + + + 20180603 + + + + + EUR + + 7.91 + VAT + 113.03 + S + 7.00 + + + 12.25 + VAT + 64.46 + S + 19.00 + + + + false + + 10.00 + 1.00 + Sondernachlass + + VAT + S + 19.00 + + + + + false + + 115.30 + 8.07 + Sondernachlass + + VAT + S + 7.00 + + + + + true + + 115.30 + 5.80 + Versandkosten + + VAT + S + 7.00 + + + + Zahlbar innerhalb 30 Tagen netto bis 04.07.2018, 3% Skonto innerhalb 10 Tagen bis 15.06.2018 + + + 180.70 + 5.80 + 9.07 + 177.43 + 20.16 + 197.59 + 50.00 + 147.59 + + + + From 55b89bed54ebcb0447d9b1bcac740968a8e50f74 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 7 Oct 2025 08:18:09 +0200 Subject: [PATCH 18/18] closes #692 --- History.md | 3 + .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 80 ++++++++++--- .../ZUGFeRD/CalculationTest.java | 6 +- .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 9 +- ...arenrechnung_based_doublecashdiscount.xml} | 109 +++--------------- 5 files changed, 92 insertions(+), 115 deletions(-) rename library/src/test/resources/cii/{extended_warenrechnung.xml => extended_warenrechnung_based_doublecashdiscount.xml} (78%) diff --git a/History.md b/History.md index 4f0f485e..f2a968b9 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,9 @@ - #932 - #933, #413, #557, #765 - make Line Calculation, e.g. total line net amount, accessible via JSON using getCalculation +- #940 +- #939 +- #692 2.19.0 ======= diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 2e105fc1..4442443a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -8,21 +8,8 @@ import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; -import org.mustangproject.Allowance; -import org.mustangproject.BankDetails; -import org.mustangproject.CalculatedInvoice; -import org.mustangproject.Charge; -import org.mustangproject.DirectDebit; -import org.mustangproject.EStandard; +import org.mustangproject.*; import org.mustangproject.Exceptions.StructureException; -import org.mustangproject.FileAttachment; -import org.mustangproject.IncludedNote; -import org.mustangproject.Invoice; -import org.mustangproject.Item; -import org.mustangproject.ReferencedDocument; -import org.mustangproject.SchemedID; -import org.mustangproject.TradeParty; -import org.mustangproject.XMLTools; import org.mustangproject.util.NodeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +48,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -606,10 +595,12 @@ public class ZUGFeRDInvoiceImporter { } zpp.addNotes(includedNotes); String rootNode = extractString("local-name(/*)"); + String potentialCashDiscountTerms=null; if (rootNode != null && Set.of("Invoice", "CreditNote").contains(rootNode)) { // UBL... // //*[local-name()="Invoice" or local-name()="CreditNote"] number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").trim(); + potentialCashDiscountTerms = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]").trim(); typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim(); String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim(); if (!issueDateStr.isEmpty()) { @@ -623,6 +614,10 @@ public class ZUGFeRDInvoiceImporter { if (!deliveryDt.isEmpty()) { deliveryDate = parseDate(deliveryDt, "yyyy-MM-dd"); } + } else { + //CII + potentialCashDiscountTerms = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]").trim(); + } String creditorReferenceID = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"CreditorReferenceID\"]").trim();//BT-90 @@ -1127,6 +1122,63 @@ public class ZUGFeRDInvoiceImporter { } } + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"ApplicableTradePaymentDiscountTerms\"]");// cash discounts, UBL unknown + NodeList cashdiscountNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + for (int i = 0; i < cashdiscountNodes.getLength(); i++) { + NodeList cashDiscountNodeChilds = cashdiscountNodes.item(i).getChildNodes(); + String chargeAmount = null; + String taxPercent = null; + CashDiscount cd=new CashDiscount(); + for (int cashDiscountChildIndex = 0; cashDiscountChildIndex < cashDiscountNodeChilds.getLength(); cashDiscountChildIndex++) { + Node currentNode=cashDiscountNodeChilds.item(cashDiscountChildIndex); + String chargeChildName = currentNode.getLocalName(); + if (chargeChildName != null) { + if (chargeChildName.equals("BasisPeriodMeasure")) { + if (currentNode.getAttributes().getNamedItem("unitCode").getNodeValue().equals("DAY")) { + cd.setDays(Integer.valueOf(XMLTools.trimOrNull(currentNode))); + } + } else if (chargeChildName.equals("CalculationPercent")) { + cd.setPercent(new BigDecimal(XMLTools.trimOrNull(currentNode))); + } + } + //appliedAmount + //AppliedTradeTax + } + if ((cd.getPercent() != null)&&(cd.getDays() != null)) { + zpp.addCashDiscount(cd); + } + } + if ((potentialCashDiscountTerms!=null&&potentialCashDiscountTerms.length()>3)) { + for (String currentLine:potentialCashDiscountTerms.split("\\n")) { + if (currentLine.startsWith("#SKONTO#")) { + CashDiscount cd=new CashDiscount(); + Pattern pattern = Pattern.compile("#TAGE=(.*?)#", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(currentLine); + boolean daysFound = matcher.find(); + String days=matcher.group(1); + pattern = Pattern.compile("#PROZENT=(.*?)#", Pattern.CASE_INSENSITIVE); + matcher = pattern.matcher(currentLine); + boolean percentFound = matcher.find(); + String percent=matcher.group(1); + + if (daysFound&&percentFound) { + cd.setDays(Integer.valueOf(days)); + cd.setPercent(new BigDecimal(percent)); + zpp.addCashDiscount(cd); + } //else : could not parse skonto + + + + +/* + String percent=; + + cd.setDays() + cd.setPercent()*/ + + } + } + } TransactionCalculator tc = new TransactionCalculator(zpp); String calculatedPayableTotal = tc.getDuePayable().toPlainString(); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 6f90dc6b..d7fd83af 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -165,13 +165,13 @@ public class CalculationTest extends ResourceCase { } - @Test +/* @Test public void testRounding() { /*** xml of official fx sample with allowances and charges * 10x100 with 10% and 50€ item discount =850€ * +8,75 charges on document level=858,75, +19%VAT=1021,91 * prepaid 500->due payable=521,91 - */ + * File inputCII = getResourceAsFile("EN16931_1_Teilrechnung_corrected.xml"); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); @@ -197,7 +197,7 @@ public class CalculationTest extends ResourceCase { } - +*/ @Test public void testTotalCalculatorGrandTotalRounding() { diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 7397a941..b99f7108 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -35,7 +35,6 @@ import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -235,9 +234,9 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } - public void testSpecifiedLogisticsChargeImport() { + public void testSpecifiedLogisticsChargeCashDiscountImport() { ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); - File expectedResult = getResourceAsFile("cii/extended_warenrechnung.xml"); + File expectedResult = getResourceAsFile("cii/extended_warenrechnung_based_doublecashdiscount.xml"); boolean hasExceptions = false; @@ -249,9 +248,11 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { hasExceptions = true; } assertFalse(hasExceptions); + assertEquals(invoice.getCashDiscounts().length,2); TransactionCalculator tc = new TransactionCalculator(invoice); assertEquals(new BigDecimal("518.99"), tc.getGrandTotal()); + } public void testItemAllowancesChargesImport() { @@ -347,7 +348,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { TransactionCalculator tc = new TransactionCalculator(invoice); assertEquals(new BigDecimal("1.00"), tc.getGrandTotal()); - + assertEquals(invoice.getCashDiscounts().length,2); assertEquals(version,2); assertTrue(new BigDecimal("1").compareTo(invoice.getZFItems()[0].getQuantity()) == 0); LineCalculator lc=invoice.getZFItems()[0].getCalculation(); diff --git a/library/src/test/resources/cii/extended_warenrechnung.xml b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml similarity index 78% rename from library/src/test/resources/cii/extended_warenrechnung.xml rename to library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml index 46665e1a..b425194a 100644 --- a/library/src/test/resources/cii/extended_warenrechnung.xml +++ b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml @@ -1,90 +1,4 @@ - - - - - @@ -545,13 +459,20 @@ WEEE-Reg-Nr.: DE87654321 19.00 - - Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto. - - 14 - 2.00 - - + + Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto. + + 14 + 2.00 + + + + Bei Zahlung innerhalb 7 Tagen gewähren wir 1,0% Skonto. + + 7 + 1.00 + + 457.20 3.00 @@ -564,4 +485,4 @@ WEEE-Reg-Nr.: DE87654321 - \ No newline at end of file +