added item delivery periods

This commit is contained in:
jstaerk
2020-11-21 12:50:55 +01:00
parent 81dc18d731
commit 39d98d1e9d
5 changed files with 115 additions and 29 deletions

View File

@@ -1,13 +1,8 @@
### 2.0.1 todo
- 2.1 support kleinunternehmer, reverse charge?
- dont show empty tax number field
- fail when no bankverbindung?
- fail when xr attrs missing?
- build xr skonto???
- *validator not to XR error on ZF files (only notices)
- xmp errors may not show correctly in log
### 2.0.1 done
2.0.1
=====
2020-11-12
- corrected VAT calculation on prices with >2 decimals (PR#195 thanks to weclapp-dev)
- have fax numbers only in appropriate profiles, i.e., extended
- do not list tax numbers for shiptotradeparties
@@ -16,6 +11,9 @@
- BigDecimal specific refactoring PR #192 Thanks to weclapp-dev
- Preserving metadata PR #193 Thanks to mr-stephan
- support zero-rated goods: confirm that VAT category code switches from S to Z on 0%VAT
- new sample invoice
- delivery period also on item level
- corrected more than 100 javadoc entries
2.0.0
=====

View File

@@ -5,12 +5,14 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
/***
* describes any invoice line
*/
public class Item implements IZUGFeRDExportableItem {
protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount;
protected Date detailedDeliveryPeriodFrom=null, detailedDeliveryPeriodTo=null;
protected String id;
protected Product product;
protected ArrayList<String> notes = null;
@@ -58,6 +60,7 @@ public class Item implements IZUGFeRDExportableItem {
return this;
}
public BigDecimal getTax() {
return tax;
}
@@ -135,11 +138,23 @@ public class Item implements IZUGFeRDExportableItem {
}
/***
* Adds a item level addition to the price (will be multiplied by quantity)
* @see org.mustangproject.Charge
* @param izac a relative or absolute charge
* @return fluent setter
*/
public Item addCharge(IZUGFeRDAllowanceCharge izac) {
Charges.add(izac);
return this;
}
/***
* Adds a item level reduction the price (will be multiplied by quantity)
* @see org.mustangproject.Allowance
* @param izac a relative or absolute allowance
* @return fluent setter
*/
public Item addAllowance(IZUGFeRDAllowanceCharge izac) {
Allowances.add(izac);
return this;
@@ -158,5 +173,37 @@ public class Item implements IZUGFeRDExportableItem {
return this;
}
/***
* specify a item level delivery period
* (apart from the document level delivery period, and the document level
* delivery day, which is probably anyway required)
*
* @param from start date
* @param to end date
* @return fluent setter
*/
public Item setDetailedDeliveryPeriod(Date from, Date to) {
detailedDeliveryPeriodFrom=from;
detailedDeliveryPeriodTo=to;
return this;
}
/***
* specifies the item level delivery period (there is also one on document level),
* this will be included in a BillingSpecifiedPeriod element
* @return the beginning of the delivery period
*/
public Date getDetailedDeliveryPeriodFrom() {
return detailedDeliveryPeriodFrom;
}
/***
* specifies the item level delivery period (there is also one on document level),
* this will be included in a BillingSpecifiedPeriod element
* @return the end of the delivery period
*/
public Date getDetailedDeliveryPeriodTo() {
return detailedDeliveryPeriodTo;
}
}

View File

@@ -27,6 +27,7 @@ package org.mustangproject.ZUGFeRD;
* */
import java.math.BigDecimal;
import java.util.Date;
public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
@@ -88,4 +89,24 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
return null;
}
/***
* specifies the item level delivery period (there is also one on document level),
* this will be included in a BillingSpecifiedPeriod element
* @return the beginning of the delivery period
*/
default Date getDetailedDeliveryPeriodFrom() {
return null;
}
/***
* specifies the item level delivery period (there is also one on document level),
* this will be included in a BillingSpecifiedPeriod element
* @return the end of the delivery period
*/
default Date getDetailedDeliveryPeriodTo() {
return null;
}
}

