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:
parent
0d4fb124b4
commit
11d51df7fa
|
@ -17,12 +17,18 @@ values (1, 1)
|
|||
, (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;
|
||||
insert into tax (company_id, name, rate)
|
||||
values (1, 'Retenció 15 %', -0.15)
|
||||
, (1, 'IVA 21 %', 0.21)
|
||||
, (1, 'IVA 10 %', 0.10)
|
||||
, (1, 'IVA 4 %', 0.04)
|
||||
insert into tax (company_id, tax_class_id, name, rate)
|
||||
values (1, 1, 'Retenció 15 %', -0.15)
|
||||
, (1, 2, 'IVA 21 %', 0.21)
|
||||
, (1, 2, 'IVA 10 %', 0.10)
|
||||
, (1, 2, 'IVA 4 %', 0.04)
|
||||
;
|
||||
|
||||
alter sequence contact_contact_id_seq restart;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
-- requires: schema_numerus
|
||||
-- requires: company
|
||||
-- requires: tax_rate
|
||||
-- requires: tax_class
|
||||
|
||||
begin;
|
||||
|
||||
|
@ -10,6 +11,7 @@ set search_path to numerus, public;
|
|||
create table tax (
|
||||
tax_id serial primary key,
|
||||
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),
|
||||
rate tax_rate not null
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -71,9 +71,10 @@ type CountryOption struct {
|
|||
}
|
||||
|
||||
type Tax struct {
|
||||
Id int
|
||||
Name string
|
||||
Rate int
|
||||
Id int
|
||||
Name string
|
||||
Class string
|
||||
Rate int
|
||||
}
|
||||
|
||||
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) {
|
||||
locale := getLocale(r)
|
||||
page := &TaxDetailsPage{
|
||||
DetailsForm: form,
|
||||
NewTaxForm: newTaxForm(locale),
|
||||
NewTaxForm: newTaxForm(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)),
|
||||
}
|
||||
mustRenderTexDetailsPage(w, r, page)
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ func mustGetCompany(r *http.Request) *Company {
|
|||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
|
|||
var taxes []*Tax
|
||||
for rows.Next() {
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -218,10 +218,11 @@ func mustGetTaxes(ctx context.Context, conn *Conn, company *Company) []*Tax {
|
|||
type taxForm struct {
|
||||
locale *Locale
|
||||
Name *InputField
|
||||
Class *SelectField
|
||||
Rate *InputField
|
||||
}
|
||||
|
||||
func newTaxForm(locale *Locale) *taxForm {
|
||||
func newTaxForm(ctx context.Context, conn *Conn, company *Company, locale *Locale) *taxForm {
|
||||
return &taxForm{
|
||||
locale: locale,
|
||||
Name: &InputField{
|
||||
|
@ -230,6 +231,13 @@ func newTaxForm(locale *Locale) *taxForm {
|
|||
Type: "text",
|
||||
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{
|
||||
Name: "tax_rate",
|
||||
Label: pgettext("input", "Rate (%)", locale),
|
||||
|
@ -248,6 +256,7 @@ func (form *taxForm) Parse(r *http.Request) error {
|
|||
return err
|
||||
}
|
||||
form.Name.FillValue(r)
|
||||
form.Class.FillValue(r)
|
||||
form.Rate.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
@ -255,6 +264,7 @@ func (form *taxForm) Parse(r *http.Request) error {
|
|||
func (form *taxForm) Validate() bool {
|
||||
validator := newFormValidator()
|
||||
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)) {
|
||||
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) {
|
||||
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 {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
@ -292,8 +304,6 @@ func HandleAddCompanyTax(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
|
|||
mustRenderTaxForm(w, r, form)
|
||||
return
|
||||
}
|
||||
conn := getConn(r)
|
||||
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())
|
||||
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())
|
||||
http.Redirect(w, r, companyURI(company, "/tax-details"), http.StatusSeeOther)
|
||||
}
|
||||
|
|
96
po/ca.po
96
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\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"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -69,7 +69,7 @@ msgstr "Preu"
|
|||
msgid "No products added yet."
|
||||
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"
|
||||
msgid "Add products"
|
||||
msgstr "Afegeix productes"
|
||||
|
@ -85,12 +85,12 @@ msgctxt "title"
|
|||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: web/template/invoices/new.gohtml:61
|
||||
#: web/template/invoices/new.gohtml:63
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
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"
|
||||
msgid "New invoice"
|
||||
msgstr "Nova factura"
|
||||
|
@ -276,7 +276,7 @@ msgctxt "title"
|
|||
msgid "Language"
|
||||
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"
|
||||
msgid "Save changes"
|
||||
msgstr "Desa canvis"
|
||||
|
@ -302,16 +302,21 @@ msgctxt "title"
|
|||
msgid "Rate (%)"
|
||||
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."
|
||||
msgstr "No hi ha cap impost."
|
||||
|
||||
#: web/template/tax-details.gohtml:77
|
||||
#: web/template/tax-details.gohtml:79
|
||||
msgctxt "title"
|
||||
msgid "New Line"
|
||||
msgstr "Nova línia"
|
||||
|
||||
#: web/template/tax-details.gohtml:88
|
||||
#: web/template/tax-details.gohtml:93
|
||||
msgctxt "action"
|
||||
msgid "Add new tax"
|
||||
msgstr "Afegeix nou impost"
|
||||
|
@ -369,71 +374,84 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||
|
||||
#: pkg/products.go:165 pkg/invoices.go:435
|
||||
#: pkg/products.go:165 pkg/invoices.go:446
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:171 pkg/invoices.go:440
|
||||
#: pkg/products.go:171 pkg/invoices.go:451
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: pkg/products.go:176 pkg/invoices.go:444
|
||||
#: pkg/products.go:176 pkg/invoices.go:455
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
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"
|
||||
msgid "Taxes"
|
||||
msgstr "Imposts"
|
||||
|
||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:383
|
||||
#: pkg/invoices.go:506
|
||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:377
|
||||
#: pkg/invoices.go:517
|
||||
msgid "Name can not be empty."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||
|
||||
#: pkg/company.go:89
|
||||
#: pkg/company.go:90
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
msgstr "Moneda"
|
||||
|
||||
#: pkg/company.go:107
|
||||
#: pkg/company.go:108
|
||||
msgid "Selected currency is not valid."
|
||||
msgstr "Heu seleccionat una moneda que no és vàlida."
|
||||
|
||||
#: pkg/company.go:229
|
||||
#: pkg/company.go:230
|
||||
msgctxt "input"
|
||||
msgid "Tax name"
|
||||
msgstr "Nom impost"
|
||||
|
||||
#: pkg/company.go:235
|
||||
#: pkg/company.go:236
|
||||
msgctxt "input"
|
||||
msgid "Tax Class"
|
||||
msgstr "Classe d’impost"
|
||||
|
||||
#: pkg/company.go:239
|
||||
msgid "Select a tax class"
|
||||
msgstr "Escolliu una classe d’impost"
|
||||
|
||||
#: pkg/company.go:243
|
||||
msgctxt "input"
|
||||
msgid "Rate (%)"
|
||||
msgstr "Percentatge"
|
||||
|
||||
#: pkg/company.go:257
|
||||
#: pkg/company.go:266
|
||||
msgid "Tax name can not be empty."
|
||||
msgstr "No podeu deixar el nom de l’impost en blanc."
|
||||
|
||||
#: pkg/company.go:258
|
||||
#: pkg/company.go:267
|
||||
msgid "Selected tax class is not valid."
|
||||
msgstr "Heu seleccionat una classe d’impost que no és vàlida."
|
||||
|
||||
#: pkg/company.go:268
|
||||
msgid "Tax rate can not be empty."
|
||||
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."
|
||||
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."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
||||
#: pkg/invoices.go:201
|
||||
#: pkg/invoices.go:207
|
||||
msgid "Select a customer to bill."
|
||||
msgstr "Escolliu un client a facturar."
|
||||
|
||||
#: pkg/invoices.go:276
|
||||
#: pkg/invoices.go:300
|
||||
msgid "Invalid action"
|
||||
msgstr "Acció invàlida."
|
||||
|
||||
#: pkg/invoices.go:327
|
||||
#: pkg/invoices.go:321
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
msgstr "Client"
|
||||
|
||||
#: pkg/invoices.go:333
|
||||
#: pkg/invoices.go:327
|
||||
msgctxt "input"
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#: pkg/invoices.go:339
|
||||
#: pkg/invoices.go:333
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Data de factura"
|
||||
|
||||
#: pkg/invoices.go:345
|
||||
#: pkg/invoices.go:339
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
msgstr "Notes"
|
||||
|
||||
#: pkg/invoices.go:384
|
||||
#: pkg/invoices.go:378
|
||||
msgid "Invoice date can not be empty."
|
||||
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."
|
||||
msgstr "La data de facturació ha de ser vàlida."
|
||||
|
||||
#: pkg/invoices.go:430
|
||||
#: pkg/invoices.go:441
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:453
|
||||
#: pkg/invoices.go:464
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Quantitat"
|
||||
|
||||
#: pkg/invoices.go:461
|
||||
#: pkg/invoices.go:472
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descompte (%)"
|
||||
|
||||
#: pkg/invoices.go:510
|
||||
#: pkg/invoices.go:521
|
||||
msgid "Quantity can not be empty."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
||||
|
||||
|
|
96
po/es.po
96
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\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"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -69,7 +69,7 @@ msgstr "Precio"
|
|||
msgid "No products added yet."
|
||||
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"
|
||||
msgid "Add products"
|
||||
msgstr "Añadir productos"
|
||||
|
@ -85,12 +85,12 @@ msgctxt "title"
|
|||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: web/template/invoices/new.gohtml:61
|
||||
#: web/template/invoices/new.gohtml:63
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
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"
|
||||
msgid "New invoice"
|
||||
msgstr "Nueva factura"
|
||||
|
@ -276,7 +276,7 @@ msgctxt "title"
|
|||
msgid "Language"
|
||||
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"
|
||||
msgid "Save changes"
|
||||
msgstr "Guardar cambios"
|
||||
|
@ -302,16 +302,21 @@ msgctxt "title"
|
|||
msgid "Rate (%)"
|
||||
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."
|
||||
msgstr "No hay impuestos."
|
||||
|
||||
#: web/template/tax-details.gohtml:77
|
||||
#: web/template/tax-details.gohtml:79
|
||||
msgctxt "title"
|
||||
msgid "New Line"
|
||||
msgstr "Nueva línea"
|
||||
|
||||
#: web/template/tax-details.gohtml:88
|
||||
#: web/template/tax-details.gohtml:93
|
||||
msgctxt "action"
|
||||
msgid "Add new tax"
|
||||
msgstr "Añadir nuevo impuesto"
|
||||
|
@ -369,71 +374,84 @@ msgstr "No podéis dejar la contraseña en blanco."
|
|||
msgid "Invalid user or password."
|
||||
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"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:171 pkg/invoices.go:440
|
||||
#: pkg/products.go:171 pkg/invoices.go:451
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: pkg/products.go:176 pkg/invoices.go:444
|
||||
#: pkg/products.go:176 pkg/invoices.go:455
|
||||
msgctxt "input"
|
||||
msgid "Price"
|
||||
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"
|
||||
msgid "Taxes"
|
||||
msgstr "Impuestos"
|
||||
|
||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:383
|
||||
#: pkg/invoices.go:506
|
||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:377
|
||||
#: pkg/invoices.go:517
|
||||
msgid "Name can not be empty."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "Habéis escogido un impuesto que no es válido."
|
||||
|
||||
#: pkg/company.go:89
|
||||
#: pkg/company.go:90
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
msgstr "Moneda"
|
||||
|
||||
#: pkg/company.go:107
|
||||
#: pkg/company.go:108
|
||||
msgid "Selected currency is not valid."
|
||||
msgstr "Habéis escogido una moneda que no es válida."
|
||||
|
||||
#: pkg/company.go:229
|
||||
#: pkg/company.go:230
|
||||
msgctxt "input"
|
||||
msgid "Tax name"
|
||||
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"
|
||||
msgid "Rate (%)"
|
||||
msgstr "Porcentaje"
|
||||
|
||||
#: pkg/company.go:257
|
||||
#: pkg/company.go:266
|
||||
msgid "Tax name can not be empty."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "Escoged un cliente a facturar."
|
||||
|
||||
#: pkg/invoices.go:276
|
||||
#: pkg/invoices.go:300
|
||||
msgid "Invalid action"
|
||||
msgstr "Acción inválida."
|
||||
|
||||
#: pkg/invoices.go:327
|
||||
#: pkg/invoices.go:321
|
||||
msgctxt "input"
|
||||
msgid "Customer"
|
||||
msgstr "Cliente"
|
||||
|
||||
#: pkg/invoices.go:333
|
||||
#: pkg/invoices.go:327
|
||||
msgctxt "input"
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#: pkg/invoices.go:339
|
||||
#: pkg/invoices.go:333
|
||||
msgctxt "input"
|
||||
msgid "Invoice Date"
|
||||
msgstr "Fecha de factura"
|
||||
|
||||
#: pkg/invoices.go:345
|
||||
#: pkg/invoices.go:339
|
||||
msgctxt "input"
|
||||
msgid "Notes"
|
||||
msgstr "Notas"
|
||||
|
||||
#: pkg/invoices.go:384
|
||||
#: pkg/invoices.go:378
|
||||
msgid "Invoice date can not be empty."
|
||||
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."
|
||||
msgstr "La fecha de factura debe ser válida."
|
||||
|
||||
#: pkg/invoices.go:430
|
||||
#: pkg/invoices.go:441
|
||||
msgctxt "input"
|
||||
msgid "Id"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: pkg/invoices.go:453
|
||||
#: pkg/invoices.go:464
|
||||
msgctxt "input"
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#: pkg/invoices.go:461
|
||||
#: pkg/invoices.go:472
|
||||
msgctxt "input"
|
||||
msgid "Discount (%)"
|
||||
msgstr "Descuento (%)"
|
||||
|
||||
#: pkg/invoices.go:510
|
||||
#: pkg/invoices.go:521
|
||||
msgid "Quantity can not be empty."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "El descuento tiene que ser un percentage entre 0 y 100."
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:tax_class from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.tax_class;
|
||||
|
||||
commit;
|
|
@ -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
|
||||
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
|
||||
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 [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
|
||||
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
|
||||
|
|
|
@ -28,6 +28,7 @@ truncate invoice cascade;
|
|||
truncate contact cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
|
@ -41,11 +42,16 @@ values (1, 2023, '5')
|
|||
, (2, 2023, '55')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (3, 1, 'IRPF -15 %', -0.15)
|
||||
, (4, 1, 'IVA 21 %', 0.21)
|
||||
, (5, 2, 'IRPF -7 %', -0.07)
|
||||
, (6, 2, 'IVA 10 %', 0.10)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
, (22, 2, 'tax')
|
||||
;
|
||||
|
||||
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)
|
||||
|
|
|
@ -24,6 +24,7 @@ set client_min_messages to warning;
|
|||
truncate product_tax cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
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')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (3, 1, 'IRPF -15 %', -0.15)
|
||||
, (4, 1, 'IVA 21 %', 0.21)
|
||||
, (5, 2, 'IRPF -7 %', -0.07)
|
||||
, (6, 2, 'IVA 10 %', 0.10)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
, (22, 2, 'tax')
|
||||
;
|
||||
|
||||
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)
|
||||
;
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ select function_privs_are('numerus', 'compute_new_invoice_amount', array ['integ
|
|||
|
||||
set client_min_messages to warning;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
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')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (2, 1, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 'IVA 10 %', 0.10)
|
||||
, (5, 1, 'IVA 21 %', 0.21)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
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(
|
||||
|
|
|
@ -24,6 +24,7 @@ set client_min_messages to warning;
|
|||
truncate product_tax cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
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')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (3, 1, 'IRPF -15 %', -0.15)
|
||||
, (4, 1, 'IVA 21 %', 0.21)
|
||||
, (5, 2, 'IRPF -7 %', -0.07)
|
||||
, (6, 2, 'IVA 10 %', 0.10)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
, (22, 2, 'tax')
|
||||
;
|
||||
|
||||
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)
|
||||
|
|
|
@ -32,6 +32,7 @@ truncate invoice cascade;
|
|||
truncate contact cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
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')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (2, 1, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 'IVA 10 %', 0.10)
|
||||
, (5, 1, 'IVA 21 %', 0.21)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
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)
|
||||
|
|
|
@ -43,6 +43,7 @@ truncate invoice_product cascade;
|
|||
truncate invoice cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate contact cascade;
|
||||
truncate company_user cascade;
|
||||
truncate company cascade;
|
||||
|
@ -64,9 +65,14 @@ values (2, 1)
|
|||
, (4, 5)
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (3, 2, 'IVA 21 %', 0.21)
|
||||
, (6, 4, 'IVA 10 %', 0.10)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (22, 2, 'vat')
|
||||
, (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)
|
||||
|
|
|
@ -32,6 +32,7 @@ truncate invoice cascade;
|
|||
truncate contact cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company cascade;
|
||||
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')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (2, 1, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 'IVA 10 %', 0.10)
|
||||
, (5, 1, 'IVA 21 %', 0.21)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
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)
|
||||
|
|
|
@ -36,6 +36,7 @@ set client_min_messages to warning;
|
|||
truncate product_tax cascade;
|
||||
truncate product cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company_user cascade;
|
||||
truncate company cascade;
|
||||
truncate auth."user" cascade;
|
||||
|
@ -56,9 +57,14 @@ values (2, 1)
|
|||
, (4, 5)
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, name, rate)
|
||||
values (3, 2, 'IVA 21 %', 0.21)
|
||||
, (6, 4, 'IVA 10 %', 0.10)
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (22, 2, 'iva')
|
||||
, (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)
|
||||
|
|
36
test/tax.sql
36
test/tax.sql
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
|||
|
||||
begin;
|
||||
|
||||
select plan(36);
|
||||
select plan(42);
|
||||
|
||||
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_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 col_type_is('tax', 'name', 'text');
|
||||
select col_not_null('tax', 'name');
|
||||
|
@ -49,6 +56,7 @@ select col_hasnt_default('tax', 'rate');
|
|||
|
||||
set client_min_messages to warning;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate company_user cascade;
|
||||
truncate company cascade;
|
||||
truncate auth."user" cascade;
|
||||
|
@ -69,14 +77,20 @@ values (2, 1)
|
|||
, (4, 5)
|
||||
;
|
||||
|
||||
insert into tax (company_id, name, rate)
|
||||
values (2, 'VAT 21 %', 0.21)
|
||||
, (2, 'IRPF -15 %', -0.15)
|
||||
, (4, 'VAT 21 %', 0.21)
|
||||
, (4, 'VAT 10 %', 0.10)
|
||||
, (4, 'VAT 5 %', 0.05)
|
||||
, (4, 'VAT 4 %', 0.04)
|
||||
, (4, 'VAT 0 %', 0.00)
|
||||
insert into tax_class(tax_class_id, company_id, name)
|
||||
values (5, 2, 'vat')
|
||||
, (6, 2, 'irpf')
|
||||
, (7, 4, 'vat')
|
||||
;
|
||||
|
||||
insert into tax (company_id, tax_class_id, name, rate)
|
||||
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
|
||||
|
@ -120,8 +134,8 @@ select throws_ok(
|
|||
reset role;
|
||||
|
||||
select throws_ok( $$
|
||||
insert into tax (company_id, name, rate)
|
||||
values (2, ' ', 0.22)
|
||||
insert into tax (company_id, tax_class_id, name, rate)
|
||||
values (2, 6, ' ', 0.22)
|
||||
$$,
|
||||
'23514', 'new row for relation "tax" violates check constraint "name_not_empty"',
|
||||
'Should not allow taxs with blank name'
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -4,6 +4,7 @@ begin;
|
|||
|
||||
select tax_id
|
||||
, company_id
|
||||
, tax_class_id
|
||||
, name
|
||||
, rate
|
||||
from numerus.tax
|
||||
|
|
|
@ -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;
|
|
@ -46,6 +46,7 @@
|
|||
<th width="50%"></th>
|
||||
<th>{{( pgettext "Tax Name" "title" )}}</th>
|
||||
<th>{{( pgettext "Rate (%)" "title" )}}</th>
|
||||
<th>{{( pgettext "Class" "title" )}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -56,6 +57,7 @@
|
|||
<td></td>
|
||||
<td>{{ .Name }}</td>
|
||||
<td>{{ .Rate }}</td>
|
||||
<td>{{ .Class }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}">
|
||||
{{ csrfToken }}
|
||||
|
@ -78,9 +80,12 @@
|
|||
<td>
|
||||
{{ template "input-field" .NewTaxForm.Name | addInputAttr `form="newtax"` }}
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<td>
|
||||
{{ template "input-field" .NewTaxForm.Rate | addInputAttr `form="newtax"` }}
|
||||
</td>
|
||||
<td>
|
||||
{{ template "select-field" .NewTaxForm.Class | addSelectAttr `form="newtax"` }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
|
|
Loading…
Reference in New Issue