Merge branch 'master' into includedNotes-items

This commit is contained in:
ean
2024-11-27 13:03:59 +01:00
16 changed files with 169 additions and 25 deletions

View File

@@ -1,3 +1,16 @@
2.15.1
=======
- #566
- have a bean contructor for direct debit
-? lineTotalAmount is null
? be able to access ID in error message
? log error IDs
- closes #579
- #581
- #576
- #578
- log error ids
2.15.0 2.15.0
======= =======
2024-11-18 2024-11-18

View File

@@ -12,10 +12,12 @@ import java.math.BigDecimal;
public class CalculatedInvoice extends Invoice implements Serializable { public class CalculatedInvoice extends Invoice implements Serializable {
protected BigDecimal grandTotal=null; protected BigDecimal grandTotal=null;
protected BigDecimal lineTotalAmount=null;
public void calculate() { public void calculate() {
TransactionCalculator tc=new TransactionCalculator(this); TransactionCalculator tc=new TransactionCalculator(this);
grandTotal=tc.getGrandTotal(); grandTotal=tc.getGrandTotal();
lineTotalAmount=tc.getValue();
} }
public BigDecimal getGrandTotal() { public BigDecimal getGrandTotal() {
if (grandTotal==null) { if (grandTotal==null) {
@@ -27,4 +29,15 @@ public class CalculatedInvoice extends Invoice implements Serializable {
grandTotal=grand; grandTotal=grand;
return this; 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,12 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
/** /**
* Debited account identifier (BT-91) * Debited account identifier (BT-91)
*/ */
protected final String IBAN; protected String IBAN;
/** /**
* Mandate reference identifier (BT-89) * Mandate reference identifier (BT-89)
*/ */
protected final String mandate; protected String mandate;
/** /**
* bean constructor * bean constructor

View File

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

View File

@@ -155,6 +155,20 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
return null; return null;
} }
/***
*
* @return the line ID
*/
default String getId() {
return null;
}
/**
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
*
* The information are written to the same xml nodes like {@link #getNotes()} but with explicit subjectCode.
* @return list of the notes
*/
default List<IncludedNote> getNotesWithSubjectCode() { default List<IncludedNote> getNotesWithSubjectCode() {
return null; return null;
} }

View File

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

View File

@@ -324,12 +324,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); NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (prepaidNodes.getLength() > 0) { if (prepaidNodes.getLength() > 0) {
zpp.setTotalPrepaidAmount(new BigDecimal(XMLTools.trimOrNull(prepaidNodes.item(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 issueDate = null;
Date dueDate = null; Date dueDate = null;
Date deliveryDate = null; Date deliveryDate = null;
@@ -628,9 +637,9 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]"); xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
String buyerReference = null; String buyerReference = null;
prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); lineTotalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (prepaidNodes.getLength() > 0) { if (lineTotalNodes.getLength() > 0) {
buyerReference = XMLTools.trimOrNull(prepaidNodes.item(0)); buyerReference = XMLTools.trimOrNull(lineTotalNodes.item(0));
} }
if (buyerReference != null) { if (buyerReference != null) {
zpp.setReferenceNumber(buyerReference); zpp.setReferenceNumber(buyerReference);

View File

@@ -28,11 +28,17 @@ import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.mustangproject.*; import org.mustangproject.*;
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.Date; import java.util.Date;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DeSerializationTest extends TestCase { public class DeSerializationTest extends ResourceCase {
public void testJackson() throws JsonProcessingException { public void testJackson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
@@ -50,6 +56,33 @@ public class DeSerializationTest extends TestCase {
} }
public void testInvoiceLine() throws JsonProcessingException {
File inputCII = getResourceAsFile("factur-x.xml");
boolean hasExceptions = false;
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
try {
zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8));
} catch (IOException e) {
hasExceptions = true;
}
CalculatedInvoice ci=new CalculatedInvoice();
try {
zii.extractInto(ci);
} catch (XPathExpressionException e) {
hasExceptions = true;
} catch (ParseException e) {
hasExceptions = true;
}
ObjectMapper mapper = new ObjectMapper();
String jsonArray = mapper.writeValueAsString(ci);
assertFalse(hasExceptions);
assertTrue(jsonArray.contains("lineTotalAmount"));
}
public void testAllowanceRead() throws JsonProcessingException { public void testAllowanceRead() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();

View File

@@ -96,8 +96,8 @@ public class ZF2PushTest extends TestCase {
fail("Exception should not be raised"); fail("Exception should not be raised");
} }
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PDF);
Invoice i=new Invoice(); Invoice i = new Invoice();
try { try {
zii.extractInto(i); zii.extractInto(i);
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
@@ -164,7 +164,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
fail("IOException should not be raised"); fail("IOException should not be raised");
} }
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_ATTACHMENTSPDF); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_ATTACHMENTSPDF);
Invoice i= null; Invoice i = null;
try { try {
i = zii.extractInvoice(); i = zii.extractInvoice();
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
@@ -172,7 +172,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
} catch (ParseException e) { } catch (ParseException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
assertEquals(senderDescription,i.getSender().getDescription()); assertEquals(senderDescription, i.getSender().getDescription());
// now check the contents (like MustangReaderTest) // now check the contents (like MustangReaderTest)
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ATTACHMENTSPDF); ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ATTACHMENTSPDF);
@@ -302,7 +302,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
// .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().setPercent(new BigDecimal(50)))));
ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addVATID("DE0815").addTaxID ("4711")) .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addVATID("DE0815").addTaxID("4711"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816") .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816")
.setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562"))) .setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562")))
.setDeliveryAddress(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816")) .setDeliveryAddress(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816"))
@@ -372,7 +372,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
fail("IOException should not be raised"); fail("IOException should not be raised");
} }
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF);
Invoice i= null; Invoice i = null;
try { try {
i = zii.extractInvoice(); i = zii.extractInvoice();
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
@@ -530,7 +530,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
.setContractReferencedDocument(contractID) .setContractReferencedDocument(contractID)
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711") .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711")
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3")) .setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3"))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15"))) .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).setId("a123").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
.addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16)))
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16))) .addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
.addCashDiscount(new CashDiscount(new BigDecimal(2), 14)) .addCashDiscount(new CashDiscount(new BigDecimal(2), 14))
@@ -562,7 +562,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
assertTrue(zi.getUTF8().contains(occurrenceFrom)); assertTrue(zi.getUTF8().contains(occurrenceFrom));
assertTrue(zi.getUTF8().contains(occurrenceTo)); assertTrue(zi.getUTF8().contains(occurrenceTo));
assertTrue(zi.getUTF8().contains(contractID)); assertTrue(zi.getUTF8().contains(contractID));
assertEquals(zi.importedInvoice.getZFItems()[0].getId(), "a123");
assertTrue(zi.getUTF8().contains("20200113")); // to contain item delivery periods assertTrue(zi.getUTF8().contains("20200113")); // to contain item delivery periods
assertTrue(zi.getUTF8().contains("20200115")); // to contain item delivery periods assertTrue(zi.getUTF8().contains("20200115")); // to contain item delivery periods