View File

@@ -107,6 +107,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
// @todo check if the two boolean args can be refactored
/***
* returns the UN/CEFACT CII XML for companies(tradeparties), which is actually
* the same for ZF1 (v 2013b) and ZF2 (v 2016b)
@@ -127,7 +128,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml += " <ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>\n"; //$NON-NLS-2$
if ((party.getContact() != null)&&(isSender||profile==Profiles.getByName("Extended"))) {
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("Extended"))) {
xml = xml + "<ram:DefinedTradeContact>\n" + " <ram:PersonName>" + XMLTools.encodeXML(party.getContact().getName())
+ "</ram:PersonName>\n";
if (party.getContact().getPhone() != null) {
@@ -137,7 +138,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " </ram:TelephoneUniversalCommunication>\n";
}
if ((party.getContact().getFax() != null)&&(profile==Profiles.getByName("Extended"))) {
if ((party.getContact().getFax() != null) && (profile == Profiles.getByName("Extended"))) {
xml = xml + " <ram:FaxUniversalCommunication>\n" + " <ram:CompleteNumber>"
+ XMLTools.encodeXML(party.getContact().getFax()) + "</ram:CompleteNumber>\n"
+ " </ram:FaxUniversalCommunication>\n";
@@ -166,13 +167,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " <ram:CountryID>" + XMLTools.encodeXML(party.getCountry())
+ "</ram:CountryID>\n"
+ " </ram:PostalTradeAddress>\n";
if ((party.getVATID() != null)&&(!isShipToTradeParty)) {
if ((party.getVATID() != null) && (!isShipToTradeParty)) {
xml += " <ram:SpecifiedTaxRegistration>\n"
+ " <ram:ID schemeID=\"VA\">" + XMLTools.encodeXML(party.getVATID())
+ "</ram:ID>\n"
+ " </ram:SpecifiedTaxRegistration>\n";
}
if ((party.getTaxID() != null)&&(!isShipToTradeParty)) {
if ((party.getTaxID() != null) && (!isShipToTradeParty)) {
xml += " <ram:SpecifiedTaxRegistration>\n"
+ " <ram:ID schemeID=\"FC\">" + XMLTools.encodeXML(party.getTaxID())
+ "</ram:ID>\n"
@@ -193,7 +194,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
protected String getAllowanceChargeStr(IZUGFeRDAllowanceCharge allowance, IAbsoluteValueProvider item) {
String percentage = "";
String chargeIndicator = "false";
if ((allowance.getPercent() != null)&&(profile==Profiles.getByName("Extended"))) {
if ((allowance.getPercent() != null) && (profile == Profiles.getByName("Extended"))) {
percentage = "<ram:CalculationPercent>" + vatFormat(allowance.getPercent()) + "</ram:CalculationPercent>";
percentage += "<ram:BasisAmount>" + item.getValue() + "</ram:BasisAmount>";
}
@@ -201,15 +202,15 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
chargeIndicator = "true";
}
String reason="";
if ((allowance.getReason()!=null)&&(profile==Profiles.getByName("Extended"))) {
String reason = "";
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
// only in extended profile
reason="<ram:Reason>"+XMLTools.encodeXML(allowance.getReason())+"</ram:Reason>";
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
}
String allowanceChargeStr = "<ram:AppliedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
"<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
reason+
reason +
"</ram:AppliedTradeAllowanceCharge>";
return allowanceChargeStr;
}
@@ -228,7 +229,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
paymentTermsDescription = trans.getPaymentTermDescription();
}
if ((paymentTermsDescription == null)&&(trans.getDocumentCode()!= org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE)) {
if ((paymentTermsDescription == null) && (trans.getDocumentCode() != org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE)) {
paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
}
@@ -378,8 +379,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " <ram:RateApplicablePercent>"
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>\n"
+ " </ram:ApplicableTradeTax>\n"
+ " <ram:SpecifiedTradeSettlementLineMonetarySummation>\n"
+ " </ram:ApplicableTradeTax>\n";
if ((currentItem.getDetailedDeliveryPeriodFrom() != null) || (currentItem.getDetailedDeliveryPeriodTo() != null)) {
xml = xml + "<ram:BillingSpecifiedPeriod>";
if (currentItem.getDetailedDeliveryPeriodFrom() != null) {
xml = xml + "<ram:StartDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(currentItem.getDetailedDeliveryPeriodFrom()) + "</udt:DateTimeString></ram:StartDateTime>";
}
if (currentItem.getDetailedDeliveryPeriodTo() != null) {
xml = xml + "<ram:EndDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(currentItem.getDetailedDeliveryPeriodTo()) + "</udt:DateTimeString></ram:EndDateTime>";
}
xml = xml + "</ram:BillingSpecifiedPeriod>";
}
xml = xml + " <ram:SpecifiedTradeSettlementLineMonetarySummation>\n"
+ " <ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
+ "</ram:LineTotalAmount>\n" // currencyID=\"EUR\"
+ " </ram:SpecifiedTradeSettlementLineMonetarySummation>\n";
@@ -481,8 +494,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
}
if (trans.getDocumentCode()== DocumentCodeTypeConstants.CORRECTEDINVOICE) {
hasDueDate=false;
if (trans.getDocumentCode() == DocumentCodeTypeConstants.CORRECTEDINVOICE) {
hasDueDate = false;
}
HashMap<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
@@ -517,7 +530,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO)!=0) {
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
xml = xml + " <ram:SpecifiedTradeAllowanceCharge>\n" +
@@ -525,7 +538,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
" <udt:Indicator>true</udt:Indicator>\n" +
" </ram:ChargeIndicator>\n" +
" <ram:ActualAmount>" + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "</ram:ActualAmount>\n" +
" <ram:Reason>"+XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent))+"</ram:Reason>\n" +
" <ram:Reason>" + XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent)) + "</ram:Reason>\n" +
" <ram:CategoryTradeTax>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>\n" +
@@ -540,13 +553,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO)!= 0) {
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
xml = xml + " <ram:SpecifiedTradeAllowanceCharge>\n" +
" <ram:ChargeIndicator>\n" +
" <udt:Indicator>false</udt:Indicator>\n" +
" </ram:ChargeIndicator>\n" +
" <ram:ActualAmount>" + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "</ram:ActualAmount>\n" +
" <ram:Reason>"+XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent))+"</ram:Reason>\n" +
" <ram:Reason>" + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent)) + "</ram:Reason>\n" +
" <ram:CategoryTradeTax>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>\n" +
@@ -620,7 +633,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
try {
zugferdRaw = xml.getBytes("UTF-8");
zugferdData=XMLTools.removeBOM(zugferdRaw);
zugferdData = XMLTools.removeBOM(zugferdRaw);
} catch (UnsupportedEncodingException e) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
}

View File

@@ -242,6 +242,9 @@ public class ZF2PushTest extends TestCase {
}
/***
* test the edge cases of the invoice class
*/
public void testPushEdge() {
String orgname = "Test company";
@@ -262,7 +265,7 @@ public class ZF2PushTest extends TestCase {
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setID("0009845"))
.setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setID("0008734").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)), price, new BigDecimal(1.0)).addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)), price, new BigDecimal(1.0)).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)))
.setDetailedDeliveryPeriod(sdf.parse("2020-01-01"),sdf.parse("2020-01-31"))
@@ -284,6 +287,10 @@ public class ZF2PushTest extends TestCase {
assertTrue(zi.getUTF8().contains("0009845"));
assertTrue(zi.getUTF8().contains("0008734"));
assertTrue(zi.getUTF8().contains("20200113")); // to contain item delivery periods
assertTrue(zi.getUTF8().contains("20200115")); // to contain item delivery periods
assertTrue(zi.getUTF8().contains("item level 1/1"));
assertTrue(zi.getUTF8().contains("DE4711")); // the VAT ID should be there...
assertFalse(zi.getUTF8().contains("DE47110")); // but not the VAT ID of the shiptotradeparty