Add the tax relation with very rough form and handler
This commit is contained in:
parent
0b8107748c
commit
666935b54c
|
@ -15,4 +15,9 @@ values (1, 1)
|
||||||
, (1, 2)
|
, (1, 2)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
insert into tax (company_id, name, rate)
|
||||||
|
values (1, 'Retenció 15 %', -0.15)
|
||||||
|
, (1, 'IVA 21 %', 0.21)
|
||||||
|
;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
-- Deploy numerus:tax to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
-- requires: company
|
||||||
|
-- requires: tax_rate
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create table tax (
|
||||||
|
tax_id serial primary key,
|
||||||
|
company_id integer not null references company,
|
||||||
|
name text not null,
|
||||||
|
rate tax_rate not null
|
||||||
|
);
|
||||||
|
|
||||||
|
grant select, insert, update, delete on table tax to invoicer;
|
||||||
|
grant select, insert, update, delete on table tax to admin;
|
||||||
|
|
||||||
|
grant usage on sequence tax_tax_id_seq to invoicer;
|
||||||
|
grant usage on sequence tax_tax_id_seq to admin;
|
||||||
|
|
||||||
|
alter table tax enable row level security;
|
||||||
|
|
||||||
|
create policy company_policy
|
||||||
|
on tax
|
||||||
|
using (
|
||||||
|
exists(
|
||||||
|
select 1
|
||||||
|
from company_user
|
||||||
|
join user_profile using (user_id)
|
||||||
|
where company_user.company_id = tax.company_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,14 @@
|
||||||
|
-- Deploy numerus:tax_rate to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create domain tax_rate as numeric
|
||||||
|
check (value > -1 and value < 1);
|
||||||
|
|
||||||
|
comment on domain country_code is
|
||||||
|
'A tax rate in the range (-1, 1)';
|
||||||
|
|
||||||
|
commit;
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,6 +76,12 @@ type CountryOption struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tax struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Rate int
|
||||||
|
}
|
||||||
|
|
||||||
type TaxDetailsPage struct {
|
type TaxDetailsPage struct {
|
||||||
Title string
|
Title string
|
||||||
BusinessName string
|
BusinessName string
|
||||||
|
@ -91,6 +98,7 @@ type TaxDetailsPage struct {
|
||||||
Countries []CountryOption
|
Countries []CountryOption
|
||||||
CurrencyCode string
|
CurrencyCode string
|
||||||
Currencies []CurrencyOption
|
Currencies []CurrencyOption
|
||||||
|
Taxes []Tax
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompanyTaxDetailsHandler() http.Handler {
|
func CompanyTaxDetailsHandler() http.Handler {
|
||||||
|
@ -125,6 +133,7 @@ func CompanyTaxDetailsHandler() http.Handler {
|
||||||
}
|
}
|
||||||
page.Countries = mustGetCountryOptions(r.Context(), conn, locale)
|
page.Countries = mustGetCountryOptions(r.Context(), conn, locale)
|
||||||
page.Currencies = mustGetCurrencyOptions(r.Context(), conn)
|
page.Currencies = mustGetCurrencyOptions(r.Context(), conn)
|
||||||
|
page.Taxes = mustGetTaxes(r.Context(), conn, company)
|
||||||
mustRenderAppTemplate(w, r, "tax-details.html", page)
|
mustRenderAppTemplate(w, r, "tax-details.html", page)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -182,3 +191,46 @@ func mustGetCurrencyOptions(ctx context.Context, conn *Conn) []CurrencyOption {
|
||||||
|
|
||||||
return currencies
|
return currencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var taxes []Tax
|
||||||
|
for rows.Next() {
|
||||||
|
var tax Tax
|
||||||
|
err = rows.Scan(&tax.Id, &tax.Name, &tax.Rate)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
taxes = append(taxes, tax)
|
||||||
|
}
|
||||||
|
if rows.Err() != nil {
|
||||||
|
panic(rows.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxes
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompanyTaxHandler() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
param := r.URL.Path
|
||||||
|
if idx := strings.LastIndexByte(param, '/'); idx >= 0 {
|
||||||
|
param = param[idx+1:]
|
||||||
|
}
|
||||||
|
conn := getConn(r)
|
||||||
|
company := mustGetCompany(r)
|
||||||
|
if taxId, err := strconv.Atoi(param); err == nil {
|
||||||
|
conn.MustExec(r.Context(), "delete from tax where tax_id = $1", taxId)
|
||||||
|
} else {
|
||||||
|
r.ParseForm()
|
||||||
|
name := r.FormValue("name")
|
||||||
|
rate, _ := strconv.Atoi(r.FormValue("rate"))
|
||||||
|
conn.MustExec(r.Context(), "insert into tax (company_id, name, rate) values ($1, $2, $3 / 100::decimal)", company.Id, name, rate)
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
func NewRouter(db *Db) http.Handler {
|
func NewRouter(db *Db) http.Handler {
|
||||||
companyRouter := http.NewServeMux()
|
companyRouter := http.NewServeMux()
|
||||||
companyRouter.Handle("/tax-details", CompanyTaxDetailsHandler())
|
companyRouter.Handle("/tax-details", CompanyTaxDetailsHandler())
|
||||||
|
companyRouter.Handle("/tax/", CompanyTaxHandler())
|
||||||
|
companyRouter.Handle("/tax", CompanyTaxHandler())
|
||||||
companyRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
companyRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
mustRenderAppTemplate(w, r, "dashboard.html", nil)
|
mustRenderAppTemplate(w, r, "dashboard.html", nil)
|
||||||
})
|
})
|
||||||
|
|
40
po/ca.po
40
po/ca.po
|
@ -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-01-28 12:22+0100\n"
|
"POT-Creation-Date: 2023-01-28 14:14+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"
|
||||||
|
@ -77,12 +77,12 @@ msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automàtic"
|
msgstr "Automàtic"
|
||||||
|
|
||||||
#: web/template/profile.html:42 web/template/tax-details.html:66
|
#: web/template/profile.html:42 web/template/tax-details.html:127
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Save changes"
|
msgid "Save changes"
|
||||||
msgstr "Desa canvis"
|
msgstr "Desa canvis"
|
||||||
|
|
||||||
#: web/template/tax-details.html:3 pkg/company.go:100
|
#: web/template/tax-details.html:3 pkg/company.go:108
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuració fiscal"
|
msgstr "Configuració fiscal"
|
||||||
|
@ -142,6 +142,40 @@ msgctxt "input"
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:74
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Tax Name"
|
||||||
|
msgstr "Nom import"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:75
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Percentatge"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:96
|
||||||
|
msgid "No taxes added yet."
|
||||||
|
msgstr "No hi ha cap impost."
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:102
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "New Line"
|
||||||
|
msgstr "Nova línia"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:106
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tax name"
|
||||||
|
msgstr "Nom impost"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:112
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Percentatge"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:119
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Add new tax"
|
||||||
|
msgstr "Afegeix nou impost"
|
||||||
|
|
||||||
#: web/template/app.html:20
|
#: web/template/app.html:20
|
||||||
msgctxt "menu"
|
msgctxt "menu"
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
|
|
40
po/es.po
40
po/es.po
|
@ -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-01-28 12:22+0100\n"
|
"POT-Creation-Date: 2023-01-28 14:14+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"
|
||||||
|
@ -77,12 +77,12 @@ msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automático"
|
msgstr "Automático"
|
||||||
|
|
||||||
#: web/template/profile.html:42 web/template/tax-details.html:66
|
#: web/template/profile.html:42 web/template/tax-details.html:127
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Save changes"
|
msgid "Save changes"
|
||||||
msgstr "Guardar cambios"
|
msgstr "Guardar cambios"
|
||||||
|
|
||||||
#: web/template/tax-details.html:3 pkg/company.go:100
|
#: web/template/tax-details.html:3 pkg/company.go:108
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Details"
|
msgid "Tax Details"
|
||||||
msgstr "Configuración fiscal"
|
msgstr "Configuración fiscal"
|
||||||
|
@ -142,6 +142,40 @@ msgctxt "input"
|
||||||
msgid "Currency"
|
msgid "Currency"
|
||||||
msgstr "Moneda"
|
msgstr "Moneda"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:74
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Tax Name"
|
||||||
|
msgstr "Nombre impuesto"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:75
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Porcentage"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:96
|
||||||
|
msgid "No taxes added yet."
|
||||||
|
msgstr "No hay impuestos."
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:102
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "New Line"
|
||||||
|
msgstr "Nueva línea"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:106
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tax name"
|
||||||
|
msgstr "Nombre impuesto"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:112
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Rate (%)"
|
||||||
|
msgstr "Porcentage"
|
||||||
|
|
||||||
|
#: web/template/tax-details.html:119
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Add new tax"
|
||||||
|
msgstr "Añadir nuevo impuesto"
|
||||||
|
|
||||||
#: web/template/app.html:20
|
#: web/template/app.html:20
|
||||||
msgctxt "menu"
|
msgctxt "menu"
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Revert numerus:tax from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop policy company_policy on numerus.tax;
|
||||||
|
drop table if exists numerus.tax;
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:tax_rate from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop domain if exists numerus.tax_rate;
|
||||||
|
|
||||||
|
commit;
|
|
@ -36,3 +36,5 @@ country_i18n [schema_numerus country_code language country] 2023-01-27T19:20:43Z
|
||||||
available_countries [schema_numerus country] 2023-01-27T18:49:28Z jordi fita mas <jordi@tandem.blog> # Add the list of available countries
|
available_countries [schema_numerus country] 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_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
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
-- Test tax
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(35);
|
||||||
|
|
||||||
|
set search_path to numerus, auth, public;
|
||||||
|
|
||||||
|
select has_table('tax');
|
||||||
|
select has_pk('tax' );
|
||||||
|
select table_privs_are('tax', 'guest', array []::text[]);
|
||||||
|
select table_privs_are('tax', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('tax', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('tax', 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
SELECT has_sequence('tax_tax_id_seq');
|
||||||
|
SELECT sequence_privs_are('tax_tax_id_seq', 'guest', array[]::text[]);
|
||||||
|
SELECT sequence_privs_are('tax_tax_id_seq', 'invoicer', array['USAGE']);
|
||||||
|
SELECT sequence_privs_are('tax_tax_id_seq', 'admin', array['USAGE']);
|
||||||
|
SELECT sequence_privs_are('tax_tax_id_seq', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
|
select has_column('tax', 'tax_id');
|
||||||
|
select col_is_pk('tax', 'tax_id');
|
||||||
|
select col_type_is('tax', 'tax_id', 'integer');
|
||||||
|
select col_not_null('tax', 'tax_id');
|
||||||
|
select col_has_default('tax', 'tax_id');
|
||||||
|
select col_default_is('tax', 'tax_id', 'nextval(''tax_tax_id_seq''::regclass)');
|
||||||
|
|
||||||
|
select has_column('tax', 'company_id');
|
||||||
|
select col_is_fk('tax', 'company_id');
|
||||||
|
select fk_ok('tax', 'company_id', 'company', 'company_id');
|
||||||
|
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', 'name');
|
||||||
|
select col_type_is('tax', 'name', 'text');
|
||||||
|
select col_not_null('tax', 'name');
|
||||||
|
select col_hasnt_default('tax', 'name');
|
||||||
|
|
||||||
|
select has_column('tax', 'rate');
|
||||||
|
select col_type_is('tax', 'rate', 'tax_rate');
|
||||||
|
select col_not_null('tax', 'rate');
|
||||||
|
select col_hasnt_default('tax', 'rate');
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate tax 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 (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)
|
||||||
|
;
|
||||||
|
|
||||||
|
prepare tax_data as
|
||||||
|
select company_id, name, rate
|
||||||
|
from tax
|
||||||
|
order by company_id, rate;
|
||||||
|
|
||||||
|
set role invoicer;
|
||||||
|
select is_empty('tax_data', 'Should show no data when cookie is not set yet');
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||||
|
select bag_eq(
|
||||||
|
'tax_data',
|
||||||
|
$$ values ( 2, 'IRPF -15 %', -0.15::tax_rate )
|
||||||
|
, ( 2, 'VAT 21 %', 0.21::tax_rate )
|
||||||
|
$$,
|
||||||
|
'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_data',
|
||||||
|
$$ values (4, 'VAT 0 %', 0.00::tax_rate)
|
||||||
|
, (4, 'VAT 4 %', 0.04::tax_rate)
|
||||||
|
, (4, 'VAT 5 %', 0.05::tax_rate)
|
||||||
|
, (4, 'VAT 10 %', 0.10::tax_rate)
|
||||||
|
, (4, 'VAT 21 %', 0.21::tax_rate)
|
||||||
|
$$,
|
||||||
|
'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_data',
|
||||||
|
'42501', 'permission denied for table tax',
|
||||||
|
'Should not allow select to guest users'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
-- Test tax_rate
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(7);
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
select has_domain('tax_rate');
|
||||||
|
select domain_type_is('tax_rate', 'numeric');
|
||||||
|
|
||||||
|
select lives_ok($$ select 0.21::tax_rate $$, 'Should be able to cast valid positive decimals to tax rate');
|
||||||
|
select lives_ok($$ select -0.15::tax_rate $$, 'Should be able to cast valid negative decimals to tax rate');
|
||||||
|
select lives_ok($$ select 0::tax_rate $$, 'Should be able to cast valid zero to tax rate');
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT 1::tax_rate $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject 100 % tax rate'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT -1::tax_rate $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject -100 % tax rate'
|
||||||
|
);
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,15 @@
|
||||||
|
-- Verify numerus:tax on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select tax_id
|
||||||
|
, company_id
|
||||||
|
, name
|
||||||
|
, rate
|
||||||
|
from numerus.tax
|
||||||
|
where false;
|
||||||
|
|
||||||
|
select 1 / count(*) from pg_class where oid = 'numerus.tax'::regclass and relrowsecurity;
|
||||||
|
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.tax'::regclass;
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify numerus:tax_rate on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select pg_catalog.has_type_privilege('numerus.tax_rate', 'usage');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -205,6 +205,15 @@ input[type="submit"]:active, button:active {
|
||||||
text-color: var(--numerus--color--white);
|
text-color: var(--numerus--color--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.icon {
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.web {
|
.web {
|
||||||
margin: 8.5rem 4rem;
|
margin: 8.5rem 4rem;
|
||||||
background-color: var(--numerus--header--background-color);
|
background-color: var(--numerus--header--background-color);
|
||||||
|
@ -248,7 +257,13 @@ main {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], input[type="password"], input[type="email"], input[type="tel"], input[type="url"], select {
|
input[type="text"]
|
||||||
|
, input[type="password"]
|
||||||
|
, input[type="email"]
|
||||||
|
, input[type="tel"]
|
||||||
|
, input[type="url"]
|
||||||
|
, input[type="number"]
|
||||||
|
, select {
|
||||||
background-color: var(--numerus--background-color);
|
background-color: var(--numerus--background-color);
|
||||||
border: 1px solid var(--numerus--color--black);
|
border: 1px solid var(--numerus--color--black);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{(pgettext "Tax Details" "title")}}</h2>
|
<h2>{{(pgettext "Tax Details" "title")}}</h2>
|
||||||
<form method="POST">
|
<form id="details" method="POST">
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<input type="text" name="business_name" id="business_name" required="required" value="{{ .BusinessName }}" placeholder="{{( pgettext "Business name" "input" )}}">
|
<input type="text" name="business_name" id="business_name" required="required" value="{{ .BusinessName }}" placeholder="{{( pgettext "Business name" "input" )}}">
|
||||||
<label for="business_name">{{( pgettext "Business name" "input" )}}</label>
|
<label for="business_name">{{( pgettext "Business name" "input" )}}</label>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<option value="{{ .Code }}" {{ if eq .Code $.CountryCode }}selected="selected"{{ end }}>{{ .Name }}</option>
|
<option value="{{ .Code }}" {{ if eq .Code $.CountryCode }}selected="selected"{{ end }}>{{ .Name }}</option>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</select>
|
</select>
|
||||||
<label for="country">{{( pgettext "Country" "input" )}}<label>
|
<label for="country">{{( pgettext "Country" "input" )}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -61,10 +61,70 @@
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</select>
|
</select>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form id="newtax" method="POST" action="{{ companyURI "/tax" }}">
|
||||||
|
</form>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<button type="submit">{{( pgettext "Save changes" "action" )}}</button>
|
<table>
|
||||||
</fieldset>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="50%"></th>
|
||||||
|
<th>{{( pgettext "Tax Name" "title" )}}</th>
|
||||||
|
<th>{{( pgettext "Rate (%)" "title" )}}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ with .Taxes }}
|
||||||
|
{{- range $tax := . }}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>{{ .Name }}</td>
|
||||||
|
<td>{{ .Rate }}</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" action="{{ companyURI "/tax"}}/{{ .Id }}">
|
||||||
|
<input type="hidden" name="_method" name="DELETE"/>
|
||||||
|
<button class="icon" aria-label="{{( gettext "Delete tax" )}}" type="submit"><i class="ri-delete-back-2-line"></i></button>
|
||||||
</form>
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{- end }}
|
||||||
|
{{ else }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">{{( gettext "No taxes added yet." )}}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{( pgettext "New Line" "title")}}</th>
|
||||||
|
<td>
|
||||||
|
<div class="input">
|
||||||
|
<input form="newtax" type="text" name="name" id="tax_name" required="required" placeholder="{{( pgettext "Tax name" "input" )}}">
|
||||||
|
<label for="tax_name">{{( pgettext "Tax name" "input" )}}</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="input">
|
||||||
|
<input form="newtax" type="number" name="rate" id="tax_rate" min="-99" max="99" required="required" placeholder="{{( pgettext "Rate (%)" "input" )}}">
|
||||||
|
<label form="newtax" for="tax_rate">{{( pgettext "Rate (%)" "input" )}}</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td colspan="2">
|
||||||
|
<button form="newtax" type="submit">{{( pgettext "Add new tax" "action" )}}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<button form="details" type="submit">{{( pgettext "Save changes" "action" )}}</button>
|
||||||
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
Loading…
Reference in New Issue