ZUGFeRD / mustangproject

Open Source Java e-Invoicing library, validator and tool (Factur-X/ZUGFeRD, UNCEFACT/CII XRechnung)
http://www.mustangproject.org
Apache License 2.0
196 stars 112 forks source link

Multiple IZUGFeRDAllowanceCharges per Item leads to issues #485

Open mrgreywater opened 12 hours ago

mrgreywater commented 12 hours ago

When exporting an invoice with multiple IZUGFeRDAllowanceCharges in an item as xml using the ZUGFeRD2PullProvider "XRechnung" profile it leads to a xml like this:

...
      <ram:SpecifiedLineTradeAgreement>
        <ram:BuyerOrderReferencedDocument>  
          <ram:LineID>000026</ram:LineID>
        </ram:BuyerOrderReferencedDocument>
        <ram:GrossPriceProductTradePrice>
          <ram:ChargeAmount>14.3000</ram:ChargeAmount>
          <ram:BasisQuantity unitCode="EA">1.0000</ram:BasisQuantity>
          <ram:AppliedTradeAllowanceCharge>
            <ram:ChargeIndicator>
              <udt:Indicator>false</udt:Indicator>
            </ram:ChargeIndicator>
            <ram:ActualAmount>14.2857</ram:ActualAmount>
          </ram:AppliedTradeAllowanceCharge>
          <ram:AppliedTradeAllowanceCharge>
            <ram:ChargeIndicator>
              <udt:Indicator>false</udt:Indicator>
            </ram:ChargeIndicator>
            <ram:ActualAmount>0.0143</ram:ActualAmount>
          </ram:AppliedTradeAllowanceCharge>
        </ram:GrossPriceProductTradePrice>
        <ram:NetPriceProductTradePrice>
          <ram:ChargeAmount>0.0000</ram:ChargeAmount>
          <ram:BasisQuantity unitCode="EA">1.0000</ram:BasisQuantity>
        </ram:NetPriceProductTradePrice>
      </ram:SpecifiedLineTradeAgreement>
...

The resulting xml cannot be loaded using the ZUGFeRDVisualizer, and instead throws this exception.

net.sf.saxon.trans.XPathException: A sequence of more than one item is not allowed as the first argument of fn:format-number() (14.2857, 0.0143) 
    at net.sf.saxon.expr.CardinalityCheckingIterator.typeError(CardinalityCheckingIterator.java:113) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.CardinalityCheckingIterator.<init>(CardinalityCheckingIterator.java:71) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.CardinalityChecker.checkCardinality(CardinalityChecker.java:258) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.CardinalityChecker$CardinalityCheckerElaborator.lambda$elaborateForPull$0(CardinalityChecker.java:506) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.elab.LazyPullEvaluator.evaluate(LazyPullEvaluator.java:39) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.FunctionCall$FunctionCallElaborator.evaluateArguments(FunctionCall.java:671) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.SystemFunctionCall$SystemFunctionCallElaborator.lambda$elaborateForPull$3(SystemFunctionCall.java:625) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.elab.PullElaborator.lambda$elaborateForPush$0(PullElaborator.java:38) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.ValueOf$ValueOfElaborator.lambda$elaborateForPush$1(ValueOf.java:420) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$3(Block.java:885) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$2(Block.java:867) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$4(Block.java:895) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$3(Block.java:885) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$2(Block.java:867) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$1(Block.java:853) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$3(Block.java:876) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:376) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.Mode.handleRuleNotNull(Mode.java:587) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:521) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.ApplyTemplates$ApplyTemplatesElaborator.lambda$elaborateForPush$1(ApplyTemplates.java:650) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$3(Block.java:885) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.NamedTemplate.expand(NamedTemplate.java:247) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.CallTemplate$CallTemplateElaborator.lambda$elaborateForPush$1(CallTemplate.java:633) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$4(Block.java:895) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$1(Block.java:853) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$1(Block.java:853) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$3(Block.java:882) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:640) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:376) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.Mode.handleRuleNotNull(Mode.java:587) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:521) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.rules.TextOnlyCopyRuleSet.process(TextOnlyCopyRuleSet.java:72) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:518) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.trans.XsltController.applyTemplates(XsltController.java:684) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.s9api.AbstractXsltTransformer.applyTemplatesToSource(AbstractXsltTransformer.java:430) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.s9api.XsltTransformer.transform(XsltTransformer.java:358) ~[validator-2.13.0-shaded.jar:2.13.0]
    at net.sf.saxon.jaxp.TransformerImpl.transform(TransformerImpl.java:75) ~[validator-2.13.0-shaded.jar:2.13.0]
    at org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.applyXSLTToHTML(ZUGFeRDVisualizer.java:391) ~[validator-2.13.0-shaded.jar:2.13.0]
    at org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.visualize(ZUGFeRDVisualizer.java:209) ~[validator-2.13.0-shaded.jar:2.13.0]

