Compare commits
No commits in common. "666935b54c8f2d735d8fe5591407ca231bba7e4c" and "7513030334e343af63be7a99e86045c6967e9b8d" have entirely different histories.
666935b54c
...
7513030334
|
@ -15,9 +15,4 @@ values (1, 1)
|
|||
, (1, 2)
|
||||
;
|
||||
|
||||
insert into tax (company_id, name, rate)
|
||||
values (1, 'Retenció 15 %', -0.15)
|
||||
, (1, 'IVA 21 %', 0.21)
|
||||
;
|
||||
|
||||
commit;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
-- 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;
|
|
@ -1,14 +0,0 @@
|
|||
-- 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,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -66,22 +65,11 @@ func getCompany(r *http.Request) *Company {
|
|||
return company.(*Company)
|
||||
}
|
||||
|
||||
type CurrencyOption struct {
|
||||
Code string
|
||||
Symbol string
|
||||
}
|
||||
|
||||
type CountryOption struct {
|
||||
Code string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Tax struct {
|
||||
Id int
|
||||
Name string
|
||||
Rate int
|
||||
}
|
||||
|
||||
type TaxDetailsPage struct {
|
||||
Title string
|
||||
BusinessName string
|
||||
|
@ -96,9 +84,6 @@ type TaxDetailsPage struct {
|
|||
PostalCode string
|
||||
CountryCode string
|
||||
Countries []CountryOption
|
||||
CurrencyCode string
|
||||
Currencies []CurrencyOption
|
||||
Taxes []Tax
|
||||
}
|
||||
|
||||
func CompanyTaxDetailsHandler() http.Handler {
|
||||
|
@ -122,18 +107,15 @@ func CompanyTaxDetailsHandler() http.Handler {
|
|||
page.City = r.FormValue("city")
|
||||
page.Province = r.FormValue("province")
|
||||
page.PostalCode = r.FormValue("postal_code")
|
||||
page.CurrencyCode = r.FormValue("currency")
|
||||
conn.MustExec(r.Context(), "update company set business_name = $1, vatin = $2, trade_name = $3, phone = parse_packed_phone_number($4, $11), email = $5, web = $6, address = $7, city = $8, province = $9, postal_code = $10, country_code = $11, currency_code = $12 where company_id = $13", page.BusinessName, page.VATIN, page.TradeName, page.Phone, page.Email, page.Web, page.Address, page.City, page.Province, page.PostalCode, page.CountryCode, page.CurrencyCode, company.Id)
|
||||
conn.MustExec(r.Context(), "update company set business_name = $1, vatin = $2, trade_name = $3, phone = parse_packed_phone_number($4, $11), email = $5, web = $6, address = $7, city = $8, province = $9, postal_code = $10, country_code = $11 where company_id = $12", page.BusinessName, page.VATIN, page.TradeName, page.Phone, page.Email, page.Web, page.Address, page.City, page.Province, page.PostalCode, page.CountryCode, company.Id)
|
||||
http.Redirect(w, r, "/company/"+company.Slug+"/tax-details", http.StatusSeeOther)
|
||||
} else {
|
||||
err := conn.QueryRow(r.Context(), "select business_name, substr(vatin::text, 3), trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code from company where company_id = $1", company.Id).Scan(&page.BusinessName, &page.VATIN, &page.TradeName, &page.Phone, &page.Email, &page.Web, &page.Address, &page.City, &page.Province, &page.PostalCode, &page.CountryCode, &page.CurrencyCode)
|
||||
err := conn.QueryRow(r.Context(), "select business_name, substr(vatin::text, 3), trade_name, phone, email, web, address, city, province, postal_code, country_code from company where company_id = $1", company.Id).Scan(&page.BusinessName, &page.VATIN, &page.TradeName, &page.Phone, &page.Email, &page.Web, &page.Address, &page.City, &page.Province, &page.PostalCode, &page.CountryCode)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
page.Countries = mustGetCountryOptions(r.Context(), conn, locale)
|
||||
page.Currencies = mustGetCurrencyOptions(r.Context(), conn)
|
||||
page.Taxes = mustGetTaxes(r.Context(), conn, company)
|
||||
mustRenderAppTemplate(w, r, "tax-details.html", page)
|
||||
})
|
||||
}
|
||||
|
@ -147,7 +129,7 @@ func mustGetCompany(r *http.Request) *Company {
|
|||
}
|
||||
|
||||
func mustGetCountryOptions(ctx context.Context, conn *Conn, locale *Locale) []CountryOption {
|
||||
rows, err := conn.Query(ctx, "select country.country_code, coalesce(i18n.name, country.name) as l10n_name from country left join country_i18n as i18n on country.country_code = i18n.country_code and i18n.lang_tag = $1 order by l10n_name", locale.Language)
|
||||
rows, err := conn.Query(ctx, "select country.country_code, coalesce(i18n.name, country.name) from country left join country_i18n as i18n on country.country_code = i18n.country_code and i18n.lang_tag = $1", locale.Language)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -168,69 +150,3 @@ func mustGetCountryOptions(ctx context.Context, conn *Conn, locale *Locale) []Co
|
|||
|
||||
return countries
|
||||
}
|
||||
|
||||
func mustGetCurrencyOptions(ctx context.Context, conn *Conn) []CurrencyOption {
|
||||
rows, err := conn.Query(ctx, "select currency_code, currency_symbol from currency order by currency_code")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var currencies []CurrencyOption
|
||||
for rows.Next() {
|
||||
var currency CurrencyOption
|
||||
err = rows.Scan(¤cy.Code, ¤cy.Symbol)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currencies = append(currencies, currency)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
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,8 +7,6 @@ import (
|
|||
func NewRouter(db *Db) http.Handler {
|
||||
companyRouter := http.NewServeMux()
|
||||
companyRouter.Handle("/tax-details", CompanyTaxDetailsHandler())
|
||||
companyRouter.Handle("/tax/", CompanyTaxHandler())
|
||||
companyRouter.Handle("/tax", CompanyTaxHandler())
|
||||
companyRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
mustRenderAppTemplate(w, r, "dashboard.html", nil)
|
||||
})
|
||||
|
|
47
po/ca.po
47
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-01-28 14:14+0100\n"
|
||||
"POT-Creation-Date: 2023-01-24 21:37+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"
|
||||
|
@ -77,12 +77,12 @@ msgctxt "language option"
|
|||
msgid "Automatic"
|
||||
msgstr "Automàtic"
|
||||
|
||||
#: web/template/profile.html:42 web/template/tax-details.html:127
|
||||
#: web/template/profile.html:42
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Desa canvis"
|
||||
|
||||
#: web/template/tax-details.html:3 pkg/company.go:108
|
||||
#: web/template/tax-details.html:3 pkg/company.go:87
|
||||
msgctxt "title"
|
||||
msgid "Tax Details"
|
||||
msgstr "Configuració fiscal"
|
||||
|
@ -132,50 +132,11 @@ msgctxt "input"
|
|||
msgid "Postal code"
|
||||
msgstr "Codi postal"
|
||||
|
||||
#: web/template/tax-details.html:52
|
||||
#: web/template/tax-details.html:47
|
||||
msgctxt "input"
|
||||
msgid "Country"
|
||||
msgstr "País"
|
||||
|
||||
#: web/template/tax-details.html:56
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
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
|
||||
msgctxt "menu"
|
||||
msgid "Account"
|
||||
|
|
47
po/es.po
47
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-01-28 14:14+0100\n"
|
||||
"POT-Creation-Date: 2023-01-24 21:37+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"
|
||||
|
@ -77,12 +77,12 @@ msgctxt "language option"
|
|||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: web/template/profile.html:42 web/template/tax-details.html:127
|
||||
#: web/template/profile.html:42
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Guardar cambios"
|
||||
|
||||
#: web/template/tax-details.html:3 pkg/company.go:108
|
||||
#: web/template/tax-details.html:3 pkg/company.go:87
|
||||
msgctxt "title"
|
||||
msgid "Tax Details"
|
||||
msgstr "Configuración fiscal"
|
||||
|
@ -132,50 +132,11 @@ msgctxt "input"
|
|||
msgid "Postal code"
|
||||
msgstr "Código postal"
|
||||
|
||||
#: web/template/tax-details.html:52
|
||||
#: web/template/tax-details.html:47
|
||||
msgctxt "input"
|
||||
msgid "Country"
|
||||
msgstr "País"
|
||||
|
||||
#: web/template/tax-details.html:56
|
||||
msgctxt "input"
|
||||
msgid "Currency"
|
||||
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
|
||||
msgctxt "menu"
|
||||
msgid "Account"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
-- Revert numerus:tax from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop policy company_policy on numerus.tax;
|
||||
drop table if exists numerus.tax;
|
||||
|
||||
commit;
|
|
@ -1,7 +0,0 @@
|
|||
-- Revert numerus:tax_rate from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop domain if exists numerus.tax_rate;
|
||||
|
||||
commit;
|
|
@ -36,5 +36,3 @@ 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
|
||||
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_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
|
||||
|
|
127
test/tax.sql
127
test/tax.sql
|
@ -1,127 +0,0 @@
|
|||
-- 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;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
-- 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;
|
|
@ -7,7 +7,4 @@ select company_id
|
|||
from numerus.company_user
|
||||
where false;
|
||||
|
||||
select 1 / count(*) from pg_class where oid = 'numerus.company'::regclass and relrowsecurity;
|
||||
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.company'::regclass;
|
||||
|
||||
rollback;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
-- 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;
|
|
@ -1,7 +0,0 @@
|
|||
-- Verify numerus:tax_rate on pg
|
||||
|
||||
begin;
|
||||
|
||||
select pg_catalog.has_type_privilege('numerus.tax_rate', 'usage');
|
||||
|
||||
rollback;
|
|
@ -205,15 +205,6 @@ input[type="submit"]:active, button:active {
|
|||
text-color: var(--numerus--color--white);
|
||||
}
|
||||
|
||||
button.icon {
|
||||
min-width: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.web {
|
||||
margin: 8.5rem 4rem;
|
||||
background-color: var(--numerus--header--background-color);
|
||||
|
@ -257,13 +248,7 @@ main {
|
|||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
input[type="text"]
|
||||
, input[type="password"]
|
||||
, input[type="email"]
|
||||
, input[type="tel"]
|
||||
, input[type="url"]
|
||||
, input[type="number"]
|
||||
, select {
|
||||
input[type="text"], input[type="password"], input[type="email"], input[type="tel"], input[type="url"], select {
|
||||
background-color: var(--numerus--background-color);
|
||||
border: 1px solid var(--numerus--color--black);
|
||||
border-radius: 0;
|
||||
|
|
|
@ -1,130 +1,58 @@
|
|||
{{ define "content" }}
|
||||
<section class="dialog-content">
|
||||
<h2>{{(pgettext "Tax Details" "title")}}</h2>
|
||||
<form id="details" method="POST">
|
||||
<div class="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>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="vatin" id="vatin" required="required" value="{{ .VATIN }}" placeholder="{{( pgettext "VAT number" "input" )}}">
|
||||
<label for="vatin">{{( pgettext "VAT number" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="trade_name" id="trade_name" value="{{ .TradeName }}" placeholder="{{( pgettext "Trade name" "input" )}}">
|
||||
<label for="trade_name">{{( pgettext "Trade name" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="tel" name="phone" id="phone" required="required" value="{{ .Phone }}" placeholder="{{( pgettext "Phone" "input" )}}">
|
||||
<label for="phone">{{( pgettext "Phone" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="email" required="required" value="{{ .Email }}" placeholder="{{( pgettext "Email" "input" )}}">
|
||||
<label for="email">{{( pgettext "Email" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="url" name="web" id="web" value="{{ .Web }}" placeholder="{{( pgettext "Web" "input" )}}">
|
||||
<label for="web">{{( pgettext "Web" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="address" id="address" class="width-2x" required="required" value="{{ .Address }}" placeholder="{{( pgettext "Address" "input" )}}">
|
||||
<label for="address">{{( pgettext "Address" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="city" id="city" required="required" value="{{ .City }}" placeholder="{{( pgettext "City" "input" )}}">
|
||||
<label for="city">{{( pgettext "City" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="province" id="province" required="required" value="{{ .City }}" placeholder="{{( pgettext "Province" "input" )}}">
|
||||
<label for="province">{{( pgettext "Province" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="postal_code" id="postal_code" required="required" value="{{ .PostalCode }}" placeholder="{{( pgettext "Postal code" "input" )}}">
|
||||
<label for="postal_code">{{( pgettext "Postal code" "input" )}}</label>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="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>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="vatin" id="vatin" required="required" value="{{ .VATIN }}" placeholder="{{( pgettext "VAT number" "input" )}}">
|
||||
<label for="vatin">{{( pgettext "VAT number" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="trade_name" id="trade_name" value="{{ .TradeName }}" placeholder="{{( pgettext "Trade name" "input" )}}">
|
||||
<label for="trade_name">{{( pgettext "Trade name" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="tel" name="phone" id="phone" required="required" value="{{ .Phone }}" placeholder="{{( pgettext "Phone" "input" )}}">
|
||||
<label for="phone">{{( pgettext "Phone" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="email" required="required" value="{{ .Email }}" placeholder="{{( pgettext "Email" "input" )}}">
|
||||
<label for="email">{{( pgettext "Email" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="url" name="web" id="web" value="{{ .Web }}" placeholder="{{( pgettext "Web" "input" )}}">
|
||||
<label for="web">{{( pgettext "Web" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="address" id="address" class="width-2x" required="required" value="{{ .Address }}" placeholder="{{( pgettext "Address" "input" )}}">
|
||||
<label for="address">{{( pgettext "Address" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="city" id="city" required="required" value="{{ .City }}" placeholder="{{( pgettext "City" "input" )}}">
|
||||
<label for="city">{{( pgettext "City" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="province" id="province" required="required" value="{{ .City }}" placeholder="{{( pgettext "Province" "input" )}}">
|
||||
<label for="province">{{( pgettext "Province" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" name="postal_code" id="postal_code" required="required" value="{{ .PostalCode }}" placeholder="{{( pgettext "Postal code" "input" )}}">
|
||||
<label for="postal_code">{{( pgettext "Postal code" "input" )}}</label>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<select id="country" name="country" class="width-fixed">
|
||||
{{- range $country := .Countries }}
|
||||
<option value="{{ .Code }}" {{ if eq .Code $.CountryCode }}selected="selected"{{ end }}>{{ .Name }}</option>
|
||||
{{- end }}
|
||||
</select>
|
||||
<label for="country">{{( pgettext "Country" "input" )}}</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
<select id="country" name="country" class="width-fixed">
|
||||
{{- range $country := .Countries }}
|
||||
<option value="{{ .Code }}" {{ if eq .Code $.CountryCode }}selected="selected"{{ end }}>{{ .Name }}</option>
|
||||
{{- end }}
|
||||
</select>
|
||||
<label for="country">{{( pgettext "Country" "input" )}}<label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend id="currency-legend">{{( pgettext "Currency" "input" )}}</legend>
|
||||
|
||||
<select id="currency" name="currency" aria-labelledby="currency-legend">
|
||||
{{- range $currency := .Currencies }}
|
||||
<option value="{{ .Code }}" {{ if eq .Code $.CurrencyCode }}selected="selected"{{ end }}>{{ .Symbol }} ({{ .Code }})</option>
|
||||
{{- end }}
|
||||
</select>
|
||||
</fieldset>
|
||||
<button type="submit">{{( pgettext "Save changes" "action" )}}</button>
|
||||
</form>
|
||||
|
||||
<form id="newtax" method="POST" action="{{ companyURI "/tax" }}">
|
||||
</form>
|
||||
|
||||
<fieldset>
|
||||
<table>
|
||||
<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>
|
||||
</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>
|
||||
{{- end }}
|
||||
|
|
Loading…
Reference in New Issue