Merge remote-tracking branch 'origin/issues/503-ubl' into issues/503-ubl

This commit is contained in:
Bharti
2024-11-28 12:27:51 +01:00
86 changed files with 20347 additions and 21116 deletions

View File

@@ -11,11 +11,13 @@ import java.math.BigDecimal;
public class CalculatedInvoice extends Invoice implements Serializable {
protected BigDecimal grandTotal=null;
protected BigDecimal grandTotal=null;
protected BigDecimal lineTotalAmount=null;
public void calculate() {
TransactionCalculator tc=new TransactionCalculator(this);
grandTotal=tc.getGrandTotal();
lineTotalAmount=tc.getValue();
}
public BigDecimal getGrandTotal() {
if (grandTotal==null) {
@@ -27,4 +29,15 @@ public class CalculatedInvoice extends Invoice implements Serializable {
grandTotal=grand;
return this;
}
public BigDecimal getLineTotalAmount() {
if (lineTotalAmount==null) {
calculate();
}
return lineTotalAmount;
}
public CalculatedInvoice setLineTotalAmount(BigDecimal total) {
lineTotalAmount=total;
return this;
}
}

View File

@@ -9,12 +9,19 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
/**
* Debited account identifier (BT-91)
*/
protected final String IBAN;
protected String IBAN;
/**
* Mandate reference identifier (BT-89)
*/
protected final String mandate;
protected String mandate;
/**
* bean constructor
*/
public DirectDebit() {
}
/***
* constructor for normal use :-)

View File

@@ -8,6 +8,14 @@ public class FileAttachment {
protected String description;
protected byte[] data;
/***
* bean contructor
*/
public FileAttachment() {
}
public FileAttachment(String filename, String mimetype, String relation, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;

View File

@@ -4,8 +4,8 @@ package org.mustangproject;
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
*/
public class IncludedNote {
private final String content;
private final SubjectCode subjectCode;
private String content;
private SubjectCode subjectCode;
private static final String INCLUDE_START = "<ram:IncludedNote>";
private static final String INCLUDE_END = "</ram:IncludedNote>";
@@ -19,6 +19,13 @@ public class IncludedNote {
this.subjectCode = subjectCode;
}
/**
* bean constructor
*/
public IncludedNote() {
}
public static IncludedNote generalNote(String content) {
return new IncludedNote(content, SubjectCode.AAI);
}

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,26 @@ 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;
}
/***
* set the cent e.g. to reach the next 5ct mark for currencies in certain countries
* e.g. in the Netherlands ("Rappenrundung")
* @param amount
* @return fluent setter
*/
public Invoice setRoundingAmount(BigDecimal amount) {
roundingAmount=amount;
return this;
}
/***
* sets a named sender contact
* @deprecated use setSender
@@ -524,8 +545,8 @@ public class Invoice implements IExportableTransaction {
/***
* this is wrong and only used from jackson
* @param iza
* @return
* @param iza the Array of allowances/charges
* @return fluent setter
*/
public Invoice setZFAllowances(Allowance[] iza) {
Allowances=new ArrayList<>();
@@ -548,8 +569,8 @@ public class Invoice implements IExportableTransaction {
/***
* this is wrong and only used from jackson
* @param iza
* @return
* @param iza the array of charges
* @return fluent setter
*/
public Invoice setZFCharges(Charge[] iza) {
Charges=new ArrayList<>();

View File

@@ -97,6 +97,9 @@ public class Item implements IZUGFeRDExportableItem {
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
.ifPresent(product::setVATPercent);
});
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> {
icnm.getAsString("LineID").ifPresent(this::setId);
});
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
// ubl

View File

@@ -92,7 +92,7 @@ public class XMLTools extends XMLWriter {
/***
* formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e.
* cuts potential 0s off the end until minDecimals
* @param value
* @param value the value to be formatted
* @param maxDecimals number of maximal scale
* @param minDecimals number of minimal scale
* @return value as String with decimals in the specified range
@@ -138,7 +138,7 @@ public class XMLTools extends XMLWriter {
}
/***
* relplaces some entities like < , > and & with their escaped pendant like &lt;
* relplaces some entities like &lt; , &gt; and &amp; with their escaped pendant like &amp;lt;
* @param s the string
* @return the "safe" string
*/

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

@@ -153,4 +153,12 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
return null;
}
/***
*
* @return the line ID
*/
default String getId() {
return null;
}
}

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 = "";
@@ -401,6 +401,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
int lineID = 0;
for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
lineID++;
String lineIDStr = Integer.toString(lineID);
if (currentItem.getId()!=null) {
lineIDStr=currentItem.getId();
}
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
@@ -408,7 +412,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
"<ram:AssociatedDocumentLineDocument>"
+ "<ram:LineID>" + lineID + "</ram:LineID>"
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>"
+ buildItemNotes(currentItem)
+ "</ram:AssociatedDocumentLineDocument>"
@@ -454,7 +458,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 +860,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 +869,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

@@ -655,6 +655,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
/**
* returns a list of LineItems
* @deprecated use invoiceimporter getZFItems
*
* @return a List of LineItem instances
*/

View File

@@ -102,7 +102,7 @@ public class ZUGFeRDInvoiceImporter {
/***
* return the file names of all files embedded into the PDF
* @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
* @see ZUGFeRDInvoiceImporter for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
* @return a ArrayList of FileAttachments, empty if none
*/
public List<FileAttachment> getFileAttachmentsPDF() {
@@ -229,7 +229,7 @@ public class ZUGFeRDInvoiceImporter {
* set the xml of a CII invoice
* @param rawXML the xml string
* @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter)
* @throws IOException
* @throws IOException if parsing xml throws it (unlikely its string based)
*/
public void setRawXML(byte[] rawXML, boolean doParse) throws IOException {
this.containsMeta = true;
@@ -249,7 +249,7 @@ public class ZUGFeRDInvoiceImporter {
/***
* set the xml of a CII invoice, simple version
* @param rawXML the cii(?) as a string
* @throws IOException
* @throws IOException if parsing xml throws it (unlikely its string based)
*/
public void setRawXML(byte[] rawXML) throws IOException {
setRawXML(rawXML, true);
@@ -400,12 +400,21 @@ public class ZUGFeRDInvoiceImporter {
}
}
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
xpr = xpath.compile("//*[local-name()=\"TotalPrepaidAmount\"]|//*[local-name()=\"PrepaidAmount\"]");
NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (prepaidNodes.getLength() > 0) {
zpp.setTotalPrepaidAmount(new BigDecimal(XMLTools.trimOrNull(prepaidNodes.item(0))));
}
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"LineTotalAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"LineExtensionAmount\"]");
NodeList lineTotalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (lineTotalNodes.getLength() > 0) {
if (zpp instanceof CalculatedInvoice) {
((CalculatedInvoice) zpp).setLineTotalAmount(new BigDecimal(XMLTools.trimOrNull(lineTotalNodes.item(0))));
}
}
Date issueDate = null;
Date dueDate = null;
Date deliveryDate = null;
@@ -430,6 +439,34 @@ public class ZUGFeRDInvoiceImporter {
}
}
}
List<IncludedNote> includedNotes = new ArrayList<>();
if ((item.getLocalName() != null) && (item.getLocalName().equals("IncludedNote"))) {
String subjectCode = "";
String content = null;
NodeList includedNodeChilds = item.getChildNodes();
for (int issueDateChildIndex = 0; issueDateChildIndex < includedNodeChilds.getLength(); issueDateChildIndex++) {
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("Content"))) {
content = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
}
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("SubjectCode"))) {
subjectCode = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
}
}
switch (subjectCode){
case "AAI": includedNotes.add(IncludedNote.generalNote(content)); break;
case "REG": includedNotes.add(IncludedNote.regulatoryNote(content)); break;
case "ABL": includedNotes.add(IncludedNote.legalNote(content)); break;
case "CUS": includedNotes.add(IncludedNote.customsNote(content)); break;
case "SUR": includedNotes.add(IncludedNote.sellerNote(content)); break;
case "TXD": includedNotes.add(IncludedNote.taxNote(content)); break;
case "ACY": includedNotes.add(IncludedNote.introductionNote(content)); break;
case "AAK": includedNotes.add(IncludedNote.discountBonusNote(content)); break;
default: includedNotes.add(IncludedNote.unspecifiedNote(content)); break;
}
}
zpp.addNotes(includedNotes);
}
}
String rootNode = extractString("local-name(/*)");
@@ -686,11 +723,16 @@ 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);
if (prepaidNodes.getLength() > 0) {
buyerReference = XMLTools.trimOrNull(prepaidNodes.item(0));
lineTotalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (lineTotalNodes.getLength() > 0) {
buyerReference = XMLTools.trimOrNull(lineTotalNodes.item(0));
}
if (buyerReference != null) {
zpp.setReferenceNumber(buyerReference);

View File

@@ -249,6 +249,12 @@ public class ZUGFeRDVisualizer {
EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new FileInputStream(xmlFilename);//rewind :-(
return toFOP(fis, theStandard);
}
protected String toFOP(InputStream is, EStandard theStandard)
throws FileNotFoundException, TransformerException {
try {
if (mXsltPDFTemplate == null) {
mXsltPDFTemplate = mFactory.newTemplates(
@@ -263,11 +269,11 @@ public class ZUGFeRDVisualizer {
//zf2 or fx
if (theStandard == EStandard.facturx) {
applyZF2XSLT(fis, iaos);
applyZF2XSLT(is, iaos);
} else if (theStandard == EStandard.ubl) {
applyUBL2XSLT(fis, iaos);
applyUBL2XSLT(is, iaos);
} else if (theStandard == EStandard.ubl_creditnote) {
applyUBLCreditNote2XSLT(fis, iaos);
applyUBLCreditNote2XSLT(is, iaos);
}