Introduce the concept of tax class

We want to show the percentage of the tax as columns in the invoice,
but until now it was not possible to have a single VAT column when
products have different VAT (e.g., 4 % and 10 %), because, as far
as the application is concerned, these where ”different taxes”.  We
also think it would be hard later on to compute the tax due to the
government.

So, tax classes is just a taxonomy to be able to have different names
and rates for the same type of tax, mostly VAT and retention in our
case.
This commit is contained in:
jordi fita mas 2023-02-28 12:02:27 +01:00
parent 0d4fb124b4
commit 11d51df7fa
21 changed files with 443 additions and 144 deletions

View File

@ -17,12 +17,18 @@ values (1, 1)
, (1, 2) , (1, 2)
; ;
alter sequence tax_class_tax_class_id_seq restart;
insert into tax_class (company_id, name)
values (1, 'IRPF')
, (1, 'IVA')
;
alter sequence tax_tax_id_seq restart; alter sequence tax_tax_id_seq restart;
insert into tax (company_id, name, rate) insert into tax (company_id, tax_class_id, name, rate)
values (1, 'Retenció 15 %', -0.15) values (1, 1, 'Retenció 15 %', -0.15)
, (1, 'IVA 21 %', 0.21) , (1, 2, 'IVA 21 %', 0.21)
, (1, 'IVA 10 %', 0.10) , (1, 2, 'IVA 10 %', 0.10)
, (1, 'IVA 4 %', 0.04) , (1, 2, 'IVA 4 %', 0.04)
; ;
alter sequence contact_contact_id_seq restart; alter sequence contact_contact_id_seq restart;

View File

@ -2,6 +2,7 @@
-- requires: schema_numerus -- requires: schema_numerus
-- requires: company -- requires: company
-- requires: tax_rate -- requires: tax_rate
-- requires: tax_class
begin; begin;
@ -10,6 +11,7 @@ set search_path to numerus, public;
create table tax ( create table tax (
tax_id serial primary key, tax_id serial primary key,
company_id integer not null references company, company_id integer not null references company,
tax_class_id integer not null references tax_class,
name text not null constraint name_not_empty check(length(trim(name)) > 0), name text not null constraint name_not_empty check(length(trim(name)) > 0),
rate tax_rate not null rate tax_rate not null
); );

34
deploy/tax_class.sql Normal file
View File

@ -0,0 +1,34 @@
-- Deploy numerus:tax_class to pg
-- requires: schema_numerus
-- requires: company
begin;
set search_path to numerus, public;
create table tax_class (
tax_class_id serial not null primary key,
company_id integer not null references company,
name text not null constraint name_not_empty check(length(trim(name)) > 0)
);
grant select, insert, update, delete on table tax_class to invoicer;
grant select, insert, update, delete on table tax_class to admin;
grant usage on sequence tax_class_tax_class_id_seq to invoicer;
grant usage on sequence tax_class_tax_class_id_seq to admin;
alter table tax_class enable row level security;
create policy company_policy
on tax_class
using (
exists(
select 1
from company_user
join user_profile using (user_id)
where company_user.company_id = tax_class.company_id
)
);
commit;

View File

