Skip to content

Commit

Permalink
Merge branch 'issues/480'
Browse files Browse the repository at this point in the history
  • Loading branch information
jstaerk committed Nov 18, 2024
2 parents 0895c98 + a92ddb2 commit a2e37ac
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 28 deletions.
19 changes: 19 additions & 0 deletions library/src/main/java/org/mustangproject/Invoice.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<>();
Expand Down Expand Up @@ -467,6 +468,24 @@ public TradeParty getSender() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,15 @@ default boolean rebateAgreementExists() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/***
* The Transactioncalculator e.g. adds the line totals and applies VAT on whole
* invoices
*
*
* @see LineCalculator
*/
public class TransactionCalculator implements IAbsoluteValueProvider {
Expand All @@ -27,7 +27,7 @@ public TransactionCalculator(IExportableTransaction trans) {
/***
* if something had already been paid in advance, this will get it from the
* transaction
*
*
* @return prepaid amount
*/
protected BigDecimal getTotalPrepaid() {
Expand All @@ -41,19 +41,19 @@ protected BigDecimal getTotalPrepaid() {
/***
* 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
*/
Expand All @@ -77,7 +77,7 @@ private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharg
/***
* 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
*/
Expand All @@ -95,7 +95,7 @@ private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAl
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() + " ";
}
}
Expand All @@ -107,7 +107,7 @@ private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAl
/***
* 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
*/
Expand All @@ -122,7 +122,7 @@ protected String getAllowanceReasonForPercent(BigDecimal percent) {

/***
* returns total of allowances for this tax rate
*
*
* @param percent a specific rate, or null for any rate
* @return the total amount
*/
Expand All @@ -134,25 +134,25 @@ protected BigDecimal getAllowancesForPercent(BigDecimal percent) {
/***
* 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);
}

/**
Expand All @@ -171,9 +171,9 @@ protected HashMap<BigDecimal, VATAmount> getVATPercentAmountMap() {
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());
Expand All @@ -193,8 +193,8 @@ protected HashMap<BigDecimal, VATAmount> getVATPercentAmountMap() {
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));
Expand All @@ -211,8 +211,8 @@ protected HashMap<BigDecimal, VATAmount> getVATPercentAmountMap() {
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));
Expand All @@ -239,4 +239,11 @@ public BigDecimal getAllowanceTotal() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public void generateXML(IExportableTransaction trans) {
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 = "";
Expand Down Expand Up @@ -454,7 +454,7 @@ public void generateXML(IExportableTransaction trans) {
if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
xml += "<ram:DesignatedProductClassification>"
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
if (classification.getClassCode().getListVersionID() != null) {
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
}
Expand Down Expand Up @@ -856,7 +856,7 @@ public void generateXML(IExportableTransaction trans) {
final String chargesTotalLine = "<ram:ChargeTotalAmount>" + currencyFormat(calc.getChargesForPercent(null)) + "</ram:ChargeTotalAmount>";

xml += "<ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
if (getProfile() != Profiles.getByName("Minimum")) {
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BASICWL"))) {
xml += "<ram:LineTotalAmount>" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>";
xml += chargesTotalLine
+ allowanceTotalLine;
Expand All @@ -865,14 +865,18 @@ public void generateXML(IExportableTransaction trans) {
// //
// currencyID=\"EUR\"
+ "<ram:TaxTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>"
+ "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>";
if (trans.getRoundingAmount() != null) {
xml += "<ram:RoundingAmount>" + currencyFormat(trans.getRoundingAmount()) + "</ram:RoundingAmount>";
}

xml += "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
// //
// currencyID=\"EUR\"
if (getProfile() != Profiles.getByName("Minimum")) {
xml += "<ram:TotalPrepaidAmount>" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>";
}
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>"
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getDuePayable()) + "</ram:DuePayableAmount>"
+ "</ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
if (trans.getInvoiceReferencedDocumentID() != null) {
xml += "<ram:InvoiceReferencedDocument>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,11 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx

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);
Expand Down
10 changes: 10 additions & 0 deletions library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,23 @@ public void testPushExport() {
.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);
} catch (IOException | ParseException e) {
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)
Expand Down

0 comments on commit a2e37ac

Please sign in to comment.