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 } diff --git a/invoice.go b/invoice.go index f04e4ec..9ba2cea 100644 --- a/invoice.go +++ b/invoice.go @@ -2,50 +2,59 @@ package ksef /**/ 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"` - 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 @@ -61,7 +70,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 +84,11 @@ func newAnnotations() *Annotations { SimplifiedProcedureBySecondTaxpayer: 2, NoMarginProcedures: 1, } + + if inv.Tax != nil && slices.Contains(inv.Tax.Tags, tax.TagReverseCharge) { + Annotations.ReverseCharge = 1 + } + return Annotations } @@ -82,7 +96,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), @@ -106,24 +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 { - 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, reg) } } @@ -136,3 +142,60 @@ func invoiceNumber(series string, code string) string { } return series + "-" + code } + +func setTaxRate(inv *Inv, rate *tax.RateTotal, cu uint32, region string) { + 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: + switch region { + case regionDomestic: + inv.DomesticZeroTaxNetSale = base + case regionEU: + inv.EUZeroTaxNetSale = base + case regionNonEU: + inv.ExportNetSale = base + } + case tax.RateExempt: + inv.TaxExemptNetSale = base + case pl.TaxRateNotPursuant: + switch region { + case regionEU: + inv.TaxNAEUNetSale = base + 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 +} diff --git a/ksef.go b/ksef.go index d6922c3..9145825 100644 --- a/ksef.go +++ b/ksef.go @@ -37,6 +37,16 @@ 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 { + 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) + } + } + invoice := &Invoice{ XMLName: xml.Name{Local: RootElementName}, XSINamespace: XSINamespace, diff --git a/lines.go b/lines.go index 23afdc7..a1e386c 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,31 @@ 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 { + if t == nil { + return "" + } + + 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 "" diff --git a/parties.go b/parties.go index 4e56452..e191795 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,51 @@ func NewBuyer(customer *org.Party) *Buyer { return buyer } +// EUCountries list of current EU coutries +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 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/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 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