From 0c75cc4473f07c3ea4c4cb70506c072b46d4b3f0 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Fri, 12 Apr 2024 13:38:41 +0200 Subject: [PATCH 01/13] Handle reverse charge addnotation To be able to handle reverse charge we need to set the correct KSeF addnotation. --- invoice.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/invoice.go b/invoice.go index f04e4ec..c84f129 100644 --- a/invoice.go +++ b/invoice.go @@ -2,6 +2,8 @@ package ksef /**/ import ( + "slices" + "github.com/invopop/gobl/bill" "github.com/invopop/gobl/regimes/pl" "github.com/invopop/gobl/tax" @@ -61,7 +63,7 @@ type Annotations struct { } // newAnnotations sets annotations data -func newAnnotations() *Annotations { +func newAnnotations(inv *bill.Invoice) *Annotations { // default values for the most common case, // For fields P_16 to P_18 and field P_23 2 means "no", 1 means "yes". // for others 1 means "yes", no value means "no" @@ -75,6 +77,11 @@ func newAnnotations() *Annotations { SimplifiedProcedureBySecondTaxpayer: 2, NoMarginProcedures: 1, } + + if slices.Contains(inv.Tax.Tags, tax.TagReverseCharge) { + Annotations.ReverseCharge = 1 + } + return Annotations } @@ -82,7 +89,7 @@ func newAnnotations() *Annotations { func NewInv(inv *bill.Invoice) *Inv { cu := inv.Currency.Def().Subunits Inv := &Inv{ - Annotations: newAnnotations(), + Annotations: newAnnotations(inv), CurrencyCode: string(inv.Currency), IssueDate: inv.IssueDate.String(), SequentialNumber: invoiceNumber(inv.Series, inv.Code), From 75d6a131d62d6b38bd7c8b24f4b4e8517f75177c Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Fri, 12 Apr 2024 14:53:13 +0200 Subject: [PATCH 02/13] Add handling of different tax rates In the previous implementation we handled only the basic tax cases, but to be able to handle international trade we need to handle more complex cases. Here we add handling of different tax rates for different cases. The cases we add are: P_13_4 - TaxiRateNetSale - special rate for taxi drivers - tax.RateSpecial + ExtKeyKSeFVATSpecial{taxi} P_13_6_1 - DomesticZeroTaxNetSale - inside Poland 0% - tax.RateZero + ExtKeyKSeFVATRegion{domestic} P_13_6_2 - EUZeroTaxNetSale - inside EU 0% - tax.RateZero + ExtKeyKSeFVATRegion{EU} P_13_6_3 - ExportNetSale - outside EU 0% - tax.RateZero + ExtKeyKSeFVATRegion{world} P_13_7 - TaxExemptNetSale - tax exempt - tax.RateExempt P_13_8 - TaxNAInternationalNetSale - tax not applicable outside EU - pl.TaxRateNotPursuant + ExtKeyKSeFVATRegion{world} P_13_9 - TaxNAEUNetSale - tax not applicable inside EU - pl.TaxRateNotPursuant + ExtKeyKSeFVATRegion{EU} This still does not cover cases of: P_13_5 - SpecialProcedureNetSale - VAT-OSS internet EU sales P_13_10 - EUServiceNetSale - reverse charge inside Poland P_13_11 - MarginNetSale - margin --- invoice.go | 139 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 49 deletions(-) diff --git a/invoice.go b/invoice.go index c84f129..550a57e 100644 --- a/invoice.go +++ b/invoice.go @@ -11,43 +11,43 @@ import ( // Inv defines the XML structure for KSeF invoice type Inv struct { - CurrencyCode string `xml:"KodWaluty"` - IssueDate string `xml:"P_1"` - IssuePlace string `xml:"P_1M,omitempty"` - SequentialNumber string `xml:"P_2"` - CompletionDate string `xml:"P_6,omitempty"` - StartDate string `xml:"P_6_Od,omitempty"` - EndDate string `xml:"P_6_Do,omitempty"` - StandardRateNetSale string `xml:"P_13_1,omitempty"` - StandardRateTax string `xml:"P_14_1,omitempty"` - StandardRateTaxConvertedToPln string `xml:"P_14_1W,omitempty"` - ReducedRateNetSale string `xml:"P_13_2,omitempty"` - ReducedRateTax string `xml:"P_14_2,omitempty"` - ReducedRateTaxConvertedToPln string `xml:"P_14_2W,omitempty"` - SuperReducedRateNetSale string `xml:"P_13_3,omitempty"` - SuperReducedRateTax string `xml:"P_14_3,omitempty"` - SuperReducedRateTaxConvertedToPln string `xml:"P_14_3W,omitempty"` - TaxiRateNetSale string `xml:"P_13_4,omitempty"` - TaxiRateTax string `xml:"P_14_4,omitempty"` - TaxiRateTaxConvertedToPln string `xml:"P_14_4W,omitempty"` - SpecialProcedureNetSale string `xml:"P_13_5,omitempty"` - SpecialProcedureTax string `xml:"P_14_5,omitempty"` - ZeroTaxExceptIntraCommunityNetSale string `xml:"P_13_6_1,omitempty"` - IntraCommunityNetSale string `xml:"P_13_6_2,omitempty"` - ExportNetSale string `xml:"P_13_6_3,omitempty"` - TaxExemptNetSale string `xml:"P_13_7,omitempty"` - InternationalNetSale string `xml:"P_13_8,omitempty"` - OtherNetSale string `xml:"P_13_9,omitempty"` - EUServiceNetSale string `xml:"P_13_10,omitempty"` - MarginNetSale string `xml:"P_13_11,omitempty"` - TotalAmountReceivable string `xml:"P_15"` - Annotations *Annotations `xml:"Adnotacje"` - InvoiceType string `xml:"RodzajFaktury"` - CorrectionReason string `xml:"PrzyczynaKorekty,omitempty"` - CorrectionType string `xml:"TypKorekty,omitempty"` - CorrectedInv *CorrectedInv `xml:"DaneFaKorygowanej,omitempty"` - Lines []*Line `xml:"FaWiersz"` - Payment *Payment `xml:"Platnosc"` + CurrencyCode string `xml:"KodWaluty"` + IssueDate string `xml:"P_1"` + IssuePlace string `xml:"P_1M,omitempty"` + SequentialNumber string `xml:"P_2"` + CompletionDate string `xml:"P_6,omitempty"` + StartDate string `xml:"P_6_Od,omitempty"` + EndDate string `xml:"P_6_Do,omitempty"` + StandardRateNetSale string `xml:"P_13_1,omitempty"` + StandardRateTax string `xml:"P_14_1,omitempty"` + StandardRateTaxConvertedToPln string `xml:"P_14_1W,omitempty"` + ReducedRateNetSale string `xml:"P_13_2,omitempty"` + ReducedRateTax string `xml:"P_14_2,omitempty"` + ReducedRateTaxConvertedToPln string `xml:"P_14_2W,omitempty"` + SuperReducedRateNetSale string `xml:"P_13_3,omitempty"` + SuperReducedRateTax string `xml:"P_14_3,omitempty"` + SuperReducedRateTaxConvertedToPln string `xml:"P_14_3W,omitempty"` + TaxiRateNetSale string `xml:"P_13_4,omitempty"` + TaxiRateTax string `xml:"P_14_4,omitempty"` + TaxiRateTaxConvertedToPln string `xml:"P_14_4W,omitempty"` + SpecialProcedureNetSale string `xml:"P_13_5,omitempty"` + SpecialProcedureTax string `xml:"P_14_5,omitempty"` + DomesticZeroTaxNetSale string `xml:"P_13_6_1,omitempty"` + EUZeroTaxNetSale string `xml:"P_13_6_2,omitempty"` + ExportNetSale string `xml:"P_13_6_3,omitempty"` + TaxExemptNetSale string `xml:"P_13_7,omitempty"` + TaxNAInternationalNetSale string `xml:"P_13_8,omitempty"` + TaxNAEUNetSale string `xml:"P_13_9,omitempty"` + EUServiceNetSale string `xml:"P_13_10,omitempty"` + MarginNetSale string `xml:"P_13_11,omitempty"` + TotalAmountReceivable string `xml:"P_15"` + Annotations *Annotations `xml:"Adnotacje"` + InvoiceType string `xml:"RodzajFaktury"` + CorrectionReason string `xml:"PrzyczynaKorekty,omitempty"` + CorrectionType string `xml:"TypKorekty,omitempty"` + CorrectedInv *CorrectedInv `xml:"DaneFaKorygowanej,omitempty"` + Lines []*Line `xml:"FaWiersz"` + Payment *Payment `xml:"Platnosc"` } // Annotations defines the XML structure for KSeF annotations @@ -119,18 +119,7 @@ func NewInv(inv *bill.Invoice) *Inv { } for _, rate := range cat.Rates { - if rate.Percent != nil { - if rate.Key == tax.RateStandard { - Inv.StandardRateNetSale = rate.Base.Rescale(cu).String() - Inv.StandardRateTax = rate.Amount.Rescale(cu).String() - } else if rate.Key == tax.RateReduced { - Inv.ReducedRateNetSale = rate.Base.Rescale(cu).String() - Inv.ReducedRateTax = rate.Amount.Rescale(cu).String() - } else if rate.Key == tax.RateSuperReduced { - Inv.SuperReducedRateNetSale = rate.Base.Rescale(cu).String() - Inv.SuperReducedRateTax = rate.Amount.Rescale(cu).String() - } - } + setTaxRate(Inv, rate, cu) } } @@ -143,3 +132,55 @@ func invoiceNumber(series string, code string) string { } return series + "-" + code } + +func setTaxRate(inv *Inv, rate *tax.RateTotal, cu uint32) { + if rate.Percent == nil { + return + } + + base := rate.Base.Rescale(cu).String() + taxAmount := rate.Amount.Rescale(cu).String() + + switch rate.Key { + case tax.RateStandard: + inv.StandardRateNetSale = base + inv.StandardRateTax = taxAmount + case tax.RateReduced: + inv.ReducedRateNetSale = base + inv.ReducedRateTax = taxAmount + case tax.RateSuperReduced: + inv.SuperReducedRateNetSale = base + inv.SuperReducedRateTax = taxAmount + case tax.RateSpecial: + if rate.Ext.Has(pl.ExtKeyKSeFVATSpecial) && rate.Ext[pl.ExtKeyKSeFVATSpecial].String() == "taxi" { + inv.TaxiRateNetSale = base + inv.TaxiRateTax = taxAmount + } + case tax.RateZero: + if !rate.Ext.Has(pl.ExtKeyKSeFVATRegion) { + return + } + + switch rate.Ext[pl.ExtKeyKSeFVATRegion].String() { + case "domestic": + inv.DomesticZeroTaxNetSale = base + case "EU": + inv.EUZeroTaxNetSale = base + case "non-EU": + inv.ExportNetSale = base + } + case tax.RateExempt: + inv.TaxExemptNetSale = base + case pl.TaxRateNotPursuant: + if !rate.Ext.Has(pl.ExtKeyKSeFVATRegion) { + return + } + + switch rate.Ext[pl.ExtKeyKSeFVATRegion].String() { + case "EU": + inv.TaxNAEUNetSale = base + case "non-EU": + inv.TaxNAInternationalNetSale = base + } + } +} From ab80e0c7b68f745c5305f8efd48908683b129c38 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Wed, 17 Apr 2024 16:24:19 +0200 Subject: [PATCH 03/13] fixup! Handle reverse charge addnotation --- invoice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoice.go b/invoice.go index 550a57e..e678551 100644 --- a/invoice.go +++ b/invoice.go @@ -78,7 +78,7 @@ func newAnnotations(inv *bill.Invoice) *Annotations { NoMarginProcedures: 1, } - if slices.Contains(inv.Tax.Tags, tax.TagReverseCharge) { + if inv.Tax != nil && slices.Contains(inv.Tax.Tags, tax.TagReverseCharge) { Annotations.ReverseCharge = 1 } From d7a873d6226101c9d38f532767ad2156c3f12199 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Wed, 17 Apr 2024 16:25:51 +0200 Subject: [PATCH 04/13] Sanitize filename when saving data When saving the data we were using the invoice number as the filename. We did not sanitize the filename which can cause errors in some cases when the file uses slashes or dots in the filename. We add a method to sanitize the filename before creating the file. --- cmd/gobl.ksef/send.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/gobl.ksef/send.go b/cmd/gobl.ksef/send.go index 2f71ab1..85c2494 100644 --- a/cmd/gobl.ksef/send.go +++ b/cmd/gobl.ksef/send.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "regexp" "time" "github.com/invopop/gobl" @@ -60,6 +61,7 @@ func (c *sendOpts) runE(cmd *cobra.Command, args []string) error { ksef_api.WithID(nip), ksef_api.WithToken(token), ksef_api.WithKeyPath(keyPath), + ksef_api.WithDebugClient(), ) env, err := SendInvoice(client, data) @@ -191,7 +193,14 @@ func saveFile(name string, data []byte) error { func filename(inv *bill.Invoice) string { if inv.Series != "" { - return inv.Series + "-" + inv.Code + ".xml" + return sanitizeFilename(inv.Series + "_" + inv.Code + ".xml") } - return inv.Code + ".xml" + return sanitizeFilename(inv.Code + ".xml") +} + +func sanitizeFilename(filename string) string { + re := regexp.MustCompile(`[^\w\.-]`) + sanitized := re.ReplaceAllString(filename, "_") + + return sanitized } From dbaa806340b97ecfaf88e142cacb8e7a646ad8b8 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Wed, 17 Apr 2024 16:39:47 +0200 Subject: [PATCH 05/13] Fix tax rates In some tax rate cases we want to set the percentage value of the tax in other cases we want to have a string saying that the line is exempt. Here we add methods to decide what value should the TaxRate have. --- lines.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lines.go b/lines.go index 23afdc7..2900349 100644 --- a/lines.go +++ b/lines.go @@ -3,6 +3,8 @@ package ksef import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/num" + "github.com/invopop/gobl/regimes/pl" + "github.com/invopop/gobl/tax" ) // Line defines the XML structure for KSeF item line @@ -32,12 +34,27 @@ func newLine(line *bill.Line) *Line { Quantity: line.Quantity.String(), UnitDiscount: unitDiscount(line), NetPriceTotal: line.Total.String(), - TaxRate: line.Taxes[0].Percent.Rescale(2).StringWithoutSymbol(), + TaxRate: newTaxRate(line.Taxes.Get(tax.CategoryVAT)), } return Line } +// newTaxRate returns tax rate as string value with one of the values: +// "23", "22", "8", "7", "5", "4", "3", "0", "np", "zw" +func newTaxRate(t *tax.Combo) string { + switch t.Rate { + case tax.RateZero: + return "0" + case tax.RateExempt: + return "zw" + case pl.TaxRateNotPursuant: + return "np" + default: + return t.Percent.Rescale(2).StringWithoutSymbol() + } +} + func unitDiscount(line *bill.Line) string { if len(line.Discounts) == 0 { return "" From 9486bb5ba4d65c4f094a9cf7cb4af5605acdb964 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Wed, 17 Apr 2024 16:42:36 +0200 Subject: [PATCH 06/13] Invert values for credit note invoices When creating a credit note we want to invert the values on the invoice. --- ksef.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ksef.go b/ksef.go index d6922c3..d131670 100644 --- a/ksef.go +++ b/ksef.go @@ -37,6 +37,14 @@ func NewDocument(env *gobl.Envelope) (*Invoice, error) { return nil, fmt.Errorf("invalid type %T", env.Document) } + // Invert if we're dealing with a credit note + if inv.Type == bill.InvoiceTypeCreditNote { + inv.Invert() + if err := inv.Calculate(); err != nil { + return nil, fmt.Errorf("inverting invoice: %w", err) + } + } + invoice := &Invoice{ XMLName: xml.Name{Local: RootElementName}, XSINamespace: XSINamespace, From f12c9922b2f762149c4157a6f590cf38ebc2ecf3 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Wed, 17 Apr 2024 17:01:16 +0200 Subject: [PATCH 07/13] Handle international customer party id In KSeF depending on customer location you need to fill in different fields. For domestic customers we need to fill in the NIP number. For customers inside the EU we need to fill in the UECode and UEVatNumber. For non EU customers we need to fill in CountryCode and IDNumber and in cases when there is no tax id number present we need to set the NoID flag. --- parties.go | 65 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/parties.go b/parties.go index 4e56452..3c2dc71 100644 --- a/parties.go +++ b/parties.go @@ -30,11 +30,11 @@ type ContactDetails struct { type Buyer struct { NIP string `xml:"DaneIdentyfikacyjne>NIP,omitempty"` // or - UECode string `xml:"DaneIdentyfikacyjne>KodUE,omitempty"` //TODO + UECode string `xml:"DaneIdentyfikacyjne>KodUE,omitempty"` UEVatNumber string `xml:"DaneIdentyfikacyjne>NrVatUE,omitempty"` // or CountryCode string `xml:"DaneIdentyfikacyjne>KodKraju,omitempty"` - IDNumber string `xml:"DaneIdentyfikacyjne>NrId,omitempty"` + IDNumber string `xml:"DaneIdentyfikacyjne>NrID,omitempty"` // or NoID int `xml:"DaneIdentyfikacyjne>BrakID,omitempty"` @@ -94,20 +94,19 @@ func NewBuyer(customer *org.Party) *Buyer { buyer := &Buyer{ Name: customer.Name, - NIP: string(customer.TaxID.Code), } - if customer.TaxID.Country == l10n.PL { + if len(customer.TaxID.Code) == 0 { + buyer.NoID = 1 + } else if customer.TaxID.Country == l10n.PL { buyer.NIP = string(customer.TaxID.Code) + } else if isEUCountry(customer.TaxID.Country) { + buyer.UEVatNumber = string(customer.TaxID.Code) + buyer.UECode = string(customer.TaxID.Country) } else { - if len(customer.TaxID.Code) > 0 { - buyer.IDNumber = string(customer.TaxID.Code) - buyer.CountryCode = string(customer.TaxID.Country) - } else { - buyer.NoID = 1 - } + buyer.IDNumber = string(customer.TaxID.Code) + buyer.CountryCode = string(customer.TaxID.Country) } - // TODO NrVatUE and UECode if applicable if len(customer.Addresses) > 0 { buyer.Address = newAddress(customer.Addresses[0]) @@ -128,6 +127,50 @@ func NewBuyer(customer *org.Party) *Buyer { return buyer } +func EUCountries() []l10n.CountryCode { + return []l10n.CountryCode{ + l10n.AT, // Austria, + l10n.BE, // Belgium, + l10n.BG, // Bulgaria, + l10n.HR, // Croatia, + l10n.CY, // Republic of Cyprus, + l10n.CZ, // Czech Republic, + l10n.DK, // Denmark, + l10n.EE, // Estonia, + l10n.FI, // Finland, + l10n.FR, // France, + l10n.DE, // Germany, + l10n.GR, // Greece, + l10n.HU, // Hungary, + l10n.IE, // Ireland, + l10n.IT, // Italy, + l10n.LV, // Latvia, + l10n.LT, // Lithuania, + l10n.LU, // Luxembourg, + l10n.MT, // Malta, + l10n.NL, // Netherlands, + l10n.PT, // Portugal, + l10n.RO, // Romania, + l10n.SK, // Slovakia, + l10n.SL, // Slovenia, + l10n.ES, // Spain, + l10n.SE, // Sweden + } +} + +func isEUCountry(country l10n.CountryCode) bool { + return contains(EUCountries(), country) +} + +func contains(slice []l10n.CountryCode, item l10n.CountryCode) bool { + for _, value := range slice { + if value == item { + return true + } + } + return false +} + func addressLine1(address *org.Address) string { if address.PostOfficeBox != "" { return address.PostOfficeBox From 5ebd23e485b3288a77436e05a44498ddb3392a52 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 13:17:12 +0200 Subject: [PATCH 08/13] fixup! Invert values for credit note invoices --- test/data/out/credit-note.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/data/out/credit-note.xml b/test/data/out/credit-note.xml index 84de0db..78ca207 100644 --- a/test/data/out/credit-note.xml +++ b/test/data/out/credit-note.xml @@ -35,9 +35,9 @@ PLN 2023-12-21 CN-002 - 200.00 - 46.00 - 246.00 + -200.00 + -46.00 + -246.00 2 2 @@ -67,9 +67,9 @@ 1 Development services HUR - 20 + -20 10.00 - 200.00 + -200.00 23 From 7702b4b48dd60503625d5823d31a1f201ea7a70b Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 14:13:21 +0200 Subject: [PATCH 09/13] fixup! Fix tax rates --- lines.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lines.go b/lines.go index 2900349..a1e386c 100644 --- a/lines.go +++ b/lines.go @@ -43,6 +43,10 @@ func newLine(line *bill.Line) *Line { // newTaxRate returns tax rate as string value with one of the values: // "23", "22", "8", "7", "5", "4", "3", "0", "np", "zw" func newTaxRate(t *tax.Combo) string { + if t == nil { + return "" + } + switch t.Rate { case tax.RateZero: return "0" From c80cf318ea2772baa74f8bd035359e98fa092b82 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 14:31:15 +0200 Subject: [PATCH 10/13] fixup! Add handling of different tax rates --- invoice.go | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/invoice.go b/invoice.go index e678551..9ba2cea 100644 --- a/invoice.go +++ b/invoice.go @@ -5,10 +5,17 @@ import ( "slices" "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/regimes/pl" "github.com/invopop/gobl/tax" ) +const ( + regionDomestic = "domestic" + regionEU = "EU" + regionNonEU = "non-EU" +) + // Inv defines the XML structure for KSeF invoice type Inv struct { CurrencyCode string `xml:"KodWaluty"` @@ -113,13 +120,16 @@ func NewInv(inv *bill.Invoice) *Inv { if inv.OperationDate != nil { Inv.CompletionDate = inv.OperationDate.String() } + + reg := region(inv) + for _, cat := range inv.Totals.Taxes.Categories { if cat.Code != tax.CategoryVAT { continue } for _, rate := range cat.Rates { - setTaxRate(Inv, rate, cu) + setTaxRate(Inv, rate, cu, reg) } } @@ -133,7 +143,7 @@ func invoiceNumber(series string, code string) string { return series + "-" + code } -func setTaxRate(inv *Inv, rate *tax.RateTotal, cu uint32) { +func setTaxRate(inv *Inv, rate *tax.RateTotal, cu uint32, region string) { if rate.Percent == nil { return } @@ -157,30 +167,35 @@ func setTaxRate(inv *Inv, rate *tax.RateTotal, cu uint32) { inv.TaxiRateTax = taxAmount } case tax.RateZero: - if !rate.Ext.Has(pl.ExtKeyKSeFVATRegion) { - return - } - - switch rate.Ext[pl.ExtKeyKSeFVATRegion].String() { - case "domestic": + switch region { + case regionDomestic: inv.DomesticZeroTaxNetSale = base - case "EU": + case regionEU: inv.EUZeroTaxNetSale = base - case "non-EU": + case regionNonEU: inv.ExportNetSale = base } case tax.RateExempt: inv.TaxExemptNetSale = base case pl.TaxRateNotPursuant: - if !rate.Ext.Has(pl.ExtKeyKSeFVATRegion) { - return - } - - switch rate.Ext[pl.ExtKeyKSeFVATRegion].String() { - case "EU": + switch region { + case regionEU: inv.TaxNAEUNetSale = base - case "non-EU": + case regionNonEU: inv.TaxNAInternationalNetSale = base } } } + +func region(inv *bill.Invoice) string { + if inv.Supplier == nil || inv.Customer == nil || inv.Supplier.TaxID == nil || inv.Customer.TaxID == nil { + return regionDomestic + } + if isEUCountry(inv.Supplier.TaxID.Country) || isEUCountry(inv.Customer.TaxID.Country) { + return regionEU + } + if inv.Supplier.TaxID.Country != l10n.PL || inv.Customer.TaxID.Country != l10n.PL { + return regionNonEU + } + return regionDomestic +} From 9b6b932a0470d42f7400dbae9781b05d8a5da295 Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 14:35:20 +0200 Subject: [PATCH 11/13] Add example invoices for different tax categories --- test/data/invoice-EU-0.json | 105 +++++++++++++++++++++ test/data/invoice-EU-na.json | 110 ++++++++++++++++++++++ test/data/invoice-domestic-0.json | 105 +++++++++++++++++++++ test/data/invoice-exempt.json | 102 ++++++++++++++++++++ test/data/invoice-nonEU-0.json | 105 +++++++++++++++++++++ test/data/invoice-nonEU-na.json | 107 +++++++++++++++++++++ test/data/invoice-taxi.json | 102 ++++++++++++++++++++ test/data/out/credit-note-pl-pl.xml | 0 test/data/out/fractalsoft-credit-note.xml | 74 +++++++++++++++ test/data/out/fractalsoft.xml | 66 +++++++++++++ test/data/out/invoice-EU-0.xml | 66 +++++++++++++ test/data/out/invoice-credit-note.xml | 0 test/data/out/invoice-domestic-0.xml | 66 +++++++++++++ test/data/out/invoice-exempt.xml | 67 +++++++++++++ test/data/out/invoice-nonEU-0.xml | 66 +++++++++++++ test/data/out/invoice-taxi.xml | 66 +++++++++++++ 16 files changed, 1207 insertions(+) create mode 100644 test/data/invoice-EU-0.json create mode 100644 test/data/invoice-EU-na.json create mode 100644 test/data/invoice-domestic-0.json create mode 100644 test/data/invoice-exempt.json create mode 100644 test/data/invoice-nonEU-0.json create mode 100644 test/data/invoice-nonEU-na.json create mode 100644 test/data/invoice-taxi.json create mode 100644 test/data/out/credit-note-pl-pl.xml create mode 100644 test/data/out/fractalsoft-credit-note.xml create mode 100644 test/data/out/fractalsoft.xml create mode 100644 test/data/out/invoice-EU-0.xml create mode 100644 test/data/out/invoice-credit-note.xml create mode 100644 test/data/out/invoice-domestic-0.xml create mode 100644 test/data/out/invoice-exempt.xml create mode 100644 test/data/out/invoice-nonEU-0.xml create mode 100644 test/data/out/invoice-taxi.xml diff --git a/test/data/invoice-EU-0.json b/test/data/invoice-EU-0.json new file mode 100644 index 0000000..03ba677 --- /dev/null +++ b/test/data/invoice-EU-0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "ES", + "code": "B85905495" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "ES" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "WDT", + "price": "50.00", + "unit": "trip" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "zero", + "percent": "0.0%", + "ext": { + "pl-ksef-vat-region": "EU" + } + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "zero", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} diff --git a/test/data/invoice-EU-na.json b/test/data/invoice-EU-na.json new file mode 100644 index 0000000..93706b4 --- /dev/null +++ b/test/data/invoice-EU-na.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "tax": { + "tags": [ + "reverse-charge" + ] + }, + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "ES", + "code": "B85905495" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "ES" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "WDT", + "price": "50.00", + "unit": "trip" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "np", + "percent": "0.0%", + "ext": { + "pl-ksef-vat-region": "EU" + } + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "np", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} diff --git a/test/data/invoice-domestic-0.json b/test/data/invoice-domestic-0.json new file mode 100644 index 0000000..7c213a8 --- /dev/null +++ b/test/data/invoice-domestic-0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "Transport of computer equipment for educational institutions", + "price": "50.00", + "unit": "trip" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "zero", + "percent": "0.0%", + "ext": { + "pl-ksef-vat-region": "domestic" + } + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "zero", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} \ No newline at end of file diff --git a/test/data/invoice-exempt.json b/test/data/invoice-exempt.json new file mode 100644 index 0000000..69f32de --- /dev/null +++ b/test/data/invoice-exempt.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "exempt service", + "price": "50.00", + "unit": "h" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "exempt", + "percent": "0.0%" + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "exempt", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} diff --git a/test/data/invoice-nonEU-0.json b/test/data/invoice-nonEU-0.json new file mode 100644 index 0000000..de297ee --- /dev/null +++ b/test/data/invoice-nonEU-0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "UK" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "specific caeses of export", + "price": "50.00", + "unit": "trip" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "zero", + "percent": "0.0%", + "ext": { + "pl-ksef-vat-region": "non-EU" + } + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "zero", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} diff --git a/test/data/invoice-nonEU-na.json b/test/data/invoice-nonEU-na.json new file mode 100644 index 0000000..c538bd8 --- /dev/null +++ b/test/data/invoice-nonEU-na.json @@ -0,0 +1,107 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "tax": { + "tags": [ + "reverse-charge" + ] + }, + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "UK", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "WDT", + "price": "50.00", + "unit": "trip" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "np", + "percent": "0.0%" + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "np", + "base": "50.00", + "percent": "0.0%", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "50.00", + "payable": "50.00" + } + } +} diff --git a/test/data/invoice-taxi.json b/test/data/invoice-taxi.json new file mode 100644 index 0000000..1437167 --- /dev/null +++ b/test/data/invoice-taxi.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "d9fbc8bc-89fe-11ee-80f4-92dde50d70fc", + "dig": { + "alg": "sha256", + "val": "a88d6b5523cbad826bf6aaacc31cc2a37ec02c403215c3c378d8e727d9abd463" + }, + "draft": true + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "type": "standard", + "series": "SAMPLE", + "code": "001", + "issue_date": "2023-12-20", + "currency": "PLN", + "supplier": { + "name": "Provide One S.L.", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "42", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ], + "emails": [ + { + "addr": "billing@example.com" + } + ] + }, + "customer": { + "name": "Sample Consumer", + "tax_id": { + "country": "PL", + "code": "1234567788" + }, + "addresses": [ + { + "num": "43", + "street": "Calle Pradillo", + "locality": "Madrid", + "region": "Madrid", + "code": "00-015", + "country": "PL" + } + ] + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "Taxi service", + "price": "50.00", + "unit": "h" + }, + "sum": "50.00", + "taxes": [ + { + "cat": "VAT", + "rate": "special", + "percent": "4.0%" + } + ], + "total": "50.00" + } + ], + "totals": { + "sum": "50.00", + "total": "50.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "special", + "base": "50.00", + "percent": "4.0%", + "amount": "2.00" + } + ], + "amount": "2.00" + } + ], + "sum": "2.00" + }, + "tax": "2.00", + "total_with_tax": "52.00", + "payable": "52.00" + } + } +} \ No newline at end of file diff --git a/test/data/out/credit-note-pl-pl.xml b/test/data/out/credit-note-pl-pl.xml new file mode 100644 index 0000000..e69de29 diff --git a/test/data/out/fractalsoft-credit-note.xml b/test/data/out/fractalsoft-credit-note.xml new file mode 100644 index 0000000..547e446 --- /dev/null +++ b/test/data/out/fractalsoft-credit-note.xml @@ -0,0 +1,74 @@ + + + + FA + 2 + 2024-04-10T00:00:00Z + GOBL.KSEF + + + + 5482686629 + FRACTAL SOFT SP. Z O.O. + + + PL + ul. TRZECH BRACI, 3/1 + 43-400, CIESZYN + + + ksef@fractalsoft.org + + + + + ES + B85905495 + Invopop S.L. + + + ES + C. de Pradillo, 42 + 28002, Madrid + + + + PLN + 2024-04-10 + 2/04/2024 + -1.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + KOR + Wycofanie faktury + 1 + + 2024-04-10 + 1/04/2024 + 1 + 5482686629-20240415-85E2CCF5E3BC-E2 + + + 1 + Rozwój oprogramowania / Software Development + -1 + 1.00 + -1.00 + np + + + \ No newline at end of file diff --git a/test/data/out/fractalsoft.xml b/test/data/out/fractalsoft.xml new file mode 100644 index 0000000..1f1d98b --- /dev/null +++ b/test/data/out/fractalsoft.xml @@ -0,0 +1,66 @@ + + + + FA + 2 + 2024-04-10T00:00:00Z + GOBL.KSEF + + + + 5482686629 + FRACTAL SOFT SP. Z O.O. + + + PL + ul. TRZECH BRACI, 3/1 + 43-400, CIESZYN + + + ksef@fractalsoft.org + + + + + ES + B85905495 + Invopop S.L. + + + ES + C. de Pradillo, 42 + 28002, Madrid + + + + PLN + 2024-04-10 + 1/04/2024 + 1.00 + + 2 + 2 + 1 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + Rozwój oprogramowania / Software Development + 1 + 1.00 + 1.00 + np + + + \ No newline at end of file diff --git a/test/data/out/invoice-EU-0.xml b/test/data/out/invoice-EU-0.xml new file mode 100644 index 0000000..cbbf22d --- /dev/null +++ b/test/data/out/invoice-EU-0.xml @@ -0,0 +1,66 @@ + + + + FA + 2 + 2023-12-20T00:00:00Z + GOBL.KSEF + + + + 1234567788 + Provide One S.L. + + + PL + Calle Pradillo, 42 + 00-015, Madrid + + + billing@example.com + + + + + 1234567788 + Sample Consumer + + + PL + Calle Pradillo, 43 + 00-015, Madrid + + + + PLN + 2023-12-20 + SAMPLE-001 + 50.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + WDT + E54 + 1 + 50.00 + 50.00 + 0 + + + \ No newline at end of file diff --git a/test/data/out/invoice-credit-note.xml b/test/data/out/invoice-credit-note.xml new file mode 100644 index 0000000..e69de29 diff --git a/test/data/out/invoice-domestic-0.xml b/test/data/out/invoice-domestic-0.xml new file mode 100644 index 0000000..956451d --- /dev/null +++ b/test/data/out/invoice-domestic-0.xml @@ -0,0 +1,66 @@ + + + + FA + 2 + 2023-12-20T00:00:00Z + GOBL.KSEF + + + + 1234567788 + Provide One S.L. + + + PL + Calle Pradillo, 42 + 00-015, Madrid + + + billing@example.com + + + + + 1234567788 + Sample Consumer + + + PL + Calle Pradillo, 43 + 00-015, Madrid + + + + PLN + 2023-12-20 + SAMPLE-001 + 50.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + Transport of computer equipment for educational institutions + E54 + 1 + 50.00 + 50.00 + 0 + + + \ No newline at end of file diff --git a/test/data/out/invoice-exempt.xml b/test/data/out/invoice-exempt.xml new file mode 100644 index 0000000..3efc6a9 --- /dev/null +++ b/test/data/out/invoice-exempt.xml @@ -0,0 +1,67 @@ + + + + FA + 2 + 2023-12-20T00:00:00Z + GOBL.KSEF + + + + 1234567788 + Provide One S.L. + + + PL + Calle Pradillo, 42 + 00-015, Madrid + + + billing@example.com + + + + + 1234567788 + Sample Consumer + + + PL + Calle Pradillo, 43 + 00-015, Madrid + + + + PLN + 2023-12-20 + SAMPLE-001 + 50.00 + 50.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + exempt service + HUR + 1 + 50.00 + 50.00 + zw + + + \ No newline at end of file diff --git a/test/data/out/invoice-nonEU-0.xml b/test/data/out/invoice-nonEU-0.xml new file mode 100644 index 0000000..efbf46f --- /dev/null +++ b/test/data/out/invoice-nonEU-0.xml @@ -0,0 +1,66 @@ + + + + FA + 2 + 2023-12-20T00:00:00Z + GOBL.KSEF + + + + 1234567788 + Provide One S.L. + + + PL + Calle Pradillo, 42 + 00-015, Madrid + + + billing@example.com + + + + + 1234567788 + Sample Consumer + + + PL + Calle Pradillo, 43 + 00-015, Madrid + + + + PLN + 2023-12-20 + SAMPLE-001 + 50.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + specific caeses of export + E54 + 1 + 50.00 + 50.00 + 0 + + + \ No newline at end of file diff --git a/test/data/out/invoice-taxi.xml b/test/data/out/invoice-taxi.xml new file mode 100644 index 0000000..91ee8bc --- /dev/null +++ b/test/data/out/invoice-taxi.xml @@ -0,0 +1,66 @@ + + + + FA + 2 + 2023-12-20T00:00:00Z + GOBL.KSEF + + + + 1234567788 + Provide One S.L. + + + PL + Calle Pradillo, 42 + 00-015, Madrid + + + billing@example.com + + + + + 1234567788 + Sample Consumer + + + PL + Calle Pradillo, 43 + 00-015, Madrid + + + + PLN + 2023-12-20 + SAMPLE-001 + 52.00 + + 2 + 2 + 2 + 2 + + 1 + + + 1 + + 2 + + 1 + + + VAT + + 1 + Taxi service + HUR + 1 + 50.00 + 50.00 + 4 + + + \ No newline at end of file From c4c424809d36303337548096602c7cdb14f4fadc Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 15:25:40 +0200 Subject: [PATCH 12/13] fixup! Invert values for credit note invoices --- ksef.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ksef.go b/ksef.go index d131670..9145825 100644 --- a/ksef.go +++ b/ksef.go @@ -39,7 +39,9 @@ func NewDocument(env *gobl.Envelope) (*Invoice, error) { // Invert if we're dealing with a credit note if inv.Type == bill.InvoiceTypeCreditNote { - inv.Invert() + if err := inv.Invert(); err != nil { + return nil, fmt.Errorf("inverting invoice: %w", err) + } if err := inv.Calculate(); err != nil { return nil, fmt.Errorf("inverting invoice: %w", err) } From cfd3d5eadfdc433f53dabbc7c89298d8e1fb91fa Mon Sep 17 00:00:00 2001 From: Grzegorz Lisowski Date: Tue, 23 Apr 2024 16:33:06 +0200 Subject: [PATCH 13/13] fixup! Handle international customer party id --- parties.go | 1 + 1 file changed, 1 insertion(+) diff --git a/parties.go b/parties.go index 3c2dc71..e191795 100644 --- a/parties.go +++ b/parties.go @@ -127,6 +127,7 @@ func NewBuyer(customer *org.Party) *Buyer { return buyer } +// EUCountries list of current EU coutries func EUCountries() []l10n.CountryCode { return []l10n.CountryCode{ l10n.AT, // Austria,