Merge branch 'issues/480'

This commit is contained in:
jstaerk
2024-11-18 09:37:34 +01:00
6 changed files with 82 additions and 28 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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 += "<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()) + "\"";
}
@@ -856,7 +856,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
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;
@@ -865,14 +865,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
// //
// 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>"

View File

@@ -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);

View File

@@ -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)