This commit is contained in:
Daniel Jeney
2025-06-25 11:28:08 +02:00
parent 78861ef0b0
commit 526cbbb23b
4 changed files with 229 additions and 21 deletions

View File

@@ -4,9 +4,12 @@ import static java.math.BigDecimal.ZERO;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -248,6 +251,104 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
return hm;
}
protected List<VATAmount> getVATAmountList()
{
final List<VATAmount> vatAmounts = new ArrayList<>();
final String vatDueDateTypeCode = this.trans.getVATDueDateTypeCode();
for (final IZUGFeRDExportableItem currentItem : this.trans.getZFItems())
{
BigDecimal percent = null;
if (currentItem.getProduct() != null)
{
percent = currentItem.getProduct().getVATPercent();
}
if (percent != null)
{
final LineCalculator lc = new LineCalculator(currentItem);
final VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode, percent);
final String reasonText = currentItem.getProduct().getTaxExemptionReason();
if (reasonText != null)
{
itemVATAmount.setVatExemptionReasonText(reasonText);
}
final Optional<VATAmount> currentVatAmount = this.getCurrentVatAmount(vatAmounts, currentItem.getProduct().getTaxCategoryCode(), percent);
if (currentVatAmount.isEmpty())
{
vatAmounts.add(itemVATAmount);
}
else
{
this.mergeAdding(currentVatAmount.get(), itemVATAmount);
}
}
}
final IZUGFeRDAllowanceCharge[] charges = this.trans.getZFCharges();
if (charges != null && charges.length > 0)
{
for (final IZUGFeRDAllowanceCharge currentCharge : charges)
{
final BigDecimal taxPercent = currentCharge.getTaxPercent();
if (taxPercent != null)
{
final String vatCategoryCode = currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S";
final Optional<VATAmount> currentChargeVatAmount = this.getCurrentVatAmount(vatAmounts, vatCategoryCode, taxPercent);
final BigDecimal chargeBasis = currentCharge.getTotalAmount(this);
final VATAmount chargeVatAmount = new VATAmount(chargeBasis, chargeBasis.multiply(taxPercent.divide(new BigDecimal(100))), vatCategoryCode,
vatDueDateTypeCode, taxPercent);
if (currentChargeVatAmount.isEmpty())
{
vatAmounts.add(chargeVatAmount);
}
else
{
this.mergeAdding(currentChargeVatAmount.get(), chargeVatAmount);
}
}
}
}
final IZUGFeRDAllowanceCharge[] allowances = this.trans.getZFAllowances();
if (allowances != null && allowances.length > 0)
{
for (final IZUGFeRDAllowanceCharge currentAllowance : allowances)
{
final BigDecimal taxPercent = currentAllowance.getTaxPercent();
if (taxPercent != null)
{
final String vatCategoryCode = currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S";
final Optional<VATAmount> currentAllowanceVatAmount = this.getCurrentVatAmount(vatAmounts, vatCategoryCode, taxPercent);
final BigDecimal allowanceNegativeBasis = currentAllowance.getTotalAmount(this).multiply(BigDecimal.valueOf(-1));
final VATAmount allowanceVATAmount = new VATAmount(allowanceNegativeBasis,
allowanceNegativeBasis.multiply(taxPercent.divide(new BigDecimal(100))),
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
vatDueDateTypeCode, taxPercent);
if (currentAllowanceVatAmount.isEmpty())
{
vatAmounts.add(allowanceVATAmount);
}
else
{
this.mergeAdding(currentAllowanceVatAmount.get(), allowanceVATAmount);
}
}
}
}
return vatAmounts;
}
public void mergeAdding(VATAmount vatAmount, VATAmount toAdd)
{
vatAmount.setBasis(vatAmount.getBasis().add(toAdd.getBasis()));
vatAmount.setCalculated(vatAmount.getCalculated().add(toAdd.getCalculated()));
if (toAdd.getVatExemptionReasonText() != null && !toAdd.getVatExemptionReasonText().isBlank())
{
Optional.ofNullable(vatAmount.getVatExemptionReasonText()).filter(reasonText -> !reasonText.equals(toAdd.getVatExemptionReasonText())).ifPresentOrElse(
text -> vatAmount.setVatExemptionReasonText(String.join(", ", text, toAdd.getVatExemptionReasonText())),
() -> vatAmount.setVatExemptionReasonText(toAdd.getVatExemptionReasonText()));
}
}
@Override
public BigDecimal getValue() {
return getTotal();
@@ -260,6 +361,15 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
public BigDecimal getAllowanceTotal() {
return getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP);
}
private Optional<VATAmount> getCurrentVatAmount(List<VATAmount> vatAmounts, String vatCategoryCode, BigDecimal percentage)
{
return vatAmounts.stream()
.filter(va -> Objects.equals(vatCategoryCode, va.getCategoryCode())
&& Optional.ofNullable(percentage).map(p -> va.getApplicablePercent() == null && p == null || p.compareTo(va.getApplicablePercent()) == 0)
.orElse(true))
.findFirst();
}
public BigDecimal getDuePayable() {
BigDecimal res = getGrandTotal().subtract(getTotalPrepaid());

View File

@@ -57,6 +57,16 @@ public class VATAmount {
this.categoryCode = categoryCode;
this.dueDateTypeCode = dueDateTypeCode;
}
public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode, String dueDateTypeCode, BigDecimal applicablePercent)
{
super();
this.basis = basis;
this.calculated = calculated;
this.categoryCode = categoryCode;
this.dueDateTypeCode = dueDateTypeCode;
this.applicablePercent = applicablePercent;
}
public BigDecimal getApplicablePercent() {
return applicablePercent;

View File

@@ -704,9 +704,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
hasDueDate = false;
}
final Map<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
final List<VATAmount> vatAmounts = calc.getVATAmountList();
for (final VATAmount amount : vatAmounts)
{
if (amount != null) {
final String amountCategoryCode = amount.getCategoryCode();
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
@@ -727,7 +727,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
+ "<ram:RateApplicablePercent>"
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>";
+ vatFormat(amount.getApplicablePercent()) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>";
}
}
}
@@ -765,19 +765,23 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} else {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
}
else
{
for (final VATAmount amount : vatAmounts)
{
if (calc.getChargesForPercent(amount.getApplicablePercent()).compareTo(BigDecimal.ZERO) != 0)
{
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent)) + "</ram:Reason>" +
"<ram:ActualAmount>" + currencyFormat(calc.getChargesForPercent(amount.getApplicablePercent())) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getChargeReasonForPercent(amount.getApplicablePercent())) + "</ram:Reason>" +
"<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" +
"<ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(amount.getApplicablePercent()) + "</ram:RateApplicablePercent>" +
"</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
@@ -808,19 +812,23 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} else {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
}
else
{
for (final VATAmount amount : vatAmounts)
{
if (calc.getAllowancesForPercent(amount.getApplicablePercent()).compareTo(BigDecimal.ZERO) != 0)
{
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>false</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent)) + "</ram:Reason>" +
"<ram:ActualAmount>" + currencyFormat(calc.getAllowancesForPercent(amount.getApplicablePercent())) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(amount.getApplicablePercent())) + "</ram:Reason>" +
"<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" +
"<ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(amount.getApplicablePercent()) + "</ram:RateApplicablePercent>" +
"</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}