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