Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Credit note #12

Merged
merged 7 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions corrected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ksef

import (
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/head"
)

// CorrectedInv defines the XML structure for KSeF correction invoice
type CorrectedInv struct {
IssueDate string `xml:"DataWystFaKorygowanej,omitempty"`
SequentialNumber string `xml:"NrFaKorygowanej,omitempty"`
KsefNumberPresent int `xml:"NrKSeF,omitempty"`
NoKsefNumberPresent int `xml:"NrKSeFN,omitempty"`
KsefNumber string `xml:"NrKSeFFaKorygowanej,omitempty"`
}

// NewCorrectedInv gets credit note data from GOBL invoice
func NewCorrectedInv(prc *bill.Preceding) *CorrectedInv {
inv := &CorrectedInv{
SequentialNumber: invoiceNumber(prc.Series, prc.Code),
}

if prc.IssueDate != nil {
inv.IssueDate = prc.IssueDate.String()
}

if id := findStamp(prc.Stamps, "ksef-id"); id != -1 {
inv.KsefNumberPresent = 1
inv.KsefNumber = prc.Stamps[id].Value
} else {
inv.NoKsefNumberPresent = 1
}

return inv
}

func findStamp(a []*head.Stamp, x string) int {
for i, n := range a {
if x == string(n.Provider) {
return i
}
}
return -1
}
60 changes: 60 additions & 0 deletions corrected_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ksef_test

import (
"testing"
"time"

ksef "github.com/invopop/gobl.ksef"
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cal"
"github.com/invopop/gobl/head"
"github.com/stretchr/testify/assert"
)