@ -71,9 +71,10 @@ type CountryOption struct {
} }
type Tax struct { type Tax struct {
Id int Id int
Name string Name string
Rate int Class string
Rate int
} }
type taxDetailsForm struct { type taxDetailsForm struct {
@ -161,10 +162,9 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
} }
func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) { func mustRenderTaxDetailsForm(w http.ResponseWriter, r *http.Request, form *taxDetailsForm) {
locale := getLocale(r)
page := &TaxDetailsPage{ page := &TaxDetailsPage{
DetailsForm: form, DetailsForm: form,
NewTaxForm: newTaxForm(locale), NewTaxForm: newTaxForm(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)),
} }
mustRenderTexDetailsPage(w, r, page) mustRenderTexDetailsPage(w, r, page)
} }
@ -193,7 +193,7 @@ func mustGetCompany(r *http.Request) *Company {
} }
func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax { func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
rows, err := conn.Query(ctx, "select tax_id, name, (rate * 100)::integer from tax where company_id = $1 order by rate, name", company.Id) rows, err := conn.Query(ctx, "select tax_id, tax.name, tax_class.name, (rate * 100)::integer from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by rate, tax.name", company.Id)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -202,7 +202,7 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
var taxes []*Tax var taxes []*Tax
for rows.Next() { for rows.Next() {
tax := &Tax{} tax := &Tax{}
err = rows.Scan(&tax.Id, &tax.Name, &tax.Rate) err = rows.Scan(&tax.Id, &tax.Name, &tax.Class, &tax.Rate)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -218,10 +218,11 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
type taxForm struct { type taxForm struct {
locale *Locale locale *Locale
Name *InputField Name *InputField
Class *SelectField
Rate *InputField Rate *InputField
} }
func newTaxForm(locale *Locale) *taxForm { func newTaxForm(ctx context.Context, conn *Conn, company *Company, locale *Locale) *taxForm {
return &taxForm{ return &taxForm{
locale: locale, locale: locale,
Name: &InputField{ Name: &InputField{
@ -230,6 +231,13 @@ func newTaxForm(locale *Locale) *taxForm {
Type: "text", Type: "text",
Required: true, Required: true,
}, },
Class: &SelectField{
Name: "tax_class",
Label: pgettext("input", "Tax Class", locale),
Options: MustGetOptions(ctx, conn, "select tax_class_id::text, name from tax_class where company_id = $1 order by name", company.Id),
Required: true,
EmptyLabel: gettext("Select a tax class", locale),
},
Rate: &InputField{ Rate: &InputField{
Name: "tax_rate", Name: "tax_rate",
Label: pgettext("input", "Rate (%)", locale), Label: pgettext("input", "Rate (%)", locale),
@ -248,6 +256,7 @@ func (form *taxForm) Parse(r *http.Request) error {
return err return err
} }
form.Name.FillValue(r) form.Name.FillValue(r)
form.Class.FillValue(r)
form.Rate.FillValue(r) form.Rate.FillValue(r)
return nil return nil
} }
@ -255,6 +264,7 @@ func (form *taxForm) Parse(r *http.Request) error {
func (form *taxForm) Validate() bool { func (form *taxForm) Validate() bool {
validator := newFormValidator() validator := newFormValidator()
validator.CheckRequiredInput(form.Name, gettext("Tax name can not be empty.", form.locale)) validator.CheckRequiredInput(form.Name, gettext("Tax name can not be empty.", form.locale))
validator.CheckValidSelectOption(form.Class, gettext("Selected tax class is not valid.", form.locale))
if validator.CheckRequiredInput(form.Rate, gettext("Tax rate can not be empty.", form.locale)) { if validator.CheckRequiredInput(form.Rate, gettext("Tax rate can not be empty.", form.locale)) {
validator.CheckValidInteger(form.Rate, -99, 99, gettext("Tax rate must be an integer between -99 and 99.", form.locale)) validator.CheckValidInteger(form.Rate, -99, 99, gettext("Tax rate must be an integer between -99 and 99.", form.locale))
} }
@ -278,7 +288,9 @@ func HandleDeleteCompanyTax(w http.ResponseWriter, r *http.Request, params httpr
func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r) locale := getLocale(r)
form := newTaxForm(locale) conn := getConn(r)
company := mustGetCompany(r)
form := newTaxForm(r.Context(), conn, company, locale)
if err := form.Parse(r); err != nil { if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
@ -292,8 +304,6 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
mustRenderTaxForm(w, r, form) mustRenderTaxForm(w, r, form)
return return
} }
conn := getConn(r) conn.MustExec(r.Context(), "insert into tax (company_id, tax_class_id, name, rate) values ($1, $2, $3, $4 / 100::decimal)", company.Id, form.Class, form.Name, form.Rate.Integer())
company := mustGetCompany(r)
conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, form.Name, form.Rate.Integer())
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther) http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
} }

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-26 17:11+0100\n" "POT-Creation-Date: 2023-02-28 11:56+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -69,7 +69,7 @@ msgstr "Preu"
msgid "No products added yet." msgid "No products added yet."
msgstr "No hi ha cap producte." msgstr "No hi ha cap producte."
#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:59 #: web/template/invoices/products.gohtml:65 web/template/invoices/new.gohtml:60
msgctxt "action" msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Afegeix productes" msgstr "Afegeix productes"
@ -85,12 +85,12 @@ msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: web/template/invoices/new.gohtml:61 #: web/template/invoices/new.gohtml:63
msgctxt "action" msgctxt "action"
msgid "Update" msgid "Update"
msgstr "Actualitza" msgstr "Actualitza"
#: web/template/invoices/new.gohtml:63 web/template/invoices/index.gohtml:13 #: web/template/invoices/new.gohtml:65 web/template/invoices/index.gohtml:13
msgctxt "action" msgctxt "action"
msgid "New invoice" msgid "New invoice"
msgstr "Nova factura" msgstr "Nova factura"
@ -276,7 +276,7 @@ msgctxt "title"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:96 #: web/template/profile.gohtml:35 web/template/tax-details.gohtml:101
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Desa canvis" msgstr "Desa canvis"
@ -302,16 +302,21 @@ msgctxt "title"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Percentatge" msgstr "Percentatge"
#: web/template/tax-details.gohtml:71 #: web/template/tax-details.gohtml:49
msgctxt "title"
msgid "Class"
msgstr "Classe"
#: web/template/tax-details.gohtml:73
msgid "No taxes added yet." msgid "No taxes added yet."
msgstr "No hi ha cap impost." msgstr "No hi ha cap impost."
#: web/template/tax-details.gohtml:77 #: web/template/tax-details.gohtml:79
msgctxt "title" msgctxt "title"
msgid "New Line" msgid "New Line"
msgstr "Nova línia" msgstr "Nova línia"
#: web/template/tax-details.gohtml:88 #: web/template/tax-details.gohtml:93
msgctxt "action" msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Afegeix nou impost" msgstr "Afegeix nou impost"
@ -369,71 +374,84 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:435 #: pkg/products.go:165 pkg/invoices.go:446
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:440 #: pkg/products.go:171 pkg/invoices.go:451
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:444 #: pkg/products.go:176 pkg/invoices.go:455
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Preu" msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:350 pkg/invoices.go:470 #: pkg/products.go:186 pkg/invoices.go:344 pkg/invoices.go:481
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Imposts" msgstr "Imposts"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:383 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:377
#: pkg/invoices.go:506 #: pkg/invoices.go:517
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc." msgstr "No podeu deixar el nom en blanc."
#: pkg/products.go:207 pkg/invoices.go:507 #: pkg/products.go:207 pkg/invoices.go:518
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc." msgstr "No podeu deixar el preu en blanc."
#: pkg/products.go:208 pkg/invoices.go:508 #: pkg/products.go:208 pkg/invoices.go:519
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El preu ha de ser un número major a zero." msgstr "El preu ha de ser un número major a zero."
#: pkg/products.go:210 pkg/invoices.go:387 pkg/invoices.go:516 #: pkg/products.go:210 pkg/invoices.go:381 pkg/invoices.go:527
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid." msgstr "Heu seleccionat un impost que no és vàlid."
#: pkg/company.go:89 #: pkg/company.go:90
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: pkg/company.go:107 #: pkg/company.go:108
msgid "Selected currency is not valid." msgid "Selected currency is not valid."
msgstr "Heu seleccionat una moneda que no és vàlida." msgstr "Heu seleccionat una moneda que no és vàlida."
#: pkg/company.go:229 #: pkg/company.go:230
msgctxt "input" msgctxt "input"
msgid "Tax name" msgid "Tax name"
msgstr "Nom impost" msgstr "Nom impost"
#: pkg/company.go:235 #: pkg/company.go:236
msgctxt "input"
msgid "Tax Class"
msgstr "Classe dimpost"
#: pkg/company.go:239
msgid "Select a tax class"
msgstr "Escolliu una classe dimpost"
#: pkg/company.go:243
msgctxt "input" msgctxt "input"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Percentatge" msgstr "Percentatge"
#: pkg/company.go:257 #: pkg/company.go:266
msgid "Tax name can not be empty." msgid "Tax name can not be empty."
msgstr "No podeu deixar el nom de limpost en blanc." msgstr "No podeu deixar el nom de limpost en blanc."
#: pkg/company.go:258 #: pkg/company.go:267
msgid "Selected tax class is not valid."
msgstr "Heu seleccionat una classe dimpost que no és vàlida."
#: pkg/company.go:268
msgid "Tax rate can not be empty." msgid "Tax rate can not be empty."
msgstr "No podeu deixar percentatge en blanc." msgstr "No podeu deixar percentatge en blanc."
#: pkg/company.go:259 #: pkg/company.go:269
msgid "Tax rate must be an integer between -99 and 99." msgid "Tax rate must be an integer between -99 and 99."
msgstr "El percentatge ha de ser entre -99 i 99." msgstr "El percentatge ha de ser entre -99 i 99."
@ -465,70 +483,70 @@ msgstr "La confirmació no és igual a la contrasenya."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Heu seleccionat un idioma que no és vàlid." msgstr "Heu seleccionat un idioma que no és vàlid."
#: pkg/invoices.go:201 #: pkg/invoices.go:207
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar." msgstr "Escolliu un client a facturar."
#: pkg/invoices.go:276 #: pkg/invoices.go:300
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida." msgstr "Acció invàlida."
#: pkg/invoices.go:327 #: pkg/invoices.go:321
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
#: pkg/invoices.go:333 #: pkg/invoices.go:327
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:339 #: pkg/invoices.go:333
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Data de factura" msgstr "Data de factura"
#: pkg/invoices.go:345 #: pkg/invoices.go:339
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notes" msgstr "Notes"
#: pkg/invoices.go:384 #: pkg/invoices.go:378
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de la factura en blanc." msgstr "No podeu deixar la data de la factura en blanc."
#: pkg/invoices.go:385 #: pkg/invoices.go:379
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La data de facturació ha de ser vàlida." msgstr "La data de facturació ha de ser vàlida."
#: pkg/invoices.go:430 #: pkg/invoices.go:441
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:453 #: pkg/invoices.go:464
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Quantitat" msgstr "Quantitat"
#: pkg/invoices.go:461 #: pkg/invoices.go:472
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descompte (%)" msgstr "Descompte (%)"
#: pkg/invoices.go:510 #: pkg/invoices.go:521
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc." msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoices.go:511 #: pkg/invoices.go:522
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La quantitat ha de ser un número major a zero." msgstr "La quantitat ha de ser un número major a zero."
#: pkg/invoices.go:513 #: pkg/invoices.go:524
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc." msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoices.go:514 #: pkg/invoices.go:525
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descompte ha de ser un percentatge entre 0 i 100." msgstr "El descompte ha de ser un percentatge entre 0 i 100."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-02-26 17:11+0100\n" "POT-Creation-Date: 2023-02-28 11:56+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -69,7 +69,7 @@ msgstr "Precio"
msgid "No products added yet." msgid "No products added yet."
msgstr "No hay productos." msgstr "No hay productos."
#: web/template/invoices/products.gohtml:64 web/template/invoices/new.gohtml:59 #: web/template/invoices/products.gohtml:65 web/template/invoices/new.gohtml:60
msgctxt "action" msgctxt "action"
msgid "Add products" msgid "Add products"
msgstr "Añadir productos" msgstr "Añadir productos"
@ -85,12 +85,12 @@ msgctxt "title"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: web/template/invoices/new.gohtml:61 #: web/template/invoices/new.gohtml:63
msgctxt "action" msgctxt "action"
msgid "Update" msgid "Update"
msgstr "Actualizar" msgstr "Actualizar"
#: web/template/invoices/new.gohtml:63 web/template/invoices/index.gohtml:13 #: web/template/invoices/new.gohtml:65 web/template/invoices/index.gohtml:13
msgctxt "action" msgctxt "action"
msgid "New invoice" msgid "New invoice"
msgstr "Nueva factura" msgstr "Nueva factura"
@ -276,7 +276,7 @@ msgctxt "title"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.gohtml:35 web/template/tax-details.gohtml:96 #: web/template/profile.gohtml:35 web/template/tax-details.gohtml:101
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar cambios" msgstr "Guardar cambios"
@ -302,16 +302,21 @@ msgctxt "title"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Porcentaje" msgstr "Porcentaje"
#: web/template/tax-details.gohtml:71 #: web/template/tax-details.gohtml:49
msgctxt "title"
msgid "Class"
msgstr "Clase"
#: web/template/tax-details.gohtml:73
msgid "No taxes added yet." msgid "No taxes added yet."
msgstr "No hay impuestos." msgstr "No hay impuestos."
#: web/template/tax-details.gohtml:77 #: web/template/tax-details.gohtml:79
msgctxt "title" msgctxt "title"
msgid "New Line" msgid "New Line"
msgstr "Nueva línea" msgstr "Nueva línea"
#: web/template/tax-details.gohtml:88 #: web/template/tax-details.gohtml:93
msgctxt "action" msgctxt "action"
msgid "Add new tax" msgid "Add new tax"
msgstr "Añadir nuevo impuesto" msgstr "Añadir nuevo impuesto"
@ -369,71 +374,84 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password." msgid "Invalid user or password."
msgstr "Nombre de usuario o contraseña inválido." msgstr "Nombre de usuario o contraseña inválido."
#: pkg/products.go:165 pkg/invoices.go:435 #: pkg/products.go:165 pkg/invoices.go:446
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:440 #: pkg/products.go:171 pkg/invoices.go:451
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:444 #: pkg/products.go:176 pkg/invoices.go:455
msgctxt "input" msgctxt "input"
msgid "Price" msgid "Price"
msgstr "Precio" msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:350 pkg/invoices.go:470 #: pkg/products.go:186 pkg/invoices.go:344 pkg/invoices.go:481
msgctxt "input" msgctxt "input"
msgid "Taxes" msgid "Taxes"
msgstr "Impuestos" msgstr "Impuestos"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:383 #: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:377
#: pkg/invoices.go:506 #: pkg/invoices.go:517
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco." msgstr "No podéis dejar el nombre en blanco."
#: pkg/products.go:207 pkg/invoices.go:507 #: pkg/products.go:207 pkg/invoices.go:518
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco." msgstr "No podéis dejar el precio en blanco."
#: pkg/products.go:208 pkg/invoices.go:508 #: pkg/products.go:208 pkg/invoices.go:519
msgid "Price must be a number greater than zero." msgid "Price must be a number greater than zero."
msgstr "El precio tiene que ser un número mayor a cero." msgstr "El precio tiene que ser un número mayor a cero."
#: pkg/products.go:210 pkg/invoices.go:387 pkg/invoices.go:516 #: pkg/products.go:210 pkg/invoices.go:381 pkg/invoices.go:527
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido." msgstr "Habéis escogido un impuesto que no es válido."
#: pkg/company.go:89 #: pkg/company.go:90
msgctxt "input" msgctxt "input"
msgid "Currency" msgid "Currency"
msgstr "Moneda" msgstr "Moneda"
#: pkg/company.go:107 #: pkg/company.go:108
msgid "Selected currency is not valid." msgid "Selected currency is not valid."
msgstr "Habéis escogido una moneda que no es válida." msgstr "Habéis escogido una moneda que no es válida."
#: pkg/company.go:229 #: pkg/company.go:230
msgctxt "input" msgctxt "input"
msgid "Tax name" msgid "Tax name"
msgstr "Nombre impuesto" msgstr "Nombre impuesto"
#: pkg/company.go:235 #: pkg/company.go:236
msgctxt "input"
msgid "Tax Class"
msgstr "Clase de impuesto"
#: pkg/company.go:239
msgid "Select a tax class"
msgstr "Escoged una clase de impuesto"
#: pkg/company.go:243
msgctxt "input" msgctxt "input"
msgid "Rate (%)" msgid "Rate (%)"
msgstr "Porcentaje" msgstr "Porcentaje"
#: pkg/company.go:257 #: pkg/company.go:266
msgid "Tax name can not be empty." msgid "Tax name can not be empty."
msgstr "No podéis dejar el nombre del impuesto en blanco." msgstr "No podéis dejar el nombre del impuesto en blanco."
#: pkg/company.go:258 #: pkg/company.go:267
msgid "Selected tax class is not valid."
msgstr "Habéis escogido una clase impuesto que no es válida."
#: pkg/company.go:268
msgid "Tax rate can not be empty." msgid "Tax rate can not be empty."
msgstr "No podéis dejar el porcentaje en blanco." msgstr "No podéis dejar el porcentaje en blanco."
#: pkg/company.go:259 #: pkg/company.go:269
msgid "Tax rate must be an integer between -99 and 99." msgid "Tax rate must be an integer between -99 and 99."
msgstr "El porcentaje tiene que estar entre -99 y 99." msgstr "El porcentaje tiene que estar entre -99 y 99."
@ -465,70 +483,70 @@ msgstr "La confirmación no corresponde con la contraseña."
msgid "Selected language is not valid." msgid "Selected language is not valid."
msgstr "Habéis escogido un idioma que no es válido." msgstr "Habéis escogido un idioma que no es válido."
#: pkg/invoices.go:201 #: pkg/invoices.go:207
msgid "Select a customer to bill." msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar." msgstr "Escoged un cliente a facturar."
#: pkg/invoices.go:276 #: pkg/invoices.go:300
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida." msgstr "Acción inválida."
#: pkg/invoices.go:327 #: pkg/invoices.go:321
msgctxt "input" msgctxt "input"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
#: pkg/invoices.go:333 #: pkg/invoices.go:327
msgctxt "input" msgctxt "input"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: pkg/invoices.go:339 #: pkg/invoices.go:333
msgctxt "input" msgctxt "input"
msgid "Invoice Date" msgid "Invoice Date"
msgstr "Fecha de factura" msgstr "Fecha de factura"
#: pkg/invoices.go:345 #: pkg/invoices.go:339
msgctxt "input" msgctxt "input"
msgid "Notes" msgid "Notes"
msgstr "Notas" msgstr "Notas"
#: pkg/invoices.go:384 #: pkg/invoices.go:378
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de la factura en blanco." msgstr "No podéis dejar la fecha de la factura en blanco."
#: pkg/invoices.go:385 #: pkg/invoices.go:379
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida." msgstr "La fecha de factura debe ser válida."
#: pkg/invoices.go:430 #: pkg/invoices.go:441
msgctxt "input" msgctxt "input"
msgid "Id" msgid "Id"
msgstr "Identificador" msgstr "Identificador"
#: pkg/invoices.go:453 #: pkg/invoices.go:464
msgctxt "input" msgctxt "input"
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: pkg/invoices.go:461 #: pkg/invoices.go:472
msgctxt "input" msgctxt "input"
msgid "Discount (%)" msgid "Discount (%)"
msgstr "Descuento (%)" msgstr "Descuento (%)"
#: pkg/invoices.go:510 #: pkg/invoices.go:521
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco." msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoices.go:511 #: pkg/invoices.go:522
msgid "Quantity must be a number greater than zero." msgid "Quantity must be a number greater than zero."
msgstr "La cantidad tiene que ser un número mayor a cero." msgstr "La cantidad tiene que ser un número mayor a cero."
#: pkg/invoices.go:513 #: pkg/invoices.go:524
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco." msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoices.go:514 #: pkg/invoices.go:525
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un percentage entre 0 y 100." msgstr "El descuento tiene que ser un percentage entre 0 y 100."

7
revert/tax_class.sql Normal file
View File

@ -0,0 +1,7 @@
-- Revert numerus:tax_class from pg
begin;
drop table if exists numerus.tax_class;
commit;

View File

@ -36,8 +36,9 @@ country_i18n [schema_numerus country_code language country] 2023-01-27T19:20:43Z
available_countries [schema_numerus country country_i18n] 2023-01-27T18:49:28Z jordi fita mas <jordi@tandem.blog> # Add the list of available countries available_countries [schema_numerus country country_i18n] 2023-01-27T18:49:28Z jordi fita mas <jordi@tandem.blog> # Add the list of available countries
company [schema_numerus extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-24T15:03:15Z jordi fita mas <jordi@tandem.blog> # Add the relation for companies company [schema_numerus extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-24T15:03:15Z jordi fita mas <jordi@tandem.blog> # Add the relation for companies
company_user [schema_numerus user company] 2023-01-24T17:50:06Z jordi fita mas <jordi@tandem.blog> # Add the relation of companies and their users company_user [schema_numerus user company] 2023-01-24T17:50:06Z jordi fita mas <jordi@tandem.blog> # Add the relation of companies and their users
tax_class [schema_numerus company] 2023-02-28T10:13:14Z jordi fita mas <jordi@tandem.blog> # Add the relation for tax classes
tax_rate [schema_numerus] 2023-01-28T11:33:39Z jordi fita mas <jordi@tandem.blog> # Add domain for tax rates tax_rate [schema_numerus] 2023-01-28T11:33:39Z jordi fita mas <jordi@tandem.blog> # Add domain for tax rates
tax [schema_numerus company tax_rate] 2023-01-28T11:45:47Z jordi fita mas <jordi@tandem.blog> # Add relation for taxes tax [schema_numerus company tax_rate tax_class] 2023-01-28T11:45:47Z jordi fita mas <jordi@tandem.blog> # Add relation for taxes
contact [schema_numerus company extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-29T12:59:18Z jordi fita mas <jordi@tandem.blog> # Add the relation for contacts contact [schema_numerus company extension_vat email extension_pg_libphonenumber extension_uri currency_code currency country_code country] 2023-01-29T12:59:18Z jordi fita mas <jordi@tandem.blog> # Add the relation for contacts
product [schema_numerus company tax] 2023-02-04T09:17:24Z jordi fita mas <jordi@tandem.blog> # Add relation for products product [schema_numerus company tax] 2023-02-04T09:17:24Z jordi fita mas <jordi@tandem.blog> # Add relation for products
parse_price [schema_public] 2023-02-05T11:04:54Z jordi fita mas <jordi@tandem.blog> # Add function to convert from price to cents parse_price [schema_public] 2023-02-05T11:04:54Z jordi fita mas <jordi@tandem.blog> # Add function to convert from price to cents

View File

@ -28,6 +28,7 @@ truncate invoice cascade;
truncate contact cascade; truncate contact cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -41,11 +42,16 @@ values (1, 2023, '5')
, (2, 2023, '55') , (2, 2023, '55')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (3, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (4, 1, 'IVA 21 %', 0.21) , (22, 2, 'tax')
, (5, 2, 'IRPF -7 %', -0.07) ;
, (6, 2, 'IVA 10 %', 0.10)
insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (3, 1, 11, 'IRPF -15 %', -0.15)
, (4, 1, 11, 'IVA 21 %', 0.21)
, (5, 2, 22, 'IRPF -7 %', -0.07)
, (6, 2, 22, 'IVA 10 %', 0.10)
; ;
insert into product (product_id, company_id, name, price) insert into product (product_id, company_id, name, price)

View File

@ -24,6 +24,7 @@ set client_min_messages to warning;
truncate product_tax cascade; truncate product_tax cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -33,11 +34,16 @@ values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '
, (2, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD') , (2, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (3, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (4, 1, 'IVA 21 %', 0.21) , (22, 2, 'tax')
, (5, 2, 'IRPF -7 %', -0.07) ;
, (6, 2, 'IVA 10 %', 0.10)
insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (3, 1, 11, 'IRPF -15 %', -0.15)
, (4, 1, 11, 'IVA 21 %', 0.21)
, (5, 2, 22, 'IRPF -7 %', -0.07)
, (6, 2, 22, 'IVA 10 %', 0.10)
; ;

View File

@ -21,6 +21,7 @@ select function_privs_are('numerus', 'compute_new_invoice_amount', array ['integ
set client_min_messages to warning; set client_min_messages to warning;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -28,11 +29,15 @@ insert into company (company_id, business_name, vatin, trade_name, phone, email,
values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR') values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (2, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (3, 1, 'IVA 4 %', 0.04) ;
, (4, 1, 'IVA 10 %', 0.10)
, (5, 1, 'IVA 21 %', 0.21) insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (2, 1, 11, 'IRPF -15 %', -0.15)
, (3, 1, 11, 'IVA 4 %', 0.04)
, (4, 1, 11, 'IVA 10 %', 0.10)
, (5, 1, 11, 'IVA 21 %', 0.21)
; ;
select is( select is(

View File

@ -24,6 +24,7 @@ set client_min_messages to warning;
truncate product_tax cascade; truncate product_tax cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -33,11 +34,16 @@ values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '
, (2, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD') , (2, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (3, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (4, 1, 'IVA 21 %', 0.21) , (22, 2, 'tax')
, (5, 2, 'IRPF -7 %', -0.07) ;
, (6, 2, 'IVA 10 %', 0.10)
insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (3, 1, 11, 'IRPF -15 %', -0.15)
, (4, 1, 11, 'IVA 21 %', 0.21)
, (5, 2, 22, 'IRPF -7 %', -0.07)
, (6, 2, 22, 'IVA 10 %', 0.10)
; ;
insert into product (product_id, company_id, slug, name, description, price) insert into product (product_id, company_id, slug, name, description, price)

View File

@ -32,6 +32,7 @@ truncate invoice cascade;
truncate contact cascade; truncate contact cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -39,11 +40,15 @@ insert into company (company_id, business_name, vatin, trade_name, phone, email,
values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR') values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (2, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (3, 1, 'IVA 4 %', 0.04) ;
, (4, 1, 'IVA 10 %', 0.10)
, (5, 1, 'IVA 21 %', 0.21) insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (2, 1, 11, 'IRPF -15 %', -0.15)
, (3, 1, 11, 'IVA 4 %', 0.04)
, (4, 1, 11, 'IVA 10 %', 0.10)
, (5, 1, 11, 'IVA 21 %', 0.21)
; ;
insert into product (product_id, company_id, name, price) insert into product (product_id, company_id, name, price)

View File

@ -43,6 +43,7 @@ truncate invoice_product cascade;
truncate invoice cascade; truncate invoice cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate contact cascade; truncate contact cascade;
truncate company_user cascade; truncate company_user cascade;
truncate company cascade; truncate company cascade;
@ -64,9 +65,14 @@ values (2, 1)
, (4, 5) , (4, 5)
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (3, 2, 'IVA 21 %', 0.21) values (22, 2, 'vat')
, (6, 4, 'IVA 10 %', 0.10) , (44, 4, 'vat')
;
insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (3, 2, 22, 'IVA 21 %', 0.21)
, (6, 4, 44, 'IVA 10 %', 0.10)
; ;
insert into product (product_id, company_id, name, price) insert into product (product_id, company_id, name, price)

View File

@ -32,6 +32,7 @@ truncate invoice cascade;
truncate contact cascade; truncate contact cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company cascade; truncate company cascade;
reset client_min_messages; reset client_min_messages;
@ -39,11 +40,15 @@ insert into company (company_id, business_name, vatin, trade_name, phone, email,
values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR') values (1, 'Company 1', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (2, 1, 'IRPF -15 %', -0.15) values (11, 1, 'tax')
, (3, 1, 'IVA 4 %', 0.04) ;
, (4, 1, 'IVA 10 %', 0.10)
, (5, 1, 'IVA 21 %', 0.21) insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (2, 1, 11, 'IRPF -15 %', -0.15)
, (3, 1, 11, 'IVA 4 %', 0.04)
, (4, 1, 11, 'IVA 10 %', 0.10)
, (5, 1, 11, 'IVA 21 %', 0.21)
; ;
insert into product (product_id, company_id, name, price) insert into product (product_id, company_id, name, price)

View File

@ -36,6 +36,7 @@ set client_min_messages to warning;
truncate product_tax cascade; truncate product_tax cascade;
truncate product cascade; truncate product cascade;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company_user cascade; truncate company_user cascade;
truncate company cascade; truncate company cascade;
truncate auth."user" cascade; truncate auth."user" cascade;
@ -56,9 +57,14 @@ values (2, 1)
, (4, 5) , (4, 5)
; ;
insert into tax (tax_id, company_id, name, rate) insert into tax_class (tax_class_id, company_id, name)
values (3, 2, 'IVA 21 %', 0.21) values (22, 2, 'iva')
, (6, 4, 'IVA 10 %', 0.10) , (44, 4, 'iva')
;
insert into tax (tax_id, company_id, tax_class_id, name, rate)
values (3, 2, 22, 'IVA 21 %', 0.21)
, (6, 4, 44, 'IVA 10 %', 0.10)
; ;
insert into product (product_id, company_id, name, description, price) insert into product (product_id, company_id, name, description, price)

View File

@ -5,7 +5,7 @@ reset client_min_messages;
begin; begin;
select plan(36); select plan(42);
set search_path to numerus, auth, public; set search_path to numerus, auth, public;
@ -36,6 +36,13 @@ select col_type_is('tax', 'company_id', 'integer');
select col_not_null('tax', 'company_id'); select col_not_null('tax', 'company_id');
select col_hasnt_default('tax', 'company_id'); select col_hasnt_default('tax', 'company_id');
select has_column('tax', 'tax_class_id');
select col_is_fk('tax', 'tax_class_id');
select fk_ok('tax', 'tax_class_id', 'tax_class', 'tax_class_id');
select col_type_is('tax', 'tax_class_id', 'integer');
select col_not_null('tax', 'tax_class_id');
select col_hasnt_default('tax', 'tax_class_id');
select has_column('tax', 'name'); select has_column('tax', 'name');
select col_type_is('tax', 'name', 'text'); select col_type_is('tax', 'name', 'text');
select col_not_null('tax', 'name'); select col_not_null('tax', 'name');
@ -49,6 +56,7 @@ select col_hasnt_default('tax', 'rate');
set client_min_messages to warning; set client_min_messages to warning;
truncate tax cascade; truncate tax cascade;
truncate tax_class cascade;
truncate company_user cascade; truncate company_user cascade;
truncate company cascade; truncate company cascade;
truncate auth."user" cascade; truncate auth."user" cascade;
@ -69,14 +77,20 @@ values (2, 1)
, (4, 5) , (4, 5)
; ;
insert into tax (company_id, name, rate) insert into tax_class(tax_class_id, company_id, name)
values (2, 'VAT 21 %', 0.21) values (5, 2, 'vat')
, (2, 'IRPF -15 %', -0.15) , (6, 2, 'irpf')
, (4, 'VAT 21 %', 0.21) , (7, 4, 'vat')
, (4, 'VAT 10 %', 0.10) ;
, (4, 'VAT 5 %', 0.05)
, (4, 'VAT 4 %', 0.04) insert into tax (company_id, tax_class_id, name, rate)
, (4, 'VAT 0 %', 0.00) values (2, 5, 'VAT 21 %', 0.21)
, (2, 6, 'IRPF -15 %', -0.15)
, (4, 7, 'VAT 21 %', 0.21)
, (4, 7, 'VAT 10 %', 0.10)
, (4, 7, 'VAT 5 %', 0.05)
, (4, 7, 'VAT 4 %', 0.04)
, (4, 7, 'VAT 0 %', 0.00)
; ;
prepare tax_data as prepare tax_data as
@ -120,8 +134,8 @@ select throws_ok(
reset role; reset role;
select throws_ok( $$ select throws_ok( $$
insert into tax (company_id, name, rate) insert into tax (company_id, tax_class_id, name, rate)
values (2, ' ', 0.22) values (2, 6, ' ', 0.22)
$$, $$,
'23514', 'new row for relation "tax" violates check constraint "name_not_empty"', '23514', 'new row for relation "tax" violates check constraint "name_not_empty"',
'Should not allow taxs with blank name' 'Should not allow taxs with blank name'

124
test/tax_class.sql Normal file
View File

@ -0,0 +1,124 @@
-- Test tax_class
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(32);
set search_path to numerus, auth, public;
select has_table('tax_class');
select has_pk('tax_class' );
select table_privs_are('tax_class', 'guest', array []::text[]);
select table_privs_are('tax_class', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('tax_class', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('tax_class', 'authenticator', array []::text[]);
select has_sequence('tax_class_tax_class_id_seq');
select sequence_privs_are('tax_class_tax_class_id_seq', 'guest', array[]::text[]);
select sequence_privs_are('tax_class_tax_class_id_seq', 'invoicer', array['USAGE']);
select sequence_privs_are('tax_class_tax_class_id_seq', 'admin', array['USAGE']);
select sequence_privs_are('tax_class_tax_class_id_seq', 'authenticator', array[]::text[]);
select has_column('tax_class', 'tax_class_id');
select col_is_pk('tax_class', 'tax_class_id');
select col_type_is('tax_class', 'tax_class_id', 'integer');
select col_not_null('tax_class', 'tax_class_id');
select col_has_default('tax_class', 'tax_class_id');
select col_default_is('tax_class', 'tax_class_id', 'nextval(''tax_class_tax_class_id_seq''::regclass)');
select has_column('tax_class', 'company_id');
select col_is_fk('tax_class', 'company_id');
select fk_ok('tax_class', 'company_id', 'company', 'company_id');
select col_type_is('tax_class', 'company_id', 'integer');
select col_not_null('tax_class', 'company_id');
select col_hasnt_default('tax_class', 'company_id');
select has_column('tax_class', 'name');
select col_type_is('tax_class', 'name', 'text');
select col_not_null('tax_class', 'name');
select col_hasnt_default('tax_class', 'name');
set client_min_messages to warning;
truncate tax_class cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD')
;
insert into company_user (company_id, user_id)
values (2, 1)
, (4, 5)
;
insert into tax_class (company_id, name)
values (2, 'VAT')
, (2, 'IRPF')
, (4, 'VAT')
, (4, 'import')
;
prepare tax_class_data as
select company_id, name
from tax_class
order by company_id;
set role invoicer;
select is_empty('tax_class_data', 'Should show no data when cookie is not set yet');
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
select bag_eq(
'tax_class_data',
$$ values (2, 'VAT')
, (2, 'IRPF')
$$,
'Should only list taxes of the companies where demo@tandem.blog is user of'
);
reset role;
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
select bag_eq(
'tax_class_data',
$$ values (4, 'VAT')
, (4, 'import')
$$,
'Should only list taxes of the companies where admin@tandem.blog is user of'
);
reset role;
select set_cookie('not-a-cookie');
select throws_ok(
'tax_class_data',
'42501', 'permission denied for table tax_class',
'Should not allow select to guest users'
);
reset role;
select throws_ok( $$
insert into tax_class (company_id, name)
values (2, ' ')
$$,
'23514', 'new row for relation "tax_class" violates check constraint "name_not_empty"',
'Should not allow classes with blank name'
);
select *
from finish();
rollback;

View File

@ -4,6 +4,7 @@ begin;
select tax_id select tax_id
, company_id , company_id
, tax_class_id
, name , name
, rate , rate
from numerus.tax from numerus.tax

14
verify/tax_class.sql Normal file
View File

@ -0,0 +1,14 @@
-- Verify numerus:tax_class on pg
begin;
select tax_class_id
, company_id
, name
from numerus.tax_class
where false;
select 1 / count(*) from pg_class where oid = 'numerus.tax_class'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.tax_class'::regclass;
rollback;

View File

@ -46,6 +46,7 @@
<th width="50%"></th> <th width="50%"></th>
<th>{{( pgettext "Tax Name" "title" )}}</th> <th>{{( pgettext "Tax Name" "title" )}}</th>
<th>{{( pgettext "Rate (%)" "title" )}}</th> <th>{{( pgettext "Rate (%)" "title" )}}</th>
<th>{{( pgettext "Class" "title" )}}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -56,6 +57,7 @@
<td></td> <td></td>
<td>{{ .Name }}</td> <td>{{ .Name }}</td>
<td>{{ .Rate }}</td> <td>{{ .Rate }}</td>
<td>{{ .Class }}</td>
<td> <td>
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}"> <form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}">
{{ csrfToken }} {{ csrfToken }}
@ -78,9 +80,12 @@
<td> <td>
{{ template "input-field" .NewTaxForm.Name | addInputAttr `form="newtax"` }} {{ template "input-field" .NewTaxForm.Name | addInputAttr `form="newtax"` }}
</td> </td>
<td colspan="2"> <td>
{{ template "input-field" .NewTaxForm.Rate | addInputAttr `form="newtax"` }} {{ template "input-field" .NewTaxForm.Rate | addInputAttr `form="newtax"` }}
</td> </td>
<td>
{{ template "select-field" .NewTaxForm.Class | addSelectAttr `form="newtax"` }}
</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"></td> <td colspan="2"></td>