Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Charges on item level #568

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
15 changes: 15 additions & 0 deletions library/src/main/java/org/mustangproject/Item.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class Item implements IZUGFeRDExportableItem {
protected ArrayList<ReferencedDocument> referencedDocuments = null;
protected ArrayList<ReferencedDocument> additionalReference = null;
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> totalAllowances = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();

/***
Expand Down Expand Up @@ -235,6 +236,15 @@ public IZUGFeRDAllowanceCharge[] getItemAllowances() {
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
}

@Override
public IZUGFeRDAllowanceCharge[] getItemTotalAllowances() {
if (totalAllowances.isEmpty()) {
return null;
} else {
return totalAllowances.toArray(new IZUGFeRDAllowanceCharge[0]);
}
}

@Override
public IZUGFeRDAllowanceCharge[] getItemCharges() {
if (Charges.isEmpty()) {
Expand Down Expand Up @@ -280,6 +290,11 @@ public Item addAllowance(IZUGFeRDAllowanceCharge izac) {
return this;
}

public Item addTotalAllowance(IZUGFeRDAllowanceCharge izac) {
totalAllowances.add(izac);
return this;
}

/***
* adds item level freetext fields (includednote)
* @param text UTF8 plain text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,89 +646,47 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
Item it = new Item(currentItemNode.getChildNodes(), recalcPrice);
zpp.addItem(it);

}

// now handling base64 encoded attachments AttachmentBinaryObject=CII, EmbeddedDocumentBinaryObject=UBL
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) {
Copy link
Contributor Author

@InnuceEAN InnuceEAN Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically moved everything below to new method parseCharge() without much changes. Ignoring whitespaces should make this diff easier to parse (?w=1)

FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
fileAttachments.add(fa);
// filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
}

// item level charges+allowances are not yet handled but a lower item price will
// be read,
// so the invoice remains arithmetically correct
// -> parse document level charges+allowances
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < chargeNodes.getLength(); i++) {
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
boolean isCharge = true;
String chargeAmount = null;
String reason = null;
String reasonCode = null;
String taxPercent = null;
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
if (chargeChildName != null) {

if (chargeChildName.equals("ChargeIndicator")) {
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
&& (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
isCharge = XMLTools.trimOrNull(indicatorChilds.item(indicatorChildIndex)).equalsIgnoreCase("true");
}
}
} else if (chargeChildName.equals("ActualAmount")) {
chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("Reason")) {
reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("ReasonCode")) {
reasonCode = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("CategoryTradeTax")) {
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
}
}
}
xpr = xpath.compile(".//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
NodeList chargeNodes = (NodeList) xpr.evaluate(currentItemNode, XPathConstants.NODESET);
for (int k = 0; k < chargeNodes.getLength(); k++) {
NodeList chargeNodeChilds = chargeNodes.item(k).getChildNodes();
Charge c = parseCharge(chargeNodeChilds, false);
if (c instanceof Allowance) {
it.addTotalAllowance(c);
} else {
it.addCharge(c);
}
}
}
}

if (isCharge) {
Charge c = new Charge(new BigDecimal(chargeAmount));
if (reason != null) {
c.setReason(reason);
}
if (reasonCode != null) {
c.setReasonCode(reasonCode);
}
if (taxPercent != null) {
c.setTaxPercent(new BigDecimal(taxPercent));
}
zpp.addCharge(c);
} else {
Allowance a = new Allowance(new BigDecimal(chargeAmount));
if (reason != null) {
a.setReason(reason);
}
if (reasonCode != null) {
a.setReasonCode(reasonCode);
}
if (taxPercent != null) {
a.setTaxPercent(new BigDecimal(taxPercent));
}
zpp.addAllowance(a);
}

// now handling base64 encoded attachments AttachmentBinaryObject=CII, EmbeddedDocumentBinaryObject=UBL
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) {
FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
fileAttachments.add(fa);
// filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
}

// item level charges+allowances are not yet handled but a lower item price will
// be read,
// so the invoice remains arithmetically correct
// -> parse document level charges+allowances
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ApplicableHeaderTradeSettlement because we only want to have header level charges here

NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < chargeNodes.getLength(); i++) {
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
Charge c = parseCharge(chargeNodeChilds, true);
if (c instanceof Allowance) {
zpp.addAllowance(c);
} else {
zpp.addCharge(c);
}
}

if (zpp.getZFItems() != null && zpp.getZFItems().length > 0) {
Copy link
Contributor Author

@InnuceEAN InnuceEAN Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was previously only done for if (nodes.getLength() != 0)
zfItems.length > 0 should basically check the same

TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getGrandTotal()
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
Expand All @@ -749,6 +707,72 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx

}

/**
* @param isHeader true = header level charge, false = item level charge
*/
private Charge parseCharge(NodeList chargeNodeChilds, boolean isHeader) {
boolean isCharge = true;
String chargeAmount = null;
String reason = null;
String reasonCode = null;
String taxPercent = null;
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
if (chargeChildName != null) {

if (chargeChildName.equals("ChargeIndicator")) {
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
&& (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
isCharge = XMLTools.trimOrNull(indicatorChilds.item(indicatorChildIndex)).equalsIgnoreCase("true");
}
}
} else if (chargeChildName.equals("ActualAmount")) {
chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("Reason")) {
reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("ReasonCode")) {
reasonCode = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (isHeader && chargeChildName.equals("CategoryTradeTax")) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added condition isHeader

NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
}
}
}
}
}

if (isCharge) {
Charge c = new Charge(new BigDecimal(chargeAmount));
if (reason != null) {
c.setReason(reason);
}
if (reasonCode != null) {
c.setReasonCode(reasonCode);
}
if (taxPercent != null) {
c.setTaxPercent(new BigDecimal(taxPercent));
}
return c;
} else {
Allowance a = new Allowance(new BigDecimal(chargeAmount));
if (reason != null) {
a.setReason(reason);
}
if (reasonCode != null) {
a.setReasonCode(reasonCode);
}
if (taxPercent != null) {
a.setTaxPercent(new BigDecimal(taxPercent));
}
return a;
}
}

protected Document getDocument() {
return document;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
Expand Down Expand Up @@ -397,4 +398,12 @@ public void testImportIncludedNotes() throws XPathExpressionException, ParseExce

}

public void testPositionAllowance() throws FileNotFoundException, XPathExpressionException, ParseException {
File inputFile = getResourceAsFile("factur-x-with-position-allowance.xml");
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(inputFile));

CalculatedInvoice invoice = new CalculatedInvoice();
zii.extractInto(invoice);
assertThat(invoice.getGrandTotal()).isEqualByComparingTo(BigDecimal.valueOf(82.63));
}
}
Loading