View File

@@ -380,6 +380,31 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
} }
@Test
public void testImportPrepaid() throws XPathExpressionException, ParseException {
InputStream inputStream = this.getClass()
.getResourceAsStream("/EN16931_1_Teilrechnung.pdf");
ZUGFeRDInvoiceImporter importer = new ZUGFeRDInvoiceImporter();
importer.doIgnoreCalculationErrors();
importer.setInputStream(inputStream);
CalculatedInvoice invoice = new CalculatedInvoice();
importer.extractInto(invoice);
boolean isBD=invoice.getTotalPrepaidAmount() instanceof BigDecimal;
assertTrue(isBD);
BigDecimal expectedPrepaid=new BigDecimal(50);
BigDecimal expectedLineTotal=new BigDecimal("180.76");
if (isBD) {
BigDecimal amread=invoice.getTotalPrepaidAmount();
BigDecimal amline=invoice.getLineTotalAmount();
assertTrue(amread.compareTo(expectedPrepaid) == 0);
assertTrue(amline.compareTo(expectedLineTotal) == 0);
}
}
@Test @Test
public void testImportIncludedNotes() throws XPathExpressionException, ParseException { public void testImportIncludedNotes() throws XPathExpressionException, ParseException {
InputStream inputStream = this.getClass() InputStream inputStream = this.getClass()

Binary file not shown.

View File

@@ -122,7 +122,9 @@ public class PDFValidator extends Validator {
} }
// step 2 validate XMP // step 2 validate XMP
final ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream); final ZUGFeRDImporter zi = new ZUGFeRDImporter();
zi.doIgnoreCalculationErrors();//of course the calculation will still be schematron checked
zi.setInputStream(inputStream);
final String xmp = zi.getXMP(); final String xmp = zi.getXMP();
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