If XRechnung doesn't permit multiple Allowances per Item, then the exporter should probably combine their amount and not create multiple entries.

mrgreywater commented 12 hours ago

Simple Repro:

    public static void main(String[] args) throws Exception {
        Invoice invoice = new Invoice();
        invoice.setIssueDate(new Date());
        invoice.setSender(new TradeParty());
        invoice.setRecipient(new TradeParty());

        invoice.addItem(new IZUGFeRDExportableItem() {
            @Override
            public IZUGFeRDExportableProduct getProduct() {
                return new IZUGFeRDExportableProduct() {
                    @Override
                    public String getUnit() {
                        return "EA";
                    }

                    @Override
                    public String getName() {
                        return "Product";
                    }

                    @Override
                    public String getDescription() {
                        return "";
                    }

                    @Override
                    public BigDecimal getVATPercent() {
                        return BigDecimal.ZERO;
                    }
                };
            }

            @Override
            public IZUGFeRDAllowanceCharge[] getItemCharges() {
                return List.of(new IZUGFeRDAllowanceCharge() {
                    @Override
                    public BigDecimal getTotalAmount(IAbsoluteValueProvider iAbsoluteValueProvider) {
                        return BigDecimal.ONE;
                    }

                    @Override
                    public String getReason() {
                        return null;
                    }

                    @Override
                    public String getReasonCode() {
                        return null;
                    }

                    @Override
                    public BigDecimal getTaxPercent() {
                        return BigDecimal.ZERO;
                    }

                    @Override
                    public boolean isCharge() {
                        return false;
                    }
                }, new IZUGFeRDAllowanceCharge() {
                    @Override
                    public BigDecimal getTotalAmount(IAbsoluteValueProvider iAbsoluteValueProvider) {
                        return BigDecimal.valueOf(2);
                    }

                    @Override
                    public String getReason() {
                        return null;
                    }

                    @Override
                    public String getReasonCode() {
                        return null;
                    }

                    @Override
                    public BigDecimal getTaxPercent() {
                        return BigDecimal.ZERO;
                    }

                    @Override
                    public boolean isCharge() {
                        return false;
                    }
                }).toArray(new IZUGFeRDAllowanceCharge[0]);
            }

            @Override
            public BigDecimal getPrice() {
                return BigDecimal.TEN;
            }

            @Override
            public BigDecimal getQuantity() {
                return BigDecimal.ONE;
            }
        });

        ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
        zf2p.setTest();
        zf2p.setProfile(Profiles.getByName("XRechnung"));
        zf2p.generateXML(invoice);

        ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
        Path tempFilePath = Files.createTempFile("xrechnung", ".xml");
        Files.write(tempFilePath, zf2p.getXML());
        zvi.visualize(tempFilePath.toString(), ZUGFeRDVisualizer.Language.EN);
        Files.deleteIfExists(tempFilePath);
    }