]> git.plutz.net Git - invoices/commitdiff
Bugfix: tax rate sections and validation
authorPaul Hänsch <paul@plutz.net>
Thu, 10 Apr 2025 01:55:11 +0000 (03:55 +0200)
committerPaul Hänsch <paul@plutz.net>
Thu, 24 Jul 2025 19:07:56 +0000 (21:07 +0200)
factur-x.sh [new file with mode: 0644]

diff --git a/factur-x.sh b/factur-x.sh
new file mode 100644 (file)
index 0000000..43aa2ed
--- /dev/null
@@ -0,0 +1,250 @@
+#!/bin/sh
+
+yield_xml() {
+  local date iban bic
+
+  date="$(isdate "$(DB3 get date)")"
+  tnfc="$(DB3 get sender 6 |grep -xE '[0-9]{3}/[0-9]{3}/[0-9]{5}')"
+  tnva="$(DB3 get sender 7 |grep -xE '[A-Z]{2}[0-9]{9}')"
+  iban="$(DB3 get sender 8)"
+   bic="$(DB3 get sender 9)"
+  duedate="$(DB3 get duedate)"
+
+  DB3 check invnum || return 1
+  [ -n "$date" ] || return 1
+  DB3 check sender 2 || return 1
+  DB3 check rcpt 2 || return 1
+  [ -n "$tnfc" -o -n "$tnva" ] || return 1
+
+  # date_due="$(date -d "$date" +%s)"
+  # date_due="$((date_due + 28 * 86400))"
+  # date_due="$(date -d @${date_due} +%Y%m%d)"
+  date="$(date -d "$date" +%Y%m%d)"
+
+  cat <<-ZUGFERD
+       <rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
+         <rsm:ExchangedDocumentContext>
+           <ram:GuidelineSpecifiedDocumentContextParameter>
+             <ram:ID>urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic</ram:ID>
+           </ram:GuidelineSpecifiedDocumentContextParameter>
+         </rsm:ExchangedDocumentContext>
+         <rsm:ExchangedDocument>
+           <ram:ID>$(DB3 get invnum |HTML)</ram:ID><!-- BT-1 -->
+           <ram:TypeCode>380</ram:TypeCode><!-- BT-3 -->
+           <ram:IssueDateTime>
+             <udt:DateTimeString format="102">${date}</udt:DateTimeString><!-- BT-2 -->
+           </ram:IssueDateTime>
+       
+           <ram:IncludedNote>
+             <ram:Content>$(DB3 get freeformtop |HTML)</ram:Content>
+           </ram:IncludedNote>
+           <ram:IncludedNote>
+             <ram:Content>$(DB3 get freeformbottom |HTML)</ram:Content>
+           </ram:IncludedNote>
+         </rsm:ExchangedDocument>
+       
+         <rsm:SupplyChainTradeTransaction>
+         $(n=0 seq=0 skip=0 pcs= ppp= desc= uc= vat= vatrate=
+           [ $n -lt "$(DB3 count tb_pcs )" ] && n="$(DB3 count tb_pcs)"
+           [ $n -lt "$(DB3 count tb_ppp )" ] && n="$(DB3 count tb_ppp)"
+           [ $n -lt "$(DB3 count tb_desc)" ] && n="$(DB3 count tb_desc)"
+         
+           while [ $seq -le $n ]; do
+             seq=$((seq + 1))
+             pcs="$(DB3 get tb_pcs  $seq |grep -m1 -xE '[0-9]+(\.00?|\.25|\.50?|\.75)?' || printf 1)"
+             ppp="$(DB3 get tb_ppp  $seq |grep -m1 -xEe '-?(\.[0-9]+|[0-9]+\.?[0-9]*)' || printf 0)"
+            desc="$(DB3 get tb_desc $seq)"
+              uc="$(DB3 get tb_uc   $seq |grep -m1 -xE 'HUR|DAY|KMT|NAR|LS' || printf LS)"
+             vat="$(DB3 get tb_vat  $seq |grep -m1 -xE 'S|Z|E|AE|K|G|O|L|M' || printf O)"
+         
+             if [ "$pcs" = 1 -a "$ppp" = 0 -a -z "$desc" ]; then
+               skip=$((skip + 1))
+               continue
+             fi
+             [ "$vat" = S ] && vatrate="$(DB3 get tb_vatrate)" || vatrate=0
+         
+             cat <<-ROW
+               <ram:IncludedSupplyChainTradeLineItem>
+                 <ram:AssociatedDocumentLineDocument>
+                   <ram:LineID>$((seq - skip))</ram:LineID>
+                 </ram:AssociatedDocumentLineDocument>
+                 <ram:SpecifiedTradeProduct>
+                   <ram:Name>$(HTML "$desc")</ram:Name>
+                 </ram:SpecifiedTradeProduct>
+                 <ram:SpecifiedLineTradeAgreement><ram:NetPriceProductTradePrice>
+                   <ram:ChargeAmount>${ppp}</ram:ChargeAmount>
+                 </ram:NetPriceProductTradePrice></ram:SpecifiedLineTradeAgreement>
+                 <ram:SpecifiedLineTradeDelivery>
+                   <ram:BilledQuantity unitCode="$uc">${pcs}</ram:BilledQuantity>
+                 </ram:SpecifiedLineTradeDelivery>
+
+                 <ram:SpecifiedLineTradeSettlement>
+                   <ram:ApplicableTradeTax>
+                     <ram:TypeCode>VAT</ram:TypeCode>
+                     <ram:CategoryCode>${vat}</ram:CategoryCode>
+                     <ram:RateApplicablePercent>$(printf %.2f "${vatrate}")</ram:RateApplicablePercent>
+                   </ram:ApplicableTradeTax>
+                   <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                     <ram:LineTotalAmount>$(awk "BEGIN { printf \"%.2f\", ${pcs} * ${ppp}; }")</ram:LineTotalAmount>
+                   </ram:SpecifiedTradeSettlementLineMonetarySummation>
+                 </ram:SpecifiedLineTradeSettlement>
+               </ram:IncludedSupplyChainTradeLineItem>
+               ROW
+           done)
+
+           <ram:ApplicableHeaderTradeAgreement>
+       
+             <ram:SellerTradeParty>
+               <ram:Name>$(DB3 get sender 1 |HTML)</ram:Name>
+               <ram:PostalTradeAddress>
+                 <ram:PostcodeCode>$(DB3 get sender 3 |HTML)</ram:PostcodeCode>
+                 $(DB3 get rcpt 2 \
+                   | sed -E '/^\r?$/d;' \
+                   | for n in One Two Three; do
+                     read -r l || break
+                     printf '<ram:Line%s>%s</ram:Line%s>' "$n" "$(HTML "$l")" "$n"
+                  done)
+                 <ram:CityName>$(DB3 get sender 4 |HTML)</ram:CityName>
+                 <ram:CountryID>$(DB3 get sender 5 |HTML)</ram:CountryID>
+               </ram:PostalTradeAddress>
+
+               $([ -n "$tnfc" ] && cat cat <<-TNFC
+               <ram:SpecifiedTaxRegistration>
+                 <ram:ID schemeID="FC">$(HTML "$tnfc")</ram:ID>
+               </ram:SpecifiedTaxRegistration>
+               TNFC
+               )
+               $([ -n "$tnva" ] && cat <<-TNVA
+               <ram:SpecifiedTaxRegistration>
+                 <ram:ID schemeID="VA">$(HTML "$tnva")</ram:ID>
+               </ram:SpecifiedTaxRegistration>
+               TNVA
+               )
+             </ram:SellerTradeParty>
+       
+             <ram:BuyerTradeParty>
+               <ram:Name>$(DB3 get rcpt 1 |HTML)</ram:Name>
+               <ram:PostalTradeAddress>
+                 <ram:PostcodeCode>$(DB3 get rcpt 3 |HTML)</ram:PostcodeCode>
+                 $(DB3 get rcpt 2 \
+                   | sed -E '/^\r?$/d;' \
+                   | for n in One Two Three; do
+                     read -r l || break
+                     printf '<ram:Line%s>%s</ram:Line%s>' "$n" "$(HTML "$l")" "$n"
+                  done)
+                 <ram:CityName>$(DB3 get rcpt 4 |HTML)</ram:CityName>
+                 <ram:CountryID>$(DB3 get rcpt 5 |HTML)</ram:CountryID>
+               </ram:PostalTradeAddress>
+             </ram:BuyerTradeParty>
+           </ram:ApplicableHeaderTradeAgreement>
+
+           <ram:ApplicableHeaderTradeDelivery>
+             <ram:ActualDeliverySupplyChainEvent>
+               <ram:OccurrenceDateTime>
+                 <udt:DateTimeString format="102">${date}</udt:DateTimeString>
+               </ram:OccurrenceDateTime>
+             </ram:ActualDeliverySupplyChainEvent>
+           </ram:ApplicableHeaderTradeDelivery>
+
+           <ram:ApplicableHeaderTradeSettlement>
+             <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
+
+             $( [ -n "$iban" ] && cat <<- IBANBICIBAN
+               <ram:SpecifiedTradeSettlementPaymentMeans>
+                 <ram:TypeCode>58</ram:TypeCode>
+                 <ram:PayeePartyCreditorFinancialAccount>
+                   <ram:IBANID>$(HTML "$iban")</ram:IBANID>
+                   <!-- ram:AccountName>$(DB3 get sender 1 |HTML)</ram:AccountName -->
+                 </ram:PayeePartyCreditorFinancialAccount>
+                 $( [ -n "$bic" -a ] && cat <<- IBANBICBIC
+                       <!-- ram:PayeeSpecifiedCreditorFinancialInstitution>
+                         <ram:BICID>$(HTML "$bic")</ram:BICID>
+                       </ram:PayeeSpecifiedCreditorFinancialInstitution -->
+                       IBANBICBIC
+                 )
+               </ram:SpecifiedTradeSettlementPaymentMeans>
+               IBANBICIBAN
+             )
+
+             $(vat= net= tax= gross= vatrate=
+               for vat in $(DB3 iterate tb_vat |sort -u); do
+                 case "$vat" in
+                  (S)  for vatrate in $(vatrates); do
+                         read -r net tax gross <<-EOF
+                               $(sumtotal "${vatrate}%")
+                               EOF
+                         cat <<-TAXBLOCK
+                               <ram:ApplicableTradeTax>
+                                 <ram:CalculatedAmount>${tax}</ram:CalculatedAmount><!-- BT-117 -->
+                                 <ram:TypeCode>VAT</ram:TypeCode>
+                                 <ram:BasisAmount>${net}</ram:BasisAmount><!-- BT-116 -->
+                                 <ram:CategoryCode>${vat}</ram:CategoryCode><!-- BT-118 -->
+                                 <ram:RateApplicablePercent>$(printf '%.2f' "$vatrate")</ram:RateApplicablePercent>
+                               </ram:ApplicableTradeTax>
+                       TAXBLOCK
+                       done
+                       ;;
+                  (Z)  read -r net tax gross <<-EOF
+                       $(sumtotal "$vat")
+                       EOF
+                       cat <<-TAXBLOCK
+                       <ram:ApplicableTradeTax>
+                         <ram:CalculatedAmount>${tax}</ram:CalculatedAmount><!-- BT-117 -->
+                         <ram:TypeCode>VAT</ram:TypeCode>
+                         <ram:BasisAmount>${net}</ram:BasisAmount><!-- BT-116 -->
+                         <ram:CategoryCode>${vat}</ram:CategoryCode><!-- BT-118 -->
+                         <ram:RateApplicablePercent>0.00</ram:RateApplicablePercent>
+                       </ram:ApplicableTradeTax>
+                       TAXBLOCK
+                       ;;
+                  (E|AE|K|G|O|L|M)
+                       read -r net tax gross <<-EOF
+                       $(sumtotal "$vat")
+                       EOF
+                       cat <<-TAXBLOCK
+                       <ram:ApplicableTradeTax>
+                         <ram:CalculatedAmount>${tax}</ram:CalculatedAmount><!-- BT-117 -->
+                         <ram:TypeCode>VAT</ram:TypeCode>
+                         <ram:ExemptionReason>$(DB3 get taxfreetext |HTML)</ram:ExemptionReason>
+                         <ram:BasisAmount>${net}</ram:BasisAmount><!-- BT-116 -->
+                         <ram:CategoryCode>${vat}</ram:CategoryCode><!-- BT-118 -->
+                         <ram:RateApplicablePercent>0.00</ram:RateApplicablePercent>
+                       </ram:ApplicableTradeTax>
+                       TAXBLOCK
+                      ;;
+                 esac
+               done
+             )
+
+             $(duedatet="$(isdate "$duedate")"
+             if [ -n "$duedatet" ] ; then
+               cat <<-DUEDATE
+               <ram:SpecifiedTradePaymentTerms>
+                 <ram:DueDateDateTime>
+                   <udt:DateTimeString format="102">$(date -d "$duedatet" +%Y%m%d)</udt:DateTimeString>
+                 </ram:DueDateDateTime>
+               </ram:SpecifiedTradePaymentTerms>
+               DUEDATE
+             else
+               cat <<-DUEDATE
+               <ram:SpecifiedTradePaymentTerms>
+                 <ram:Description>${duedate}</ram:Description>
+               </ram:SpecifiedTradePaymentTerms>
+               DUEDATE
+             fi)
+
+             <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+               <ram:LineTotalAmount>${net}</ram:LineTotalAmount>
+               <ram:ChargeTotalAmount>0.00</ram:ChargeTotalAmount>
+               <ram:AllowanceTotalAmount>0.00</ram:AllowanceTotalAmount>
+               <ram:TaxBasisTotalAmount>${net}</ram:TaxBasisTotalAmount>
+               <ram:TaxTotalAmount currencyID="EUR">${tax}</ram:TaxTotalAmount>
+               <ram:GrandTotalAmount>${gross}</ram:GrandTotalAmount>
+               <ram:DuePayableAmount>${gross}</ram:DuePayableAmount>
+             </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+
+           </ram:ApplicableHeaderTradeSettlement>
+         </rsm:SupplyChainTradeTransaction>
+       </rsm:CrossIndustryInvoice>
+       ZUGFERD
+}