From d488681a27f25e3b554726378b4825518cd59ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Tue, 18 Jun 2024 15:56:38 +0200 Subject: [PATCH] Portugal (#6) Support PT tax regime, with local specific data requirements. --------- Co-authored-by: Sam Lown --- components/bill/invoice/invoice.templ | 2 + components/bill/invoice/invoice_templ.go | 9 +- components/bill/invoice/notes.templ | 36 ++- components/bill/invoice/notes_templ.go | 85 +++++- components/bill/invoice/taxes.templ | 13 + components/bill/invoice/taxes_templ.go | 25 ++ components/footer.templ | 17 +- components/footer_templ.go | 17 +- components/org/party.templ | 17 ++ components/org/party_templ.go | 69 ++++- components/regimes/pt/at.templ | 43 ++++ components/regimes/pt/at_templ.go | 88 +++++++ components/regimes/pt/pt.go | 99 +++++++ components/t/i18n.templ | 14 +- components/t/i18n_templ.go | 14 +- examples/out/invoice-limited-company.html | 5 +- examples/out/invoice-tax-included.html | 5 +- examples/out/pt-invoice-exempt.html | 298 ++++++++++++++++++++++ examples/pt-invoice-exempt.json | 109 ++++++++ go.mod | 2 +- go.sum | 4 + locales/de/app.yml | 3 +- locales/en/app.yml | 5 +- locales/en/countries.yml | 2 +- locales/es/app.yml | 3 +- locales/fr/app.yml | 3 +- locales/gr/app.yml | 3 +- locales/it/app.yml | 3 +- locales/locales.go | 2 +- locales/pl/app.yml | 3 +- locales/pt/app.yml | 134 ++++++++++ locales/pt/countries.yml | 251 ++++++++++++++++++ locales/pt/currencies.yml | 168 ++++++++++++ 33 files changed, 1505 insertions(+), 46 deletions(-) create mode 100644 components/regimes/pt/at.templ create mode 100644 components/regimes/pt/at_templ.go create mode 100644 components/regimes/pt/pt.go create mode 100644 examples/out/pt-invoice-exempt.html create mode 100644 examples/pt-invoice-exempt.json create mode 100644 locales/pt/app.yml create mode 100644 locales/pt/countries.yml create mode 100644 locales/pt/currencies.yml diff --git a/components/bill/invoice/invoice.templ b/components/bill/invoice/invoice.templ index 5c04565..81c26b2 100644 --- a/components/bill/invoice/invoice.templ +++ b/components/bill/invoice/invoice.templ @@ -13,6 +13,7 @@ import ( "github.com/invopop/gobl.html/components/regimes/mx" "github.com/invopop/gobl.html/internal" "github.com/invopop/gobl.html/components/regimes/pl" + "github.com/invopop/gobl.html/components/regimes/pt" ) // Invoice renders a complete GOBL bill.Invoice object. @@ -44,6 +45,7 @@ templ Invoice(env *gobl.Envelope, inv *bill.Invoice) { @co.DIANQR(env, inv) @mx.CFDI(env, inv) @pl.KSeFQR(env) + @pt.ATQR(env) } diff --git a/components/bill/invoice/invoice_templ.go b/components/bill/invoice/invoice_templ.go index 0a9da39..3d478b5 100644 --- a/components/bill/invoice/invoice_templ.go +++ b/components/bill/invoice/invoice_templ.go @@ -18,6 +18,7 @@ import ( "github.com/invopop/gobl.html/components/regimes/es" "github.com/invopop/gobl.html/components/regimes/mx" "github.com/invopop/gobl.html/components/regimes/pl" + "github.com/invopop/gobl.html/components/regimes/pt" "github.com/invopop/gobl.html/components/t" "github.com/invopop/gobl.html/internal" "github.com/invopop/gobl/bill" @@ -117,6 +118,10 @@ func Invoice(env *gobl.Envelope, inv *bill.Invoice) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = pt.ATQR(env).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -182,7 +187,7 @@ func title(inv *bill.Invoice) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(supplierAlias(inv)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/invoice.templ`, Line: 57, Col: 25} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/invoice.templ`, Line: 59, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -208,7 +213,7 @@ func title(inv *bill.Invoice) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(code(inv.Series, inv.Code)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/invoice.templ`, Line: 65, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/invoice.templ`, Line: 67, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/components/bill/invoice/notes.templ b/components/bill/invoice/notes.templ index 238ee9c..7786f90 100644 --- a/components/bill/invoice/notes.templ +++ b/components/bill/invoice/notes.templ @@ -10,6 +10,7 @@ import ( "github.com/invopop/gobl.html/components/t" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/renderer/html" ) var markdown goldmark.Markdown @@ -20,11 +21,15 @@ func init() { goldmark.WithExtensions( extension.NewLinkify(), ), + goldmark.WithRendererOptions( + // NOTE: Please remove this! + html.WithUnsafe(), + ), ) } templ notes(inv *bill.Invoice) { - if len(inv.Notes) > 0 { + if len(inv.Notes) > 0 || inv.Supplier.Registration != nil {
@t.Scope("billing.invoice.notes") {

@@ -47,8 +52,20 @@ templ noteRegSummary(reg *org.Registration) {
@t.Scope(".reg") { - @t.T(".inscription") + @t.T(".registration") + if reg.Capital != nil { + if a := registrationCapital(ctx, reg); a != "" { + + @t.T(".capital", i18n.M{"amount": a}) + + } + } + if reg.Office != "" { + + { reg.Office } + + } if reg.Book != "" { @t.T(".book", i18n.M{"id": reg.Book}) @@ -79,6 +96,11 @@ templ noteRegSummary(reg *org.Registration) { @t.T(".entry", i18n.M{"id": reg.Entry}) } + if reg.Other != "" { + + { reg.Other } + + } }
} @@ -90,3 +112,13 @@ func renderNote(note *cbc.Note) string { } return buf.String() } + +func registrationCapital(ctx context.Context, reg *org.Registration) string { + if reg.Capital != nil { + if reg.Currency != "" { + return t.LocalizeCurrency(ctx, *reg.Capital, reg.Currency) + } + return t.LocalizeMoney(ctx, *reg.Capital) + } + return "" +} diff --git a/components/bill/invoice/notes_templ.go b/components/bill/invoice/notes_templ.go index 80fbd10..2b9a053 100644 --- a/components/bill/invoice/notes_templ.go +++ b/components/bill/invoice/notes_templ.go @@ -20,6 +20,7 @@ import ( "github.com/invopop/gobl/org" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/renderer/html" ) var markdown goldmark.Markdown @@ -30,6 +31,10 @@ func init() { goldmark.WithExtensions( extension.NewLinkify(), ), + goldmark.WithRendererOptions( + // NOTE: Please remove this! + html.WithUnsafe(), + ), ) } @@ -46,7 +51,7 @@ func notes(inv *bill.Invoice) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - if len(inv.Notes) > 0 { + if len(inv.Notes) > 0 || inv.Supplier.Registration != nil { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -141,7 +146,7 @@ func noteRegSummary(reg *org.Registration) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = t.T(".inscription").Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = t.T(".registration").Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -149,6 +154,49 @@ func noteRegSummary(reg *org.Registration) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + if reg.Capital != nil { + if a := registrationCapital(ctx, reg); a != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = t.T(".capital", i18n.M{"amount": a}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if reg.Office != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(reg.Office) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/notes.templ`, Line: 65, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } if reg.Book != "" { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { @@ -253,6 +301,29 @@ func noteRegSummary(reg *org.Registration) templ.Component { return templ_7745c5c3_Err } } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if reg.Other != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(reg.Other) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/notes.templ`, Line: 100, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } if !templ_7745c5c3_IsBuffer { _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) } @@ -280,3 +351,13 @@ func renderNote(note *cbc.Note) string { } return buf.String() } + +func registrationCapital(ctx context.Context, reg *org.Registration) string { + if reg.Capital != nil { + if reg.Currency != "" { + return t.LocalizeCurrency(ctx, *reg.Capital, reg.Currency) + } + return t.LocalizeMoney(ctx, *reg.Capital) + } + return "" +} diff --git a/components/bill/invoice/taxes.templ b/components/bill/invoice/taxes.templ index b32974d..e644d68 100644 --- a/components/bill/invoice/taxes.templ +++ b/components/bill/invoice/taxes.templ @@ -62,6 +62,8 @@ templ taxRateRow(inv *bill.Invoice, cat *tax.CategoryTotal, rate *tax.RateTotal, } else { @t.L(*rate.Percent) } + } else if txt := taxExemptionCode(rate.Ext); txt != "" { + { txt } } else { — } @@ -96,3 +98,14 @@ func taxCategoryTotalName(ctx context.Context, inv *bill.Invoice, cat *tax.Categ category := r.Category(cat.Code) return category.Name.In(t.Lang(ctx)) } + +// taxExemptionCode looks at the exemption reasons and tries to extract the +// first code. We'll see if this is a good idea or not. +func taxExemptionCode(ext tax.Extensions) string { + if len(ext) > 0 { + for _, v := range ext { + return fmt.Sprintf("(%s)", v.String()) + } + } + return "" +} diff --git a/components/bill/invoice/taxes_templ.go b/components/bill/invoice/taxes_templ.go index c43bf11..4ef4605 100644 --- a/components/bill/invoice/taxes_templ.go +++ b/components/bill/invoice/taxes_templ.go @@ -187,6 +187,20 @@ func taxRateRow(inv *bill.Invoice, cat *tax.CategoryTotal, rate *tax.RateTotal, return templ_7745c5c3_Err } } + } else if txt := taxExemptionCode(rate.Ext); txt != "" { + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(txt) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/bill/invoice/taxes.templ`, Line: 65, Col: 9} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } else { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("— ") if templ_7745c5c3_Err != nil { @@ -242,3 +256,14 @@ func taxCategoryTotalName(ctx context.Context, inv *bill.Invoice, cat *tax.Categ category := r.Category(cat.Code) return category.Name.In(t.Lang(ctx)) } + +// taxExemptionCode looks at the exemption reasons and tries to extract the +// first code. We'll see if this is a good idea or not. +func taxExemptionCode(ext tax.Extensions) string { + if len(ext) > 0 { + for _, v := range ext { + return fmt.Sprintf("(%s)", v.String()) + } + } + return "" +} diff --git a/components/footer.templ b/components/footer.templ index d87c76e..24533b3 100644 --- a/components/footer.templ +++ b/components/footer.templ @@ -1,10 +1,13 @@ package components import ( + "strings" + "github.com/invopop/gobl" "github.com/invopop/gobl.html/components/images" "github.com/invopop/ctxi18n/i18n" "github.com/invopop/gobl.html/internal" + "github.com/invopop/gobl.html/components/regimes/pt" ) templ footerPrint(env *gobl.Envelope) { @@ -53,11 +56,15 @@ func pageNumber(ctx context.Context) string { } func footerNotesText(ctx context.Context, env *gobl.Envelope) string { - if opts := internal.Options(ctx); opts.Notes != "" { - return opts.Notes + out := make([]string, 0) + // special case for PT + if txt := pt.FooterNotes(env); txt != "" { + out = append(out, txt) } - if env.Head.Notes != "" { - return env.Head.Notes + if opts := internal.Options(ctx); opts.Notes != "" { + out = append(out, opts.Notes) + } else if env.Head.Notes != "" { + out = append(out, env.Head.Notes) } - return "" + return strings.Join(out, " · ") } diff --git a/components/footer_templ.go b/components/footer_templ.go index f3fb94d..86597c2 100644 --- a/components/footer_templ.go +++ b/components/footer_templ.go @@ -11,9 +11,12 @@ import "io" import "bytes" import ( + "strings" + "github.com/invopop/ctxi18n/i18n" "github.com/invopop/gobl" "github.com/invopop/gobl.html/components/images" + "github.com/invopop/gobl.html/components/regimes/pt" "github.com/invopop/gobl.html/internal" ) @@ -200,11 +203,15 @@ func pageNumber(ctx context.Context) string { } func footerNotesText(ctx context.Context, env *gobl.Envelope) string { - if opts := internal.Options(ctx); opts.Notes != "" { - return opts.Notes + out := make([]string, 0) + // special case for PT + if txt := pt.FooterNotes(env); txt != "" { + out = append(out, txt) } - if env.Head.Notes != "" { - return env.Head.Notes + if opts := internal.Options(ctx); opts.Notes != "" { + out = append(out, opts.Notes) + } else if env.Head.Notes != "" { + out = append(out, env.Head.Notes) } - return "" + return strings.Join(out, " · ") } diff --git a/components/org/party.templ b/components/org/party.templ index 633c9eb..78b96dc 100644 --- a/components/org/party.templ +++ b/components/org/party.templ @@ -30,6 +30,9 @@ templ Party(party *org.Party) { if len(party.Emails) > 0 { @emails(party.Emails) } + if len(party.Websites) > 0 { + @websites(party.Websites) + } if showTaxID(party) { @taxID(party) } @@ -65,6 +68,18 @@ templ emails(emails []*org.Email) { } +templ websites(websites []*org.Website) { + for _, web := range websites { +
+ if web.Label != "" { + @t.T(".website_label", i18n.M{"label": web.Label, "url": web.URL}) + } else { + @t.T(".website", i18n.M{"url": web.URL}) + } +
+ } +} + templ identities(idents []*org.Identity) { for _, ident := range idents {
@@ -123,6 +138,8 @@ func taxIDLabel(ctx context.Context, party *org.Party) string { return "RFC" case l10n.FR: return "TVA" + case l10n.PT: + return "NIF" default: return i18n.T(ctx, ".labels.default") } diff --git a/components/org/party_templ.go b/components/org/party_templ.go index 76ae701..c4918c6 100644 --- a/components/org/party_templ.go +++ b/components/org/party_templ.go @@ -113,6 +113,16 @@ func Party(party *org.Party) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + if len(party.Websites) > 0 { + templ_7745c5c3_Err = websites(party.Websites).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } if showTaxID(party) { templ_7745c5c3_Err = taxID(party).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { @@ -262,7 +272,7 @@ func emails(emails []*org.Email) templ.Component { }) } -func identities(idents []*org.Identity) templ.Component { +func websites(websites []*org.Website) templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -275,6 +285,47 @@ func identities(idents []*org.Identity) templ.Component { templ_7745c5c3_Var8 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + for _, web := range websites { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if web.Label != "" { + templ_7745c5c3_Err = t.T(".website_label", i18n.M{"label": web.Label, "url": web.URL}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = t.T(".website", i18n.M{"url": web.URL}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func identities(idents []*org.Identity) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) for _, ident := range idents { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { @@ -304,9 +355,9 @@ func partyExtensions(party *org.Party) templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent } ctx = templ.ClearChildren(ctx) for _, k := range extensionKeys(party.Ext) { @@ -315,12 +366,12 @@ func partyExtensions(party *org.Party) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(txt) + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(txt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/org/party.templ`, Line: 79, Col: 9} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/org/party.templ`, Line: 94, Col: 9} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -377,6 +428,8 @@ func taxIDLabel(ctx context.Context, party *org.Party) string { return "RFC" case l10n.FR: return "TVA" + case l10n.PT: + return "NIF" default: return i18n.T(ctx, ".labels.default") } diff --git a/components/regimes/pt/at.templ b/components/regimes/pt/at.templ new file mode 100644 index 0000000..fb86426 --- /dev/null +++ b/components/regimes/pt/at.templ @@ -0,0 +1,43 @@ +package pt + +import ( + "github.com/invopop/gobl" +) + +templ ATQR(env *gobl.Envelope) { + if atcud := atATCUD(env); atcud != "" { + if qr := atQR(env); qr != "" { + @generateATQR(atcud, qr) + } + } +} + +templ generateATQR(atcud, qr string) { + +
+
+
+ ATCUD: { atcud } +
+
+ +
+
+
+} diff --git a/components/regimes/pt/at_templ.go b/components/regimes/pt/at_templ.go new file mode 100644 index 0000000..aca725c --- /dev/null +++ b/components/regimes/pt/at_templ.go @@ -0,0 +1,88 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.590 +package pt + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import ( + "github.com/invopop/gobl" +) + +func ATQR(env *gobl.Envelope) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if atcud := atATCUD(env); atcud != "" { + if qr := atQR(env); qr != "" { + templ_7745c5c3_Err = generateATQR(atcud, qr).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func generateATQR(atcud, qr string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
ATCUD: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(atcud) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/regimes/pt/at.templ`, Line: 35, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/components/regimes/pt/pt.go b/components/regimes/pt/pt.go new file mode 100644 index 0000000..41b4c0f --- /dev/null +++ b/components/regimes/pt/pt.go @@ -0,0 +1,99 @@ +// Package pt provides the Portuguese regime specific output. +package pt + +import ( + "bytes" + "encoding/base64" + "fmt" + + "github.com/invopop/gobl" + "github.com/invopop/gobl/regimes/pt" + go_qr "github.com/piglig/go-qr" +) + +// Parameters required by the AT for the QR code generator +const ( + // Minimum version + atQRMinVer = 9 + + // Error correction level + atQRCorLvl = go_qr.Medium +) + +// FooterNotes handles the special case when a document contains special +// notes that need to be added to the footer +func FooterNotes(env *gobl.Envelope) string { + if appID := atAppID(env); appID != "" { + if hash := atHash(env); hash != "" { + return fmt.Sprintf("%s · Processado por programa certificado %s/AT", hash, appID) + } + } + return "" +} + +// generateQR implements a custom QR code generator that complies with the AT spec. +func generateQR(qrval string) string { + // The AT spec requires the QR code to be encoded in binary/bytes mode + seg, err := go_qr.MakeBytes([]byte(qrval)) + if err != nil { + return "" + } + + segs := []*go_qr.QrSegment{seg} + + // Encode according to the AT specs + qr, err := go_qr.EncodeSegments(segs, atQRCorLvl, atQRMinVer, go_qr.MaxVersion, -1, false) + if err != nil { + return "" + } + + conf := go_qr.NewQrCodeImgConfig(10, 4) + + buf := new(bytes.Buffer) + if err := qr.WriteAsSVG(conf, buf, "#FFFFFF", "#000000"); err != nil { + return "" + } + + str := base64.StdEncoding.EncodeToString(buf.Bytes()) + return "data:image/svg+xml;base64," + str +} + +func atAppID(env *gobl.Envelope) string { + for _, stamp := range env.Head.Stamps { + switch stamp.Provider { + case pt.StampProviderATAppID: + return stamp.Value + } + } + return "" +} + +func atHash(env *gobl.Envelope) string { + for _, stamp := range env.Head.Stamps { + switch stamp.Provider { + case pt.StampProviderATHash: + return stamp.Value + } + } + return "" +} + +func atATCUD(env *gobl.Envelope) string { + for _, stamp := range env.Head.Stamps { + switch stamp.Provider { + case pt.StampProviderATATCUD: + return stamp.Value + } + } + return "" +} + +func atQR(env *gobl.Envelope) string { + for _, stamp := range env.Head.Stamps { + switch stamp.Provider { + case pt.StampProviderATQR: + return stamp.Value + } + } + return "" +} diff --git a/components/t/i18n.templ b/components/t/i18n.templ index 30b9cab..ff3fe1f 100644 --- a/components/t/i18n.templ +++ b/components/t/i18n.templ @@ -28,19 +28,19 @@ templ L(a any) { // LM localizes amounts using the currency and formatter defined in the context. templ LM(a num.Amount) { - { localizeMoney(ctx, a) } + { LocalizeMoney(ctx, a) } } // LC localizes and amount using the formatting defined by the specified currency, // ignoring any formatter defined in the context. templ LC(a num.Amount, cur currency.Code) { - { localizeCurrency(ctx, a, cur) } + { LocalizeCurrency(ctx, a, cur) } } // LCD localizes and amount using the formatting defined by the specified currency, // and uses the disembiguate symbol. templ LCD(a num.Amount, cur currency.Code) { - { localizeCurrency(ctx, a, cur, currency.WithDisambiguateSymbol()) } + { LocalizeCurrency(ctx, a, cur, currency.WithDisambiguateSymbol()) } } // Scope helps set a scope around the context @@ -77,12 +77,16 @@ func Localize(ctx context.Context, a any) string { } } -func localizeMoney(ctx context.Context, a num.Amount) string { +// LocalizeMoney will localise a monetary amount using the context's currency +// and formatter. +func LocalizeMoney(ctx context.Context, a num.Amount) string { f := numFormatter(ctx) return f.Amount(a) } -func localizeCurrency(_ context.Context, a num.Amount, cur currency.Code, opts ...currency.FormatOption) string { +// LocalizeCurrency will produce a localized string representation of the given +// amount for the provided currency. +func LocalizeCurrency(_ context.Context, a num.Amount, cur currency.Code, opts ...currency.FormatOption) string { f := cur.Def().Formatter(opts...) return f.Amount(a) } diff --git a/components/t/i18n_templ.go b/components/t/i18n_templ.go index 31d862f..1ae267a 100644 --- a/components/t/i18n_templ.go +++ b/components/t/i18n_templ.go @@ -126,7 +126,7 @@ func LM(a num.Amount) templ.Component { } ctx = templ.ClearChildren(ctx) var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizeMoney(ctx, a)) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(LocalizeMoney(ctx, a)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/t/i18n.templ`, Line: 30, Col: 24} } @@ -157,7 +157,7 @@ func LC(a num.Amount, cur currency.Code) templ.Component { } ctx = templ.ClearChildren(ctx) var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizeCurrency(ctx, a, cur)) + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(LocalizeCurrency(ctx, a, cur)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/t/i18n.templ`, Line: 36, Col: 32} } @@ -188,7 +188,7 @@ func LCD(a num.Amount, cur currency.Code) templ.Component { } ctx = templ.ClearChildren(ctx) var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(localizeCurrency(ctx, a, cur, currency.WithDisambiguateSymbol())) + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(LocalizeCurrency(ctx, a, cur, currency.WithDisambiguateSymbol())) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/t/i18n.templ`, Line: 42, Col: 67} } @@ -257,12 +257,16 @@ func Localize(ctx context.Context, a any) string { } } -func localizeMoney(ctx context.Context, a num.Amount) string { +// LocalizeMoney will localise a monetary amount using the context's currency +// and formatter. +func LocalizeMoney(ctx context.Context, a num.Amount) string { f := numFormatter(ctx) return f.Amount(a) } -func localizeCurrency(_ context.Context, a num.Amount, cur currency.Code, opts ...currency.FormatOption) string { +// LocalizeCurrency will produce a localized string representation of the given +// amount for the provided currency. +func LocalizeCurrency(_ context.Context, a num.Amount, cur currency.Code, opts ...currency.FormatOption) string { f := cur.Def().Formatter(opts...) return f.Amount(a) } diff --git a/examples/out/invoice-limited-company.html b/examples/out/invoice-limited-company.html index e30b84e..eb192cb 100644 --- a/examples/out/invoice-limited-company.html +++ b/examples/out/invoice-limited-company.html @@ -346,7 +346,10 @@

- Inscribed in: + Registration: + + + Registro Mercantil de Madrid Volume 27590 diff --git a/examples/out/invoice-tax-included.html b/examples/out/invoice-tax-included.html index 54fd42b..0be435a 100644 --- a/examples/out/invoice-tax-included.html +++ b/examples/out/invoice-tax-included.html @@ -354,7 +354,10 @@

- Inscribed in: + Registration: + + + Registro Mercantil de Madrid Volume 27590 diff --git a/examples/out/pt-invoice-exempt.html b/examples/out/pt-invoice-exempt.html new file mode 100644 index 0000000..d943e61 --- /dev/null +++ b/examples/out/pt-invoice-exempt.html @@ -0,0 +1,298 @@ + + + + GOBL HTML Generator + + + + + + + + + + +
+
+ + + vVWj + + · Processado por programa certificado 192/AT + + + Page + + 1 + + of + + 1 + + + +
+
+
+
+
+
+
+ IX test +
+
+

+ Invoice +

+

+ FT IXTEST-01/27 +

+
+
+

+ Summary +

+
    +
  • + + Issue Date + + + 2024-06-18 + +
  • +
  • + + Currency + + + Euro (EUR) + +
  • +
+
+
+
+
+

+ Supplier +

+
+
+ IX test +
+
+ + Rua do Hotelzinho, Lisboa, 1000-000 + +
+
+ NIF: (PT) 508025338 +
+
+
+
+

+ Customer +

+
+
+ Maria Santos Silva +
+
+
+
+
+
+

+ Lines +

+ + + + + + + + + + + + + + + + + + + + + +
+ # + + Description + + Qty. + + Price + + VAT + + Total +
+ 1 + + + Noite em quarto duplo + + + 1 + + €100,00 + + — + + €100,00 +
+
+
+
+

+ Totals +

+ + + + + + + + + + + + + + + +
+ Sum + + €100,00 +
+ Tax + + €0,00 +
+ Total to pay + + €100,00 +
+
+
+

+ Taxes +

+ + + + + + + + + + + + + + + + + +
+ Tax + + Base + + Rate + + Amount +
+ VAT + + €100,00 + + (M41) + + €0,00 +
+
+
+
+

+ Notes +

+
+ + Registration: + + + Social Capital €2.000.000,00 + + + CRC Porto sob nº123123123 - SIRPEEE: PT000123 + +
+
+
+ +
+
+
+ ATCUD: AAJFJ3JYDP-27 +
+
+ +
+
+
+
+
+ +
+ + \ No newline at end of file diff --git a/examples/pt-invoice-exempt.json b/examples/pt-invoice-exempt.json new file mode 100644 index 0000000..bcb336f --- /dev/null +++ b/examples/pt-invoice-exempt.json @@ -0,0 +1,109 @@ +{ + "$schema": "https://gobl.org/draft-0/envelope", + "head": { + "uuid": "01902adc-05c8-7000-8703-60c8dcf87bd6", + "dig": { + "alg": "sha256", + "val": "8ae55bfaea91bf68e15511581b0eb5fdef72fbaf84904b4dd9ea75d2bb1643c2" + }, + "stamps": [ + { + "prv": "at-atcud", + "val": "AAJFJ3JYDP-27" + }, + { + "prv": "at-qr", + "val": "A:508025338*B:999999990*C:PT*D:FT*E:N*F:20240618*G:FT IXTEST-01/27*H:AAJFJ3JYDP-27*I1:PT*I2:100.00*N:0.00*O:100.00*Q:vVWj*R:192" + }, + { + "prv": "at-hash", + "val": "vVWj" + }, + { + "prv": "at-app-id", + "val": "192" + } + ] + }, + "doc": { + "$schema": "https://gobl.org/draft-0/bill/invoice", + "uuid": "01902adc-055a-7000-88ea-0dc88769ee3c", + "type": "standard", + "code": "FT IXTEST-01/27", + "issue_date": "2024-06-18", + "currency": "EUR", + "supplier": { + "uuid": "28437140-ce65-481a-a58c-9de1fc8dfedd", + "name": "IX test", + "tax_id": { + "country": "PT", + "code": "508025338" + }, + "addresses": [ + { + "street": "Rua do Hotelzinho", + "locality": "Lisboa", + "code": "1000-000" + } + ], + "registration": { + "capital": "2000000.00", + "currency": "EUR", + "other": "CRC Porto sob nº123123123 - SIRPEEE: PT000123" + } + }, + "customer": { + "name": "Maria Santos Silva" + }, + "lines": [ + { + "i": 1, + "quantity": "1", + "item": { + "name": "Noite em quarto duplo", + "price": "100.00" + }, + "sum": "100.00", + "taxes": [ + { + "cat": "VAT", + "rate": "exempt", + "ext": { + "pt-exemption-code": "M41" + } + } + ], + "total": "100.00" + } + ], + "totals": { + "sum": "100.00", + "total": "100.00", + "taxes": { + "categories": [ + { + "code": "VAT", + "rates": [ + { + "key": "exempt", + "ext": { + "pt-exemption-code": "M41" + }, + "base": "100.00", + "amount": "0.00" + } + ], + "amount": "0.00" + } + ], + "sum": "0.00" + }, + "tax": "0.00", + "total_with_tax": "100.00", + "payable": "100.00" + } + }, + "sigs": [ + "eyJhbGciOiJFUzI1NiIsImtpZCI6IjlkNzkzOGM5LTcxOTUtNDEyMy04NDE5LTY4MjA0NjMwMGVjMyJ9.eyJ1dWlkIjoiMDE5MDJhZGMtMDVjOC03MDAwLTg3MDMtNjBjOGRjZjg3YmQ2IiwiZGlnIjp7ImFsZyI6InNoYTI1NiIsInZhbCI6IjhhZTU1YmZhZWE5MWJmNjhlMTU1MTE1ODFiMGViNWZkZWY3MmZiYWY4NDkwNGI0ZGQ5ZWE3NWQyYmIxNjQzYzIifSwic3RhbXBzIjpbeyJwcnYiOiJhdC1hdGN1ZCIsInZhbCI6IkFBSkZKM0pZRFAtMjcifSx7InBydiI6ImF0LXFyIiwidmFsIjoiQTo1MDgwMjUzMzgqQjo5OTk5OTk5OTAqQzpQVCpEOkZUKkU6TipGOjIwMjQwNjE4Kkc6RlQgSVhURVNULTAxLzI3Kkg6QUFKRkozSllEUC0yNypJMTpQVCpJMjoxMDAuMDAqTjowLjAwKk86MTAwLjAwKlE6dlZXaipSOjE5MiJ9LHsicHJ2IjoiYXQtaGFzaCIsInZhbCI6InZWV2oifSx7InBydiI6ImF0LWFwcC1pZCIsInZhbCI6IjE5MiJ9XX0.kFgvcooSXribSVkgKfR5km2WrpyNVIY2qNQC1J_WxTPXaLavgWPGW0seYyZpcYhv0TA_FmUFUTteD-U-pOksDw" + ] +} diff --git a/go.mod b/go.mod index d741f4a..ac3db78 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/a-h/templ v0.2.598 github.com/go-resty/resty/v2 v2.12.0 github.com/invopop/ctxi18n v0.6.0 - github.com/invopop/gobl v0.78.0 + github.com/invopop/gobl v0.79.3 github.com/invopop/princepdf v0.0.0-20240408123340-585be3cab91a github.com/labstack/echo/v4 v4.11.4 github.com/piglig/go-qr v0.2.4 diff --git a/go.sum b/go.sum index beae2d5..82fd343 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,10 @@ github.com/invopop/gobl v0.77.0 h1:fONqGuP73J3bqDLE+2TI75QfXDWGc9EwJFTtIK2SL8c= github.com/invopop/gobl v0.77.0/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0= github.com/invopop/gobl v0.78.0 h1:V6rbeXYX/wtQYPMQBKQgfWvDlRvyJYRhSmaFTwyE2LE= github.com/invopop/gobl v0.78.0/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0= +github.com/invopop/gobl v0.79.2 h1:Zl1qoJrEcddic8SEr6QrxKszQFyYuLvLt8VD85qdfTw= +github.com/invopop/gobl v0.79.2/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0= +github.com/invopop/gobl v0.79.3 h1:aXNS/COXzWdEhctUL3f0LvygAyEaqxiM20spEmFeVvE= +github.com/invopop/gobl v0.79.3/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/princepdf v0.0.0-20240408123340-585be3cab91a h1:xt18LlIfizLkFgLi+vK/m2SWOsAbQwVwQgbkzxKY0eU= diff --git a/locales/de/app.yml b/locales/de/app.yml index ba253f1..a894e72 100644 --- a/locales/de/app.yml +++ b/locales/de/app.yml @@ -103,13 +103,14 @@ de: notes: title: "Hinweise" reg: + capital: "Sozialkapital %{amount}" + registration: "Registrierung:" book: "Buch %{id}" volume: "Band %{id}" sheet: "Blatt %{id}" section: "Abschnitt %{id}" page: "Seite %{id}" entry: "Eintrag %{id}" - inscription: "Eingetragen in:" regimes: co: diff --git a/locales/en/app.yml b/locales/en/app.yml index fd06ddf..824519b 100644 --- a/locales/en/app.yml +++ b/locales/en/app.yml @@ -106,13 +106,14 @@ en: notes: title: "Notes" reg: + capital: "Social Capital %{amount}" + registration: "Registration:" book: "Book %{id}" volume: "Volume %{id}" sheet: "Sheet %{id}" section: "Section %{id}" page: "Page %{id}" entry: "Entry %{id}" - inscription: "Inscribed in:" regimes: pl: @@ -131,6 +132,8 @@ en: one: "Email: %{addr}" other: "Emails: %{addr}" email_label: "%{addr} (%{label})" + website: "Web: %{url}" + website_label: "%{label}: %{url}" identity: "%{label}: %{code}" identity_code: "Identity code" po_box: "P.O. Box %{po_box}" diff --git a/locales/en/countries.yml b/locales/en/countries.yml index dc9f976..a454117 100644 --- a/locales/en/countries.yml +++ b/locales/en/countries.yml @@ -248,4 +248,4 @@ en: EH: "Western Sahara" YE: "Yemen" ZM: "Zambia" - ZW: "Zimbabwe" \ No newline at end of file + ZW: "Zimbabwe" diff --git a/locales/es/app.yml b/locales/es/app.yml index 5b7faf7..a351865 100644 --- a/locales/es/app.yml +++ b/locales/es/app.yml @@ -105,13 +105,14 @@ es: notes: title: "Notas" reg: + capital: "Capital Social %{amount}" + registration: "Registro:" book: "Libro %{id}" volume: "Volumen %{id}" sheet: "Hoja %{id}" section: "Sección %{id}" page: "Página %{id}" entry: "Entrada %{id}" - inscription: "Inscrito en:" regimes: co: diff --git a/locales/fr/app.yml b/locales/fr/app.yml index bb5ec67..0ee8369 100644 --- a/locales/fr/app.yml +++ b/locales/fr/app.yml @@ -94,13 +94,14 @@ fr: notes: title: "Notes" reg: + capital: "Capital social %{amount}" + registration: "Inscrit au registre:" book: "Livre %{id}" volume: "Volume %{id}" sheet: "Feuille %{id}" section: "Section %{id}" page: "Page %{id}" entry: "Entrée %{id}" - inscription: "Inscrit dans:" regimes: co: cufe: "CUFE: %{cufe}" diff --git a/locales/gr/app.yml b/locales/gr/app.yml index e02c36b..a624872 100644 --- a/locales/gr/app.yml +++ b/locales/gr/app.yml @@ -105,13 +105,14 @@ gr: notes: title: "Σημειώσεις" reg: + registration: "Εγγραφή:" + capital: "Κοινωνικό Κεφάλαιο %{amount}" book: "Βιβλίο %{id}" volume: "Τόμος %{id}" sheet: "Φύλλο %{id}" section: "Ενότητα %{id}" page: "Σελίδα %{id}" entry: "Καταχώρηση %{id}" - inscription: "Εγγεγραμμένο σε:" regimes: co: diff --git a/locales/it/app.yml b/locales/it/app.yml index ad27404..57545b7 100644 --- a/locales/it/app.yml +++ b/locales/it/app.yml @@ -94,13 +94,14 @@ it: notes: title: "Note" reg: + capital: "Capitale sociale %{amount}" + registration: "Registrazione:" book: "Libro %{id}" volume: "Volume %{id}" sheet: "Foglio %{id}" section: "Sezione %{id}" page: "Pagina %{id}" entry: "Voce %{id}" - inscription: "Iscritto in:" regimes: co: cufe: "CUFE: %{cufe}" diff --git a/locales/locales.go b/locales/locales.go index 8330b53..dd12760 100644 --- a/locales/locales.go +++ b/locales/locales.go @@ -3,7 +3,7 @@ package locales import "embed" -//go:embed de en es fr gr it pl +//go:embed de en es fr gr it pl pt // Content is the embedded content for the locales. var Content embed.FS diff --git a/locales/pl/app.yml b/locales/pl/app.yml index ca3fc41..dcc6059 100644 --- a/locales/pl/app.yml +++ b/locales/pl/app.yml @@ -103,13 +103,14 @@ pl: notes: title: "Uwagi" reg: + capital: "Kapitał zakładowy %{amount}" + registration: "Rejestracja:" book: "Księga %{id}" volume: "Tom %{id}" sheet: "Arkusz %{id}" section: "Sekcja %{id}" page: "Strona %{id}" entry: "Wpis %{id}" - inscription: "Zapisane w:" regimes: co: diff --git a/locales/pt/app.yml b/locales/pt/app.yml new file mode 100644 index 0000000..24f4e20 --- /dev/null +++ b/locales/pt/app.yml @@ -0,0 +1,134 @@ +pt: + page: "Página %{page} de %{count}" + + billing: + invoice: + title: + standard: "Fatura" + standard-simplified: "Fatura Simplificada" + proforma: "Fatura Proforma" + corrective: "Fatura Corretiva" + credit-note: "Nota de Crédito" + debit-note: "Nota de Débito" + + summary: + title: "Resumo" + issue_date: "Data de Emissão" + value_date: "Data de Valor" + operation_date: "Data de Operação" + currency: "Moeda" + currency_value: "%{desc} (%{code})" + preceding_invoice: "Fatura Anterior" + order: "Ordem" + order_period: "Período" + order_period_label: "%{label}" + order_period_range: "%{start} a %{end}" + + supplier: + title: "Fornecedor" + + customer: + title: "Cliente" + + lines: + title: "Linhas" + i: "#" + ref: "Ref." + description: "Descrição" + quantity: "Qtd." + unit: "Unidade" + price: "Preço" + discount: "Desc." + charges: "Encargos" + total: "Total" + + totals: + sum: "Soma" + discount: "Desconto" + charge: "Encargo" + title: "Totais" + prices_include: "%{tax} incluído" + total: "Total" + taxes: "Imposto" + total_with_tax: "Total com imposto" + total_to_pay: "Total a pagar" + outlay: "Despesa %{i}: %{txt}" + advance: "Adiantamento: %{date} %{txt}" + advance_sum: "Soma dos adiantamentos" + due_sum: "Total devido" + exchange_rate: "Câmbio para %{to} (%{amount})" + + taxes: + title: "Impostos" + category: "Imposto" + base: "Base" + rate: "Taxa" + amount: "Quantia" + + payment: + title: "Pagamento" + instructions: + method: "Método de pagamento" + ref: "Referência" + bank_data: "Instruções de transferência bancária" + card: "Pagamento com Cartão" + card_detail: "Pagamento efetuado com cartão terminando em %{last4} pertencente a %{holder}" + observations: "Notas de Pagamento" + bank_name: "Banco: %{name}" + iban: "IBAN: %{iban}" + account_number: "Número da conta: %{num}" + bic: "BIC: %{bic}" + online: "Online" + methods: + card: "Cartão bancário" + credit_transfer: "Transferência bancária" + cash: "Dinheiro" + direct_debit: "Débito direto" + online: "Online" + terms: + key: "Condições de Pagamento" + notes: "Notas das Condições de Pagamento" + due_dates: "Datas de vencimento" + keys: + na: "Não especificado" + end_of_month: "Fim do mês" + due_date: "Na data de vencimento" + deferred: "Após a data de vencimento" + proximo: "Próximo mês" + instant: "Na entrega da fatura" + elective: "Escolhido pelo cliente" + pending: "A determinar pelo fornecedor" + advanced: "Pago" + delivery: "Na entrega" + + notes: + title: "Notas" + reg: + capital: "Capital Social %{amount}" + regisration: "Registada:" + book: "Livro %{id}" + volume: "Volume %{id}" + sheet: "Folha %{id}" + section: "Seção %{id}" + page: "Página %{id}" + entry: "Entrada %{id}" + + organizing: + party: + tax_id: "%{label}: (%{country}) %{code}" + tel: "Tel: %{num}" + tel_label: "Tel: %{num} (%{label})" + email: + one: "Email: %{addr}" + other: "Emails: %{addr}" + email_label: "%{addr} (%{label})" + identity: "%{label}: %{code}" + identity_code: "Código de Identidade" + po_box: "Caixa Postal %{po_box}" + labels: + default: "Código Fiscal" + ext: "%{label}: %{value}" + + address: + label: "%{label}:" + country: "(%{country})" diff --git a/locales/pt/countries.yml b/locales/pt/countries.yml new file mode 100644 index 0000000..30f3408 --- /dev/null +++ b/locales/pt/countries.yml @@ -0,0 +1,251 @@ +pt: + country_names: + AF: "Afeganistão" + AX: "Ilhas Åland" + AL: "Albânia" + DZ: "Argélia" + AS: "Samoa Americana" + AD: "Andorra" + AO: "Angola" + AI: "Anguila" + AQ: "Antártica" + AG: "Antígua e Barbuda" + AR: "Argentina" + AM: "Armênia" + AW: "Aruba" + AU: "Austrália" + AT: "Áustria" + AZ: "Azerbaijão" + BS: "Bahamas" + BH: "Bahrein" + BD: "Bangladesh" + BB: "Barbados" + BY: "Bielorrússia" + BE: "Bélgica" + BZ: "Belize" + BJ: "Benin" + BM: "Bermudas" + BT: "Butão" + BO: "Bolívia, Estado Plurinacional da" + BQ: "Bonaire, Santo Eustáquio e Saba" + BA: "Bósnia e Herzegovina" + BW: "Botsuana" + BV: "Ilha Bouvet" + BR: "Brasil" + IO: "Território Britânico do Oceano Índico" + BN: "Brunei Darussalam" + BG: "Bulgária" + BF: "Burkina Faso" + BI: "Burundi" + CV: "Cabo Verde" + KH: "Camboja" + CM: "Camarões" + CA: "Canadá" + KY: "Ilhas Cayman" + CF: "República Centro-Africana" + TD: "Chade" + CL: "Chile" + CN: "China" + CX: "Ilha Christmas" + CC: "Ilhas Cocos (Keeling)" + CO: "Colômbia" + KM: "Comores" + CG: "Congo" + CD: "Congo, República Democrática do" + CK: "Ilhas Cook" + CR: "Costa Rica" + CI: "Costa do Marfim" + HR: "Croácia" + CU: "Cuba" + CW: "Curaçao" + CY: "Chipre" + CZ: "Tchéquia" + DK: "Dinamarca" + DJ: "Djibuti" + DM: "Dominica" + DO: "República Dominicana" + EC: "Equador" + EG: "Egito" + SV: "El Salvador" + GQ: "Guiné Equatorial" + ER: "Eritreia" + EE: "Estônia" + SZ: "Eswatini" + ET: "Etiópia" + FK: "Ilhas Falkland" + FO: "Ilhas Faroe" + FJ: "Fiji" + FI: "Finlândia" + FR: "França" + GF: "Guiana Francesa" + PF: "Polinésia Francesa" + TF: "Terras Austrais Francesas" + GA: "Gabão" + GM: "Gâmbia" + GE: "Geórgia" + DE: "Alemanha" + GH: "Gana" + GI: "Gibraltar" + GR: "Grécia" + GL: "Groenlândia" + GD: "Granada" + GP: "Guadalupe" + GU: "Guam" + GT: "Guatemala" + GG: "Guernsey" + GN: "Guiné" + GW: "Guiné-Bissau" + GY: "Guiana" + HT: "Haiti" + HM: "Ilhas Heard e McDonald" + VA: "Santa Sé (o)" + HN: "Honduras" + HK: "Hong Kong" + HU: "Hungria" + IS: "Islândia" + IN: "Índia" + ID: "Indonésia" + IR: "Irã, República Islâmica do" + IQ: "Iraque" + IE: "Irlanda" + IM: "Ilha de Man" + IL: "Israel" + IT: "Itália" + JM: "Jamaica" + JP: "Japão" + JE: "Jersey" + JO: "Jordânia" + KZ: "Cazaquistão" + KE: "Quênia" + KI: "Quiribati" + KP: "Coreia, República Popular Democrática da" + KR: "Coreia, República da" + KW: "Kuwait" + KG: "Quirguistão" + LA: "República Democrática Popular do Laos" + LV: "Letônia" + LB: "Líbano" + LS: "Lesoto" + LR: "Libéria" + LY: "Líbia" + LI: "Liechtenstein" + LT: "Lituânia" + LU: "Luxemburgo" + MO: "Macau" + MK: "Macedônia do Norte" + MG: "Madagáscar" + MW: "Malawi" + MY: "Malásia" + MV: "Maldivas" + ML: "Mali" + MT: "Malta" + MH: "Ilhas Marshall" + MQ: "Martinica" + MR: "Mauritânia" + MU: "Maurícia" + YT: "Mayotte" + MX: "México" + FM: "Micronésia, Estados Federados da" + MD: "Moldávia, República da" + MC: "Mônaco" + MN: "Mongólia" + ME: "Montenegro" + MS: "Montserrat" + MA: "Marrocos" + MZ: "Moçambique" + MM: "Mianmar" + NA: "Namíbia" + NR: "Nauru" + NP: "Nepal" + NL: "Países Baixos" + NC: "Nova Caledônia" + NZ: "Nova Zelândia" + NI: "Nicarágua" + NE: "Níger" + NG: "Nigéria" + NU: "Niue" + NF: "Ilha Norfolk" + MP: "Ilhas Marianas do Norte" + NO: "Noruega" + OM: "Omã" + PK: "Paquistão" + PW: "Palau" + PS: "Palestina, Estado da" + PA: "Panamá" + PG: "Papua Nova Guiné" + PY: "Paraguai" + PE: "Peru" + PH: "Filipinas" + PN: "Pitcairn" + PL: "Polônia" + PT: "Portugal" + PR: "Porto Rico" + QA: "Catar" + RE: "Reunião" + RO: "Romênia" + RU: "Federação Russa" + RW: "Ruanda" + BL: "São Bartolomeu" + SH: "Santa Helena, Ascensão e Tristão da Cunha" + KN: "São Cristóvão e Neves" + LC: "Santa Lúcia" + MF: "São Martinho (parte francesa)" + PM: "Saint Pierre e Miquelon" + VC: "São Vicente e Granadinas" + WS: "Samoa" + SM: "San Marino" + ST: "São Tomé e Príncipe" + SA: "Arábia Saudita" + SN: "Senegal" + RS: "Sérvia" + SC: "Seicheles" + SL: "Serra Leoa" + SG: "Singapura" + SX: "Sint Maarten (parte holandesa)" + SK: "Eslováquia" + SI: "Eslovênia" + SB: "Ilhas Salomão" + SO: "Somália" + ZA: "África do Sul" + GS: "Geórgia do Sul e Ilhas Sandwich do Sul" + SS: "Sudão do Sul" + ES: "Espanha" + LK: "Sri Lanka" + SD: "Sudão" + SR: "Suriname" + SJ: "Svalbard e Jan Mayen" + SE: "Suécia" + CH: "Suíça" + SY: "República Árabe da Síria" + TW: "Taiwan (Província da China)" + TJ: "Tajiquistão" + TZ: "Tanzânia, República Unida da" + TH: "Tailândia" + TL: "Timor-Leste" + TG: "Togo" + TK: "Toquelau" + TO: "Tonga" + TT: "Trinidad e Tobago" + TN: "Tunísia" + TR: "Turquia" + TM: "Turquemenistão" + TC: "Ilhas Turcas e Caicos" + TV: "Tuvalu" + UG: "Uganda" + UA: "Ucrânia" + AE: "Emirados Árabes Unidos" + GB: "Reino Unido" + US: "Estados Unidos da América" + UM: "Ilhas Menores Distantes dos Estados Unidos" + UY: "Uruguai" + UZ: "quistão" + VU: "Vanuatu" + VE: "Venezuela, República Bolivariana da" + VN: "Vietnã" + VG: "Ilhas Virgens (Britânicas)" + VI: "Ilhas Virgens (EUA)" + WF: "Wallis e Futuna" + EH: "Saara Ocidental" + YE: "Iémen" + ZM: "Zâmbia" + ZW: "Zimbábue" diff --git a/locales/pt/currencies.yml b/locales/pt/currencies.yml new file mode 100644 index 0000000..c76a055 --- /dev/null +++ b/locales/pt/currencies.yml @@ -0,0 +1,168 @@ +pt: + currencies: + AED: Dirham dos Emirados Árabes Unidos + AFN: Afegane + ALL: Lek + AMD: Dram Armênio + ANG: Florim das Antilhas Holandesas + AOA: Kwanza + ARS: Peso Argentino + AUD: Dólar Australiano + AWG: Florim Arubano + AZN: Manat Azeri + BAM: Marco Conversível + BBD: Dólar de Barbados + BDT: Taka + BGN: Lev Búlgaro + BHD: Dinar Bareinita + BIF: Franco Burundês + BMD: Dólar das Bermudas + BND: Dólar de Brunei + BOB: Boliviano + BOV: Mvdol + BRL: Real Brasileiro + BSD: Dólar Bahamense + BTN: Ngultrum + BWP: Pula + BYN: Rublo Bielorrusso + BZD: Dólar Belizeño + CAD: Dólar Canadense + CDF: Franco Congolês + CHE: Euro WIR + CHF: Franco Suíço + CHW: Franco WIR + CLF: Unidade de Fomento + CLP: Peso Chileno + CNY: Yuan Chinês + COP: Peso Colombiano + COU: Unidade de Valor Real + CRC: Colón Costarriquenho + CUC: Peso Cubano Conversível + CUP: Peso Cubano + CVE: Escudo Cabo-Verdiano + CZK: Coroa Checa + DJF: Franco Djibutiano + DKK: Coroa Dinamarquesa + DOP: Peso Dominicano + DZD: Dinar Argelino + EGP: Libra Egípcia + ERN: Nakfa + ETB: Birr Etíope + EUR: Euro + FJD: Dólar Fijiano + FKP: Libra das Ilhas Falkland + GBP: Libra Esterlina + GEL: Lari Georgiano + GHS: Cedi Ganês + GIP: Libra de Gibraltar + GMD: Dalasi + GNF: Franco Guineano + GTQ: Quetzal + GYD: Dólar Guianense + HKD: Dólar de Hong Kong + HNL: Lempira + HRK: Kuna Croata + HTG: Gourde + HUF: Forint + IDR: Rupia Indonésia + ILS: Novo Shekel Israelense + INR: Rúpia Indiana + IQD: Dinar Iraquiano + IRR: Rial Iraniano + ISK: Coroa Islandesa + JMD: Dólar Jamaicano + JOD: Dinar Jordaniano + JPY: Iene + KES: Xelim Queniano + KGS: Som Quirguiz + KHR: Riel + KMF: Franco Comoriano + KPW: Won Norte-Coreano + KRW: Won Sul-Coreano + KWD: Dinar Kuwaitiano + KYD: Dólar das Ilhas Cayman + KZT: Tenge + LAK: Kip + LBP: Libra Libanesa + LKR: Rúpia de Sri Lanka + LRD: Dólar Liberiano + LSL: Loti + LYD: Dinar Líbio + MAD: Dirham Marroquino + MDL: Leu Moldavo + MGA: Ariary Malgaxe + MKD: Denar + MMK: Kyat + MNT: Tugrik + MOP: Pataca + MRU: Ouguiya + MUR: Rúpia Mauriciana + MVR: Rufiyaa + MWK: Kwacha Malauiano + MXN: Peso Mexicano + MXV: Unidade de Investimento (UDI) Mexicana + MYR: Ringgit Malaio + MZN: Metical Moçambicano + NAD: Dólar Namibiano + NGN: Naira + NIO: Córdoba + NOK: Coroa Norueguesa + NPR: Rúpia Nepalesa + NZD: Dólar Neozelandês + OMR: Rial Omanense + PAB: Balboa + PEN: Sol + PGK: Kina + PHP: Peso Filipino + PKR: Rúpia Paquistanesa + PLN: Złoty + PYG: Guarani + QAR: Rial Catariano + RON: Leu Romeno + RSD: Dinar Sérvio + RUB: Rublo Russo + RWF: Franco Ruandês + SAR: Rial Saudita + SBD: Dólar das Ilhas Salomão + SCR: Rúpia de Seicheles + SDG: Libra Sudanesa + SEK: Coroa Sueca + SGD: Dólar de Singapura + SHP: Libra de Santa Helena + SLL: Leone + SOS: Xelim Somali + SRD: Dólar Surinamês + SSP: Libra Sul-Sudanesa + STN: Dobra + SVC: Colón Salvadoreño + SYP: Libra Síria + SZL: Lilangeni + THB: Baht + TJS: Somoni + TMT: Manat Turcomeno + TND: Dinar Tunisiano + TOP: Paʻanga + TRY: Lira Turca + TTD: Dólar de Trinidad e Tobago + TWD: Novo Dólar Taiwanês + TZS: Xelim Tanzaniano + UAH: Hryvnia + UGX: Xelim Ugandense + USD: Dólar Americano + USN: Dólar Americano (Próximo Dia) + UYI: Peso Uruguaio em Unidades Indexadas + UYU: Peso Uruguaio + UYW: Unidade Nominal de Salário Uruguaia + UZS: Som Uzbeque + VES: Bolívar Venezuelano + VND: Dong Vietnamita + VUV: Vatu + WST: Tala + XAF: Franco CFA BEAC + XCD: Dólar do Caribe Oriental + XOF: Franco CFA BCEAO + XPF: Franco CFP + YER: Rial Iemenita + ZAR: Rand Sul-Africano + ZMW: Kwacha Zambiano + ZWL: Dólar Zimbabuano