diff --git a/History.md b/History.md index 0ebebdfa..248a960c 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,18 @@ +2.15.1 +======= +- #566 Failed to parse PDF - Could not reproduce the invoice +? be able to access ID in error message +- closes #579 prepaidamount is only read in UBL +- #581 parse lineTotalAmount +- also parse TaxBasisAmount +- #576 read lineid, #578 set lineid +- log error ids +- #503 import more ubl +- allow jackson to run over more classes, e.g., DirectDebit, bean contructor for direct debit +- allow json includedNotes +- support importing duePayableAmount + + 2.15.0 ======= 2024-11-18 diff --git a/library/pom.xml b/library/pom.xml index 6e5e6daa..b9e92200 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -116,6 +116,12 @@ org.dom4j dom4j 2.1.4 + + + * + * + + diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java index 9c26d02e..e378f767 100644 --- a/library/src/main/java/org/mustangproject/Allowance.java +++ b/library/src/main/java/org/mustangproject/Allowance.java @@ -1,12 +1,16 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import java.math.BigDecimal; /*** * (absolute) allowances on item and document level */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class Allowance extends Charge { /*** diff --git a/library/src/main/java/org/mustangproject/BankDetails.java b/library/src/main/java/org/mustangproject/BankDetails.java index c6edcc86..a16976f3 100644 --- a/library/src/main/java/org/mustangproject/BankDetails.java +++ b/library/src/main/java/org/mustangproject/BankDetails.java @@ -2,11 +2,14 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment; /** * provides e.g. the IBAN to transfer money to :-) */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class BankDetails implements IZUGFeRDTradeSettlementPayment { /** * the bank account number @@ -15,15 +18,17 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { /** * BIC, I believe it's optional */ - protected String BIC=null; + protected String BIC = null; /** * the "name" of the bank account (holder) */ - protected String accountName=null; + protected String accountName = null; + /*** * bean constructor */ - public BankDetails() { } + public BankDetails() { + } /*** * constructor for IBAN only :-) @@ -32,6 +37,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { public BankDetails(String IBAN) { this.IBAN = IBAN; } + /*** * constructor for normal use :-) * @param IBAN the IBAN as string @@ -55,6 +61,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { * identify the IBAN. Of course you will specify your own IBAN in full length but * if you deduct from a customer's account you may e.g. leave out the first or last * digits so that nobody spying on the invoice gets to know the complete number + * * @param IBAN the "IBAN ID", i.e. the IBAN or parts of it * @return fluent setter */ @@ -81,9 +88,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { return this; } - /*** - * getOwn... methods will be removed in the future in favor of Tradeparty (e.g. Sender) class - * */ + + /* + I'd really like to get rid of all those getOwn... methods some time but in this case they are in the interface :-( + */ @Override @Deprecated @JsonIgnore @@ -101,11 +109,12 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { /** * set Holder + * * @param name account name (usually account holder if != sender) * @return fluent setter */ public BankDetails setAccountName(String name) { - accountName=name; + accountName = name; return this; } @@ -115,5 +124,4 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { } - } diff --git a/library/src/main/java/org/mustangproject/CalculatedInvoice.java b/library/src/main/java/org/mustangproject/CalculatedInvoice.java index 729d0f9b..194441b1 100644 --- a/library/src/main/java/org/mustangproject/CalculatedInvoice.java +++ b/library/src/main/java/org/mustangproject/CalculatedInvoice.java @@ -2,6 +2,8 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.TransactionCalculator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,13 +11,21 @@ import org.slf4j.LoggerFactory; import java.io.Serializable; import java.math.BigDecimal; +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class CalculatedInvoice extends Invoice implements Serializable { - protected BigDecimal grandTotal=null; + protected BigDecimal lineTotalAmount=null; + protected BigDecimal duePayable=null; + protected BigDecimal grandTotal=null; + protected BigDecimal taxBasis=null; public void calculate() { TransactionCalculator tc=new TransactionCalculator(this); grandTotal=tc.getGrandTotal(); + lineTotalAmount=tc.getValue(); + duePayable=tc.getDuePayable(); + taxBasis= tc.getTaxBasis(); } public BigDecimal getGrandTotal() { if (grandTotal==null) { @@ -27,4 +37,37 @@ public class CalculatedInvoice extends Invoice implements Serializable { grandTotal=grand; return this; } + public BigDecimal getTaxBasis() { + if (taxBasis==null) { + calculate(); + } + return taxBasis; + } + public CalculatedInvoice setTaxBasis(BigDecimal basis) { + taxBasis=basis; + return this; + } + + public BigDecimal getDuePayable() { + if (duePayable==null) { + calculate(); + } + return duePayable; + } + public CalculatedInvoice setDuePayable(BigDecimal due) { + duePayable=due; + return this; + } + + public BigDecimal getLineTotalAmount() { + if (lineTotalAmount==null) { + calculate(); + } + return lineTotalAmount; + } + public CalculatedInvoice setLineTotalAmount(BigDecimal total) { + lineTotalAmount=total; + return this; + } + } diff --git a/library/src/main/java/org/mustangproject/CashDiscount.java b/library/src/main/java/org/mustangproject/CashDiscount.java index c7fb7a03..2151489a 100644 --- a/library/src/main/java/org/mustangproject/CashDiscount.java +++ b/library/src/main/java/org/mustangproject/CashDiscount.java @@ -1,5 +1,7 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IZUGFeRDCashDiscount; import java.math.BigDecimal; @@ -7,6 +9,8 @@ import java.math.BigDecimal; /*** * A class to represent discounts for early payments ("Skonto") */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class CashDiscount implements IZUGFeRDCashDiscount { /*** @@ -31,6 +35,31 @@ public class CashDiscount implements IZUGFeRDCashDiscount { this.days = days; } + /*** + * bean contructor + */ + public CashDiscount() { + + } + + public BigDecimal getPercent() { + return percent; + } + + public CashDiscount setPercent(BigDecimal percent) { + this.percent = percent; + return this; + } + + public Integer getDays() { + return days; + } + + public CashDiscount setDays(Integer days) { + this.days = days; + return this; + } + /*** * @return this particular cash discount as cross industry invoice XML */ diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index 0e081397..6232611d 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -1,6 +1,8 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; @@ -9,6 +11,8 @@ import java.math.BigDecimal; /*** * Absolute and relative charges for document and item level */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class Charge implements IZUGFeRDAllowanceCharge { /** diff --git a/library/src/main/java/org/mustangproject/ClassCode.java b/library/src/main/java/org/mustangproject/ClassCode.java index 8b0a58cc..75fd59e9 100644 --- a/library/src/main/java/org/mustangproject/ClassCode.java +++ b/library/src/main/java/org/mustangproject/ClassCode.java @@ -20,12 +20,16 @@ */ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * A schemed classification for products. The scheme can be anything defined in UNTDID 7143. */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class ClassCode { private final String listID; private final String code; diff --git a/library/src/main/java/org/mustangproject/Contact.java b/library/src/main/java/org/mustangproject/Contact.java index f690d0d8..cead6f86 100644 --- a/library/src/main/java/org/mustangproject/Contact.java +++ b/library/src/main/java/org/mustangproject/Contact.java @@ -1,6 +1,7 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -11,6 +12,7 @@ import org.w3c.dom.NodeList; * @see TradeParty */ @JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class Contact implements IZUGFeRDExportableContact { /** diff --git a/library/src/main/java/org/mustangproject/DesignatedProductClassification.java b/library/src/main/java/org/mustangproject/DesignatedProductClassification.java index ac74bf36..1917568d 100644 --- a/library/src/main/java/org/mustangproject/DesignatedProductClassification.java +++ b/library/src/main/java/org/mustangproject/DesignatedProductClassification.java @@ -20,12 +20,16 @@ */ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IDesignatedProductClassification; /** * An implementation of {@link IDesignatedProductClassification} for describing a {@link org.mustangproject.Product} * */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class DesignatedProductClassification implements IDesignatedProductClassification { private final ClassCode classCode; private String className; diff --git a/library/src/main/java/org/mustangproject/DirectDebit.java b/library/src/main/java/org/mustangproject/DirectDebit.java index bc47204d..59da5b42 100644 --- a/library/src/main/java/org/mustangproject/DirectDebit.java +++ b/library/src/main/java/org/mustangproject/DirectDebit.java @@ -1,10 +1,14 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit; /** * provides e.g. the IBAN to transfer money to :-) */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class DirectDebit implements IZUGFeRDTradeSettlementDebit { /** * Debited account identifier (BT-91) diff --git a/library/src/main/java/org/mustangproject/FileAttachment.java b/library/src/main/java/org/mustangproject/FileAttachment.java index ba924af3..ed1fe39f 100644 --- a/library/src/main/java/org/mustangproject/FileAttachment.java +++ b/library/src/main/java/org/mustangproject/FileAttachment.java @@ -1,5 +1,10 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class FileAttachment { protected String filename; diff --git a/library/src/main/java/org/mustangproject/IncludedNote.java b/library/src/main/java/org/mustangproject/IncludedNote.java index 9133bcca..db34e793 100644 --- a/library/src/main/java/org/mustangproject/IncludedNote.java +++ b/library/src/main/java/org/mustangproject/IncludedNote.java @@ -1,10 +1,16 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + /** * A grouping of business terms to indicate accounting-relevant free texts including a qualification of these. */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class IncludedNote { private String content; + private SubjectCode subjectCode; private static final String INCLUDE_START = ""; @@ -14,7 +20,7 @@ public class IncludedNote { private static final String SUBJECT_CODE_START = ""; private static final String SUBJECT_CODE_END = ""; - private IncludedNote(String content, SubjectCode subjectCode) { + public IncludedNote(String content, SubjectCode subjectCode) { this.content = content; this.subjectCode = subjectCode; } @@ -29,24 +35,31 @@ public class IncludedNote { public static IncludedNote generalNote(String content) { return new IncludedNote(content, SubjectCode.AAI); } + public static IncludedNote regulatoryNote(String content) { return new IncludedNote(content, SubjectCode.REG); } + public static IncludedNote legalNote(String content) { return new IncludedNote(content, SubjectCode.ABL); } + public static IncludedNote customsNote(String content) { return new IncludedNote(content, SubjectCode.CUS); } + public static IncludedNote sellerNote(String content) { return new IncludedNote(content, SubjectCode.SUR); } + public static IncludedNote taxNote(String content) { return new IncludedNote(content, SubjectCode.TXD); } + public static IncludedNote introductionNote(String content) { return new IncludedNote(content, SubjectCode.ACY); } + public static IncludedNote discountBonusNote(String content) { return new IncludedNote(content, SubjectCode.AAK); } @@ -68,10 +81,17 @@ public class IncludedNote { return subjectCode; } - public IncludedNote setSubjectCode(SubjectCode subjectCode) { - this.subjectCode = subjectCode; - return this; - } + + public IncludedNote setSubjectCode(SubjectCode subjectCode) { + this.subjectCode = subjectCode; + return this; + } + + public IncludedNote setContent(String content) { + this.content = content; + return this; + } + public String toCiiXml(){ String result = INCLUDE_START + CONTENT_START + diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 88e6cc53..10ba057f 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -413,6 +413,11 @@ public class Invoice implements IExportableTransaction { return includedNotes; } + public Invoice setNotesWithSubjectCode(List theList) { + includedNotes=theList; + return this; + } + @Override public String getCurrency() { return currency; diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index d726ed08..5d624adb 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -12,7 +12,11 @@ import org.w3c.dom.NodeList; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.List; +import java.util.HashMap; +import java.util.Map; /*** * describes any invoice line @@ -37,6 +41,8 @@ public class Item implements IZUGFeRDExportableItem { protected ArrayList additionalReference = null; protected ArrayList Allowances = new ArrayList<>(); protected ArrayList Charges = new ArrayList<>(); + protected List includedNotes = null; + //protected HashMap attributes = new HashMap<>(); /*** * default constructor @@ -64,11 +70,39 @@ public class Item implements IZUGFeRDExportableItem { // ubl //we need: name description unitcode //and we additionally have vat% - setProduct(new Product()); + + // Bharti's homework 20241126:Streams https://www.youtube.com/watch?v=Lf01cBzmuXw + // and Lambdas https://www.youtube.com/watch?v=HCyx31NW8xg + setProduct(new Product(itemMap.getNode("Item").get())); icnm.getAsString("Name").ifPresent(product::setName); + icnm.getAsString("Description").ifPresent(product::setDescription); + + icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> { + SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID); + }); + + icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> { + BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID); + }); + +// String name = icnm.getAsStringOrNull("Name"); +// String val = icnm.getAsStringOrNull("Value"); +// if (name != null && val != null) { +// if (attributes == null) { +// attributes = new HashMap<>(); +// } +// product.attributes.put(name, val); +// } + //icnm.getNode("AdditionalItemProperty").flatMap(n ->n.getAttributes()).ifPresent(product::setAttributes); + + + 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 @@ -83,21 +117,37 @@ public class Item implements IZUGFeRDExportableItem { product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue()); }); + itemMap.getAllNodes("DocumentReference").map(ReferencedDocument::fromNode) + .forEach(this::addAdditionalReference); + + // ubl + + itemMap.getAsNodeMap("OrderLineReference") + // ubl + .flatMap(bordNodes -> bordNodes.getAsString("LineID")) + .ifPresent(this::addReferencedLineID); + + + + + itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> { icnm.getAsNodeMap("BuyerOrderReferencedDocument") .flatMap(bordNodes -> bordNodes.getAsString("LineID")) .ifPresent(this::addReferencedLineID); + icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> { npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice); npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity); }); - icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode) - .forEach(this::addReferencedDocument); + icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode). + forEach(this::addReferencedDocument); }); - itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct); + itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//CII + itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//UBL // RequestedQuantity is for Order-X, BilledQuantity for FX and ZF itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery") @@ -125,6 +175,38 @@ public class Item implements IZUGFeRDExportableItem { icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference); }); + + itemMap.getAsString("Note").ifPresent(this::addNote); + itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> { + List includedNotes = new ArrayList<>(); + adld.getAllNodes("IncludedNote").forEach(item -> { + 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)); + } + } + boolean foundCode = false; + for (SubjectCode code : SubjectCode.values()) { + if (code.toString().equals(subjectCode)) { + includedNotes.add(new IncludedNote(content, code)); + foundCode = true; + break; + } + } + if (!foundCode) { + includedNotes.add(new IncludedNote(content, null)); + } + }); + addNotes(includedNotes); + }); } public Item addReferencedLineID(String s) { @@ -372,4 +454,19 @@ public class Item implements IZUGFeRDExportableItem { return detailedDeliveryPeriodTo; } + public IZUGFeRDExportableItem addNotes(Collection notes) { + if (notes == null) { + return this; + } + if (includedNotes == null) { + includedNotes = new ArrayList<>(); + } + includedNotes.addAll(notes); + return this; + } + + @Override + public List getNotesWithSubjectCode() { + return includedNotes; + } } diff --git a/library/src/main/java/org/mustangproject/LegalOrganisation.java b/library/src/main/java/org/mustangproject/LegalOrganisation.java index 95f61629..705420c2 100644 --- a/library/src/main/java/org/mustangproject/LegalOrganisation.java +++ b/library/src/main/java/org/mustangproject/LegalOrganisation.java @@ -1,6 +1,7 @@ package org.mustangproject; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.*; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -9,6 +10,7 @@ import org.w3c.dom.NodeList; * A organisation, i.e. usually a company */ @JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class LegalOrganisation implements IZUGFeRDLegalOrganisation { protected SchemedID schemedID = null; diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 1263d593..83cf4543 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -6,6 +6,7 @@ import org.mustangproject.ZUGFeRD.IDesignatedProductClassification; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct; import org.mustangproject.util.NodeMap; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import java.math.BigDecimal; import java.util.ArrayList; @@ -59,11 +60,15 @@ public class Product implements IZUGFeRDExportableProduct { } }); + + + nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID); nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID); nodeMap.getAsString("Name").ifPresent(this::setName); nodeMap.getAsString("Description").ifPresent(this::setDescription); + nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> { String key = apcNodes.getAsStringOrNull("Description"); String value = apcNodes.getAsStringOrNull("Value"); @@ -75,6 +80,26 @@ public class Product implements IZUGFeRDExportableProduct { } }); + + //UBL + nodeMap.getAsNodeMap("AdditionalItemProperty").ifPresent(aipNodes -> { + String name = aipNodes.getAsStringOrNull("Name"); + String val = aipNodes.getAsStringOrNull("Value"); + if (name != null && val != null) { + if (attributes == null) { + attributes = new HashMap<>(); + } + attributes.put(name, val); + } + }); + + nodeMap.getAsNodeMap("CommodityClassification").ifPresent(dpcNodes -> { + String className = dpcNodes.getAsStringOrNull("ClassName"); + dpcNodes.getNode("ItemClassificationCode").map(ClassCode::fromNode).ifPresent(classCode -> + classifications.add(new DesignatedProductClassification(classCode, className))); + }); + + //UBL nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> { String className = dpcNodes.getAsStringOrNull("ClassName"); dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode -> @@ -115,6 +140,7 @@ public class Product implements IZUGFeRDExportableProduct { return this; } + /*** * * @return e.g. intra-commnunity supply or small business diff --git a/library/src/main/java/org/mustangproject/ReferencedDocument.java b/library/src/main/java/org/mustangproject/ReferencedDocument.java index 4d2151ca..9255017f 100644 --- a/library/src/main/java/org/mustangproject/ReferencedDocument.java +++ b/library/src/main/java/org/mustangproject/ReferencedDocument.java @@ -1,9 +1,13 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.util.NodeMap; import org.w3c.dom.Node; +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class ReferencedDocument implements IReferencedDocument { String issuerAssignedID; @@ -22,7 +26,7 @@ public class ReferencedDocument implements IReferencedDocument { this.referenceTypeCode = referenceTypeCode; } - /*** + /*** * sets an ID assigned by the sender * @param issuerAssignedID the ID as a string :-) */ @@ -66,8 +70,8 @@ public class ReferencedDocument implements IReferencedDocument { return null; } NodeMap nodes = new NodeMap(node); - return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID"), - nodes.getAsStringOrNull("TypeCode"), + return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"), + nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"), nodes.getAsStringOrNull("ReferenceTypeCode")); } } diff --git a/library/src/main/java/org/mustangproject/SchemedID.java b/library/src/main/java/org/mustangproject/SchemedID.java index 9df8a2d9..6cbac0ec 100644 --- a/library/src/main/java/org/mustangproject/SchemedID.java +++ b/library/src/main/java/org/mustangproject/SchemedID.java @@ -1,5 +1,11 @@ package org.mustangproject; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) public class SchemedID { protected String scheme; protected String id; diff --git a/library/src/main/java/org/mustangproject/SubjectCode.java b/library/src/main/java/org/mustangproject/SubjectCode.java index c58070fd..808c09d4 100644 --- a/library/src/main/java/org/mustangproject/SubjectCode.java +++ b/library/src/main/java/org/mustangproject/SubjectCode.java @@ -36,5 +36,9 @@ public enum SubjectCode { /** * Discount and bonus agreements */ - AAK + AAK, + /** + * Vehicle licence number + */ + ABZ } diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index a45d57ba..73b1ae81 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -2,6 +2,7 @@ package org.mustangproject; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,6 +17,7 @@ import org.w3c.dom.NodeList; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /*** * A organisation, i.e. usually a company */ @@ -62,6 +64,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } + protected void parseFromUBL(NodeList nodes) { if (nodes.getLength() > 0) { @@ -71,8 +74,34 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { if (currentItemNode.getLocalName() != null) { String currentUBLChild = currentItemNode.getLocalName(); - if (currentUBLChild.equals("Party")) { +// if (currentUBLChild.equals("Delivery")) { +// NodeList delivery = currentItemNode.getChildNodes(); +// for (int deliveryIndex = 0; deliveryIndex < delivery.getLength(); deliveryIndex++) { +// if (delivery.item(deliveryIndex).getLocalName() != null) { +// Node currentNode = delivery.item(deliveryIndex); +// if (currentNode.getLocalName().equals("DeliveryLocation")) { +// NodeList deliveryLocation = currentNode.getChildNodes(); +// for (int deliveryLocationIndex = 0; deliveryLocationIndex < deliveryLocation.getLength(); deliveryLocationIndex++) { +// if (deliveryLocation.item(deliveryLocationIndex).getLocalName() != null) { +// if (deliveryLocation.item(deliveryLocationIndex).getLocalName().equals("ID")) { +// //Node currentNode = partyID.item(partyIDIndex); +// setID(deliveryLocation.item(deliveryLocationIndex).getTextContent()); +// if ((deliveryLocation.item(deliveryLocationIndex).getAttributes() != null && +// (deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID") != null)) +// ) { +// SchemedID sID = new SchemedID().setScheme(deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID").getTextContent()); +// addGlobalID(sID); +// } +// } +// } +// } +// } +// +// } +// } +// } + if (currentUBLChild.equals("Party")) { NodeList party = currentItemNode.getChildNodes(); for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) { if (party.item(partyIndex).getLocalName() != null) { @@ -90,42 +119,64 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } } + if (party.item(partyIndex).getLocalName().equals("EndpointID")) { + Node currentNode = party.item(partyIndex); + if ((currentNode.getAttributes() != null && + (currentNode.getAttributes().getNamedItem("schemeID") != null)) + && (party.item(partyIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("EM")) + ) { + setEmail(currentNode.getTextContent()); + } - if (currentTopElementName.equals("PartyTaxScheme")) { - NodeList partyTaxScheme = party.item(partyIndex).getChildNodes(); - for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) { - if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) { - if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) { - setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); + } + if (currentTopElementName.equals("PartyIdentification")) { + NodeList partyID = party.item(partyIndex).getChildNodes(); + for (int partyIDIndex = 0; partyIDIndex < partyID.getLength(); partyIDIndex++) { + if (partyID.item(partyIDIndex).getLocalName() != null) { + if (partyID.item(partyIDIndex).getLocalName().equals("ID")) { + Node currentNode = partyID.item(partyIDIndex); + if ((currentNode.getAttributes() != null && + (currentNode.getAttributes().getNamedItem("schemeID") != null)) + ) { + SchemedID sID = new SchemedID().setScheme(currentNode.getAttributes().getNamedItem("schemeID").getTextContent()).setId(currentNode.getTextContent()); + addGlobalID(sID); + + } + else { + setID(currentNode.getTextContent()); + } } } + } } + + if (currentTopElementName.equals("PartyTaxScheme")) { + NodeList partyTaxScheme = party.item(partyIndex).getChildNodes(); + String CompanyId = null; + for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) { + if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) { + if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) { + CompanyId = (partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); + } + if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("TaxScheme")) { + NodeList taxSchemechilds = partyTaxScheme.item(partyTaxSchemeIndex).getChildNodes(); + for (int taxSchemechildsIndex = 0; taxSchemechildsIndex < taxSchemechilds.getLength(); taxSchemechildsIndex++) { + if (taxSchemechilds.item(taxSchemechildsIndex).getLocalName() != null) { + if (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("FC") || (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("NOVAT"))) { + setTaxID(CompanyId); + } else { + setVATID(CompanyId); + } + } + } + } + } + + } } -// if (currentTopElementName.equals("PartyTaxScheme")) { -// NodeList partyTaxScheme = party.item(partyIndex).getChildNodes(); -// for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) { -// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) { -// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("TaxScheme")) { -// NodeList taxScheme = partyTaxScheme.item(partyTaxSchemeIndex).getChildNodes(); -// for (int taxSchemeIndex = 0 ; taxSchemeIndex < taxScheme.getLength(); taxSchemeIndex++) { -// if (taxScheme.item(taxSchemeIndex).getLocalName() != null) { -// if(taxScheme.item(taxSchemeIndex).getLocalName().equals("ID")){ -// if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) { -// setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); -// } else { -// setVATID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); -// } -// } -// } -// } -// } -// -// } -// } -// } /* UBL only: formally it can have a name as well but BT27 party name *should* be stored in so overwrite if one exists @@ -133,17 +184,37 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { if (currentTopElementName.equals("PartyLegalEntity")) { NodeList legal = party.item(partyIndex).getChildNodes(); + LegalOrganisation lo = null; for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) { if (legal.item(legalChildIndex).getLocalName() != null) { + if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) { - setName(legal.item(legalChildIndex).getTextContent()); + if (lo == null) { + lo = new LegalOrganisation(); + } + lo.setTradingBusinessName(legal.item(legalChildIndex).getTextContent()); } if (legal.item(legalChildIndex).getLocalName().equals("CompanyLegalForm")) { setDescription(legal.item(legalChildIndex).getTextContent()); } + if (legal.item(legalChildIndex).getLocalName().equals("CompanyID")) { + if (lo == null) { + lo = new LegalOrganisation(); + } + if (legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID")!=null) { + SchemedID sid = new SchemedID(legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue(), legal.item(legalChildIndex).getTextContent()); + lo.setSchemedID(sid); + } + } + // we dont have that attribute yet in the legalorganisation: CompanyLegalForm + if (lo != null) { + setLegalOrganisation(lo); + } + } } } + if (currentTopElementName.equals("PostalAddress")) { NodeList postal = party.item(partyIndex).getChildNodes(); @@ -191,8 +262,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } - - } if (postal.item(postalChildIndex).getLocalName().equals("Name")) { setName(postal.item(postalChildIndex).getTextContent()); @@ -210,7 +279,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } } - } if (currentUBLChild.equals("GlobalID")) { @@ -224,57 +292,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { NodeList contact = nodes.item(nodeIndex).getChildNodes(); setContact(new Contact(contact)); } - - if (currentUBLChild.equals("PostalTradeAddress")) { - NodeList postal = nodes.item(nodeIndex).getChildNodes(); - for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) { - if (postal.item(postalChildIndex).getLocalName() != null) { - if (postal.item(postalChildIndex).getLocalName().equals("LineOne")) { - setStreet(postal.item(postalChildIndex).getTextContent()); - } - if (postal.item(postalChildIndex).getLocalName().equals("LineTwo")) { - setAdditionalAddress(postal.item(postalChildIndex).getTextContent()); - } - if (postal.item(postalChildIndex).getLocalName().equals("LineThree")) { - setAdditionalAddressExtension(postal.item(postalChildIndex).getTextContent()); - } - if (postal.item(postalChildIndex).getLocalName().equals("CityName")) { - setLocation(postal.item(postalChildIndex).getTextContent()); - } - if (postal.item(postalChildIndex).getLocalName().equals("PostcodeCode")) { - setZIP(postal.item(postalChildIndex).getTextContent()); - } - if (postal.item(postalChildIndex).getLocalName().equals("CountryID")) { - setCountry(postal.item(postalChildIndex).getTextContent()); - } - - } - } - - } - - if (currentUBLChild.equals("PartyTaxScheme")) { - NodeList taxChilds = nodes.item(nodeIndex).getChildNodes(); - for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { - if (taxChilds.item(taxChildIndex).getLocalName() != null) { - if ((taxChilds.item(taxChildIndex).getLocalName().equals("TaxScheme"))) { - if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { - if (taxChilds.item(taxChildIndex).getLocalName().equals("ID")) { - if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { - setVATID(taxChilds.item(taxChildIndex).getTextContent()); - } -// setTaxID(partyTaxScheme.item(partyTaxSchemeIndex).getTextContent()); - if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("ID").getNodeValue().equals("FC")) { - if (taxChilds.item(taxChildIndex).getLocalName().equals("CompanyID")) { - setTaxID(taxChilds.item(taxChildIndex).getTextContent()); - } - } - } - } - } - } - } - } } } } @@ -558,6 +575,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /** * primarily for invoiceimporter and JSON + * * @return the list of sepa mandates */ public List getDebitDetails() { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index e5e25cae..45c210ea 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -28,7 +28,9 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; import java.util.Date; +import java.util.List; +import org.mustangproject.IncludedNote; import org.mustangproject.Item; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -153,4 +155,21 @@ 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 getNotesWithSubjectCode() { + return null; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 1eb1a6d2..0b7ca5e3 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -149,7 +149,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * * @return item sum +- charges/allowances */ - protected BigDecimal getTaxBasis() { + public 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); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 8729881d..ff5aa321 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -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 = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } @@ -408,7 +412,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) { xml += "" + "" - + "" + lineID + "" + + "" + lineIDStr + "" + buildItemNotes(currentItem) + "" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index a5d16dae..91f86690 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -343,6 +343,9 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the sender's account IBAN code */ public String getIBAN() { + if ((importedInvoice==null)||(importedInvoice.getTradeSettlement()==null)) { + return null; + } for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) { if (settlement instanceof IZUGFeRDTradeSettlementDebit) { return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN(); @@ -356,8 +359,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { public String getHolder() { - - return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']"); } @@ -366,7 +367,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @return the total payable amount */ public String getAmount() { - return importedInvoice.getGrandTotal().toPlainString(); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 46594a26..8833161d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -11,6 +11,7 @@ import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.mustangproject.*; import org.mustangproject.Exceptions.ArithmetricException; import org.mustangproject.Exceptions.StructureException; +import org.mustangproject.util.NodeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -273,6 +274,10 @@ public class ZUGFeRDInvoiceImporter { } } + public void setID(String id) { + String ud = id; + } + /*** * This will parse a XML into the given invoice object * @param zpp the invoice to be altered @@ -301,13 +306,129 @@ public class ZUGFeRDInvoiceImporter { zpp.setDeliveryAddress(new TradeParty(deliveryNodes)); } + List includedNotes = new ArrayList<>(); + + //UBL... + XPathExpression UBLNotesEx = xpath.compile("/*[local-name()=\"Invoice\"]/*[local-name()=\"Note\"]"); + NodeList UBLNotesNd = (NodeList) UBLNotesEx.evaluate(getDocument(), XPathConstants.NODESET); + if ((UBLNotesNd != null) && (UBLNotesNd.getLength() > 0)) { + for (int nodeIndex = 0; nodeIndex < UBLNotesNd.getLength(); nodeIndex++) { + includedNotes.add(IncludedNote.generalNote(UBLNotesNd.item(nodeIndex).getTextContent())); + } + zpp.addNotes(includedNotes); + } + + XPathExpression shipExUBL = xpath.compile("//*[local-name()=\"Delivery\"]"); + Node deliveryNode = (Node) shipExUBL.evaluate(getDocument(), XPathConstants.NODE); + + if (deliveryNode != null) { + TradeParty delivery = new TradeParty(); + new NodeMap(deliveryNode).getAsNodeMap("DeliveryLocation").ifPresent( + deliveryLocationNodeMap -> { + + deliveryLocationNodeMap.getNode("ID").ifPresent(s -> { + if (s.getAttributes().getNamedItem("schemeID") != null) { + SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent()); + delivery.addGlobalID(sID); + } + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("StreetName").ifPresent(t -> delivery.setStreet(t)); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("CityName").ifPresent(t -> delivery.setLocation(t)); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("PostalZone").ifPresent(t -> delivery.setZIP(t)); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsNodeMap("Country").ifPresent(t -> t.getAsString("IdentificationCode").ifPresent(u -> delivery.setCountry(u))); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsNodeMap("AddressLine").ifPresent(t -> t.getAsString("Line").ifPresent(u -> delivery.setAdditionalAddressExtension(u))); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); + }); + deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { + s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); + }); + }); + + + new NodeMap(deliveryNode).getAsNodeMap("DeliveryParty").ifPresent(partyMap -> { + partyMap.getAsNodeMap("PartyName").ifPresent(s -> { + s.getAsString("Name").ifPresent(t -> delivery.setName(t)); + }); + }); + String street, name, additionalStreet, city, postal, countrySubentity, line, country = null; +/* + String idx = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"ID\"]"); + street = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name()=\"Address\"]/*[local-name()=\"StreetName\"]"); + additionalStreet = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"AdditionalStreetName\"]"); + city = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"CityName\"]"); + postal = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"PostalZone\"]"); + countrySubentity = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]/*[local-name() = \"CountrySubentity\"]"); + line = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]//*[local-name() = \"AddressLine\"]/*[local-name() = \"Line\"]"); + country = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"Address\"]//*[local-name() = \"Country\"]/*[local-name() = \"IdentificationCode\"]"); + name = extractString("//*[local-name()=\"DeliveryLocation\"]/*[local-name() = \"DeliveryParty\"]//*[local-name() = \"PartyName\"]/*[local-name() = \"Name\"]"); +*/ + zpp.setDeliveryAddress(delivery); + /* + zpp.setDeliveryAddress(new TradeParty() + .setStreet(street) + .setAdditionalAddress(additionalStreet) + .setLocation(city) + .setZIP(postal) + .setAdditionalAddressExtension(line) + .setCountry(country) + .setName(name) + ); +*/ + + } + xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*"); NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - xpr = xpath.compile("//*[local-name()=\"PayeeTradeParty\"]|//*[local-name()=\"PayeeParty\"]/*"); + xpr = xpath.compile("//*[local-name()=\"PayeeTradeParty\"]"); NodeList payeeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + // UBL + XPathExpression shipPayee = xpath.compile("//*[local-name()=\"PayeeParty\"]/*"); + NodeList ublPayeeNodes = (NodeList) shipPayee.evaluate(getDocument(), XPathConstants.NODESET); + +// if(ublPayeeNodes != null) { +// TradeParty payee = new TradeParty(); +// NodeMap nodeMap = new NodeMap(ublPayeeNodes).getAsNodeMap("PayeeParty").get(); +// nodeMap.getNode("ID").ifPresent(s -> { +// SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent()); +// payee.addGlobalID(sID); +// }); +// } + //NodeList UBLpayeeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + zpp.setPayee(new TradeParty(ublPayeeNodes)); +// TradeParty payee =new TradeParty(); +// NodeMap payeeID = new NodeMap(UBLpayeeNodes).getAsNodeMap("PartyIdentification").get(); +// if (payeeID !=null) { +// payeeID.getAsString("Name").ifPresent(t->payee.setName(t)); +// } +// if (payeeNodes != null) { +// TradeParty payee =new TradeParty(); +// NodeMap nodeMap = new NodeMap(payeeNodes).getAsNodeMap("PartyIdentification").get(); +// if (nodeMap != null) { +// nodeMap.getNode("ID").ifPresent(s -> { +// SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent()); +// payee.addGlobalID(sID); +// }); +// } +// } + + xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]"); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); @@ -324,16 +445,47 @@ public class ZUGFeRDInvoiceImporter { } } - xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]"); + xpr = xpath.compile("//*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"TaxExclusiveAmount\"]"); + BigDecimal expectedTaxBasis = null; + NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + if (basisNodes.getLength() > 0) { + expectedTaxBasis = new BigDecimal(XMLTools.trimOrNull(basisNodes.item(0))); + if (zpp instanceof CalculatedInvoice) { + // usually we would re-calculate the invoice to get expectedGrandTotal + // however, for "minimal" invoices or other invoices without lines + // this will not work + ((CalculatedInvoice) zpp).setTaxBasis(expectedTaxBasis); + } + } + + 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)))); + } + } + + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"DuePayableAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableAmount\"]"); + NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + if (lineDueNodes.getLength() > 0) { + if (zpp instanceof CalculatedInvoice) { + ((CalculatedInvoice) zpp).setDuePayable(new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)))); + } + } + Date issueDate = null; Date dueDate = null; Date deliveryDate = null; String despatchAdviceReferencedDocument = null; + for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) { Node exchangedDocumentNode = ExchangedDocumentNodes.item(i); NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes(); @@ -354,7 +506,6 @@ public class ZUGFeRDInvoiceImporter { } } } - List includedNotes = new ArrayList<>(); if ((item.getLocalName() != null) && (item.getLocalName().equals("IncludedNote"))) { String subjectCode = ""; String content = null; @@ -369,21 +520,39 @@ public class ZUGFeRDInvoiceImporter { 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; + 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); } } + zpp.addNotes(includedNotes); String rootNode = extractString("local-name(/*)"); if (rootNode.equals("Invoice")) { // UBL... @@ -399,7 +568,8 @@ public class ZUGFeRDInvoiceImporter { deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt); } } - xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]"); + + xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]|//*[local-name()=\"Delivery\"]"); NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) { @@ -471,7 +641,7 @@ public class ZUGFeRDInvoiceImporter { } - String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|//*[local-name()=\"DocumentCurrencyCode\"]") ; + String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|//*[local-name()=\"DocumentCurrencyCode\"]"); zpp.setCurrency(currency); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]"); @@ -590,7 +760,21 @@ public class ZUGFeRDInvoiceImporter { } } } +// if ((paymentMeansChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DirectDebitMandateID"))) { +// directDebitMandateID = paymentTermChilds.item(paymentTermChildIndex).getTextContent(); +// } + if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null) + && (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PaymentMandate"))) { + NodeList paymentMandateChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes(); + for (int paymentMandateChildIndex = 0; paymentMandateChildIndex < paymentMandateChilds.getLength(); paymentMandateChildIndex++) { + if ((paymentMandateChilds.item(paymentMandateChildIndex).getLocalName() != null) && (paymentMandateChilds.item(paymentMandateChildIndex).getLocalName().equals("ID"))) { + directDebitMandateID = paymentMandateChilds.item(paymentMandateChildIndex).getTextContent(); + } + } + + } } + } zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode); @@ -608,29 +792,32 @@ public class ZUGFeRDInvoiceImporter { if (buyerOrderIssuerAssignedID != null) { zpp.setBuyerOrderReferencedDocumentID(buyerOrderIssuerAssignedID); - } - else { + } else { zpp.setBuyerOrderReferencedDocumentID(extractString("//*[local-name()=\"OrderReference\"]/*[local-name()=\"ID\"]")); } if (sellerOrderIssuerAssignedID != null) { zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID); + } else { + zpp.setSellerOrderReferencedDocumentID(extractString("//*[local-name()=\"OrderReference\"]/*[local-name()=\"SalesOrderID\"]")); } if (despatchAdviceReferencedDocument != null) { zpp.setDespatchAdviceReferencedDocumentID(despatchAdviceReferencedDocument); + } else { + zpp.setDespatchAdviceReferencedDocumentID(extractString("//*[local-name()=\"DespatchDocumentReference\"]/*[local-name()=\"ID\"]")); } 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())) { + 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); @@ -662,7 +849,7 @@ public class ZUGFeRDInvoiceImporter { // be read, // so the invoice remains arithmetically correct // -> parse document level charges+allowances - xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]"); + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]|//*[local-name()=\"AllowanceCharge\"]");//CII and UBL NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); for (int i = 0; i < chargeNodes.getLength(); i++) { NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes(); @@ -676,24 +863,34 @@ public class ZUGFeRDInvoiceImporter { 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"); + if (chargeNodeChilds.item(chargeChildIndex).getTextContent().trim().equalsIgnoreCase("false")) { + // UBL + isCharge = false; + } else if (chargeNodeChilds.item(chargeChildIndex).getTextContent().trim().equalsIgnoreCase("true")) { + // still UBL + isCharge = true; + } else { + //CII + 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")) { + + } else if (chargeChildName.equals("ActualAmount") || chargeChildName.equals("Amount")) { chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); - } else if (chargeChildName.equals("Reason")) { + } else if (chargeChildName.equals("Reason") || chargeChildName.equals("AllowanceChargeReason")) { reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); - } else if (chargeChildName.equals("ReasonCode")) { + } else if (chargeChildName.equals("ReasonCode") || chargeChildName.equals("AllowanceChargeReasonCode")) { reasonCode = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); - } else if (chargeChildName.equals("CategoryTradeTax")) { + } else if (chargeChildName.equals("CategoryTradeTax") || chargeChildName.equals("TaxCategory")) { 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"))) { + if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent") || taxItemName.equals("Percent"))) { taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex)); } } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index 7c6d409d..a9c35c1b 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -24,15 +24,25 @@ package org.mustangproject.ZUGFeRD; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import junit.framework.TestCase; +import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; import org.mustangproject.*; +import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants; +import javax.xml.xpath.XPathExpressionException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.ParseException; +import java.text.SimpleDateFormat; 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 +60,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(); @@ -121,14 +158,57 @@ public class DeSerializationTest extends TestCase { " }\n" + " ]\n" + "}", Invoice.class); - TransactionCalculator tc=new TransactionCalculator(fromJSON); - assertEquals(tc.getGrandTotal(),new BigDecimal("234.43")); + TransactionCalculator tc = new TransactionCalculator(fromJSON); + assertEquals(tc.getGrandTotal(), new BigDecimal("234.43")); assertEquals(fromJSON.getNumber(), fromJSON.getNumber()); assertEquals(fromJSON.getZFItems().length, fromJSON.getZFItems().length); } + public void testIssuerAssignedIDRoundtrip() { + String occurrenceFrom = "20201001"; + String occurrenceTo = "20201005"; + String contractID = "376zreurzu0983"; + + String orgID = "0009845"; + String orgname = "Test company"; + String number = "123"; + String priceStr = "1.00"; + String taxID = "9990815"; + BigDecimal price = new BigDecimal(priceStr); + Invoice newInvoiceFromJSON = null; + boolean hasExceptions = false; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + try { + SchemedID gtin = new SchemedID("0160", "2001015001325"); + SchemedID gln = new SchemedID("0088", "4304171000002"); + Invoice i = new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSellerOrderReferencedDocumentID("9384").setBuyerOrderReferencedDocumentID("28934") + .setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo)) + .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setEmail("sender@test.org").setID(orgID).addVATID("DE0815")) + .setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110")) + .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", "", "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)) + .setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(i); + newInvoiceFromJSON = mapper.readValue(json, Invoice.class); + } catch (ParseException e) { + hasExceptions = true; + } catch (JsonProcessingException e) { + hasExceptions = true; + } + assertEquals(newInvoiceFromJSON.getBuyerOrderReferencedDocumentID(), "28934"); + assertFalse(hasExceptions); + + + } public void testDueDateRoundtrip() throws JsonProcessingException { @@ -186,4 +266,389 @@ public class DeSerializationTest extends TestCase { } + public void testDirectDebit() throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + + // [{"stringValue":"a","intValue":1,"booleanValue":true}, + // {"stringValue":"bc","intValue":3,"booleanValue":false}] + + boolean exceptions = false; + String theXML = ""; + try { + Invoice fromJSON = mapper.readValue("\n" + + "\t{\n" + + "\t\t\"documentCode\": \"380\",\n" + + "\t\t\"number\": \"F20220031\",\n" + + "\t\t\"referenceNumber\": \"SERVEXEC\",\n" + + "\t\t\"buyerOrderReferencedDocumentID\": \"PO201925478\",\n" + + "\t\t\"ownOrganisationName\": \"LE FOURNISSEUR\",\n" + + "\t\t\"currency\": \"EUR\",\n" + + "\t\t\"issueDate\": \"2022-01-30T23:00:00.000+00:00\",\n" + + "\t\t\"dueDate\": \"2022-03-01T23:00:00.000+00:00\",\n" + + "\t\t\"deliveryDate\": \"2022-01-27T23:00:00.000+00:00\",\n" + + "\t\t\"sender\": {\n" + + "\t\t\"name\": \"LE FOURNISSEUR\",\n" + + "\t\t\t\"zip\": \"75018\",\n" + + "\t\t\t\"street\": \"35 rue d'ici\",\n" + + "\t\t\t\"location\": \"PARIS\",\n" + + "\t\t\t\"country\": \"FR\",\n" + + "\t\t\t\"vatID\": \"FR11123456782\",\n" + + "\t\t\t\"additionalAddress\": \"Seller line 2\",\n" + + "\t\t\t\"additionalAddressExtension\": \"Seller line 3\",\n" + + "\t\t\t\"bankDetails\": [\n" + + "\t\t{\n" + + "\t\t\t\"accountName\": null,\n" + + "\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\",\n" + + "\t\t\t\"bic\": \"BIC_MONCOMPTE\"\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"debitDetails\": [\n" + + "\t\t{\n" + + "\t\t\t\"mandate\": \"MANDATE PT\",\n" + + "\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\"\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"contact\": {\n" + + "\t\t\t\"name\": \"M. CONTACT\",\n" + + "\t\t\t\t\"phone\": \"01 02 03 54 87\",\n" + + "\t\t\t\t\"email\": \"seller@seller.com\",\n" + + "\t\t\t\t\"zip\": null,\n" + + "\t\t\t\t\"street\": null,\n" + + "\t\t\t\t\"location\": null,\n" + + "\t\t\t\t\"country\": null,\n" + + "\t\t\t\t\"fax\": null,\n" + + "\t\t\t\t\"vatid\": null,\n" + + "\t\t\t\t\"id\": null,\n" + + "\t\t\t\t\"additionalAddress\": null\n" + + "\t\t},\n" + + "\t\t\"email\": \"moi@seller.com\",\n" + + "\t\t\t\"vatid\": \"FR11123456782\",\n" + + "\t\t\t\"id\": \"123\",\n" + + "\t\t\t\"legalOrganisation\": {\n" + + "\t\t\t\"schemedID\": null,\n" + + "\t\t\t\t\"tradingBusinessName\": \"SELLER TRADE NAME\"\n" + + "\t\t},\n" + + "\t\t\"globalID\": \"587451236587\",\n" + + "\t\t\t\"globalIDScheme\": \"0088\"\n" + + "\t},\n" + + "\t\t\"recipient\": {\n" + + "\t\t\"name\": \"LE CLIENT\",\n" + + "\t\t\t\"zip\": \"06000\",\n" + + "\t\t\t\"street\": \"MON ADRESSE LIGNE 1\",\n" + + "\t\t\t\"location\": \"MA VILLE\",\n" + + "\t\t\t\"country\": \"FR\",\n" + + "\t\t\t\"vatID\": \"FR 05 987 654 321\",\n" + + "\t\t\t\"additionalAddress\": \"Buyer line 2\",\n" + + "\t\t\t\"additionalAddressExtension\": \"Buyer line 3\",\n" + + "\t\t\t\"contact\": {\n" + + "\t\t\t\"name\": \"Buyer contact name\",\n" + + "\t\t\t\t\"phone\": \"01 01 25 45 87\",\n" + + "\t\t\t\t\"email\": \"buyer@buyer.com\",\n" + + "\t\t\t\t\"zip\": null,\n" + + "\t\t\t\t\"street\": null,\n" + + "\t\t\t\t\"location\": null,\n" + + "\t\t\t\t\"country\": null,\n" + + "\t\t\t\t\"fax\": null,\n" + + "\t\t\t\t\"vatid\": null,\n" + + "\t\t\t\t\"id\": null,\n" + + "\t\t\t\t\"additionalAddress\": null\n" + + "\t\t},\n" + + "\t\t\"email\": \"me@buyer.com\",\n" + + "\t\t\t\"vatid\": \"FR 05 987 654 321\",\n" + + "\t\t\t\"legalOrganisation\": {\n" + + "\t\t\t\"schemedID\": null,\n" + + "\t\t\t\t\"tradingBusinessName\": null\n" + + "\t\t},\n" + + "\t\t\"globalID\": \"3654789851\",\n" + + "\t\t\t\"globalIDScheme\": \"0088\"\n" + + "\t},\n" + + "\t\t\"deliveryAddress\": {\n" + + "\t\t\"name\": \"DEL Name\",\n" + + "\t\t\t\"zip\": \"06000\",\n" + + "\t\t\t\"street\": \"DEL ADRESSE LIGNE 1\",\n" + + "\t\t\t\"location\": \"NICE\",\n" + + "\t\t\t\"country\": \"FR\",\n" + + "\t\t\t\"additionalAddress\": \"DEL line 2\",\n" + + "\t\t\t\"id\": \"PRIVATE_ID_DEL\"\n" + + "\t},\n" + + "\t\t\"payee\": {\n" + + "\t\t\"name\": \"PAYEE NAME\",\n" + + "\t\t\t\"legalOrganisation\": {\n" + + "\t\t\t\"schemedID\": null,\n" + + "\t\t\t\t\"tradingBusinessName\": null\n" + + "\t\t},\n" + + "\t\t\"globalID\": \"587451236586\",\n" + + "\t\t\t\"globalIDScheme\": \"0088\"\n" + + "\t},\n" + + "\t\t\"sellerOrderReferencedDocumentID\": \"SALES REF 2547\",\n" + + "\t\t\"despatchAdviceReferencedDocumentID\": \"DESPADV002\",\n" + + "\t\t\"grandTotal\": 107.82,\n" + + "\t\t\"detailedDeliveryPeriodFrom\": \"2021-12-31T23:00:00.000+00:00\",\n" + + "\t\t\"detailedDeliveryPeriodTo\": \"2022-12-30T23:00:00.000+00:00\",\n" + + "\t\t\"notesWithSubjectCode\": [\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"FOURNISSEUR F SARL au capital de 50 000 EUR\",\n" + + "\t\t\t\"subjectCode\": \"REG\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"RCS MAVILLE 123 456 782\",\n" + + "\t\t\t\"subjectCode\": \"ABL\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"35 ma rue a moi, code postal Ville Pays – contact@masociete.fr - www.masociete.fr – N° TVA : FR32 123 456 789\",\n" + + "\t\t\t\"subjectCode\": \"AAI\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"Tout retard de paiement engendre une pénalité exigible à compter de la date d'échéance, calculée sur la base de trois fois le taux d'intérêt légal.\",\n" + + "\t\t\t\"subjectCode\": null\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"Indemnité forfaitaire pour frais de recouvrement en cas de retard de paiement : 40 €.\",\n" + + "\t\t\t\"subjectCode\": null\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"content\": \"Les réglements reçus avant la date d'échéance ne donneront pas lieu à escompte.\",\n" + + "\t\t\t\"subjectCode\": null\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"zfallowances\": [\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": null,\n" + + "\t\t\t\"reasonCode\": \"100\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 2,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"REMISE VOLUME\",\n" + + "\t\t\t\"reasonCode\": \"71\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": null,\n" + + "\t\t\t\"reasonCode\": \"100\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1.4,\n" + + "\t\t\t\"taxPercent\": 20,\n" + + "\t\t\t\"reason\": \"REMISE COMMERCIALE\",\n" + + "\t\t\t\"reasonCode\": \"100\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1.2,\n" + + "\t\t\t\"taxPercent\": 10,\n" + + "\t\t\t\"reason\": \"REMISE COMMERCIALE\",\n" + + "\t\t\t\"reasonCode\": \"100\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"zfcharges\": [\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": null,\n" + + "\t\t\t\"reasonCode\": \"ADL\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 1,\n" + + "\t\t\t\"taxPercent\": null,\n" + + "\t\t\t\"reason\": \"FRAIS PALETTE\",\n" + + "\t\t\t\"reasonCode\": null,\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 2.8,\n" + + "\t\t\t\"taxPercent\": 20,\n" + + "\t\t\t\"reason\": \"FRAIS DEPLACEMENT\",\n" + + "\t\t\t\"reasonCode\": \"FC\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"percent\": null,\n" + + "\t\t\t\"totalAmount\": 0.6,\n" + + "\t\t\t\"taxPercent\": 10,\n" + + "\t\t\t\"reason\": \"FRAIS DEPLACEMENT\",\n" + + "\t\t\t\"reasonCode\": \"ADR\",\n" + + "\t\t\t\"categoryCode\": \"S\"\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"tradeSettlement\": [\n" + + "\t\t{\n" + + "\t\t\t\"accountName\": null,\n" + + "\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\",\n" + + "\t\t\t\"bic\": \"BIC_MONCOMPTE\"\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"mandate\": \"MANDATE PT\",\n" + + "\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\"\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"zfitems\": [\n" + + "\t\t{\n" + + "\t\t\t\"price\": 60,\n" + + "\t\t\t\"quantity\": 1,\n" + + "\t\t\t\"basisQuantity\": 1,\n" + + "\t\t\t\"product\": {\n" + + "\t\t\t\"unit\": \"C62\",\n" + + "\t\t\t\t\"name\": \"REMBOURSEMENT AFFRANCHISSEMENT\",\n" + + "\t\t\t\t\"description\": \"Description\",\n" + + "\t\t\t\t\"taxCategoryCode\": \"Z\",\n" + + "\t\t\t\t\"vatpercent\": 0,\n" + + "\t\t\t\t\"reverseCharge\": false,\n" + + "\t\t\t\t\"intraCommunitySupply\": false,\n" + + "\t\t\t\t\"globalID\": \"598785412598745\",\n" + + "\t\t\t\t\"globalIDScheme\": \"0160\"\n" + + "\t\t},\n" + + "\t\t\t\"buyerOrderReferencedDocumentLineID\": \"1\",\n" + + "\t\t\t\"value\": 60\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"price\": 30,\n" + + "\t\t\t\"quantity\": 3,\n" + + "\t\t\t\"basisQuantity\": 3,\n" + + "\t\t\t\"product\": {\n" + + "\t\t\t\"unit\": \"C62\",\n" + + "\t\t\t\t\"name\": \"FOURNITURES DIVERSES\",\n" + + "\t\t\t\t\"description\": \"Description\",\n" + + "\t\t\t\t\"taxCategoryCode\": \"S\",\n" + + "\t\t\t\t\"vatpercent\": 20,\n" + + "\t\t\t\t\"reverseCharge\": false,\n" + + "\t\t\t\t\"intraCommunitySupply\": false\n" + + "\t\t},\n" + + "\t\t\t\"buyerOrderReferencedDocumentLineID\": \"3\",\n" + + "\t\t\t\"value\": 30\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"price\": 12,\n" + + "\t\t\t\"quantity\": 1,\n" + + "\t\t\t\"basisQuantity\": 1,\n" + + "\t\t\t\"product\": {\n" + + "\t\t\t\"unit\": \"C62\",\n" + + "\t\t\t\t\"name\": \"APPEL\",\n" + + "\t\t\t\t\"description\": \"Description\",\n" + + "\t\t\t\t\"taxCategoryCode\": \"S\",\n" + + "\t\t\t\t\"vatpercent\": 10,\n" + + "\t\t\t\t\"reverseCharge\": false,\n" + + "\t\t\t\t\"intraCommunitySupply\": false\n" + + "\t\t},\n" + + "\t\t\t\"buyerOrderReferencedDocumentLineID\": \"2\",\n" + + "\t\t\t\"value\": 12\n" + + "\t\t}\n" + + " ],\n" + + "\t\t\"ownStreet\": \"35 rue d'ici\",\n" + + "\t\t\"ownCountry\": \"FR\",\n" + + "\t\t\"ownZIP\": \"75018\",\n" + + "\t\t\"ownLocation\": \"PARIS\",\n" + + "\t\t\"ownVATID\": \"FR11123456782\",\n" + + "\t\t\"valid\": false\n" + + "\t}\n", Invoice.class); + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(fromJSON); + theXML = new String(zf2p.getXML()); + } catch (Exception e) { + exceptions = true; + } + assertTrue(theXML.contains("pour frais de recouvrement en cas de retard de paiement")); + assertFalse(exceptions); + + } + + } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 9ff1a945..24c73e0f 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -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 diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index b9342e1e..00377327 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -22,12 +22,15 @@ package org.mustangproject.ZUGFeRD; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.commons.codec.binary.StringUtils; import org.junit.jupiter.api.Test; import org.mustangproject.*; 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; @@ -278,12 +281,16 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { public void testXRImport() { boolean hasExceptions = false; - ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + ZUGFeRDImporter zii = new ZUGFeRDImporter(); + + int version=-1; try { zii.fromXML(new String(Files.readAllBytes(Paths.get("./target/testout-XR-Edge.xml")), StandardCharsets.UTF_8)); - + version=zii.getVersion(); } catch (IOException e) { hasExceptions = true; + } catch (Exception e) { + throw new RuntimeException(e); } Invoice invoice = null; @@ -293,8 +300,17 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { hasExceptions = true; } assertFalse(hasExceptions); + + + TransactionCalculator tc = new TransactionCalculator(invoice); assertEquals(new BigDecimal("1.00"), tc.getGrandTotal()); + + assertEquals(version,2); + assertTrue(new BigDecimal("1").compareTo(invoice.getZFItems()[0].getQuantity()) == 0); + LineCalculator lc=new LineCalculator(invoice.getZFItems()[0]); + assertTrue(new BigDecimal("1").compareTo(lc.getItemTotalNetAmount()) == 0); + assertTrue(invoice.getTradeSettlement().length == 1); assertTrue(invoice.getTradeSettlement()[0] instanceof IZUGFeRDTradeSettlementPayment); IZUGFeRDTradeSettlementPayment paym = (IZUGFeRDTradeSettlementPayment) invoice.getTradeSettlement()[0]; @@ -377,6 +393,35 @@ 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"); + BigDecimal expectedDue=new BigDecimal("147.65"); + if (isBD) { + BigDecimal amread=invoice.getTotalPrepaidAmount(); + BigDecimal importedLineTotal=invoice.getLineTotalAmount(); + BigDecimal importedDuePayable=invoice.getDuePayable(); + assertTrue(amread.compareTo(expectedPrepaid) == 0); + assertTrue(importedLineTotal.compareTo(expectedLineTotal) == 0); + assertTrue(importedDuePayable.compareTo(expectedDue) == 0); + } + + } + + @Test public void testImportIncludedNotes() throws XPathExpressionException, ParseException { InputStream inputStream = this.getClass() @@ -397,4 +442,25 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } + @Test + public void testImportPositionIncludedNotes() throws FileNotFoundException, XPathExpressionException, ParseException { + File inputFile = getResourceAsFile("ZTESTZUGFERD_1_INVDSS_012015738820PDF-1.pdf"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + assertEquals(1, invoice.getZFItems().length); + assertEquals(8, invoice.getZFItems()[0].getNotesWithSubjectCode().size()); + assertEquals("FB-LE 9999", invoice.getZFItems()[0].getNotesWithSubjectCode().stream().filter(note -> note.getSubjectCode().equals(SubjectCode.ABZ)).findFirst().get().getContent()); + } + + @Test + public void testImportXRechnungPositionNote() throws FileNotFoundException, XPathExpressionException, ParseException { + File inputFile = getResourceAsFile("TESTXRECHNUNG_INVDSS_012015776085.XML"); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(inputFile)); + + Invoice invoice = zii.extractInvoice(); + assertEquals(1, invoice.getZFItems().length); + assertFalse(invoice.getZFItems()[0].getNotes() == null); + assertEquals(1, invoice.getZFItems()[0].getNotes().length); + } } diff --git a/library/src/test/resources/EN16931_1_Teilrechnung.pdf b/library/src/test/resources/EN16931_1_Teilrechnung.pdf new file mode 100644 index 00000000..0bc3ea77 Binary files /dev/null and b/library/src/test/resources/EN16931_1_Teilrechnung.pdf differ diff --git a/library/src/test/resources/TESTXRECHNUNG_INVDSS_012015776085.XML b/library/src/test/resources/TESTXRECHNUNG_INVDSS_012015776085.XML new file mode 100644 index 00000000..1541326d --- /dev/null +++ b/library/src/test/resources/TESTXRECHNUNG_INVDSS_012015776085.XML @@ -0,0 +1,157 @@ + + urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + 2015776085 + 2024-11-01 + 380 + #AAI# Wenn bereits bezahlt, nur zu den Akten legen. Für Verzug und Verzugszinsen gelten die Bestimmungen der §§ 286 - 288 BGB. Der Schuldner einer Entgeltforderung kommt spätestens in Verzug, wenn er nicht innerhalb von 30 Tagen nach Fälligkeit und Zugang einer Rechnung oder gleichwertigen Zahlungsaufstellung leistet. Die Lieferung von Waren und Fahrzeugen erfolgt unter Eigentumsvorbehalt. + #PAI# Rechnungsbetrag zahlbar ohne Abzug sofort nach Erhalt der Rechnung. + EUR + 02-ZZ987654-99 + + 2024-11-01 + 2024-11-30 + + + N/A + + + 63435555 + + + + DE238574172 + + Alphabet Fuhrparkmanagement GmbH + + + Lilienthalallee 26 + München + 80786 + + DE + + + + DE238574172 + + VAT + + + + Alphabet Fuhrparkmanagement GmbH + HRB 181098 + + + Yildiz Tolga + +49899-- + Tolga.Yildiz@alphabet.de + + + + + + N/A + + 00987654 + + + Muster GmbH + + + Musterweg 1 + Kiel + 24105 + + DE + + + + Muster GmbH + + + Mustermann Max + + + + + 2024-11-30 + + 00987654 + + Musterweg 1 + Kiel + 24105 + + DE + + + + + + Muster GmbH + + + + + 30 + + DE34700700100156919301 + Alphabet Fuhrparkmanagement GmbH + + DEUTDEMMXXX + + + + + Rechnungsbetrag zahlbar ohne Abzug sofort nach Erhalt der Rechnung. + + + 161.69 + + 851.00 + 161.69 + + S + 19.00 + + VAT + + + + + + 851.00 + 851.00 + 1012.69 + 1012.69 + + + 1 + #AAI# Finanzrate vom 01.11.2024 bis 30.11.2024 #ABZ/Kennzeichen#SH-9 18E #AKG/Fahrgestellnummer#WBY51EJ080CR55555 #BA/Kilometerstand#0 #AKV/LeasingNr#63435555 + 1.00 + 851.00 + + 2024-11-01 + 2024-11-30 + + + FL-Rate + FL-Rate + + 90101 + + + S + 19.00 + + VAT + + + + + 851.00 + 1.00 + + + diff --git a/library/src/test/resources/ZTESTZUGFERD_1_INVDSS_012015738820PDF-1.pdf b/library/src/test/resources/ZTESTZUGFERD_1_INVDSS_012015738820PDF-1.pdf new file mode 100644 index 00000000..7b220689 Binary files /dev/null and b/library/src/test/resources/ZTESTZUGFERD_1_INVDSS_012015738820PDF-1.pdf differ diff --git a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml b/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml index 1b88d4d4..8b5bb189 100644 --- a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml +++ b/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml @@ -1,8 +1,5 @@ - + BT-23 Business Process Type @@ -19,11 +16,11 @@ invoice note text - AAA + #AAA# invoice note text 2 - AAA + #AAA# @@ -35,9 +32,7 @@ - Item standar identifier - - + Item standar identifier Item seller's identifier Item buyer's identifier Item name @@ -98,9 +93,9 @@ false 1.00 - 100.00 + 1000.00 10.00 - 95 + 55 Invoice line allowance reason @@ -108,17 +103,18 @@ true 1.00 - 100.00 + 1000.00 10.00 AAA Invoice line charge reason - 100.00 + 1000.00 Line object identifier 130 + 6789 @@ -147,7 +143,7 @@ 0.00 - 100.00 + 1000.00 @@ -159,7 +155,7 @@ Seller name Seller additional legal information - + Seller legal identifier Seller trading name @@ -181,7 +177,7 @@ Seller country subdivision - Seller electronic address + Seller electronic address DE12345677 @@ -216,7 +212,7 @@ Buyer country subdivision - Buyer electronic address + Buyer electronic address IE394838894 @@ -256,7 +252,7 @@ rst 130 - AAA + 0090 456 @@ -315,32 +311,32 @@ IT1212341234123412 Payment account name - - - + BSCTCH22 + - 5.00 + 50.00 VAT - 100.00 + 1000.00 S - + 29 5.00 0.00 VAT Exemtion reason text - 100.00 + 1000.00 E - VATEX-EU-O + Exemption reason code 29 0.00 @@ -357,9 +353,9 @@ false 1.00 - 100.00 + 1000.00 10.00 - 95 + 55 Doc allowance reason text VAT @@ -372,7 +368,7 @@ true 1.00 - 100.00 + 1000.00 10.00 AAA Doc charge reason text @@ -390,16 +386,16 @@ Mandate reference identifier - 200.00 + 2000.00 10.00 10.00 - 200.00 - 5.00 - 4.60 + 2000.00 + 50.00 + 46.00 0.00 - 205.00 + 2050.00 0.00 - 205.00 + 2050.00 abc123 diff --git a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml b/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml index 6aec88d9..6e850cc1 100644 --- a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml +++ b/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml @@ -1,365 +1,401 @@ - - urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 - urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + + urn:cen.eu:en16931:2017 + BT-23 Business Process Type Test_EeISI_100 2018-11-12 2018-11-30 380 - #AAA#invoice note text - #AAA#invoice note text 2 + ##AAA##invoice note text + ##AAA##invoice note text 2 EUR NOK uvz 123 - 2018-11-12 - 2018-11-30 - - - abc - def - - - - abc123 - 2018-10-04 - - - - lmn - - - ghi - - - 789 - - - Supporting document ref - Supporting document descr - - ZGVmYXVsdA== - - External document location - - - - - rst - 130 - - - 456 - - - - Seller electronic address - - Seller identifier 1 - - - Seller identifier 2 - - - Seller trading name - - - Seller address line 1 - Seller address line 2 - Seller city - 12345 - Seller country subdivision - - Seller address line 3 - - - DE - - - - DE12345677 - - VAT - - - - DE49294093 - - FC - - - - Seller name - Seller additional legal information - - - Seller contact point - +41 345 654455 - seller@contact.de - - - - - - Buyer electronic address - - Buyer identifier - - - Buyer trading name - - - Buyer address line 1 - Buyer address line 2 - Buyer city - 34562 - Buyer country subdivision - - Buyer address line 3 - - - IE - - - - IE394838894 - - VAT - - - - Buyer name - Buyer legal registration identifier - - - Buyer contact point - +353 2948584 - buyer@contact.ie - - - - - - Payee identifier - - - Payee name - - - - - Tax representative name - - - Tax representative address line 1 - Tax representative address line 2 - Tax representative city - 23455 - Tax representative country subdivision - - Tax representative address line 3 - - - DE - - - - DE3949053 - - VAT - - - - - 2018-12-04 - - deliver location identifier - - Deliver to address line 1 - Deliver to address line 2 - Deliver to city - 98765 - Deliver to country subdivision - - Deliver to address line 3 - - - IE - - - - - - Deliver to party name - - - - - total amount - - - false - 95 - Doc allowance reason text - 1.00 - 10 - 1000 - - S - 5 - - VAT - - - - - true - AAA - Doc charge reason text - 1.00 - 10 - 1000 - - S - 5 - - VAT - - - - - 50 - - 1000 - 50 - - S - 5 - - VAT - - - - - 1000 - 0 - - E - 0 - VATEX-EU-O - Exemtion reason text - - VAT - - - - - - 46 - - - 200 - 200 - 205 - 10 - 10 - 0 - 205 - - - 1a - Invoice line note - 10 - 1000 - 6789 - 2018-11-12 2018-11-30 - - - 12345 - - - Line object identifier + 35 + + + abc + def + + + + abc123 + 2018-10-04 + + + + lmn + + + ghi + + + opq + + + 789 + + + rst 130 - - + + + Supporting document ref + Supporting document descr + + ZGVmYXVsdA== + + External document location + + + + + 456 + + + + Seller electronic address + + Seller identifier 1 + + + Seller identifier 2 + + + Seller trading name + + + Seller address line 1 + Seller address line 2 + Seller city + 12345 + Seller country subdivision + + Seller address line 3 + + + DE + + + + DE12345677 + + VAT + + + + DE49294093 + + NOVAT + + + + Seller name + Seller legal identifier + Seller additional legal information + + + Seller contact point + +41 345 654455 + seller@contact.de + + + + + + Buyer electronic address + + 0190:Buyer identifier + + + Buyer trading name + + + Buyer address line 1 + Buyer address line 2 + Buyer city + 34562 + Buyer country subdivision + + Buyer address line 3 + + + IE + + + + IE394838894 + + VAT + + + + Buyer name + Buyer legal registration identifier + + + Buyer contact point + +353 2948584 + buyer@contact.ie + + + + + + Payee identifier + + + Payee name + + + Payee legal registration identifier + + + + + Tax representative name + + + Tax representative address line 1 + Tax representative address line 2 + Tax representative city + 23455 + Tax representative country subdivision + + Tax representative address line 3 + + + DE + + + + DE3949053 + + VAT + + + + + 2018-12-04 + + deliver location identifier + + Deliver to address line 1 + Deliver to address line 2 + Deliver to city + 98765 + Deliver to country subdivision + + Deliver to address line 3 + + + IE + + + + + + Deliver to party name + + + + + 4 + Remittance information + + 1234 + mandatory network id + Payment card holder name + + + IT1212341234123412 + Payment account name + + BSCTCH22 + + + + IT1212341234123413 + Payment account name 2 + + BSCTCH22 + + + + Mandate reference identifier + + Debited account identifier + + + + + total amount + + false - 95 - Invoice line allowance reason - 1.00 - 10 - 1000 - - + 55 + Doc allowance reason text + 1.0000 + 10.00 + 1000.00 + + S + 5.00 + + VAT + + + + true AAA - Invoice line charge reason - 1.00 - 10 - 1000 - - - Item description - Item name - - Item buyer's identifier - - - Item seller's identifier - - - Item standar identifier - - - IT - - - Item classification identifier0 - - - S - 5 - - VAT - - - - Color - Red - - - Size - L - - - - 10 - 1 + Doc charge reason text + 1.0000 + 10.00 + 1000.00 + + S + 5.00 + + VAT + + + + + 46.00 + + + 50.00 + + 1000.00 + 50.00 + + S + 5.00 + + VAT + + + + + 1000.00 + 0.00 + + E + 0.00 + Exemption reason code + Exemtion reason text + + VAT + + + + + + 2000.00 + 2000.00 + 2050.00 + 10.00 + 10.00 + 2050.00 + + + 1a + Invoice line note + 10.00000000 + 1000.00 + 6789 + + 2018-11-12 + 2018-11-30 + + + 12345 + + + Line object identifier + 130 + - false - 1 - 11 + false + 55 + Invoice line allowance reason + 1 + 10 + 1000 - + + true + AAA + Invoice line charge reason + 1 + 10 + 1000 + + + Item description + Item name + + Item buyer's identifier + + + Item seller's identifier + + + Item standar identifier + + + IT + + + Item classification identifier0 + + + S + 5.00 + + VAT + + + + Color + Red + + + Size + L + + + + 10.00 + 1.00 + + false + 1 + 11 + + - 1b - 10 - 1000 - - Item name 2 - - E - 0 - - VAT - - - - - 10 - + 1b + 10.00000000 + 1000.00 + + Item name 2 + + E + 0.00 + + VAT + + + + + 10.00 + - + diff --git a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel2.ubl.xml b/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel2.ubl.xml deleted file mode 100644 index 7e71103c..00000000 --- a/library/src/test/resources/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel2.ubl.xml +++ /dev/null @@ -1,349 +0,0 @@ - - - urn:cen.eu:en16931:2017 - BT-23 Business Process Type - Test_EeISI_100 - 2018-11-12 - 2018-11-30 - 380 - ##AAA##invoice note text - ##AAA##invoice note text 2 - EUR - NOK - uvz - 123 - - 2018-11-12 - 2018-11-30 - 35 - - - abc - def - - - - abc123 - 2018-10-04 - - - - lmn - - - ghi - - - opq - - - 789 - - - rst - 130 - - - Supporting document ref - Supporting document descr - - ZGVmYXVsdA== - - External document location - - - - - 456 - - - - Seller electronic address - - Seller identifier 1 - - - Seller identifier 2 - - - Bank assigned creditor identifier - - - Seller trading name - - - Seller address line 1 - Seller address line 2 - Seller city - 12345 - Seller country subdivision - - Seller address line 3 - - - DE - - - - DE12345677 - - VAT - - - - DE49294093 - - NOVAT - - - - Seller name - Seller legal identifier - Seller additional legal information - - - Seller contact point - +41 345 654455 - seller@contact.de - - - - - - Buyer electronic address - - 0190:Buyer identifier - - - Buyer trading name - - - Buyer address line 1 - Buyer address line 2 - Buyer city - 34562 - Buyer country subdivision - - Buyer address line 3 - - - IE - - - - IE394838894 - - VAT - - - - Buyer name - Buyer legal registration identifier - - - Buyer contact point - +353 2948584 - buyer@contact.ie - - - - - - Payee identifier - - - Payee name - - - Payee legal registration identifier - - - - - Tax representative name - - - Tax representative address line 1 - Tax representative address line 2 - Tax representative city - 23455 - Tax representative country subdivision - - Tax representative address line 3 - - - DE - - - - DE3949053 - - VAT - - - - - 2018-12-04 - - deliver location identifier - - Deliver to address line 1 - Deliver to address line 2 - Deliver to city - 98765 - Deliver to country subdivision - - Deliver to address line 3 - - - IE - - - - - - Deliver to party name - - - - - 4 - Remittance information - - 1234 - mandatory network id - Payment card holder name - - - IT1212341234123412 - Payment account name - - BSCTCH22 - - - - IT1212341234123413 - Payment account name 2 - - BSCTCH22 - - - - Mandate reference identifier - - Debited account identifier - - - - - total amount - - - 50.00 - - 1000.00 - 50.00 - - S - 5.00 - - VAT - - - - - 1000.00 - 0.00 - - E - 0.00 - Exemption reason code - Exemtion reason text - - VAT - - - - - - 200.00 - 200.00 - 205.00 - 205.00 - - - 1a - Invoice line note - 10.00000000 - 1000.00 - 6789 - - 2018-11-12 - 2018-11-30 - - - 12345 - - - Line object identifier - 130 - - - Item description - Item name - - Item buyer's identifier - - - Item seller's identifier - - - Item standar identifier - - - IT - - - Item classification identifier0 - - - S - 5.00 - - VAT - - - - Color - Red - - - Size - L - - - - 10.00 - 1.00 - - - - - 1b - 10.00000000 - 100.00 - - Item name 2 - - E - 0.00 - - VAT - - - - - 10.00 - - - diff --git a/validator/pom.xml b/validator/pom.xml index b09a497b..36ef5550 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -44,6 +44,12 @@ org.dom4j dom4j 2.1.4 + + + * + * + + commons-io diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java index c42caa9e..f967eb15 100644 --- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java @@ -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(); diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java index a74c9f1c..73b95f2e 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java @@ -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 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; } diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java b/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java index d5a9b244..b0d6b5a1 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java @@ -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; } diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 48dfd6f6..336701b3 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -424,11 +424,6 @@ public class XMLValidator extends Validator { */ public void validateSchematron(String xml, String xsltFilename, int section, ESeverity defaultSeverity) throws IrrecoverableValidationError { ISchematronResource aResSCH = null; - ESeverity severity=defaultSeverity; - if (defaultSeverity!=ESeverity.notice) { - severity=ESeverity.error; - } - aResSCH = SchematronResourceXSLT.fromClassPath(xsltFilename); if (aResSCH != null) { @@ -453,6 +448,7 @@ public class XMLValidator extends Validator { String thisFailText = ""; String thisFailID = ""; + String thisFailIDStr = ""; String thisFailTest = ""; String thisFailLocation = ""; if (failedAsserts.getLength() > 0) { @@ -461,7 +457,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(); @@ -470,14 +467,15 @@ public class XMLValidator extends Validator { thisFailLocation = currentFailNode.getAttributes().getNamedItem("location").getNodeValue(); } - if (currentFailNode.getAttributes().getNamedItem("flag") != null) { + ESeverity severity; + if (defaultSeverity == ESeverity.notice) { + severity = defaultSeverity; + } else if (currentFailNode.getAttributes().getNamedItem("flag") != null + && currentFailNode.getAttributes().getNamedItem("flag").getNodeValue().equals("warning")) { // the XR issues warnings with flag=warning - if (currentFailNode.getAttributes().getNamedItem("flag").getNodeValue().equals("warning")) { - if (defaultSeverity!=ESeverity.notice) { - severity=ESeverity.warning; - } - } - + severity = ESeverity.warning; + } else { + severity = ESeverity.error; } NodeList failChilds = currentFailNode.getChildNodes(); @@ -494,8 +492,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++; diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index edd9a73d..d21317b1 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -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(); } diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java index 2eb284f9..55affaa9 100644 --- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java @@ -216,10 +216,10 @@ public class ZUGFeRDValidatorTest extends ResourceCase { assertThat(res).valueByXPath("count(//error)") .asInt() - .isEqualTo(1); + .isEqualTo(2); assertThat(res).valueByXPath("count(//warning)") .asInt() - .isEqualTo(3); + .isEqualTo(2); assertThat(res).valueByXPath("count(//notice)") .asInt()