func TestNewCorrectedInv(t *testing.T) {
t.Run("sets invoice number", func(t *testing.T) {
prc := &bill.Preceding{
Series: "SAMPLE",
Code: "001",
}

cor := ksef.NewCorrectedInv(prc)

assert.Equal(t, "SAMPLE-001", cor.SequentialNumber)
})

t.Run("sets issue date", func(t *testing.T) {
prc := &bill.Preceding{
IssueDate: cal.NewDate(2024, time.March, 14),
}

cor := ksef.NewCorrectedInv(prc)

assert.Equal(t, "2024-03-14", cor.IssueDate)
})

t.Run("sets no ksef number flag", func(t *testing.T) {
prc := &bill.Preceding{}

cor := ksef.NewCorrectedInv(prc)

assert.Equal(t, 1, cor.NoKsefNumberPresent)
})

t.Run("sets ksef number", func(t *testing.T) {
ksefID := "123"
prc := &bill.Preceding{
Stamps: []*head.Stamp{
{
Provider: "ksef-id",
Value: ksefID,
},
},
}

cor := ksef.NewCorrectedInv(prc)

assert.Equal(t, 1, cor.KsefNumberPresent)
assert.Equal(t, ksefID, cor.KsefNumber)
})
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.20

require (
github.com/go-resty/resty/v2 v2.11.0
github.com/invopop/gobl v0.65.1
github.com/invopop/gobl v0.71.0
github.com/jarcoal/httpmock v1.3.1
github.com/joho/godotenv v1.5.1
github.com/spf13/cobra v1.7.0
Expand All @@ -16,6 +16,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/gobl v0.65.1 h1:hiyEIXXoQkA1hr8KKyKvULC6WgF+nzqkulmuUDDbe1k=
github.com/invopop/gobl v0.65.1/go.mod h1:Jau+ajdfUCBPVH9VMor6aeYq3S9o7HuSNm07QxxxomE=
github.com/invopop/gobl v0.70.2-0.20240327123942-b13cd5993b4f h1:rvwjM7KXyjGZIQY0pFjZOCLXJCdhIRhCD7VY51QKuE4=
github.com/invopop/gobl v0.70.2-0.20240327123942-b13cd5993b4f/go.mod h1:Jau+ajdfUCBPVH9VMor6aeYq3S9o7HuSNm07QxxxomE=
github.com/invopop/gobl v0.71.0 h1:GOlIw0EhJYIXeTUU3Mld0OMaFZbNxGj2V6XugtzHKH0=
github.com/invopop/gobl v0.71.0/go.mod h1:fe+jhOCarDr5AH9z9s2QlJ1o20Atr3mBsKYVcscxzlw=
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/invopop/validation v0.3.0 h1:o260kbjXzoBO/ypXDSSrCLL7SxEFUXBsX09YTE9AxZw=
github.com/invopop/validation v0.3.0/go.mod h1:qIBG6APYLp2Wu3/96p3idYjP8ffTKVmQBfKiZbw0Hts=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
Expand Down Expand Up @@ -109,5 +114,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
98 changes: 59 additions & 39 deletions invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,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"`
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"`
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"`
}

// Annotations defines the XML structure for KSeF annotations
Expand Down Expand Up @@ -77,17 +80,27 @@ func newAnnotations() *Annotations {

// NewInv gets invoice data from GOBL invoice
func NewInv(inv *bill.Invoice) *Inv {
cu := inv.Currency.Def().Units
cu := inv.Currency.Def().Subunits
Inv := &Inv{
Annotations: newAnnotations(),
CurrencyCode: string(inv.Currency),
IssueDate: inv.IssueDate.String(),
SequentialNumber: inv.Series + inv.Code,
SequentialNumber: invoiceNumber(inv.Series, inv.Code),
TotalAmountReceivable: inv.Totals.Payable.Rescale(cu).String(),
Lines: NewLines(inv.Lines),
Payment: NewPayment(inv.Payment, inv.Totals),
}

if len(inv.Preceding) > 0 {
for _, prc := range inv.Preceding {
Inv.CorrectedInv = NewCorrectedInv(prc)
Inv.CorrectionReason = prc.Reason
if prc.Ext.Has(pl.ExtKeyKSeFEffectiveDate) {
Inv.CorrectionType = prc.Ext[pl.ExtKeyKSeFEffectiveDate].Code().String()
}
}
}

ss := inv.ScenarioSummary()
Inv.InvoiceType = ss.Codes[pl.KeyFAVATInvoiceType].String()
if inv.OperationDate != nil {
Expand All @@ -100,13 +113,13 @@ func NewInv(inv *bill.Invoice) *Inv {

for _, rate := range cat.Rates {
if rate.Percent != nil {
if rate.Percent.Amount.Float64() >= 0.15 {
if rate.Key == tax.RateStandard {
Inv.StandardRateNetSale = rate.Base.Rescale(cu).String()
Inv.StandardRateTax = rate.Amount.Rescale(cu).String()
} else if rate.Percent.Amount.Float64() >= 0.06 {
} else if rate.Key == tax.RateReduced {
Inv.ReducedRateNetSale = rate.Base.Rescale(cu).String()
Inv.ReducedRateTax = rate.Amount.Rescale(cu).String()
} else if rate.Percent.Amount.Float64() >= 0.04 {
} else if rate.Key == tax.RateSuperReduced {
Inv.SuperReducedRateNetSale = rate.Base.Rescale(cu).String()
Inv.SuperReducedRateTax = rate.Amount.Rescale(cu).String()
}
Expand All @@ -116,3 +129,10 @@ func NewInv(inv *bill.Invoice) *Inv {

return Inv
}

func invoiceNumber(series string, code string) string {
if series == "" {
return code
}
return series + "-" + code
}
87 changes: 87 additions & 0 deletions invoice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ksef_test

import (
"testing"

ksef "github.com/invopop/gobl.ksef"
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/currency"
"github.com/invopop/gobl/l10n"
"github.com/invopop/gobl/org"
"github.com/invopop/gobl/regimes/pl"
"github.com/invopop/gobl/tax"
"github.com/stretchr/testify/assert"
)

func TestNewInv(t *testing.T) {
t.Run("sets preceding invoice", func(t *testing.T) {
inv := &bill.Invoice{
Currency: currency.PLN,
Supplier: &org.Party{
TaxID: &tax.Identity{
Country: l10n.PL,
},
},
Totals: &bill.Totals{
Taxes: &tax.Total{},
},
Preceding: []*bill.Preceding{
{},
},
}

invoice := ksef.NewInv(inv)

assert.NotNil(t, invoice.CorrectedInv)
})

t.Run("sets correction reason", func(t *testing.T) {
reason := "example reason"

inv := &bill.Invoice{
Currency: currency.PLN,
Supplier: &org.Party{
TaxID: &tax.Identity{
Country: l10n.PL,
},
},
Totals: &bill.Totals{
Taxes: &tax.Total{},
},
Preceding: []*bill.Preceding{
{
Reason: reason,
},
},
}

invoice := ksef.NewInv(inv)

assert.Equal(t, reason, invoice.CorrectionReason)
})

t.Run("sets correction type", func(t *testing.T) {
inv := &bill.Invoice{
Currency: currency.PLN,
Supplier: &org.Party{
TaxID: &tax.Identity{
Country: l10n.PL,
},
},
Totals: &bill.Totals{
Taxes: &tax.Total{},
},
Preceding: []*bill.Preceding{
{
Ext: tax.Extensions{
pl.ExtKeyKSeFEffectiveDate: "1",
},
},
},
}

invoice := ksef.NewInv(inv)

assert.Equal(t, "1", invoice.CorrectionType)
})
}
Loading
Loading