From a92ddb2f163186b5ae9e4a98baff980a81e77eca Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 18 Nov 2024 09:35:32 +0100 Subject: [PATCH] attempts on supporting netherlands' Euro --- .../main/java/org/mustangproject/Invoice.java | 19 +++++++ .../ZUGFeRD/IExportableTransaction.java | 9 ++++ .../ZUGFeRD/TransactionCalculator.java | 51 +++++++++++-------- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 16 +++--- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 5 ++ .../mustangproject/ZUGFeRD/ZF2PushTest.java | 10 ++++ 6 files changed, 82 insertions(+), 28 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 346c5f43..db5f5b4a 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -66,6 +66,7 @@ public class Invoice implements IExportableTransaction { protected String despatchAdviceReferencedDocumentID = null; protected String vatDueDateTypeCode = null; protected String creditorReferenceID; // required when direct debit is used. + private BigDecimal roundingAmount=null; public Invoice() { ZFItems = new ArrayList<>(); @@ -467,6 +468,24 @@ public class Invoice implements IExportableTransaction { return sender; } + /*** + * for currency rounding differences to 5ct e.g. in Netherlands ("Rappenrundung") + * @return null if not set, otherwise BigDecimal of Euros + */ + @Override + public BigDecimal getRoundingAmount() { + return roundingAmount; + } + + /*** + * for currency rounding differences to 5ct e.g. in Netherlands ("Rappenrundung") + * @return fluent setter + */ + public Invoice setRoundingAmount(BigDecimal amount) { + roundingAmount=amount; + return this; + } + /*** * sets a named sender contact * @deprecated use setSender diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index f43b5f62..0a67a6be 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -321,6 +321,15 @@ public interface IExportableTransaction { return false; } + /** + * supplier identification assigned by the costumer + * + * @return the sender's identification + */ + default BigDecimal getRoundingAmount() { + return null; + } + /** * get reference document number typically used for Invoice Corrections Will be * added as IncludedNote in comfort profile diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 1e1f757f..1eb1a6d2 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -10,7 +10,7 @@ import java.util.stream.Stream; /*** * The Transactioncalculator e.g. adds the line totals and applies VAT on whole * invoices - * + * * @see LineCalculator */ public class TransactionCalculator implements IAbsoluteValueProvider { @@ -27,7 +27,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * if something had already been paid in advance, this will get it from the * transaction - * + * * @return prepaid amount */ protected BigDecimal getTotalPrepaid() { @@ -41,19 +41,19 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * the invoice total with VAT, allowances and * charges, WITHOUT considering prepaid amount - * + * * @return the invoice total including taxes */ public BigDecimal getGrandTotal() { - final BigDecimal res = getTaxBasis(); + BigDecimal basis = getTaxBasis(); return getVATPercentAmountMap().values().stream().map(VATAmount::getCalculated) - .map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(res); + .map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(basis); } /*** * returns total of charges for this tax rate - * + * * @param percent a specific rate, or null for any rate * @return the total amount */ @@ -77,7 +77,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * returns a (potentially concatenated) string of charge reasons, or "Charges" * if none are defined - * + * * @param percent a specific rate, or null for any rate * @return the space separated String */ @@ -95,7 +95,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { if ((charges != null) && (charges.length > 0)) { for (IZUGFeRDAllowanceCharge currentCharge : charges) { if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0) - && currentCharge.getReason() != null) { + && currentCharge.getReason() != null) { res += currentCharge.getReason() + " "; } } @@ -107,7 +107,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * returns a (potentially concatenated) string of allowance reasons, or * "Allowances", if none are defined - * + * * @param percent a specific rate, or null for any rate * @return the space separated String */ @@ -122,7 +122,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * returns total of allowances for this tax rate - * + * * @param percent a specific rate, or null for any rate * @return the total amount */ @@ -134,25 +134,25 @@ public class TransactionCalculator implements IAbsoluteValueProvider { /*** * returns the total net value of all items, without document level * charges/allowances - * + * * @return item sum */ protected BigDecimal getTotal() { BigDecimal dec = Stream.of(trans.getZFItems()).map(LineCalculator::new) - .map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add); + .map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add); return dec; } /*** * returns the total net value of the invoice, including charges/allowances on * document level - * + * * @return item sum +- charges/allowances */ protected BigDecimal getTaxBasis() { return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP)) - .subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP)) - .setScale(2, RoundingMode.HALF_UP); + .subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP)) + .setScale(2, RoundingMode.HALF_UP); } /** @@ -171,9 +171,9 @@ public class TransactionCalculator implements IAbsoluteValueProvider { if (percent != null) { LineCalculator lc = new LineCalculator(currentItem); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); - String reasonText=currentItem.getProduct().getTaxExemptionReason(); - if (reasonText!=null) { + currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); + String reasonText = currentItem.getProduct().getTaxExemptionReason(); + if (reasonText != null) { itemVATAmount.setVatExemptionReasonText(reasonText); } VATAmount current = hm.get(percent.stripTrailingZeros()); @@ -193,8 +193,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider { VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros()); if (theAmount == null) { theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, - currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S", - vatDueDateTypeCode); + currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S", + vatDueDateTypeCode); } theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this))); BigDecimal factor = taxPercent.divide(new BigDecimal(100)); @@ -211,8 +211,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider { VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros()); if (theAmount == null) { theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, - currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S", - vatDueDateTypeCode); + currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S", + vatDueDateTypeCode); } theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this))); BigDecimal factor = taxPercent.divide(new BigDecimal(100)); @@ -239,4 +239,11 @@ public class TransactionCalculator implements IAbsoluteValueProvider { return getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP); } + public BigDecimal getDuePayable() { + BigDecimal res = getGrandTotal().subtract(getTotalPrepaid()); + if (trans.getRoundingAmount() != null) { + res = res.add(trans.getRoundingAmount()); + } + return res; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 03ed3099..8729881d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -334,7 +334,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { this.trans = trans; this.calc = new TransactionCalculator(trans); - boolean hasDueDate = trans.getDueDate()!=null; + boolean hasDueDate = trans.getDueDate() != null; final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); String exemptionReason = ""; @@ -454,7 +454,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) { for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) { xml += "" - + ""; xml += ""; - if (getProfile() != Profiles.getByName("Minimum")) { + if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BASICWL"))) { xml += "" + currencyFormat(calc.getTotal()) + ""; xml += chargesTotalLine + allowanceTotalLine; @@ -865,14 +865,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { // // // currencyID=\"EUR\" + "" - + currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "" - + "" + currencyFormat(calc.getGrandTotal()) + ""; + + currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + ""; + if (trans.getRoundingAmount() != null) { + xml += "" + currencyFormat(trans.getRoundingAmount()) + ""; + } + + xml += "" + currencyFormat(calc.getGrandTotal()) + ""; // // // currencyID=\"EUR\" if (getProfile() != Profiles.getByName("Minimum")) { xml += "" + currencyFormat(calc.getTotalPrepaid()) + ""; } - xml += "" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "" + xml += "" + currencyFormat(calc.getDuePayable()) + "" + ""; if (trans.getInvoiceReferencedDocumentID() != null) { xml += "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index d9bd7e42..219f230b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -621,6 +621,11 @@ public class ZUGFeRDInvoiceImporter { zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim()); + String rounding=extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"Party\"]/*[local-name()=\"PayableRoundingAmount\"]"); + if ((rounding!=null)&&(!rounding.isEmpty())) { + zpp.setRoundingAmount(new BigDecimal(rounding.trim())); + } + xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]"); String buyerReference = null; prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index be949c34..9ff1a945 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -88,6 +88,7 @@ public class ZF2PushTest extends TestCase { .addItem(new Item(new Product("Design (hours)", "Of a sample invoice", "HUR", new BigDecimal(7)), price, new BigDecimal(1.0))) .addItem(new Item(new Product("Ballons", "various colors, ~2000ml", "H87", new BigDecimal(19)), new BigDecimal("0.79"), new BigDecimal(400.0))) .addItem(new Item(new Product("Hot air „heiße Luft“ (litres)", "", "LTR", new BigDecimal(19)), new BigDecimal("0.025"), new BigDecimal(800.0))) + .setRoundingAmount(new BigDecimal("1")) ); ze.export(TARGET_PDF); @@ -95,6 +96,15 @@ public class ZF2PushTest extends TestCase { fail("Exception should not be raised"); } +ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF); + Invoice i=new Invoice(); + try { + zii.extractInto(i); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } // now check the contents (like MustangReaderTest)