diff --git a/History.md b/History.md index 5bde7d5a..b4a2391c 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,7 @@ #786 add getCalculation on calculatedInvoice Transactioncalculator getTaxDetails to include correct percentage, calculated amounts +#764 2.16.5 ======= diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java index e378f767..d8b5f3fe 100644 --- a/library/src/main/java/org/mustangproject/Allowance.java +++ b/library/src/main/java/org/mustangproject/Allowance.java @@ -3,8 +3,10 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider; import java.math.BigDecimal; +import java.math.RoundingMode; /*** * (absolute) allowances on item and document level @@ -29,6 +31,22 @@ public class Allowance extends Charge { } + + + @Override + public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) { + if(totalAmount != null) { + return totalAmount; + } else if (percent!=null) { + BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); +// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); + BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); + return singlePriceDiff; + } else { + throw new RuntimeException("percent must be set"); + } + } + /*** * Always to return false for IZUGFeRDAllowanceCharge * @return false since its not supposed to be calculated negatively diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index 1b3320bf..c906a704 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -7,6 +7,7 @@ import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import java.math.BigDecimal; +import java.math.RoundingMode; /*** * Absolute and relative charges for document and item level @@ -23,6 +24,10 @@ public class Charge implements IZUGFeRDAllowanceCharge { * the absolute value if not percentage */ protected BigDecimal totalAmount; + /** + * the value the percentage is applied upon + */ + protected BigDecimal basisAmount; /** * the tax rate the charge belongs to */ @@ -103,6 +108,22 @@ public class Charge implements IZUGFeRDAllowanceCharge { } + @Override + public BigDecimal getBasisAmount() { + return basisAmount; + } + + /*** + * sets a potential basis for the potential percentage + * @param basis the basis amount + * @return fluid setter + */ + public Charge setBasisAmount(BigDecimal basis) { + this.basisAmount = basis; + return this; + } + + @Override public String getReasonCode() { return reasonCode; @@ -121,10 +142,13 @@ public class Charge implements IZUGFeRDAllowanceCharge { @Override public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) { - if (percent!=null) { - return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100))); - } else if(totalAmount != null) { + if(totalAmount != null) { return totalAmount; + } else if (percent!=null) { + BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); +// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); + BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice); + return singlePriceDiff; } 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 54f20084..6d57722d 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -72,8 +72,6 @@ public class Item implements IZUGFeRDExportableItem { //we need: name description unitcode //and we additionally have vat% - // Bharti's homework 20241126:Streams https://www.youtube.com/watch?v=Lf01cBzmuXw - // and Lambdas https://www.youtube.com/watch?v=HCyx31NW8xg setProduct(new Product(itemMap.getNode("Item").get())); icnm.getAsString("Name").ifPresent(product::setName); icnm.getAsString("Description").ifPresent(product::setDescription); @@ -86,17 +84,6 @@ public class Item implements IZUGFeRDExportableItem { BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID); }); -// String name = icnm.getAsStringOrNull("Name"); -// String val = icnm.getAsStringOrNull("Value"); -// if (name != null && val != null) { -// if (attributes == null) { -// attributes = new HashMap<>(); -// } -// product.attributes.put(name, val); -// } - //icnm.getNode("AdditionalItemProperty").flatMap(n ->n.getAttributes()).ifPresent(product::setAttributes); - - icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent")) .ifPresent(product::setVATPercent); }); @@ -175,11 +162,12 @@ public class Item implements IZUGFeRDExportableItem { .ifPresent(product::setTaxExemptionReason); icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> { - stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> { //CII + stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> { String isChargeString = ci.getAsString("Indicator").get(); - String percentString = stac.getAsStringOrNull("CalculationPercent");//MultiplierFactorNumeric - String amountString = stac.getAsStringOrNull("ActualAmount");//Amount - String reason = stac.getAsStringOrNull("Reason");//AllowanceChargeReason + String percentString = stac.getAsStringOrNull("CalculationPercent"); + String amountString = stac.getAsStringOrNull("ActualAmount"); + String basisAmountString = stac.getAsStringOrNull("BasisAmount"); + String reason = stac.getAsStringOrNull("Reason"); Charge izac = new Charge(); if (isChargeString.equalsIgnoreCase("false")) { izac = new Allowance(); @@ -188,6 +176,12 @@ public class Item implements IZUGFeRDExportableItem { } if (amountString != null) { izac.setTotalAmount(new BigDecimal(amountString)); + if (percentString!=null&&(percentString!="0")) { + izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity())); + } + } + if (basisAmountString != null) { + izac.setBasisAmount(new BigDecimal(basisAmountString)); } if (percentString != null) { izac.setPercent(new BigDecimal(percentString)); @@ -288,6 +282,16 @@ public class Item implements IZUGFeRDExportableItem { return this; } + @Override public IZUGFeRDAllowanceCharge[] getAllowances() { + IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()]; + return Allowances.toArray(izac); + } + + @Override public IZUGFeRDAllowanceCharge[] getCharges() { + IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()]; + return Charges.toArray(izac); + } + /*** * BT 132 (issue https://github.com/ZUGFeRD/mustangproject/issues/247) * @return the line ID of the order (BT132) @@ -461,6 +465,7 @@ public class Item implements IZUGFeRDExportableItem { * @return fluent setter */ public Item addAllowance(IZUGFeRDAllowanceCharge izac) { + Allowances.add(izac); return this; } diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 4f0397ee..93e48a79 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -32,6 +32,8 @@ public class Product implements IZUGFeRDExportableProduct { protected boolean isIntraCommunitySupply = false; protected SchemedID globalId = null; protected String countryOfOrigin = null; + protected ArrayList charges=new ArrayList<>(); + protected ArrayList allowances=new ArrayList<>(); protected HashMap attributes = new HashMap<>(); protected List classifications = new ArrayList<>(); @@ -393,4 +395,42 @@ public class Product implements IZUGFeRDExportableProduct { this.classifications.add(classification); return this; } + + public Product addCharge(Charge e) { + charges.add(e); + return this; + } + + public Product addAllowance(Allowance a) { + allowances.add(a); + return this; + } + + /*** + * returns the AppliedTradeAllowanceCharges of this product which are actually Charges + * @return array of or null, if none + */ + @Override + public Charge[] getCharges() { + if (charges.size()==0) { + return null; + } + Charge[] chargeArr = new Charge[charges.size()]; + return charges.toArray(chargeArr); + } + + @Override + /*** + * returns the AppliedTradeAllowanceCharges of this product which are actually Allowances + * @return array of or null, if none + */ + public Allowance[] getAllowances() { + if (allowances.size()==0) { + return null; + } + Allowance[] allowanceArr = new Allowance[allowances.size()]; + return allowances.toArray(allowanceArr); + } + + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDAllowanceCharge.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDAllowanceCharge.java index 0c61ba5b..bcdab413 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDAllowanceCharge.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDAllowanceCharge.java @@ -38,6 +38,12 @@ public interface IZUGFeRDAllowanceCharge { */ default BigDecimal getPercent() {return null;} + /*** + * returns a basis the precentage is calculated from + * @return null or the basis + */ + default BigDecimal getBasisAmount() {return null;} + /*** * get a description for the allowance/charge * @return the description diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 6d68cdff..1dee20c2 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -43,7 +43,11 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ * item level discounts * @return array of the discounts on a single item */ + @Deprecated default IZUGFeRDAllowanceCharge[] getItemAllowances() { + if (getProduct()!=null) { + return getProduct().getAllowances(); + } return null; } @@ -51,10 +55,27 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ * item level price additions * @return array of the additional charges on the item */ + @Deprecated default IZUGFeRDAllowanceCharge[] getItemCharges() { + if (getProduct()!=null) { + return getProduct().getCharges(); + } return null; } + @Deprecated + default IZUGFeRDAllowanceCharge[] getAllowances() { + return null; + } + + /** + * item level price additions + * @return array of the additional charges on the item + */ + @Deprecated + default IZUGFeRDAllowanceCharge[] getCharges() { + return null; + } /*** * BT 132 (issue https://github.com/ZUGFeRD/mustangproject/issues/247) @@ -147,9 +168,9 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ } /*** - * specify allowances amount for the line item total + * get all (allowances and charges) SpecifiedTradeAllowanceCharges * - * @return the sum of allowances for this item + * @return the real item lecel SpecifiedTradeAllowanceCharges */ default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() { return null; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java index ae9eb5b8..6ee5f22e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -166,6 +166,25 @@ public interface IZUGFeRDExportableProduct { return null; } + + /** + * product level discounts (AppliedTradeAllowanceCharge, will change net price) + * @return array of the discounts on a single product + */ + default IZUGFeRDAllowanceCharge[] getAllowances() { + return null; + } + + /** + * product level charges (AppliedTradeAllowanceCharge, will change net price) + * @return array of the additional charges on the product + */ + default IZUGFeRDAllowanceCharge[] getCharges() { + return null; + } + + + /** * Detailed information about the product * diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index d17ccfec..d5f06a9a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -2,30 +2,49 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Arrays; +import java.util.List; /*** * the linecalculator does the math within an item line, and e.g. calculates quantity*price. * @see TransactionCalculator */ public class LineCalculator { - private final BigDecimal price; - private final BigDecimal priceGross; - private final BigDecimal itemTotalNetAmount; - private final BigDecimal itemTotalVATAmount; - private BigDecimal allowance = BigDecimal.ZERO; - private BigDecimal charge = BigDecimal.ZERO; - private BigDecimal allowanceItemTotal = BigDecimal.ZERO; + protected BigDecimal price; + protected BigDecimal priceGross; + protected BigDecimal itemTotalNetAmount; + protected BigDecimal itemTotalVATAmount; + protected BigDecimal lineAllowance = BigDecimal.ZERO; + protected BigDecimal lineCharge = BigDecimal.ZERO; + protected BigDecimal itemAllowance = BigDecimal.ZERO; + protected BigDecimal itemCharge = BigDecimal.ZERO; + protected BigDecimal allowanceItemTotal = BigDecimal.ZERO; public LineCalculator(IZUGFeRDExportableItem currentItem) { if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { - addAllowance(allowance.getTotalAmount(currentItem)); + 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)); + } } if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) { for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { - addCharge(charge.getTotalAmount(currentItem)); + 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)); + } } if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) { @@ -42,22 +61,39 @@ public class LineCalculator { vatPercent = BigDecimal.ZERO; } BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100)); - priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159 - price = priceGross.subtract(allowance).add(charge); BigDecimal quantity=BigDecimal.ZERO; if ((currentItem!=null)&&(currentItem.getQuantity()!=null)) { quantity=currentItem.getQuantity(); } + price=currentItem.getPrice(); + priceGross=price; +// price=price.subtract(itemAllowance).add(itemCharge); +// BigDecimal delta=charge.subtract(allowanceItemTotal).subtract(allowance); +// delta=delta.divide(currentItem.getQuantity(), 18, RoundingMode.HALF_UP); + + BigDecimal delta=BigDecimal.ZERO; + if (currentItem.getProduct().getAllowances()!=null) { + for (IZUGFeRDAllowanceCharge ccaf:currentItem.getProduct().getAllowances()) { + delta=delta.subtract(ccaf.getTotalAmount(currentItem)); + } + } + if (currentItem.getProduct().getCharges()!=null) { + for (IZUGFeRDAllowanceCharge ccaf : currentItem.getProduct().getCharges()) { + delta = delta.subtract(ccaf.getTotalAmount(currentItem)); + } + } + + price=price.add(delta); // Division/Zero occurred here. // Used the setScale only because that's also done in getBasisQuantity BigDecimal basisQuantity = currentItem.getBasisQuantity().compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ONE.setScale(4) : currentItem.getBasisQuantity(); itemTotalNetAmount = quantity.multiply(price).divide(basisQuantity, 18, RoundingMode.HALF_UP) - .subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP); - itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator); + .add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP); + itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);//.setScale(2, RoundingMode.HALF_UP); } public BigDecimal getPrice() { @@ -80,16 +116,27 @@ public class LineCalculator { return priceGross; } - public void addAllowance(BigDecimal b) { - allowance = allowance.add(b); + public void addLineAllowance(BigDecimal b) { + lineAllowance = lineAllowance.add(b); } - public void addCharge(BigDecimal b) { - charge = charge.add(b); + public void addLineCharge(BigDecimal b) { + lineCharge = lineCharge.add(b); + } + + public void addItemAllowance(BigDecimal b) { + itemAllowance = itemAllowance.add(b); + } + + public void addItemCharge(BigDecimal b) { + itemCharge = itemCharge.add(b); } public void addAllowanceItemTotal(BigDecimal b) { allowanceItemTotal = allowanceItemTotal.add(b); } + public void subtractAllowanceItemTotal(BigDecimal b) { + allowanceItemTotal = allowanceItemTotal.subtract(b); + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 6ff410e5..523e27ad 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -321,11 +321,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } String reason = ""; - if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) { + boolean isEN16931=(profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")); + if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) { reason = "" + XMLTools.encodeXML(allowance.getReason()) + ""; } String reasonCode = ""; - if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) { + if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) { // only in XRechnung profile reasonCode = "" + allowance.getReasonCode() + ""; } @@ -433,24 +434,29 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + ""; } String allowanceChargeStr = ""; - if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { - for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { + if (currentItem.getProduct().getAllowances() != null && currentItem.getProduct().getAllowances().length > 0) { + for (final IZUGFeRDAllowanceCharge allowance : currentItem.getProduct().getAllowances()) { allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem); } } - if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) { - for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { + if (currentItem.getProduct().getCharges() != null && currentItem.getProduct().getCharges().length > 0) { + for (final IZUGFeRDAllowanceCharge charge : currentItem.getProduct().getCharges()) { allowanceChargeStr += getAllowanceChargeStr(charge, currentItem); } } String itemTotalAllowanceChargeStr = ""; - if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) { - for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getItemTotalAllowances()) { + if (currentItem.getAllowances() != null && currentItem.getAllowances().length > 0) { + for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getAllowances()) { itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalAllowance, currentItem); } } + if (currentItem.getCharges() != null && currentItem.getCharges().length > 0) { + for (final IZUGFeRDAllowanceCharge itemTotalCharges : currentItem.getCharges()) { + itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalCharges, currentItem); + } + } xml += "" + XMLTools.encodeXML(currentItem.getProduct().getName()) + ""; if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) { @@ -553,7 +559,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += ""; } - xml += itemTotalAllowanceChargeStr; + // item charges/allowances + if (!itemTotalAllowanceChargeStr.isEmpty()) { + xml += itemTotalAllowanceChargeStr ; + } xml += "" + "" + currencyFormat(lc.getItemTotalNetAmount()) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 53f4562b..f27cce5b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -383,29 +383,7 @@ public class ZUGFeRDInvoiceImporter { }); }); String street, name, additionalStreet, city, postal, countrySubentity, line, country = null; -/* - String idx = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"ID\"]"); - street = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name()=\"Address\"]/*[local-name()=\"StreetName\"]"); - additionalStreet = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"AdditionalStreetName\"]"); - city = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"CityName\"]"); - postal = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"PostalZone\"]"); - countrySubentity = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"CountrySubentity\"]"); - line = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]//*[local-name() = \"AddressLine\"]/*[local-name() = \"Line\"]"); - country = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]//*[local-name() = \"Country\"]/*[local-name() = \"IdentificationCode\"]"); - name = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"DeliveryParty\"]//*[local-name() = \"PartyName\"]/*[local-name() = \"Name\"]"); -*/ zpp.setDeliveryAddress(delivery); - /* - zpp.setDeliveryAddress(new TradeParty() - .setStreet(street) - .setAdditionalAddress(additionalStreet) - .setLocation(city) - .setZIP(postal) - .setAdditionalAddressExtension(line) - .setCountry(country) - .setName(name) - ); -*/ } @@ -420,14 +398,6 @@ public class ZUGFeRDInvoiceImporter { XPathExpression shipPayee = xpath.compile("//*[local-name()=\"PayeeParty\"]/*"); NodeList ublPayeeNodes = (NodeList) shipPayee.evaluate(getDocument(), XPathConstants.NODESET); -// if(ublPayeeNodes != null) { -// TradeParty payee = new TradeParty(); -// NodeMap nodeMap = new NodeMap(ublPayeeNodes).getAsNodeMap("PayeeParty").get(); -// nodeMap.getNode("ID").ifPresent(s -> { -// SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent()); -// payee.addGlobalID(sID); -// }); -// } //NodeList UBLpayeeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (ublPayeeNodes.getLength() > 0) { zpp.setPayee(new TradeParty(ublPayeeNodes)); @@ -836,9 +806,6 @@ public class ZUGFeRDInvoiceImporter { } } } -// if ((paymentMeansChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DirectDebitMandateID"))) { -// directDebitMandateID = paymentTermChilds.item(paymentTermChildIndex).getTextContent(); -// } if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PaymentMandate"))) { NodeList paymentMandateChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes(); @@ -974,6 +941,7 @@ public class ZUGFeRDInvoiceImporter { NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes(); boolean isCharge = true; String chargeAmount = null; + String basisAmount = null; String reason = null; String reasonCode = null; String taxPercent = null; @@ -1001,6 +969,8 @@ public class ZUGFeRDInvoiceImporter { } else if (chargeChildName.equals("ActualAmount") || chargeChildName.equals("Amount")) { chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); + } else if (chargeChildName.equals("BasisAmount")) { + basisAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); } else if (chargeChildName.equals("Reason") || chargeChildName.equals("AllowanceChargeReason")) { reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); } else if (chargeChildName.equals("ReasonCode") || chargeChildName.equals("AllowanceChargeReasonCode")) { @@ -1028,6 +998,9 @@ public class ZUGFeRDInvoiceImporter { if (taxPercent != null) { c.setTaxPercent(new BigDecimal(taxPercent)); } + if (basisAmount != null) { + c.setBasisAmount(new BigDecimal(basisAmount)); + } zpp.addCharge(c); } else { Allowance a = new Allowance(new BigDecimal(chargeAmount)); @@ -1040,6 +1013,9 @@ public class ZUGFeRDInvoiceImporter { if (taxPercent != null) { a.setTaxPercent(new BigDecimal(taxPercent)); } + if (basisAmount != null) { + a.setBasisAmount(new BigDecimal(basisAmount)); + } zpp.addAllowance(a); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 9f3f3ea2..7fc94f5f 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -54,9 +54,9 @@ public class CalculationTest extends ResourceCase { final LineCalculator calculator = new LineCalculator(currentItem); - assertEquals(valueOf(133.857).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); - assertEquals(valueOf(1606.28).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); - assertEquals(valueOf(257.0048).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); + assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); + assertEquals(valueOf(1769.89).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); + assertEquals(valueOf(283.1824).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); } @Test @@ -74,17 +74,20 @@ public class CalculationTest extends ResourceCase { final LineCalculator calculator = new LineCalculator(currentItem); - assertEquals(valueOf(163.603).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); - assertEquals(valueOf(1963.24).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); - assertEquals(valueOf(314.1184).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); + assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); + assertEquals(valueOf(1799.63).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); + assertEquals(valueOf(287.9408).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); } @Test public void testLineCalculatorForeignCurrencyExample() { - -/* +/*** 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("Extended_fremdwaehrung.xml"); -inputCII=new File("C:\\Users\\jstaerk\\workspace\\XMLExamples\\zfdiverses\\20250407\\fremdwaehrung.xml"); + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(); Invoice invoice=null; zii.doIgnoreCalculationErrors(); @@ -104,9 +107,9 @@ inputCII=new File("C:\\Users\\jstaerk\\workspace\\XMLExamples\\zfdiverses\\20250 final TransactionCalculator calculator = new TransactionCalculator(invoice); - assertEquals(valueOf(521.91).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); + assertEquals(valueOf(521.91).stripTrailingZeros(), calculator.getDuePayable().stripTrailingZeros()); + - */ } @@ -221,9 +224,94 @@ inputCII=new File("C:\\Users\\jstaerk\\workspace\\XMLExamples\\zfdiverses\\20250 invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte")); } TransactionCalculator calculator = new TransactionCalculator(invoice); - assertEquals(valueOf(101.86).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); + assertEquals(valueOf(307.18).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); } + public void testSimpleItemPercentAllowance() { + /*** + * 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", "", "H84", BigDecimal.ZERO); + item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00)); + + item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); + invoice.addItem(item); + + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(new BigDecimal(5), calculator.getGrandTotal().stripTrailingZeros()); + } + + public void testSimpleItemTotalAllowance() { + /*** + * a product with net 1 and qty 5 and absolute _item_ allowance of 1 should return 4 as line total, 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", "", "H84", BigDecimal.ZERO); + item = new Item(product, new BigDecimal("1.00"), new BigDecimal(5.00)); + + item.addAllowance(new Allowance(new BigDecimal(1)).setTaxPercent(BigDecimal.ZERO)); + invoice.addItem(item); + + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(new BigDecimal(4), calculator.getGrandTotal().stripTrailingZeros()); + } + + /** * LineCalculator should not throw an exception when calculating a non-terminating decimal expansion * */ diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index bb41fb5a..fd0e7b97 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -395,7 +395,7 @@ public class DeSerializationTest extends ResourceCase { try { Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class); TransactionCalculator tc=new TransactionCalculator(newInvoiceFromJSON); - assertEquals(new BigDecimal("19.52"),tc.getGrandTotal()); + assertEquals(new BigDecimal("18.92"),tc.getGrandTotal()); } catch (JsonProcessingException e) { throw new RuntimeException(e); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 10f28a3a..9c60b75b 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -141,7 +141,7 @@ public class ZF2PushTest extends TestCase { String senderDescription = "Kleinunternehmer"; String taxID = "9990815"; - String theNote="oh lala"; + String theNote = "oh lala"; BigDecimal price = new BigDecimal(priceStr); try { InputStream SOURCE_PDF = this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf"); @@ -177,7 +177,7 @@ public class ZF2PushTest extends TestCase { throw new RuntimeException(e); } - assertEquals(i.getZFItems()[0].getNotesWithSubjectCode().get(0).getContent(),theNote); + assertEquals(i.getZFItems()[0].getNotesWithSubjectCode().get(0).getContent(), theNote); assertEquals(senderDescription, i.getSender().getDescription()); // now check the contents (like MustangReaderTest) @@ -255,16 +255,16 @@ public class ZF2PushTest extends TestCase { // ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname,"teststr", "55232","teststadt","DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) // .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50))))); - Invoice i=new Invoice().setDueDate(new Date()).setIssueDate(new Date()) + Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE") .setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562"))) .setNumber(number) .addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("AReason").setTaxPercent(new BigDecimal(19))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance(new BigDecimal("0.1")))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50)))) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance(new BigDecimal("0.1")).setReasonCode("95"))) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50)).setReason("In love with salesperson"))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(2.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("AnotherReason"))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addCharge(new Charge(new BigDecimal(1))).addAllowance(new Allowance(new BigDecimal("1")))); + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("Yet another reason")).addAllowance(new Allowance(new BigDecimal("1")).setReason("Something completely strange"))); ze.setTransaction(i); @@ -275,21 +275,20 @@ public class ZF2PushTest extends TestCase { fail("IOException should not be raised"); } - // now check the contents (like MustangReaderTest) - ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ITEMCHARGESALLOWANCESPDF); - - assertEquals("EUR", zi.getInvoiceCurrencyCode()); - assertTrue(zi.getUTF8().contains("0911623562")); // fax number - assertTrue(zi.getUTF8().contains("ABK")); - - // Reading ZUGFeRD - assertEquals("19.52", zi.getAmount()); - assertEquals(orgname, zi.getHolder()); - assertEquals(number, zi.getForeignReference()); try { + // now check the contents (like MustangReaderTest) + ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ITEMCHARGESALLOWANCESPDF); + assertEquals("EUR", zi.getInvoiceCurrencyCode()); + assertTrue(zi.getUTF8().contains("0911623562")); // fax number + assertTrue(zi.getUTF8().contains("ABK")); + + // Reading ZUGFeRD + assertEquals("18.92", zi.getAmount()); + assertEquals(orgname, zi.getHolder()); + assertEquals(number, zi.getForeignReference()); assertEquals(zi.getVersion(), 2); } catch (Exception e) { - e.printStackTrace(); + fail("Exception should not be raised"); } } @@ -595,7 +594,7 @@ public class ZF2PushTest extends TestCase { assertEquals(1, i.getInvoiceReferencedDocuments().size()); assertEquals("abcd1234", i.getInvoiceReferencedDocuments().get(0).getIssuerAssignedID()); assertEquals("4304171000002", i.getRecipient().getGlobalID()); - SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); assertEquals(occurrenceFrom, sdf.format(i.getDetailedDeliveryPeriodFrom())); assertEquals(occurrenceTo, sdf.format(i.getDetailedDeliveryPeriodTo())); assertEquals("2001015001325", i.getZFItems()[0].getProduct().getGlobalID()); @@ -629,7 +628,7 @@ public class ZF2PushTest extends TestCase { .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) .setNumber(number) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19)))) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)).addAllowance(new Allowance(BigDecimal.ONE)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19)))) .addAllowance(new Allowance(new BigDecimal(600)).setTaxPercent(new BigDecimal(19))) ); String theXML = new String(ze.getProvider().getXML()); @@ -645,7 +644,7 @@ public class ZF2PushTest extends TestCase { assertEquals("EUR", zi.getInvoiceCurrencyCode()); // Reading ZUGFeRD - assertEquals("4046.00", zi.getAmount()); + assertEquals("10805.20", zi.getAmount()); assertEquals(orgname, zi.getHolder()); assertEquals(number, zi.getForeignReference()); try { @@ -675,8 +674,8 @@ public class ZF2PushTest extends TestCase { .setNumber(number) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)))) - .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19))) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"))) + .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt")) ); String theXML = new String(ze.getProvider().getXML()); assertTrue(theXML.contains("