View File

@@ -135,6 +135,22 @@ public class ValidationContext {
return String.join(",", errorcodes); return String.join(",", errorcodes);
} }
/***
*
* @return the unique error IDs as comma separated string
*/
public String getCSVIDResult() {
final ArrayList<String> errorIDs = new ArrayList<>();
for (final ValidationResultItem validationResultItem : results) {
if (!validationResultItem.getID().isEmpty()) {
final String errorID=validationResultItem.getID();
errorIDs.add(errorID);
}
}
return String.join(",", errorIDs);
}
public void setInvalid() { public void setInvalid() {
isValid = false; isValid = false;
} }

View File

@@ -12,6 +12,7 @@ public class ValidationResultItem {
protected String message, location=null; protected String message, location=null;
protected int section =-1; protected int section =-1;
protected String id =""; // e.g. "FX-SCH-A-000026"
private ESeverity severity=ESeverity.error; private ESeverity severity=ESeverity.error;
@@ -103,6 +104,15 @@ public class ValidationResultItem {
return severity; return severity;
} }
public ValidationResultItem setID(String id) {
this.id=id;
return this;
}
public String getID() {
return id;
}
public int getSection() { public int getSection() {
return section; return section;
} }

View File

@@ -453,6 +453,7 @@ public class XMLValidator extends Validator {
String thisFailText = ""; String thisFailText = "";
String thisFailID = ""; String thisFailID = "";
String thisFailIDStr = "";
String thisFailTest = ""; String thisFailTest = "";
String thisFailLocation = ""; String thisFailLocation = "";
if (failedAsserts.getLength() > 0) { if (failedAsserts.getLength() > 0) {
@@ -461,7 +462,8 @@ public class XMLValidator extends Validator {
//nodes.item(i).getTextContent())) { //nodes.item(i).getTextContent())) {
Node currentFailNode = failedAsserts.item(nodeIndex); Node currentFailNode = failedAsserts.item(nodeIndex);
if (currentFailNode.getAttributes().getNamedItem("id") != null) { if (currentFailNode.getAttributes().getNamedItem("id") != null) {
thisFailID = " [ID " + currentFailNode.getAttributes().getNamedItem("id").getNodeValue() + "]"; thisFailID = currentFailNode.getAttributes().getNamedItem("id").getNodeValue();
thisFailIDStr = " [ID " + thisFailID + "]";
} }
if (currentFailNode.getAttributes().getNamedItem("test") != null) { if (currentFailNode.getAttributes().getNamedItem("test") != null) {
thisFailTest = currentFailNode.getAttributes().getNamedItem("test").getNodeValue(); thisFailTest = currentFailNode.getAttributes().getNamedItem("test").getNodeValue();
@@ -494,8 +496,8 @@ public class XMLValidator extends Validator {
LOGGER.info("FailedAssert ", thisFailText); LOGGER.info("FailedAssert ", thisFailText);
context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailID + " from " + xsltFilename + ")") context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailIDStr + " from " + xsltFilename + ")")
.setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section) .setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID)
.setPart(EPart.fx)); .setPart(EPart.fx));
failedRules++; failedRules++;

View File

@@ -318,7 +318,7 @@ public class ZUGFeRDValidator {
LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid") LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid")
+ " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile() + " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile()
+ " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:[" + context.getCSVResult() + " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:[" + context.getCSVResult()
+ "] " + toBeAppended); + "] ErrorIDs: [" + context.getCSVIDResult() + "]" + toBeAppended);
wasCompletelyValid = ((pdfValidity) && (xmlValidity)); wasCompletelyValid = ((pdfValidity) && (xmlValidity));
return sw.toString(); return sw.toString();
} }