Merge branch 'master' into includedNotes-items
This commit is contained in:
13
History.md
13
History.md
@@ -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
|
||||
=======
|
||||
2024-11-18
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ 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
|
||||
|
||||
@@ -72,6 +72,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
|
||||
|
||||
@@ -155,6 +155,20 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
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() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
@@ -628,9 +637,9 @@ public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
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);
|
||||
|
||||
@@ -28,11 +28,17 @@ import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.*;
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class DeSerializationTest extends TestCase {
|
||||
public class DeSerializationTest extends ResourceCase {
|
||||
public void testJackson() throws JsonProcessingException {
|
||||
|
||||
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 {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@@ -61,9 +61,9 @@ public class ZF2PushTest extends TestCase {
|
||||
final String TARGET_REVERSECHARGEPDF = "./target/testout-ZF2PushReverseCharge.pdf";
|
||||
|
||||
public void testPushExport() {
|
||||
/***
|
||||
* This writes to a filename like an official sample, please consider when changing (probably better not?)
|
||||
*/
|
||||
/***
|
||||
* This writes to a filename like an official sample, please consider when changing (probably better not?)
|
||||
*/
|
||||
// the writing part
|
||||
String orgname = "Bei Spiel GmbH";
|
||||
String number = "RE-20201121/508";
|
||||
@@ -96,8 +96,8 @@ public class ZF2PushTest extends TestCase {
|
||||
fail("Exception should not be raised");
|
||||
}
|
||||
|
||||
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
Invoice i=new Invoice();
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
Invoice i = new Invoice();
|
||||
try {
|
||||
zii.extractInto(i);
|
||||
} catch (XPathExpressionException e) {
|
||||
@@ -164,7 +164,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
fail("IOException should not be raised");
|
||||
}
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_ATTACHMENTSPDF);
|
||||
Invoice i= null;
|
||||
Invoice i = null;
|
||||
try {
|
||||
i = zii.extractInvoice();
|
||||
} catch (XPathExpressionException e) {
|
||||
@@ -172,7 +172,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
assertEquals(senderDescription,i.getSender().getDescription());
|
||||
assertEquals(senderDescription, i.getSender().getDescription());
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
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)))));
|
||||
|
||||
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")
|
||||
.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"))
|
||||
@@ -372,7 +372,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
fail("IOException should not be raised");
|
||||
}
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF);
|
||||
Invoice i= null;
|
||||
Invoice i = null;
|
||||
try {
|
||||
i = zii.extractInvoice();
|
||||
} catch (XPathExpressionException e) {
|
||||
@@ -530,7 +530,7 @@ ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
.setContractReferencedDocument(contractID)
|
||||
.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"))
|
||||
.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)))
|
||||
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
|
||||
.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(occurrenceTo));
|
||||
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("20200115")); // to contain item delivery periods
|
||||
|
||||
|
||||
@@ -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
|
||||
public void testImportIncludedNotes() throws XPathExpressionException, ParseException {
|
||||
InputStream inputStream = this.getClass()
|
||||
|
||||
BIN
library/src/test/resources/EN16931_1_Teilrechnung.pdf
Normal file
BIN
library/src/test/resources/EN16931_1_Teilrechnung.pdf
Normal file
Binary file not shown.
@@ -122,7 +122,9 @@ public class PDFValidator extends Validator {
|
||||
}
|
||||
|
||||
// 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 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
|
||||
@@ -135,6 +135,22 @@ public class ValidationContext {
|
||||
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() {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ValidationResultItem {
|
||||
|
||||
protected String message, location=null;
|
||||
protected int section =-1;
|
||||
protected String id =""; // e.g. "FX-SCH-A-000026"
|
||||
|
||||
|
||||
private ESeverity severity=ESeverity.error;
|
||||
@@ -103,6 +104,15 @@ public class ValidationResultItem {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public ValidationResultItem setID(String id) {
|
||||
this.id=id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getSection() {
|
||||
return section;
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ public class XMLValidator extends Validator {
|
||||
|
||||
String thisFailText = "";
|
||||
String thisFailID = "";
|
||||
String thisFailIDStr = "";
|
||||
String thisFailTest = "";
|
||||
String thisFailLocation = "";
|
||||
if (failedAsserts.getLength() > 0) {
|
||||
@@ -461,7 +462,8 @@ public class XMLValidator extends Validator {
|
||||
//nodes.item(i).getTextContent())) {
|
||||
Node currentFailNode = failedAsserts.item(nodeIndex);
|
||||
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) {
|
||||
thisFailTest = currentFailNode.getAttributes().getNamedItem("test").getNodeValue();
|
||||
@@ -494,8 +496,8 @@ public class XMLValidator extends Validator {
|
||||
|
||||
LOGGER.info("FailedAssert ", thisFailText);
|
||||
|
||||
context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailID + " from " + xsltFilename + ")")
|
||||
.setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section)
|
||||
context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailIDStr + " from " + xsltFilename + ")")
|
||||
.setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID)
|
||||
.setPart(EPart.fx));
|
||||
failedRules++;
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ public class ZUGFeRDValidator {
|
||||
LOGGER.info("Parsed PDF:" + pdfResult + " XML:" + (xmlValidity ? "valid" : "invalid")
|
||||
+ " Signature:" + Signature + " Checksum:" + sha1Checksum + " Profile:" + context.getProfile()
|
||||
+ " Version:" + context.getGeneration() + " Took:" + duration + "ms Errors:[" + context.getCSVResult()
|
||||
+ "] " + toBeAppended);
|
||||
+ "] ErrorIDs: [" + context.getCSVIDResult() + "]" + toBeAppended);
|
||||
wasCompletelyValid = ((pdfValidity) && (xmlValidity));
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user