diff --git a/demo/demo.sql b/demo/demo.sql index d78dd13..68040d8 100644 --- a/demo/demo.sql +++ b/demo/demo.sql @@ -42,12 +42,12 @@ values (123, 123, 'Retenció 15 %', -0.15) ; alter sequence contact_contact_id_seq restart with 123; -select add_contact (123, 'Melcior', '1', 'Rei Blanc', '0732621', 'melcio@reismags.cat', '', 'C/ Principal, 1', 'Shiraz', 'Fars', '1', 'IR', array['pesebre', 'mag']); -select add_contact (123, 'Gaspar', '2', 'Rei Ros', '111', 'gaspar@reismags.cat', '', 'C/ Principal, 2', 'Nova Delhi', 'Delhi', '2', 'IN', array['pesebre', 'mag']); -select add_contact (123, 'Baltasar', '3', 'Rei Negre', '1-111-111', 'baltasar@reismags.cat', '', 'C/ Principal, 3', 'Sanaa', 'Sanaa', '3', 'YE', array['pesebre', 'mag']); -select add_contact (123, 'Caganera', '41414141L', '', '222 222 222', 'caganera@pesebre.cat', '', 'C/ De l’Hort, 4', 'Olot', 'Girona', '17800', 'ES', array['pesebre', 'persona']); -select add_contact (123, 'Bou', '41414142C', '', '333 333 333', 'bou@pesebre.cat', '', 'C/ De la Palla, 5', 'Sant Climent Sescebes', 'Girona', '17751', 'ES', array['pesebre', 'bestia']); -select add_contact (123, 'Rabadà', '41414143K', '', '444 444 444', 'rabada@pesebre.cat', '', 'C/ De les Ovelles, 6', 'Fornells de la Selva', 'Girona', '17458', 'ES', array['pesebre', 'persona']); +select add_contact (123, 'Melcior', '0732621', 'melcio@reismags.cat', '', '(Rei Blanc,1,"C/ Principal, 1",Shiraz,Fars,1,IR)', array['pesebre', 'mag']); +select add_contact (123, 'Gaspar', '111', 'gaspar@reismags.cat', '', '(Rei Ros,2,"C/ Principal, 2",Nova Delhi,Delhi,2,IN)', array['pesebre', 'mag']); +select add_contact (123, 'Baltasar', '1-111-111', 'baltasar@reismags.cat', '', '(Rei Negre,3,"C/ Principal, 3",Sanaa,Sanaa,3,YE)', array['pesebre', 'mag']); +select add_contact (123, 'Caganera', '222 222 222', 'caganera@pesebre.cat', '', '(Caganera,41414141L,"C/ De l’Hort, 4",Olot,Girona,17800,ES)', array['pesebre', 'persona']); +select add_contact (123, 'Bou', '333 333 333', 'bou@pesebre.cat', '', '(Bou,41414142C,"C/ De la Palla, 5",Sant Climent Sescebes,Girona,17751,ES)', array['pesebre', 'bestia']); +select add_contact (123, 'Rabadà', '444 444 444', 'rabada@pesebre.cat', '', '(Rabadà,41414143K,"C/ De les Ovelles, 6",Fornells de la Selva,Girona,17458,ES)', array['pesebre', 'persona']); alter sequence product_product_id_seq restart with 123; select add_product(123, 'Or', 'Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.', '55.92', array[124], array['metall']); diff --git a/deploy/add_contact.sql b/deploy/add_contact.sql index 86d54fb..67f5057 100644 --- a/deploy/add_contact.sql +++ b/deploy/add_contact.sql @@ -7,21 +7,47 @@ -- requires: country_code -- requires: contact -- requires: tag_name +-- requires: tax_details begin; set search_path to numerus, public; -create or replace function add_contact(company_id integer, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +create or replace function add_contact(company_id integer, name text, phone text, email email, web uri, tax_details tax_details, tags tag_name[]) returns uuid as $$ declare cid integer; cslug uuid; begin - insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, tags) - values (add_contact.company_id, add_contact.business_name, (add_contact.country_code || add_contact.vatin)::vatin, add_contact.trade_name, parse_packed_phone_number(add_contact.phone, add_contact.country_code), add_contact.email, add_contact.web, add_contact.address, add_contact.city, add_contact.province, add_contact.postal_code, add_contact.country_code, add_contact.tags) + insert into contact (company_id, name, tags) + values (add_contact.company_id, add_contact.name, add_contact.tags) returning contact_id, slug - into cid, cslug; + into cid, cslug + ; + + if tax_details is not null then + insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) + values (cid, tax_details.business_name, (tax_details.country_code || tax_details.vatin)::vatin, tax_details.address, tax_details.city, tax_details.province, tax_details.postal_code, tax_details.country_code) + ; + end if; + + if phone is not null and trim(phone) <> '' then + insert into contact_phone (contact_id, phone) + values (cid, parse_packed_phone_number(add_contact.phone, coalesce(tax_details.country_code, 'ES'))) + ; + end if; + + if email is not null and trim(email) <> '' then + insert into contact_email (contact_id, email) + values (cid, add_contact.email) + ; + end if; + + if web is not null and web <> '' then + insert into contact_web (contact_id, uri) + values (cid, add_contact.web) + ; + end if; return cslug; end @@ -29,8 +55,10 @@ $$ language plpgsql ; -revoke execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; -grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; -grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; +revoke execute on function add_contact(integer, text, text, email, uri, tax_details, tag_name[]) from public; +grant execute on function add_contact(integer, text, text, email, uri, tax_details, tag_name[]) to invoicer; +grant execute on function add_contact(integer, text, text, email, uri, tax_details, tag_name[]) to admin; + +drop function if exists add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]); commit; diff --git a/deploy/add_contact@v0.sql b/deploy/add_contact@v0.sql new file mode 100644 index 0000000..86d54fb --- /dev/null +++ b/deploy/add_contact@v0.sql @@ -0,0 +1,36 @@ +-- Deploy numerus:add_contact to pg +-- requires: schema_numerus +-- requires: extension_vat +-- requires: email +-- requires: extension_pg_libphonenumber +-- requires: extension_uri +-- requires: country_code +-- requires: contact +-- requires: tag_name + +begin; + +set search_path to numerus, public; + +create or replace function add_contact(company_id integer, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +$$ +declare + cid integer; + cslug uuid; +begin + insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, tags) + values (add_contact.company_id, add_contact.business_name, (add_contact.country_code || add_contact.vatin)::vatin, add_contact.trade_name, parse_packed_phone_number(add_contact.phone, add_contact.country_code), add_contact.email, add_contact.web, add_contact.address, add_contact.city, add_contact.province, add_contact.postal_code, add_contact.country_code, add_contact.tags) + returning contact_id, slug + into cid, cslug; + + return cslug; +end +$$ + language plpgsql +; + +revoke execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; +grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; +grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; + +commit; diff --git a/deploy/contact_email.sql b/deploy/contact_email.sql new file mode 100644 index 0000000..39d54ee --- /dev/null +++ b/deploy/contact_email.sql @@ -0,0 +1,39 @@ +-- Deploy numerus:contact_email to pg +-- requires: roles +-- requires: schema_numerus +-- requires: email +-- requires: contact + +begin; + +set search_path to numerus, public; + +create table contact_email ( + contact_id integer primary key references contact, + email email not null +); + +grant select, insert, update, delete on table contact_email to invoicer; +grant select, insert, update, delete on table contact_email to admin; + +alter table contact_email enable row level security; + +create policy company_policy +on contact_email +using ( + exists( + select 1 + from contact + where contact.contact_id = contact_email.contact_id + ) +); + +insert into contact_email +select contact_id, email +from contact; + +alter table contact + drop column email +; + +commit; diff --git a/deploy/contact_phone.sql b/deploy/contact_phone.sql new file mode 100644 index 0000000..2439d63 --- /dev/null +++ b/deploy/contact_phone.sql @@ -0,0 +1,38 @@ +-- Deploy numerus:contact_phone to pg +-- requires: roles +-- requires: schema_numerus +-- requires: extension_pg_libphonenumber + +begin; + +set search_path to numerus, public; + +create table contact_phone ( + contact_id integer primary key references contact, + phone packed_phone_number not null +); + +grant select, insert, update, delete on table contact_phone to invoicer; +grant select, insert, update, delete on table contact_phone to admin; + +alter table contact_phone enable row level security; + +create policy company_policy +on contact_phone +using ( + exists( + select 1 + from contact + where contact.contact_id = contact_phone.contact_id + ) +); + +insert into contact_phone +select contact_id, phone +from contact; + +alter table contact + drop column phone +; + +commit; diff --git a/deploy/contact_tax_details.sql b/deploy/contact_tax_details.sql new file mode 100644 index 0000000..a994d57 --- /dev/null +++ b/deploy/contact_tax_details.sql @@ -0,0 +1,60 @@ +-- Deploy numerus:contact_tax_details to pg +-- requires: roles +-- requires: schema_numerus +-- requires: contact +-- requires: extension_vat +-- requires: country_code +-- requires: country + +begin; + +set search_path to numerus, public; + +create table contact_tax_details ( + contact_id integer primary key references contact, + business_name text not null constraint business_name_not_empty check(length(trim(business_name)) > 1), + vatin vatin not null, + address text not null, + city text not null, + province text not null, + postal_code text not null, + country_code country_code not null references country +); + +alter table contact_tax_details enable row level security; + +create policy company_policy +on contact_tax_details +using ( + exists( + select 1 + from contact + where contact.contact_id = contact_tax_details.contact_id + ) +); + +grant select, insert, update, delete on table contact_tax_details to invoicer; +grant select, insert, update, delete on table contact_tax_details to admin; + +insert into contact_tax_details +select contact_id, business_name, vatin, address, city, province, postal_code, country_code +from contact; + +update contact set trade_name = business_name where trade_name = ''; + +alter table contact + rename column trade_name to name +; + +alter table contact + drop column business_name +, drop column vatin +, drop column address +, drop column city +, drop column province +, drop column postal_code +, drop column country_code +, add constraint name_not_empty check(length(trim(name)) > 1) +; + +commit; diff --git a/deploy/contact_web.sql b/deploy/contact_web.sql new file mode 100644 index 0000000..e10332c --- /dev/null +++ b/deploy/contact_web.sql @@ -0,0 +1,39 @@ +-- Deploy numerus:contact_web to pg +-- requires: roles +-- requires: schema_numerus +-- requires: extension_uri +-- requires: contact + +begin; + +set search_path to numerus, public; + +create table contact_web ( + contact_id integer primary key references contact, + uri uri not null +); + +grant select, insert, update, delete on table contact_web to invoicer; +grant select, insert, update, delete on table contact_web to admin; + +alter table contact_web enable row level security; + +create policy company_policy +on contact_web +using ( + exists( + select 1 + from contact + where contact.contact_id = contact_web.contact_id + ) +); + +insert into contact_web +select contact_id, web +from contact; + +alter table contact + drop column web +; + +commit; diff --git a/deploy/edit_contact.sql b/deploy/edit_contact.sql index 90e3908..32bb75d 100644 --- a/deploy/edit_contact.sql +++ b/deploy/edit_contact.sql @@ -7,29 +7,20 @@ -- requires: contact -- requires: extension_vat -- requires: extension_pg_libphonenumber +-- requires: tax_details begin; set search_path to numerus, public; -create or replace function edit_contact(contact_slug uuid, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +create or replace function edit_contact(contact_slug uuid, name text, phone text, email email, web uri, tax_details tax_details, tags tag_name[]) returns uuid as $$ declare cid integer; company integer; begin update contact - set business_name = edit_contact.business_name - , vatin = (edit_contact.country_code || edit_contact.vatin)::vatin - , trade_name = edit_contact.trade_name - , phone = parse_packed_phone_number( edit_contact.phone, edit_contact.country_code) - , email = edit_contact.email - , web = edit_contact.web - , address = edit_contact.address - , city = edit_contact.city - , province = edit_contact.province - , postal_code = edit_contact.postal_code - , country_code = edit_contact.country_code + set name = edit_contact.name , tags = edit_contact.tags where slug = contact_slug returning contact_id, company_id @@ -40,14 +31,72 @@ begin return null; end if; + if tax_details is null then + delete + from contact_tax_details + where contact_id = cid + ; + else + insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) + values (cid, tax_details.business_name, (tax_details.country_code || tax_details.vatin)::vatin, tax_details.address, tax_details.city, tax_details.province, tax_details.postal_code, tax_details.country_code) + on conflict (contact_id) do update + set business_name = excluded.business_name + , vatin = excluded.vatin + , address = excluded.address + , city = excluded.city + , province = excluded.province + , postal_code = excluded.postal_code + , country_code = excluded.country_code + ; + end if; + + if phone is null or trim(phone) = '' then + delete from contact_phone + where contact_id = cid + ; + else + insert into contact_phone (contact_id, phone) + values (cid, parse_packed_phone_number(edit_contact.phone, coalesce(tax_details.country_code, 'ES'))) + on conflict (contact_id) do update + set phone = excluded.phone + ; + end if; + + if email is null or trim(email) = '' then + delete from contact_email + where contact_id = cid + ; + else + insert into contact_email (contact_id, email) + values (cid, edit_contact.email) + on conflict (contact_id) do update + set email = excluded.email + ; + end if; + + if web is null or web = '' then + delete from contact_web + where contact_id = cid + ; + else + insert into contact_web (contact_id, uri) + values (cid, edit_contact.web) + on conflict (contact_id) do update + set uri = excluded.uri + ; + end if; + return contact_slug; end $$ language plpgsql ; -revoke execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; -grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; -grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; +revoke execute on function edit_contact(uuid, text, text, email, uri, tax_details, tag_name[]) from public; +grant execute on function edit_contact(uuid, text, text, email, uri, tax_details, tag_name[]) to invoicer; +grant execute on function edit_contact(uuid, text, text, email, uri, tax_details, tag_name[]) to admin; + + +drop function if exists edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]); commit; diff --git a/deploy/edit_contact@v0.sql b/deploy/edit_contact@v0.sql new file mode 100644 index 0000000..90e3908 --- /dev/null +++ b/deploy/edit_contact@v0.sql @@ -0,0 +1,53 @@ +-- Deploy numerus:edit_contact to pg +-- requires: schema_numerus +-- requires: email +-- requires: extension_uri +-- requires: country_code +-- requires: tag_name +-- requires: contact +-- requires: extension_vat +-- requires: extension_pg_libphonenumber + +begin; + +set search_path to numerus, public; + +create or replace function edit_contact(contact_slug uuid, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +$$ +declare + cid integer; + company integer; +begin + update contact + set business_name = edit_contact.business_name + , vatin = (edit_contact.country_code || edit_contact.vatin)::vatin + , trade_name = edit_contact.trade_name + , phone = parse_packed_phone_number( edit_contact.phone, edit_contact.country_code) + , email = edit_contact.email + , web = edit_contact.web + , address = edit_contact.address + , city = edit_contact.city + , province = edit_contact.province + , postal_code = edit_contact.postal_code + , country_code = edit_contact.country_code + , tags = edit_contact.tags + where slug = contact_slug + returning contact_id, company_id + into cid, company + ; + + if cid is null then + return null; + end if; + + return contact_slug; +end +$$ + language plpgsql +; + +revoke execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; +grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; +grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; + +commit; diff --git a/deploy/invoice_contact_id_fkey.sql b/deploy/invoice_contact_id_fkey.sql new file mode 100644 index 0000000..529f4c9 --- /dev/null +++ b/deploy/invoice_contact_id_fkey.sql @@ -0,0 +1,13 @@ +-- Deploy numerus:invoice_contact_id_fkey to pg +-- requires: schema_numerus +-- requires: invoice +-- requires: contact_tax_details + +begin; + +alter table numerus.invoice + drop constraint invoice_contact_id_fkey +, add foreign key (contact_id) references numerus.contact_tax_details (contact_id) +; + +commit; diff --git a/deploy/tax_details.sql b/deploy/tax_details.sql new file mode 100644 index 0000000..9e8db75 --- /dev/null +++ b/deploy/tax_details.sql @@ -0,0 +1,20 @@ +-- Deploy numerus:tax_details to pg +-- requires: schema_numerus +-- requires: extension_vat +-- requires: country_code + +begin; + +set search_path to numerus, public; + +create type tax_details as ( + business_name text, + vatin text, + address text, + city text, + province text, + postal_code text, + country_code country_code +); + +commit; diff --git a/pkg/company.go b/pkg/company.go index 35aca21..8c154f6 100644 --- a/pkg/company.go +++ b/pkg/company.go @@ -85,7 +85,18 @@ type PaymentMethod struct { } type taxDetailsForm struct { - *contactForm + locale *Locale + TradeName *InputField + BusinessName *InputField + VATIN *InputField + Phone *InputField + Email *InputField + Web *InputField + Address *InputField + City *InputField + Province *InputField + PostalCode *InputField + Country *SelectField Currency *SelectField InvoiceNumberFormat *InputField NextInvoiceNumber *InputField @@ -96,7 +107,94 @@ type taxDetailsForm struct { func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDetailsForm { return &taxDetailsForm{ - contactForm: newContactForm(ctx, conn, locale), + locale: locale, + TradeName: &InputField{ + Name: "trade_name", + Label: pgettext("input", "Trade name", locale), + Type: "text", + }, + Phone: &InputField{ + Name: "phone", + Label: pgettext("input", "Phone", locale), + Type: "tel", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="tel"`, + }, + }, + Email: &InputField{ + Name: "email", + Label: pgettext("input", "Email", locale), + Type: "email", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="email"`, + }, + }, + Web: &InputField{ + Name: "web", + Label: pgettext("input", "Web", locale), + Type: "url", + Attributes: []template.HTMLAttr{ + `autocomplete="url"`, + }, + }, + BusinessName: &InputField{ + Name: "business_name", + Label: pgettext("input", "Business name", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="organization"`, + `minlength="2"`, + }, + }, + VATIN: &InputField{ + Name: "vatin", + Label: pgettext("input", "VAT number", locale), + Type: "text", + Required: true, + }, + Address: &InputField{ + Name: "address", + Label: pgettext("input", "Address", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="address-line1"`, + }, + }, + City: &InputField{ + Name: "city", + Label: pgettext("input", "City", locale), + Type: "text", + Required: true, + }, + Province: &InputField{ + Name: "province", + Label: pgettext("input", "Province", locale), + Type: "text", + Required: true, + }, + PostalCode: &InputField{ + Name: "postal_code", + Label: pgettext("input", "Postal code", locale), + Type: "text", + Required: true, + Attributes: []template.HTMLAttr{ + `autocomplete="postal-code"`, + }, + }, + Country: &SelectField{ + Name: "country", + Label: pgettext("input", "Country", locale), + Options: mustGetCountryOptions(ctx, conn, locale), + Required: true, + Selected: []string{"ES"}, + Attributes: []template.HTMLAttr{ + `autocomplete="country"`, + }, + }, Currency: &SelectField{ Name: "currency", Label: pgettext("input", "Currency", locale), @@ -143,9 +241,20 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta } func (form *taxDetailsForm) Parse(r *http.Request) error { - if err := form.contactForm.Parse(r); err != nil { + if err := r.ParseForm(); err != nil { return err } + form.TradeName.FillValue(r) + form.BusinessName.FillValue(r) + form.VATIN.FillValue(r) + form.Phone.FillValue(r) + form.Email.FillValue(r) + form.Web.FillValue(r) + form.Address.FillValue(r) + form.City.FillValue(r) + form.Province.FillValue(r) + form.PostalCode.FillValue(r) + form.Country.FillValue(r) form.Currency.FillValue(r) form.InvoiceNumberFormat.FillValue(r) form.NextInvoiceNumber.FillValue(r) @@ -157,12 +266,39 @@ func (form *taxDetailsForm) Parse(r *http.Request) error { func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool { validator := newFormValidator() + + country := "" + if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) { + country = form.Country.Selected[0] + } + + validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale)) + validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale)) + if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) { + validator.CheckValidVATINInput(form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale)) + } + if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) { + validator.CheckValidPhoneInput(form.Phone, country, gettext("This value is not a valid phone number.", form.locale)) + } + if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) { + validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale)) + } + if form.Web.Val != "" { + validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale)) + } + validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale)) + validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale)) + validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale)) + if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) { + validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale)) + } validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale)) validator.CheckRequiredInput(form.InvoiceNumberFormat, gettext("Invoice number format can not be empty.", form.locale)) validator.CheckValidInteger(form.NextInvoiceNumber, 1, math.MaxInt32, gettext("Next invoice number must be a number greater than zero.", form.locale)) validator.CheckRequiredInput(form.QuoteNumberFormat, gettext("Quotation number format can not be empty.", form.locale)) validator.CheckValidInteger(form.NextQuoteNumber, 1, math.MaxInt32, gettext("Next quotation number must be a number greater than zero.", form.locale)) - return form.contactForm.Validate(ctx, conn) && validator.AllOK() + + return validator.AllOK() } func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm { diff --git a/pkg/contacts.go b/pkg/contacts.go index 7b969e6..77e06a5 100644 --- a/pkg/contacts.go +++ b/pkg/contacts.go @@ -69,7 +69,7 @@ type editContactPage struct { func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, slug string, form *contactForm) { page := &editContactPage{ Slug: slug, - ContactName: form.BusinessName.Val, + ContactName: form.Name.String(), Form: form, } mustRenderMainTemplate(w, r, "contacts/edit.gohtml", page) @@ -95,7 +95,7 @@ func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Param return } company := mustGetCompany(r) - conn.MustExec(r.Context(), "select add_contact($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Tags) + conn.MustExec(r.Context(), "select add_contact($1, $2, $3, $4, $5, $6, $7)", company.Id, form.Name, form.Phone.ValueOrNil(), form.Email.ValueOrNil(), form.Web.ValueOrNil(), form.TaxDetails(), form.Tags) htmxRedirect(w, r, companyURI(company, "/contacts")) } @@ -115,7 +115,7 @@ func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprout mustRenderEditContactForm(w, r, params[0].Value, form) return } - slug := conn.MustGetText(r.Context(), "", "select edit_contact($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", params[0].Value, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Tags) + slug := conn.MustGetText(r.Context(), "", "select edit_contact($1, $2, $3, $4, $5, $6, $7)", params[0].Value, form.Name, form.Phone.ValueOrNil(), form.Email.ValueOrNil(), form.Web.ValueOrNil(), form.TaxDetails(), form.Tags) if slug == "" { http.NotFound(w, r) } @@ -177,7 +177,7 @@ func mustCollectContactEntries(ctx context.Context, conn *Conn, company *Company if filters != nil { name := strings.TrimSpace(filters.Name.String()) if name != "" { - appendWhere("contact.business_name ilike $%d", "%"+name+"%") + appendWhere("contact.name ilike $%d", "%"+name+"%") } if len(filters.Tags.Tags) > 0 { if filters.TagsCondition.Selected == "and" { @@ -189,13 +189,15 @@ func mustCollectContactEntries(ctx context.Context, conn *Conn, company *Company } rows := conn.MustQuery(ctx, fmt.Sprintf(` select slug - , business_name - , email - , phone + , name + , coalesce(email::text, '') + , coalesce(phone::text, '') , tags from contact + left join contact_email using (contact_id) + left join contact_phone using (contact_id) where (%s) - order by business_name + order by name `, strings.Join(where, ") AND (")), args...) defer rows.Close() @@ -215,24 +217,59 @@ func mustCollectContactEntries(ctx context.Context, conn *Conn, company *Company } type contactForm struct { - locale *Locale - BusinessName *InputField - VATIN *InputField - TradeName *InputField - Phone *InputField - Email *InputField - Web *InputField - Address *InputField - City *InputField - Province *InputField - PostalCode *InputField - Country *SelectField - Tags *TagsField + locale *Locale + Name *InputField + HasTaxDetails *CheckField + BusinessName *InputField + VATIN *InputField + Phone *InputField + Email *InputField + Web *InputField + Address *InputField + City *InputField + Province *InputField + PostalCode *InputField + Country *SelectField + Tags *TagsField } func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactForm { return &contactForm{ locale: locale, + Name: &InputField{ + Name: "name", + Label: pgettext("input", "Name", locale), + Type: "text", + Required: true, + }, + Phone: &InputField{ + Name: "phone", + Label: pgettext("input", "Phone", locale), + Type: "tel", + Attributes: []template.HTMLAttr{ + `autocomplete="tel"`, + }, + }, + Email: &InputField{ + Name: "email", + Label: pgettext("input", "Email", locale), + Type: "email", + Attributes: []template.HTMLAttr{ + `autocomplete="email"`, + }, + }, + Web: &InputField{ + Name: "web", + Label: pgettext("input", "Web", locale), + Type: "url", + Attributes: []template.HTMLAttr{ + `autocomplete="url"`, + }, + }, + HasTaxDetails: &CheckField{ + Name: "has_tax_details", + Label: pgettext("input", "Need to input tax details", locale), + }, BusinessName: &InputField{ Name: "business_name", Label: pgettext("input", "Business name", locale), @@ -249,37 +286,6 @@ func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactFor Type: "text", Required: true, }, - TradeName: &InputField{ - Name: "trade_name", - Label: pgettext("input", "Trade name", locale), - Type: "text", - }, - Phone: &InputField{ - Name: "phone", - Label: pgettext("input", "Phone", locale), - Type: "tel", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="tel"`, - }, - }, - Email: &InputField{ - Name: "email", - Label: pgettext("input", "Email", locale), - Type: "email", - Required: true, - Attributes: []template.HTMLAttr{ - `autocomplete="email"`, - }, - }, - Web: &InputField{ - Name: "web", - Label: pgettext("input", "Web", locale), - Type: "url", - Attributes: []template.HTMLAttr{ - `autocomplete="url"`, - }, - }, Address: &InputField{ Name: "address", Label: pgettext("input", "Address", locale), @@ -331,9 +337,10 @@ func (form *contactForm) Parse(r *http.Request) error { if err := r.ParseForm(); err != nil { return err } + form.Name.FillValue(r) + form.HasTaxDetails.FillValue(r) form.BusinessName.FillValue(r) form.VATIN.FillValue(r) - form.TradeName.FillValue(r) form.Phone.FillValue(r) form.Email.FillValue(r) form.Web.FillValue(r) @@ -349,42 +356,50 @@ func (form *contactForm) Parse(r *http.Request) error { func (form *contactForm) Validate(ctx context.Context, conn *Conn) bool { validator := newFormValidator() - country := "" - if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) { - country = form.Country.Selected[0] + country := "ES" + if form.HasTaxDetails.Checked { + if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) { + country = form.Country.Selected[0] + } + validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale)) + validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale)) + if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) { + validator.CheckValidVATINInput(form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale)) + } + validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale)) + validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale)) + validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale)) + + if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) { + validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale)) + } } - validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale)) - validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale)) - if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) { - validator.CheckValidVATINInput(form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale)) - } - if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) { + validator.CheckRequiredInput(form.Name, gettext("Name can not be empty.", form.locale)) + validator.CheckInputMinLength(form.Name, 2, gettext("Name must have at least two letters.", form.locale)) + + if form.Phone.Val != "" { validator.CheckValidPhoneInput(form.Phone, country, gettext("This value is not a valid phone number.", form.locale)) } - if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) { + if form.Email.Val != "" { validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale)) } if form.Web.Val != "" { validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale)) } - validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale)) - validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale)) - validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale)) - if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) { - validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale)) - } + return validator.AllOK() } func (form *contactForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool { return !notFoundErrorOrPanic(conn.QueryRow(ctx, ` - select business_name + select name + , vatin is not null + , business_name , substr(vatin::text, 3) - , trade_name , phone , email - , web + , uri , address , city , province @@ -392,11 +407,16 @@ func (form *contactForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s , country_code , tags from contact + left join contact_email using (contact_id) + left join contact_phone using (contact_id) + left join contact_web using (contact_id) + left join contact_tax_details using (contact_id) where slug = $1 `, slug).Scan( + form.Name, + form.HasTaxDetails, form.BusinessName, form.VATIN, - form.TradeName, form.Phone, form.Email, form.Web, @@ -408,6 +428,21 @@ func (form *contactForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s form.Tags)) } +func (form *contactForm) TaxDetails() *CustomerTaxDetails { + if !form.HasTaxDetails.Checked { + return nil + } + return &CustomerTaxDetails{ + BusinessName: form.BusinessName.String(), + VATIN: form.VATIN.String(), + Address: form.Address.String(), + City: form.City.String(), + Province: form.Province.String(), + PostalCode: form.PostalCode.String(), + CountryCode: form.Country.String(), + } +} + func ServeEditContactTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) { conn := getConn(r) locale := getLocale(r) diff --git a/pkg/expenses.go b/pkg/expenses.go index e821add..e882603 100644 --- a/pkg/expenses.go +++ b/pkg/expenses.go @@ -52,7 +52,7 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, company *Company , invoice_date , invoice_number , to_price(amount, decimal_digits) - , contact.business_name + , contact.name , coalesce(attachment.original_filename, '') , expense.tags from expense diff --git a/pkg/form.go b/pkg/form.go index 6b6b895..35c9719 100644 --- a/pkg/form.go +++ b/pkg/form.go @@ -59,6 +59,13 @@ func (field *InputField) Value() (driver.Value, error) { return field.Val, nil } +func (field *InputField) ValueOrNil() driver.Valuer { + if field.Val == "" { + return nil + } + return field +} + func (field *InputField) FillValue(r *http.Request) { field.Val = strings.TrimSpace(r.FormValue(field.Name)) } @@ -287,6 +294,37 @@ func (field *RadioField) isValidOption(selected string) bool { return field.FindOption(selected) != nil } +type CheckField struct { + Name string + Label string + Checked bool + Attributes []template.HTMLAttr + Required bool + Errors []error +} + +func (field *CheckField) FillValue(r *http.Request) { + field.Checked = len(r.Form[field.Name]) > 0 +} + +func (field *CheckField) Scan(value interface{}) error { + if value == nil { + field.Checked = false + return nil + } + switch v := value.(type) { + case bool: + field.Checked = v + default: + field.Checked, _ = strconv.ParseBool(fmt.Sprintf("%v", v)) + } + return nil +} + +func (field *CheckField) Value() (driver.Value, error) { + return field.Checked, nil +} + type FileField struct { Name string Label string diff --git a/pkg/invoices.go b/pkg/invoices.go index 2a672c6..a6120e2 100644 --- a/pkg/invoices.go +++ b/pkg/invoices.go @@ -64,7 +64,7 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, locale *Locale, select invoice.slug , invoice_date , invoice_number - , contact.business_name + , contact.name , invoice.tags , invoice.invoice_status , isi18n.name @@ -379,7 +379,41 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri } var invoiceId int var decimalDigits int - if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, decimal_digits, invoice_number, invoice_date, notes, instructions, business_name, vatin, phone, email, address, city, province, postal_code, to_price(subtotal, decimal_digits), to_price(total, decimal_digits) from invoice join payment_method using (payment_method_id) join contact using (contact_id) join invoice_amount using (invoice_id) join currency using (currency_code) where invoice.slug = $1", slug).Scan(&invoiceId, &decimalDigits, &inv.Number, &inv.Date, &inv.Notes, &inv.PaymentInstructions, &inv.Invoicee.Name, &inv.Invoicee.VATIN, &inv.Invoicee.Phone, &inv.Invoicee.Email, &inv.Invoicee.Address, &inv.Invoicee.City, &inv.Invoicee.Province, &inv.Invoicee.PostalCode, &inv.Subtotal, &inv.Total)) { + if notFoundErrorOrPanic(conn.QueryRow(ctx, ` + select invoice_id + , decimal_digits + , invoice_number + , invoice_date + , notes + , instructions + , business_name + , vatin + , address + , city + , province + , postal_code + , to_price(subtotal, decimal_digits) + , to_price(total, decimal_digits) + from invoice + join payment_method using (payment_method_id) + join contact_tax_details using (contact_id) + join invoice_amount using (invoice_id) + join currency using (currency_code) + where invoice.slug = $1`, slug).Scan( + &invoiceId, + &decimalDigits, + &inv.Number, + &inv.Date, + &inv.Notes, + &inv.PaymentInstructions, + &inv.Invoicee.Name, + &inv.Invoicee.VATIN, + &inv.Invoicee.Address, + &inv.Invoicee.City, + &inv.Invoicee.Province, + &inv.Invoicee.PostalCode, + &inv.Subtotal, + &inv.Total)) { return nil } if err := conn.QueryRow(ctx, "select business_name, vatin, phone, email, address, city, province, postal_code, legal_disclaimer from company where company_id = $1", company.Id).Scan(&inv.Invoicer.Name, &inv.Invoicer.VATIN, &inv.Invoicer.Phone, &inv.Invoicer.Email, &inv.Invoicer.Address, &inv.Invoicer.City, &inv.Invoicer.Province, &inv.Invoicer.PostalCode, &inv.LegalDisclaimer); err != nil { @@ -605,7 +639,7 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co Name: "customer", Label: pgettext("input", "Customer", locale), Required: true, - Options: mustGetContactOptions(ctx, conn, company), + Options: mustGetCustomerOptions(ctx, conn, company), }, Date: &InputField{ Name: "date", @@ -826,7 +860,11 @@ func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*Sel } func mustGetContactOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption { - return MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id) + return MustGetOptions(ctx, conn, "select contact_id::text, name from contact where company_id = $1 order by name", company.Id) +} + +func mustGetCustomerOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption { + return MustGetOptions(ctx, conn, "select contact_id::text, name from contact join contact_tax_details using (contact_id) where company_id = $1 order by name", company.Id) } func mustGetDefaultPaymentMethod(ctx context.Context, conn *Conn, company *Company) string { diff --git a/pkg/pgtypes.go b/pkg/pgtypes.go index 422e948..25326a5 100644 --- a/pkg/pgtypes.go +++ b/pkg/pgtypes.go @@ -7,6 +7,38 @@ import ( "github.com/jackc/pgx/v4" ) +type CustomerTaxDetails struct { + BusinessName string + VATIN string + Address string + City string + Province string + PostalCode string + CountryCode string +} + +func (src CustomerTaxDetails) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { + typeName := "tax_details" + dt, ok := ci.DataTypeForName(typeName) + if !ok { + return nil, fmt.Errorf("unable to find oid for type name %v", typeName) + } + values := []interface{}{ + src.BusinessName, + src.VATIN, + src.Address, + src.City, + src.Province, + src.PostalCode, + src.CountryCode, + } + ct := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder) + if err := ct.Set(values); err != nil { + return nil, err + } + return ct.EncodeBinary(ci, buf) +} + type NewInvoiceProductArray []*invoiceProductForm func (src NewInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { @@ -262,6 +294,32 @@ func registerPgTypes(ctx context.Context, conn *pgx.Conn) error { return err } + countryCodeOID, err := registerPgType(ctx, conn, &pgtype.Text{}, "country_code") + if err != nil { + return err + } + + taxDetailsType, err := pgtype.NewCompositeType( + "tax_details", + []pgtype.CompositeTypeField{ + {"business_name", pgtype.TextOID}, + {"vatin", pgtype.TextOID}, + {"address", pgtype.TextOID}, + {"city", pgtype.TextOID}, + {"province", pgtype.TextOID}, + {"postal_code", pgtype.TextOID}, + {"discount_rate", countryCodeOID}, + }, + conn.ConnInfo(), + ) + if err != nil { + return err + } + _, err = registerPgType(ctx, conn, taxDetailsType, taxDetailsType.TypeName()) + if err != nil { + return err + } + _, err = conn.Exec(ctx, "reset role") return err } diff --git a/pkg/quote.go b/pkg/quote.go index c36b629..e7fd893 100644 --- a/pkg/quote.go +++ b/pkg/quote.go @@ -62,7 +62,7 @@ func mustCollectQuoteEntries(ctx context.Context, conn *Conn, locale *Locale, fi select quote.slug , quote_date , quote_number - , coalesce(contact.business_name, '') + , coalesce(contact.name, '') , quote.tags , quote.quote_status , isi18n.name @@ -333,6 +333,7 @@ type quote struct { Date time.Time Quoter taxDetails HasQuotee bool + HasTaxDetails bool Quotee taxDetails TermsAndConditions string Notes string @@ -372,10 +373,9 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string , notes , coalesce(instructions, '') , contact_id is not null - , coalesce(business_name, '') + , coalesce(business_name, contact.name, '') + , contact_tax_details.contact_id is not null , coalesce(vatin::text, '') - , coalesce(phone::text, '') - , coalesce(email, '') , coalesce(address, '') , coalesce(city, '') , coalesce(province, '') @@ -387,6 +387,7 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string left join payment_method using (payment_method_id) left join quote_contact using (quote_id) left join contact using (contact_id) + left join contact_tax_details using (contact_id) join quote_amount using (quote_id) join currency using (currency_code) where quote.slug = $1`, slug).Scan( @@ -399,9 +400,8 @@ func mustGetQuote(ctx context.Context, conn *Conn, company *Company, slug string &quo.PaymentInstructions, &quo.HasQuotee, &quo.Quotee.Name, + &quo.HasTaxDetails, &quo.Quotee.VATIN, - &quo.Quotee.Phone, - &quo.Quotee.Email, &quo.Quotee.Address, &quo.Quotee.City, &quo.Quotee.Province, diff --git a/po/ca.po b/po/ca.po index a93e276..68340ea 100644 --- a/po/ca.po +++ b/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-06-20 11:35+0200\n" +"POT-Creation-Date: 2023-06-30 21:08+0200\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -65,7 +65,7 @@ msgstr "Nom" #: web/template/invoices/products.gohtml:50 #: web/template/invoices/view.gohtml:62 web/template/quotes/products.gohtml:50 -#: web/template/quotes/view.gohtml:71 web/template/products/index.gohtml:42 +#: web/template/quotes/view.gohtml:73 web/template/products/index.gohtml:42 msgctxt "title" msgid "Price" msgstr "Preu" @@ -95,15 +95,15 @@ msgstr "Desfes" #: web/template/invoices/new.gohtml:60 web/template/invoices/view.gohtml:67 #: web/template/invoices/edit.gohtml:61 web/template/quotes/new.gohtml:61 -#: web/template/quotes/view.gohtml:76 web/template/quotes/edit.gohtml:62 +#: web/template/quotes/view.gohtml:78 web/template/quotes/edit.gohtml:62 msgctxt "title" msgid "Subtotal" msgstr "Subtotal" #: web/template/invoices/new.gohtml:70 web/template/invoices/view.gohtml:71 #: web/template/invoices/view.gohtml:111 web/template/invoices/edit.gohtml:71 -#: web/template/quotes/new.gohtml:71 web/template/quotes/view.gohtml:80 -#: web/template/quotes/view.gohtml:120 web/template/quotes/edit.gohtml:72 +#: web/template/quotes/new.gohtml:71 web/template/quotes/view.gohtml:82 +#: web/template/quotes/view.gohtml:122 web/template/quotes/edit.gohtml:72 msgctxt "title" msgid "Total" msgstr "Total" @@ -116,7 +116,7 @@ msgstr "Actualitza" #: web/template/invoices/new.gohtml:90 web/template/invoices/edit.gohtml:91 #: web/template/quotes/new.gohtml:91 web/template/quotes/edit.gohtml:92 -#: web/template/contacts/new.gohtml:39 web/template/contacts/edit.gohtml:43 +#: web/template/contacts/new.gohtml:44 web/template/contacts/edit.gohtml:48 #: web/template/expenses/new.gohtml:33 web/template/expenses/edit.gohtml:38 #: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36 msgctxt "action" @@ -230,22 +230,22 @@ msgctxt "action" msgid "Download invoice" msgstr "Descarrega factura" -#: web/template/invoices/view.gohtml:61 web/template/quotes/view.gohtml:70 +#: web/template/invoices/view.gohtml:61 web/template/quotes/view.gohtml:72 msgctxt "title" msgid "Concept" msgstr "Concepte" -#: web/template/invoices/view.gohtml:64 web/template/quotes/view.gohtml:73 +#: web/template/invoices/view.gohtml:64 web/template/quotes/view.gohtml:75 msgctxt "title" msgid "Discount" msgstr "Descompte" -#: web/template/invoices/view.gohtml:66 web/template/quotes/view.gohtml:75 +#: web/template/invoices/view.gohtml:66 web/template/quotes/view.gohtml:77 msgctxt "title" msgid "Units" msgstr "Unitats" -#: web/template/invoices/view.gohtml:101 web/template/quotes/view.gohtml:110 +#: web/template/invoices/view.gohtml:101 web/template/quotes/view.gohtml:112 msgctxt "title" msgid "Tax Base" msgstr "Base imposable" @@ -280,7 +280,7 @@ msgctxt "input" msgid "(Max. %s)" msgstr "(Màx. %s)" -#: web/template/form.gohtml:194 +#: web/template/form.gohtml:200 msgctxt "action" msgid "Filters" msgstr "Filtra" @@ -382,7 +382,7 @@ msgctxt "action" msgid "Download quotation" msgstr "Descarrega pressupost" -#: web/template/quotes/view.gohtml:63 +#: web/template/quotes/view.gohtml:65 msgid "Terms and Conditions:" msgstr "Condicions d’acceptació:" @@ -648,7 +648,7 @@ msgctxt "title" msgid "Edit Product “%s”" msgstr "Edició del producte «%s»" -#: pkg/login.go:37 pkg/profile.go:40 pkg/contacts.go:268 +#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:255 msgctxt "input" msgid "Email" msgstr "Correu-e" @@ -658,11 +658,11 @@ msgctxt "input" msgid "Password" msgstr "Contrasenya" -#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:365 +#: pkg/login.go:70 pkg/company.go:283 pkg/profile.go:89 msgid "Email can not be empty." msgstr "No podeu deixar el correu-e en blanc." -#: pkg/login.go:71 pkg/profile.go:90 pkg/contacts.go:366 +#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:385 msgid "This value is not a valid email. It should be like name@domain.com." msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat." @@ -674,16 +674,16 @@ msgstr "No podeu deixar la contrasenya en blanc." msgid "Invalid user or password." msgstr "Nom d’usuari o contrasenya incorrectes." -#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:871 -#: pkg/contacts.go:135 +#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909 +#: pkg/contacts.go:135 pkg/contacts.go:241 msgctxt "input" msgid "Name" msgstr "Nom" #: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630 #: pkg/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174 -#: pkg/invoices.go:623 pkg/invoices.go:1170 pkg/contacts.go:140 -#: pkg/contacts.go:325 +#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:140 +#: pkg/contacts.go:331 msgctxt "input" msgid "Tags" msgstr "Etiquetes" @@ -716,147 +716,250 @@ msgstr "Qualsevol" msgid "Invoices must have at least one of the specified labels." msgstr "Les factures han de tenir com a mínim una de les etiquetes." -#: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:885 +#: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:923 msgctxt "input" msgid "Description" msgstr "Descripció" -#: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:889 +#: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:927 msgctxt "input" msgid "Price" msgstr "Preu" -#: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:918 +#: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:956 msgctxt "input" msgid "Taxes" msgstr "Imposts" -#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:967 +#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005 +#: pkg/contacts.go:378 msgid "Name can not be empty." msgstr "No podeu deixar el nom en blanc." -#: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:968 +#: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:1006 msgid "Price can not be empty." msgstr "No podeu deixar el preu en blanc." -#: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:969 +#: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:1007 msgid "Price must be a number greater than zero." msgstr "El preu ha de ser un número major a zero." #: pkg/products.go:313 pkg/quote.go:929 pkg/expenses.go:213 pkg/expenses.go:218 -#: pkg/invoices.go:977 +#: pkg/invoices.go:1015 msgid "Selected tax is not valid." msgstr "Heu seleccionat un impost que no és vàlid." #: pkg/products.go:314 pkg/quote.go:930 pkg/expenses.go:214 pkg/expenses.go:219 -#: pkg/invoices.go:978 +#: pkg/invoices.go:1016 msgid "You can only select a tax of each class." msgstr "Només podeu seleccionar un impost de cada classe." -#: pkg/company.go:102 +#: pkg/company.go:113 +msgctxt "input" +msgid "Trade name" +msgstr "Nom comercial" + +#: pkg/company.go:118 pkg/contacts.go:247 +msgctxt "input" +msgid "Phone" +msgstr "Telèfon" + +#: pkg/company.go:136 pkg/contacts.go:263 +msgctxt "input" +msgid "Web" +msgstr "Web" + +#: pkg/company.go:144 pkg/contacts.go:275 +msgctxt "input" +msgid "Business name" +msgstr "Nom i cognoms" + +#: pkg/company.go:154 pkg/contacts.go:285 +msgctxt "input" +msgid "VAT number" +msgstr "DNI / NIF" + +#: pkg/company.go:160 pkg/contacts.go:291 +msgctxt "input" +msgid "Address" +msgstr "Adreça" + +#: pkg/company.go:169 pkg/contacts.go:300 +msgctxt "input" +msgid "City" +msgstr "Població" + +#: pkg/company.go:175 pkg/contacts.go:306 +msgctxt "input" +msgid "Province" +msgstr "Província" + +#: pkg/company.go:181 pkg/contacts.go:312 +msgctxt "input" +msgid "Postal code" +msgstr "Codi postal" + +#: pkg/company.go:190 pkg/contacts.go:321 +msgctxt "input" +msgid "Country" +msgstr "País" + +#: pkg/company.go:200 msgctxt "input" msgid "Currency" msgstr "Moneda" -#: pkg/company.go:109 +#: pkg/company.go:207 msgctxt "input" msgid "Invoice number format" msgstr "Format del número de factura" -#: pkg/company.go:115 +#: pkg/company.go:213 msgctxt "input" msgid "Next invoice number" msgstr "Següent número de factura" -#: pkg/company.go:124 +#: pkg/company.go:222 msgctxt "input" msgid "Quotation number format" msgstr "Format del número de pressupost" -#: pkg/company.go:130 +#: pkg/company.go:228 msgctxt "input" msgid "Next quotation number" msgstr "Següent número de pressupost" -#: pkg/company.go:139 +#: pkg/company.go:237 msgctxt "input" msgid "Legal disclaimer" msgstr "Nota legal" -#: pkg/company.go:160 +#: pkg/company.go:271 pkg/contacts.go:361 +msgid "Selected country is not valid." +msgstr "Heu seleccionat un país que no és vàlid." + +#: pkg/company.go:275 pkg/contacts.go:364 +msgid "Business name can not be empty." +msgstr "No podeu deixar el nom i els cognoms en blanc." + +#: pkg/company.go:276 pkg/contacts.go:365 +msgid "Business name must have at least two letters." +msgstr "Nom i cognoms han de tenir com a mínim dues lletres." + +#: pkg/company.go:277 pkg/contacts.go:366 +msgid "VAT number can not be empty." +msgstr "No podeu deixar el DNI o NIF en blanc." + +#: pkg/company.go:278 pkg/contacts.go:367 +msgid "This value is not a valid VAT number." +msgstr "Aquest valor no és un DNI o NIF vàlid." + +#: pkg/company.go:280 +msgid "Phone can not be empty." +msgstr "No podeu deixar el telèfon en blanc." + +#: pkg/company.go:281 pkg/contacts.go:382 +msgid "This value is not a valid phone number." +msgstr "Aquest valor no és un telèfon vàlid." + +#: pkg/company.go:287 pkg/contacts.go:388 +msgid "This value is not a valid web address. It should be like https://domain.com/." +msgstr "Aquest valor no és una adreça web vàlida. Hauria de ser similar a https://domini.cat/." + +#: pkg/company.go:289 pkg/contacts.go:369 +msgid "Address can not be empty." +msgstr "No podeu deixar l’adreça en blanc." + +#: pkg/company.go:290 pkg/contacts.go:370 +msgid "City can not be empty." +msgstr "No podeu deixar la població en blanc." + +#: pkg/company.go:291 pkg/contacts.go:371 +msgid "Province can not be empty." +msgstr "No podeu deixar la província en blanc." + +#: pkg/company.go:292 pkg/contacts.go:373 +msgid "Postal code can not be empty." +msgstr "No podeu deixar el codi postal en blanc." + +#: pkg/company.go:293 pkg/contacts.go:374 +msgid "This value is not a valid postal code." +msgstr "Aquest valor no és un codi postal vàlid." + +#: pkg/company.go:295 msgid "Selected currency is not valid." msgstr "Heu seleccionat una moneda que no és vàlida." -#: pkg/company.go:161 +#: pkg/company.go:296 msgid "Invoice number format can not be empty." msgstr "No podeu deixar el format del número de factura en blanc." -#: pkg/company.go:162 +#: pkg/company.go:297 msgid "Next invoice number must be a number greater than zero." msgstr "El següent número de factura ha de ser un número major a zero." -#: pkg/company.go:163 +#: pkg/company.go:298 msgid "Quotation number format can not be empty." msgstr "No podeu deixar el format del número de pressupost en blanc." -#: pkg/company.go:164 +#: pkg/company.go:299 msgid "Next quotation number must be a number greater than zero." msgstr "El següent número de pressupost ha de ser un número major a zero." -#: pkg/company.go:427 +#: pkg/company.go:563 msgctxt "input" msgid "Tax name" msgstr "Nom impost" -#: pkg/company.go:433 +#: pkg/company.go:569 msgctxt "input" msgid "Tax Class" msgstr "Classe d’impost" -#: pkg/company.go:436 +#: pkg/company.go:572 msgid "Select a tax class" msgstr "Escolliu una classe d’impost" -#: pkg/company.go:440 +#: pkg/company.go:576 msgctxt "input" msgid "Rate (%)" msgstr "Percentatge" -#: pkg/company.go:463 +#: pkg/company.go:599 msgid "Tax name can not be empty." msgstr "No podeu deixar el nom de l’impost en blanc." -#: pkg/company.go:464 +#: pkg/company.go:600 msgid "Selected tax class is not valid." msgstr "Heu seleccionat una classe d’impost que no és vàlida." -#: pkg/company.go:465 +#: pkg/company.go:601 msgid "Tax rate can not be empty." msgstr "No podeu deixar percentatge en blanc." -#: pkg/company.go:466 +#: pkg/company.go:602 msgid "Tax rate must be an integer between -99 and 99." msgstr "El percentatge ha de ser entre -99 i 99." -#: pkg/company.go:529 +#: pkg/company.go:665 msgctxt "input" msgid "Payment method name" msgstr "Nom del mètode de pagament" -#: pkg/company.go:535 +#: pkg/company.go:671 msgctxt "input" msgid "Instructions" msgstr "Instruccions" -#: pkg/company.go:553 +#: pkg/company.go:689 msgid "Payment method name can not be empty." msgstr "No podeu deixar el nom del mètode de pagament en blanc." -#: pkg/company.go:554 +#: pkg/company.go:690 msgid "Payment instructions can not be empty." msgstr "No podeu deixar les instruccions de pagament en blanc." -#: pkg/quote.go:147 pkg/quote.go:608 pkg/invoices.go:147 pkg/invoices.go:606 +#: pkg/quote.go:147 pkg/quote.go:608 pkg/invoices.go:147 pkg/invoices.go:640 msgctxt "input" msgid "Customer" msgstr "Client" @@ -901,8 +1004,8 @@ msgstr "Els pressuposts han de tenir com a mínim una de les etiquetes." msgid "quotations.zip" msgstr "pressuposts.zip" -#: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:555 -#: pkg/invoices.go:1145 pkg/invoices.go:1153 +#: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:589 +#: pkg/invoices.go:1183 pkg/invoices.go:1191 msgid "Invalid action" msgstr "Acció invàlida." @@ -920,12 +1023,12 @@ msgctxt "input" msgid "Terms and conditions" msgstr "Condicions d’acceptació" -#: pkg/quote.go:625 pkg/invoices.go:618 +#: pkg/quote.go:625 pkg/invoices.go:652 msgctxt "input" msgid "Notes" msgstr "Notes" -#: pkg/quote.go:634 pkg/invoices.go:628 +#: pkg/quote.go:634 pkg/invoices.go:662 msgctxt "input" msgid "Payment Method" msgstr "Mètode de pagament" @@ -938,7 +1041,7 @@ msgstr "Escolliu un mètode de pagament." msgid "Selected quotation status is not valid." msgstr "Heu seleccionat un estat de pressupost que no és vàlid." -#: pkg/quote.go:673 pkg/invoices.go:665 +#: pkg/quote.go:673 pkg/invoices.go:699 msgid "Selected customer is not valid." msgstr "Heu seleccionat un client que no és vàlid." @@ -950,21 +1053,21 @@ msgstr "No podeu deixar la data del pressupost en blanc." msgid "Quotation date must be a valid date." msgstr "La data del pressupost ha de ser vàlida." -#: pkg/quote.go:679 pkg/invoices.go:669 +#: pkg/quote.go:679 pkg/invoices.go:703 msgid "Selected payment method is not valid." msgstr "Heu seleccionat un mètode de pagament que no és vàlid." -#: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:861 pkg/invoices.go:866 +#: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:899 pkg/invoices.go:904 msgctxt "input" msgid "Id" msgstr "Identificador" -#: pkg/quote.go:851 pkg/invoices.go:899 +#: pkg/quote.go:851 pkg/invoices.go:937 msgctxt "input" msgid "Quantity" msgstr "Quantitat" -#: pkg/quote.go:860 pkg/invoices.go:908 +#: pkg/quote.go:860 pkg/invoices.go:946 msgctxt "input" msgid "Discount (%)" msgstr "Descompte (%)" @@ -973,23 +1076,23 @@ msgstr "Descompte (%)" msgid "Quotation product ID must be a number greater than zero." msgstr "L’ID del producte de pressupost ha de ser un número major a zero." -#: pkg/quote.go:917 pkg/invoices.go:965 +#: pkg/quote.go:917 pkg/invoices.go:1003 msgid "Product ID must be a positive number or zero." msgstr "L’ID del producte ha de ser un número positiu o zero." -#: pkg/quote.go:923 pkg/invoices.go:971 +#: pkg/quote.go:923 pkg/invoices.go:1009 msgid "Quantity can not be empty." msgstr "No podeu deixar la quantitat en blanc." -#: pkg/quote.go:924 pkg/invoices.go:972 +#: pkg/quote.go:924 pkg/invoices.go:1010 msgid "Quantity must be a number greater than zero." msgstr "La quantitat ha de ser un número major a zero." -#: pkg/quote.go:926 pkg/invoices.go:974 +#: pkg/quote.go:926 pkg/invoices.go:1012 msgid "Discount can not be empty." msgstr "No podeu deixar el descompte en blanc." -#: pkg/quote.go:927 pkg/invoices.go:975 +#: pkg/quote.go:927 pkg/invoices.go:1013 msgid "Discount must be a percentage between 0 and 100." msgstr "El descompte ha de ser un percentatge entre 0 i 100." @@ -1070,7 +1173,7 @@ msgctxt "input" msgid "Invoice number" msgstr "Número de factura" -#: pkg/expenses.go:161 pkg/invoices.go:612 +#: pkg/expenses.go:161 pkg/invoices.go:646 msgctxt "input" msgid "Invoice Date" msgstr "Data de factura" @@ -1089,7 +1192,7 @@ msgstr "Fitxer" msgid "Selected contact is not valid." msgstr "Heu seleccionat un contacte que no és vàlid." -#: pkg/expenses.go:212 pkg/invoices.go:667 +#: pkg/expenses.go:212 pkg/invoices.go:701 msgid "Invoice date must be a valid date." msgstr "La data de facturació ha de ser vàlida." @@ -1110,142 +1213,49 @@ msgctxt "input" msgid "Invoice Number" msgstr "Número de factura" -#: pkg/invoices.go:153 pkg/invoices.go:600 +#: pkg/invoices.go:153 pkg/invoices.go:634 msgctxt "input" msgid "Invoice Status" msgstr "Estat de la factura" -#: pkg/invoices.go:448 +#: pkg/invoices.go:482 msgid "Select a customer to bill." msgstr "Escolliu un client a facturar." -#: pkg/invoices.go:549 +#: pkg/invoices.go:583 msgid "invoices.zip" msgstr "factures.zip" -#: pkg/invoices.go:664 +#: pkg/invoices.go:698 msgid "Selected invoice status is not valid." msgstr "Heu seleccionat un estat de factura que no és vàlid." -#: pkg/invoices.go:666 +#: pkg/invoices.go:700 msgid "Invoice date can not be empty." msgstr "No podeu deixar la data de la factura en blanc." -#: pkg/invoices.go:802 +#: pkg/invoices.go:836 #, c-format msgid "Re: quotation #%s of %s" msgstr "Ref: pressupost núm. %s del %s" -#: pkg/invoices.go:803 +#: pkg/invoices.go:837 msgctxt "to_char" msgid "MM/DD/YYYY" msgstr "DD/MM/YYYY" -#: pkg/invoices.go:962 +#: pkg/invoices.go:1000 msgid "Invoice product ID must be a number greater than zero." msgstr "L’ID del producte de factura ha de ser un número major a zero." -#: pkg/contacts.go:238 +#: pkg/contacts.go:271 msgctxt "input" -msgid "Business name" -msgstr "Nom i cognoms" +msgid "Need to input tax details" +msgstr "Necessito poder facturar aquest contacte" -#: pkg/contacts.go:248 -msgctxt "input" -msgid "VAT number" -msgstr "DNI / NIF" - -#: pkg/contacts.go:254 -msgctxt "input" -msgid "Trade name" -msgstr "Nom comercial" - -#: pkg/contacts.go:259 -msgctxt "input" -msgid "Phone" -msgstr "Telèfon" - -#: pkg/contacts.go:277 -msgctxt "input" -msgid "Web" -msgstr "Web" - -#: pkg/contacts.go:285 -msgctxt "input" -msgid "Address" -msgstr "Adreça" - -#: pkg/contacts.go:294 -msgctxt "input" -msgid "City" -msgstr "Població" - -#: pkg/contacts.go:300 -msgctxt "input" -msgid "Province" -msgstr "Província" - -#: pkg/contacts.go:306 -msgctxt "input" -msgid "Postal code" -msgstr "Codi postal" - -#: pkg/contacts.go:315 -msgctxt "input" -msgid "Country" -msgstr "País" - -#: pkg/contacts.go:353 -msgid "Selected country is not valid." -msgstr "Heu seleccionat un país que no és vàlid." - -#: pkg/contacts.go:357 -msgid "Business name can not be empty." -msgstr "No podeu deixar el nom i els cognoms en blanc." - -#: pkg/contacts.go:358 -msgid "Business name must have at least two letters." -msgstr "Nom i cognoms han de tenir com a mínim dues lletres." - -#: pkg/contacts.go:359 -msgid "VAT number can not be empty." -msgstr "No podeu deixar el DNI o NIF en blanc." - -#: pkg/contacts.go:360 -msgid "This value is not a valid VAT number." -msgstr "Aquest valor no és un DNI o NIF vàlid." - -#: pkg/contacts.go:362 -msgid "Phone can not be empty." -msgstr "No podeu deixar el telèfon en blanc." - -#: pkg/contacts.go:363 -msgid "This value is not a valid phone number." -msgstr "Aquest valor no és un telèfon vàlid." - -#: pkg/contacts.go:369 -msgid "This value is not a valid web address. It should be like https://domain.com/." -msgstr "Aquest valor no és una adreça web vàlida. Hauria de ser similar a https://domini.cat/." - -#: pkg/contacts.go:371 -msgid "Address can not be empty." -msgstr "No podeu deixar l’adreça en blanc." - -#: pkg/contacts.go:372 -msgid "City can not be empty." -msgstr "No podeu deixar la població en blanc." - -#: pkg/contacts.go:373 -msgid "Province can not be empty." -msgstr "No podeu deixar la província en blanc." - -#: pkg/contacts.go:374 -msgid "Postal code can not be empty." -msgstr "No podeu deixar el codi postal en blanc." - -#: pkg/contacts.go:375 -msgid "This value is not a valid postal code." -msgstr "Aquest valor no és un codi postal vàlid." +#: pkg/contacts.go:379 +msgid "Name must have at least two letters." +msgstr "El nom ha de tenir com a mínim dues lletres." #~ msgctxt "action" #~ msgid "Update contact" diff --git a/po/es.po b/po/es.po index bf58736..561d0cc 100644 --- a/po/es.po +++ b/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-06-20 11:35+0200\n" +"POT-Creation-Date: 2023-06-30 21:08+0200\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -65,7 +65,7 @@ msgstr "Nombre" #: web/template/invoices/products.gohtml:50 #: web/template/invoices/view.gohtml:62 web/template/quotes/products.gohtml:50 -#: web/template/quotes/view.gohtml:71 web/template/products/index.gohtml:42 +#: web/template/quotes/view.gohtml:73 web/template/products/index.gohtml:42 msgctxt "title" msgid "Price" msgstr "Precio" @@ -95,15 +95,15 @@ msgstr "Deshacer" #: web/template/invoices/new.gohtml:60 web/template/invoices/view.gohtml:67 #: web/template/invoices/edit.gohtml:61 web/template/quotes/new.gohtml:61 -#: web/template/quotes/view.gohtml:76 web/template/quotes/edit.gohtml:62 +#: web/template/quotes/view.gohtml:78 web/template/quotes/edit.gohtml:62 msgctxt "title" msgid "Subtotal" msgstr "Subtotal" #: web/template/invoices/new.gohtml:70 web/template/invoices/view.gohtml:71 #: web/template/invoices/view.gohtml:111 web/template/invoices/edit.gohtml:71 -#: web/template/quotes/new.gohtml:71 web/template/quotes/view.gohtml:80 -#: web/template/quotes/view.gohtml:120 web/template/quotes/edit.gohtml:72 +#: web/template/quotes/new.gohtml:71 web/template/quotes/view.gohtml:82 +#: web/template/quotes/view.gohtml:122 web/template/quotes/edit.gohtml:72 msgctxt "title" msgid "Total" msgstr "Total" @@ -116,7 +116,7 @@ msgstr "Actualizar" #: web/template/invoices/new.gohtml:90 web/template/invoices/edit.gohtml:91 #: web/template/quotes/new.gohtml:91 web/template/quotes/edit.gohtml:92 -#: web/template/contacts/new.gohtml:39 web/template/contacts/edit.gohtml:43 +#: web/template/contacts/new.gohtml:44 web/template/contacts/edit.gohtml:48 #: web/template/expenses/new.gohtml:33 web/template/expenses/edit.gohtml:38 #: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36 msgctxt "action" @@ -230,22 +230,22 @@ msgctxt "action" msgid "Download invoice" msgstr "Descargar factura" -#: web/template/invoices/view.gohtml:61 web/template/quotes/view.gohtml:70 +#: web/template/invoices/view.gohtml:61 web/template/quotes/view.gohtml:72 msgctxt "title" msgid "Concept" msgstr "Concepto" -#: web/template/invoices/view.gohtml:64 web/template/quotes/view.gohtml:73 +#: web/template/invoices/view.gohtml:64 web/template/quotes/view.gohtml:75 msgctxt "title" msgid "Discount" msgstr "Descuento" -#: web/template/invoices/view.gohtml:66 web/template/quotes/view.gohtml:75 +#: web/template/invoices/view.gohtml:66 web/template/quotes/view.gohtml:77 msgctxt "title" msgid "Units" msgstr "Unidades" -#: web/template/invoices/view.gohtml:101 web/template/quotes/view.gohtml:110 +#: web/template/invoices/view.gohtml:101 web/template/quotes/view.gohtml:112 msgctxt "title" msgid "Tax Base" msgstr "Base imponible" @@ -280,7 +280,7 @@ msgctxt "input" msgid "(Max. %s)" msgstr "(Máx. %s)" -#: web/template/form.gohtml:194 +#: web/template/form.gohtml:200 msgctxt "action" msgid "Filters" msgstr "Filtrar" @@ -382,7 +382,7 @@ msgctxt "action" msgid "Download quotation" msgstr "Descargar presupuesto" -#: web/template/quotes/view.gohtml:63 +#: web/template/quotes/view.gohtml:65 msgid "Terms and Conditions:" msgstr "Condiciones de aceptación:" @@ -648,7 +648,7 @@ msgctxt "title" msgid "Edit Product “%s”" msgstr "Edición del producto «%s»" -#: pkg/login.go:37 pkg/profile.go:40 pkg/contacts.go:268 +#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:255 msgctxt "input" msgid "Email" msgstr "Correo-e" @@ -658,11 +658,11 @@ msgctxt "input" msgid "Password" msgstr "Contraseña" -#: pkg/login.go:70 pkg/profile.go:89 pkg/contacts.go:365 +#: pkg/login.go:70 pkg/company.go:283 pkg/profile.go:89 msgid "Email can not be empty." msgstr "No podéis dejar el correo-e en blanco." -#: pkg/login.go:71 pkg/profile.go:90 pkg/contacts.go:366 +#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:385 msgid "This value is not a valid email. It should be like name@domain.com." msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es." @@ -674,16 +674,16 @@ 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:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:871 -#: pkg/contacts.go:135 +#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909 +#: pkg/contacts.go:135 pkg/contacts.go:241 msgctxt "input" msgid "Name" msgstr "Nombre" #: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630 #: pkg/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174 -#: pkg/invoices.go:623 pkg/invoices.go:1170 pkg/contacts.go:140 -#: pkg/contacts.go:325 +#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:140 +#: pkg/contacts.go:331 msgctxt "input" msgid "Tags" msgstr "Etiquetes" @@ -716,147 +716,250 @@ msgstr "Cualquiera" msgid "Invoices must have at least one of the specified labels." msgstr "Las facturas deben tener como mínimo una de las etiquetas." -#: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:885 +#: pkg/products.go:269 pkg/quote.go:837 pkg/invoices.go:923 msgctxt "input" msgid "Description" msgstr "Descripción" -#: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:889 +#: pkg/products.go:274 pkg/quote.go:841 pkg/invoices.go:927 msgctxt "input" msgid "Price" msgstr "Precio" -#: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:918 +#: pkg/products.go:284 pkg/quote.go:870 pkg/expenses.go:167 pkg/invoices.go:956 msgctxt "input" msgid "Taxes" msgstr "Impuestos" -#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:967 +#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005 +#: pkg/contacts.go:378 msgid "Name can not be empty." msgstr "No podéis dejar el nombre en blanco." -#: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:968 +#: pkg/products.go:310 pkg/quote.go:920 pkg/invoices.go:1006 msgid "Price can not be empty." msgstr "No podéis dejar el precio en blanco." -#: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:969 +#: pkg/products.go:311 pkg/quote.go:921 pkg/invoices.go:1007 msgid "Price must be a number greater than zero." msgstr "El precio tiene que ser un número mayor a cero." #: pkg/products.go:313 pkg/quote.go:929 pkg/expenses.go:213 pkg/expenses.go:218 -#: pkg/invoices.go:977 +#: pkg/invoices.go:1015 msgid "Selected tax is not valid." msgstr "Habéis escogido un impuesto que no es válido." #: pkg/products.go:314 pkg/quote.go:930 pkg/expenses.go:214 pkg/expenses.go:219 -#: pkg/invoices.go:978 +#: pkg/invoices.go:1016 msgid "You can only select a tax of each class." msgstr "Solo podéis escoger un impuesto de cada clase." -#: pkg/company.go:102 +#: pkg/company.go:113 +msgctxt "input" +msgid "Trade name" +msgstr "Nombre comercial" + +#: pkg/company.go:118 pkg/contacts.go:247 +msgctxt "input" +msgid "Phone" +msgstr "Teléfono" + +#: pkg/company.go:136 pkg/contacts.go:263 +msgctxt "input" +msgid "Web" +msgstr "Web" + +#: pkg/company.go:144 pkg/contacts.go:275 +msgctxt "input" +msgid "Business name" +msgstr "Nombre y apellidos" + +#: pkg/company.go:154 pkg/contacts.go:285 +msgctxt "input" +msgid "VAT number" +msgstr "DNI / NIF" + +#: pkg/company.go:160 pkg/contacts.go:291 +msgctxt "input" +msgid "Address" +msgstr "Dirección" + +#: pkg/company.go:169 pkg/contacts.go:300 +msgctxt "input" +msgid "City" +msgstr "Población" + +#: pkg/company.go:175 pkg/contacts.go:306 +msgctxt "input" +msgid "Province" +msgstr "Provincia" + +#: pkg/company.go:181 pkg/contacts.go:312 +msgctxt "input" +msgid "Postal code" +msgstr "Código postal" + +#: pkg/company.go:190 pkg/contacts.go:321 +msgctxt "input" +msgid "Country" +msgstr "País" + +#: pkg/company.go:200 msgctxt "input" msgid "Currency" msgstr "Moneda" -#: pkg/company.go:109 +#: pkg/company.go:207 msgctxt "input" msgid "Invoice number format" msgstr "Formato del número de factura" -#: pkg/company.go:115 +#: pkg/company.go:213 msgctxt "input" msgid "Next invoice number" msgstr "Siguiente número de factura" -#: pkg/company.go:124 +#: pkg/company.go:222 msgctxt "input" msgid "Quotation number format" msgstr "Formato del número de presupuesto" -#: pkg/company.go:130 +#: pkg/company.go:228 msgctxt "input" msgid "Next quotation number" msgstr "Siguiente número de presupuesto" -#: pkg/company.go:139 +#: pkg/company.go:237 msgctxt "input" msgid "Legal disclaimer" msgstr "Nota legal" -#: pkg/company.go:160 +#: pkg/company.go:271 pkg/contacts.go:361 +msgid "Selected country is not valid." +msgstr "Habéis escogido un país que no es válido." + +#: pkg/company.go:275 pkg/contacts.go:364 +msgid "Business name can not be empty." +msgstr "No podéis dejar el nombre y los apellidos en blanco." + +#: pkg/company.go:276 pkg/contacts.go:365 +msgid "Business name must have at least two letters." +msgstr "El nombre y los apellidos deben contener como mínimo dos letras." + +#: pkg/company.go:277 pkg/contacts.go:366 +msgid "VAT number can not be empty." +msgstr "No podéis dejar el DNI o NIF en blanco." + +#: pkg/company.go:278 pkg/contacts.go:367 +msgid "This value is not a valid VAT number." +msgstr "Este valor no es un DNI o NIF válido." + +#: pkg/company.go:280 +msgid "Phone can not be empty." +msgstr "No podéis dejar el teléfono en blanco." + +#: pkg/company.go:281 pkg/contacts.go:382 +msgid "This value is not a valid phone number." +msgstr "Este valor no es un teléfono válido." + +#: pkg/company.go:287 pkg/contacts.go:388 +msgid "This value is not a valid web address. It should be like https://domain.com/." +msgstr "Este valor no es una dirección web válida. Tiene que ser parecida a https://dominio.es/." + +#: pkg/company.go:289 pkg/contacts.go:369 +msgid "Address can not be empty." +msgstr "No podéis dejar la dirección en blanco." + +#: pkg/company.go:290 pkg/contacts.go:370 +msgid "City can not be empty." +msgstr "No podéis dejar la población en blanco." + +#: pkg/company.go:291 pkg/contacts.go:371 +msgid "Province can not be empty." +msgstr "No podéis dejar la provincia en blanco." + +#: pkg/company.go:292 pkg/contacts.go:373 +msgid "Postal code can not be empty." +msgstr "No podéis dejar el código postal en blanco." + +#: pkg/company.go:293 pkg/contacts.go:374 +msgid "This value is not a valid postal code." +msgstr "Este valor no es un código postal válido válido." + +#: pkg/company.go:295 msgid "Selected currency is not valid." msgstr "Habéis escogido una moneda que no es válida." -#: pkg/company.go:161 +#: pkg/company.go:296 msgid "Invoice number format can not be empty." msgstr "No podéis dejar el formato del número de factura en blanco." -#: pkg/company.go:162 +#: pkg/company.go:297 msgid "Next invoice number must be a number greater than zero." msgstr "El siguiente número de factura tiene que ser un número mayor a cero." -#: pkg/company.go:163 +#: pkg/company.go:298 msgid "Quotation number format can not be empty." msgstr "No podéis dejar el formato del número de presupuesto en blanco." -#: pkg/company.go:164 +#: pkg/company.go:299 msgid "Next quotation number must be a number greater than zero." msgstr "El siguiente número de presupuesto tiene que ser un número mayor a cero." -#: pkg/company.go:427 +#: pkg/company.go:563 msgctxt "input" msgid "Tax name" msgstr "Nombre impuesto" -#: pkg/company.go:433 +#: pkg/company.go:569 msgctxt "input" msgid "Tax Class" msgstr "Clase de impuesto" -#: pkg/company.go:436 +#: pkg/company.go:572 msgid "Select a tax class" msgstr "Escoged una clase de impuesto" -#: pkg/company.go:440 +#: pkg/company.go:576 msgctxt "input" msgid "Rate (%)" msgstr "Porcentaje" -#: pkg/company.go:463 +#: pkg/company.go:599 msgid "Tax name can not be empty." msgstr "No podéis dejar el nombre del impuesto en blanco." -#: pkg/company.go:464 +#: pkg/company.go:600 msgid "Selected tax class is not valid." msgstr "Habéis escogido una clase impuesto que no es válida." -#: pkg/company.go:465 +#: pkg/company.go:601 msgid "Tax rate can not be empty." msgstr "No podéis dejar el porcentaje en blanco." -#: pkg/company.go:466 +#: pkg/company.go:602 msgid "Tax rate must be an integer between -99 and 99." msgstr "El porcentaje tiene que estar entre -99 y 99." -#: pkg/company.go:529 +#: pkg/company.go:665 msgctxt "input" msgid "Payment method name" msgstr "Nombre del método de pago" -#: pkg/company.go:535 +#: pkg/company.go:671 msgctxt "input" msgid "Instructions" msgstr "Instrucciones" -#: pkg/company.go:553 +#: pkg/company.go:689 msgid "Payment method name can not be empty." msgstr "No podéis dejar el nombre del método de pago en blanco." -#: pkg/company.go:554 +#: pkg/company.go:690 msgid "Payment instructions can not be empty." msgstr "No podéis dejar las instrucciones de pago en blanco." -#: pkg/quote.go:147 pkg/quote.go:608 pkg/invoices.go:147 pkg/invoices.go:606 +#: pkg/quote.go:147 pkg/quote.go:608 pkg/invoices.go:147 pkg/invoices.go:640 msgctxt "input" msgid "Customer" msgstr "Cliente" @@ -901,8 +1004,8 @@ msgstr "Los presupuestos deben tener como mínimo una de las etiquetas." msgid "quotations.zip" msgstr "presupuestos.zip" -#: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:555 -#: pkg/invoices.go:1145 pkg/invoices.go:1153 +#: pkg/quote.go:556 pkg/quote.go:1085 pkg/quote.go:1093 pkg/invoices.go:589 +#: pkg/invoices.go:1183 pkg/invoices.go:1191 msgid "Invalid action" msgstr "Acción inválida." @@ -920,12 +1023,12 @@ msgctxt "input" msgid "Terms and conditions" msgstr "Condiciones de aceptación" -#: pkg/quote.go:625 pkg/invoices.go:618 +#: pkg/quote.go:625 pkg/invoices.go:652 msgctxt "input" msgid "Notes" msgstr "Notas" -#: pkg/quote.go:634 pkg/invoices.go:628 +#: pkg/quote.go:634 pkg/invoices.go:662 msgctxt "input" msgid "Payment Method" msgstr "Método de pago" @@ -938,7 +1041,7 @@ msgstr "Escoged un método e pago." msgid "Selected quotation status is not valid." msgstr "Habéis escogido un estado de presupuesto que no es válido." -#: pkg/quote.go:673 pkg/invoices.go:665 +#: pkg/quote.go:673 pkg/invoices.go:699 msgid "Selected customer is not valid." msgstr "Habéis escogido un cliente que no es válido." @@ -950,21 +1053,21 @@ msgstr "No podéis dejar la fecha del presupuesto en blanco." msgid "Quotation date must be a valid date." msgstr "La fecha de presupuesto debe ser válida." -#: pkg/quote.go:679 pkg/invoices.go:669 +#: pkg/quote.go:679 pkg/invoices.go:703 msgid "Selected payment method is not valid." msgstr "Habéis escogido un método de pago que no es válido." -#: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:861 pkg/invoices.go:866 +#: pkg/quote.go:813 pkg/quote.go:818 pkg/invoices.go:899 pkg/invoices.go:904 msgctxt "input" msgid "Id" msgstr "Identificador" -#: pkg/quote.go:851 pkg/invoices.go:899 +#: pkg/quote.go:851 pkg/invoices.go:937 msgctxt "input" msgid "Quantity" msgstr "Cantidad" -#: pkg/quote.go:860 pkg/invoices.go:908 +#: pkg/quote.go:860 pkg/invoices.go:946 msgctxt "input" msgid "Discount (%)" msgstr "Descuento (%)" @@ -973,23 +1076,23 @@ msgstr "Descuento (%)" msgid "Quotation product ID must be a number greater than zero." msgstr "El ID de producto de presupuesto tiene que ser un número mayor a cero." -#: pkg/quote.go:917 pkg/invoices.go:965 +#: pkg/quote.go:917 pkg/invoices.go:1003 msgid "Product ID must be a positive number or zero." msgstr "El ID de producto tiene que ser un número positivo o cero." -#: pkg/quote.go:923 pkg/invoices.go:971 +#: pkg/quote.go:923 pkg/invoices.go:1009 msgid "Quantity can not be empty." msgstr "No podéis dejar la cantidad en blanco." -#: pkg/quote.go:924 pkg/invoices.go:972 +#: pkg/quote.go:924 pkg/invoices.go:1010 msgid "Quantity must be a number greater than zero." msgstr "La cantidad tiene que ser un número mayor a cero." -#: pkg/quote.go:926 pkg/invoices.go:974 +#: pkg/quote.go:926 pkg/invoices.go:1012 msgid "Discount can not be empty." msgstr "No podéis dejar el descuento en blanco." -#: pkg/quote.go:927 pkg/invoices.go:975 +#: pkg/quote.go:927 pkg/invoices.go:1013 msgid "Discount must be a percentage between 0 and 100." msgstr "El descuento tiene que ser un porcentaje entre 0 y 100." @@ -1070,7 +1173,7 @@ msgctxt "input" msgid "Invoice number" msgstr "Número de factura" -#: pkg/expenses.go:161 pkg/invoices.go:612 +#: pkg/expenses.go:161 pkg/invoices.go:646 msgctxt "input" msgid "Invoice Date" msgstr "Fecha de factura" @@ -1089,7 +1192,7 @@ msgstr "Archivo" msgid "Selected contact is not valid." msgstr "Habéis escogido un contacto que no es válido." -#: pkg/expenses.go:212 pkg/invoices.go:667 +#: pkg/expenses.go:212 pkg/invoices.go:701 msgid "Invoice date must be a valid date." msgstr "La fecha de factura debe ser válida." @@ -1110,142 +1213,49 @@ msgctxt "input" msgid "Invoice Number" msgstr "Número de factura" -#: pkg/invoices.go:153 pkg/invoices.go:600 +#: pkg/invoices.go:153 pkg/invoices.go:634 msgctxt "input" msgid "Invoice Status" msgstr "Estado de la factura" -#: pkg/invoices.go:448 +#: pkg/invoices.go:482 msgid "Select a customer to bill." msgstr "Escoged un cliente a facturar." -#: pkg/invoices.go:549 +#: pkg/invoices.go:583 msgid "invoices.zip" msgstr "facturas.zip" -#: pkg/invoices.go:664 +#: pkg/invoices.go:698 msgid "Selected invoice status is not valid." msgstr "Habéis escogido un estado de factura que no es válido." -#: pkg/invoices.go:666 +#: pkg/invoices.go:700 msgid "Invoice date can not be empty." msgstr "No podéis dejar la fecha de la factura en blanco." -#: pkg/invoices.go:802 +#: pkg/invoices.go:836 #, c-format msgid "Re: quotation #%s of %s" msgstr "Ref: presupuesto n.º %s del %s" -#: pkg/invoices.go:803 +#: pkg/invoices.go:837 msgctxt "to_char" msgid "MM/DD/YYYY" msgstr "DD/MM/YYYY" -#: pkg/invoices.go:962 +#: pkg/invoices.go:1000 msgid "Invoice product ID must be a number greater than zero." msgstr "El ID de producto de factura tiene que ser un número mayor a cero." -#: pkg/contacts.go:238 +#: pkg/contacts.go:271 msgctxt "input" -msgid "Business name" -msgstr "Nombre y apellidos" +msgid "Need to input tax details" +msgstr "Necesito facturar este contacto" -#: pkg/contacts.go:248 -msgctxt "input" -msgid "VAT number" -msgstr "DNI / NIF" - -#: pkg/contacts.go:254 -msgctxt "input" -msgid "Trade name" -msgstr "Nombre comercial" - -#: pkg/contacts.go:259 -msgctxt "input" -msgid "Phone" -msgstr "Teléfono" - -#: pkg/contacts.go:277 -msgctxt "input" -msgid "Web" -msgstr "Web" - -#: pkg/contacts.go:285 -msgctxt "input" -msgid "Address" -msgstr "Dirección" - -#: pkg/contacts.go:294 -msgctxt "input" -msgid "City" -msgstr "Población" - -#: pkg/contacts.go:300 -msgctxt "input" -msgid "Province" -msgstr "Provincia" - -#: pkg/contacts.go:306 -msgctxt "input" -msgid "Postal code" -msgstr "Código postal" - -#: pkg/contacts.go:315 -msgctxt "input" -msgid "Country" -msgstr "País" - -#: pkg/contacts.go:353 -msgid "Selected country is not valid." -msgstr "Habéis escogido un país que no es válido." - -#: pkg/contacts.go:357 -msgid "Business name can not be empty." -msgstr "No podéis dejar el nombre y los apellidos en blanco." - -#: pkg/contacts.go:358 -msgid "Business name must have at least two letters." -msgstr "El nombre y los apellidos deben contener como mínimo dos letras." - -#: pkg/contacts.go:359 -msgid "VAT number can not be empty." -msgstr "No podéis dejar el DNI o NIF en blanco." - -#: pkg/contacts.go:360 -msgid "This value is not a valid VAT number." -msgstr "Este valor no es un DNI o NIF válido." - -#: pkg/contacts.go:362 -msgid "Phone can not be empty." -msgstr "No podéis dejar el teléfono en blanco." - -#: pkg/contacts.go:363 -msgid "This value is not a valid phone number." -msgstr "Este valor no es un teléfono válido." - -#: pkg/contacts.go:369 -msgid "This value is not a valid web address. It should be like https://domain.com/." -msgstr "Este valor no es una dirección web válida. Tiene que ser parecida a https://dominio.es/." - -#: pkg/contacts.go:371 -msgid "Address can not be empty." -msgstr "No podéis dejar la dirección en blanco." - -#: pkg/contacts.go:372 -msgid "City can not be empty." -msgstr "No podéis dejar la población en blanco." - -#: pkg/contacts.go:373 -msgid "Province can not be empty." -msgstr "No podéis dejar la provincia en blanco." - -#: pkg/contacts.go:374 -msgid "Postal code can not be empty." -msgstr "No podéis dejar el código postal en blanco." - -#: pkg/contacts.go:375 -msgid "This value is not a valid postal code." -msgstr "Este valor no es un código postal válido válido." +#: pkg/contacts.go:379 +msgid "Name must have at least two letters." +msgstr "El nombre debe contener como mínimo dos letras." #~ msgctxt "action" #~ msgid "Update contact" diff --git a/revert/add_contact.sql b/revert/add_contact.sql index 9437949..6a48fca 100644 --- a/revert/add_contact.sql +++ b/revert/add_contact.sql @@ -1,7 +1,39 @@ --- Revert numerus:add_contact from pg +-- Deploy numerus:add_contact to pg +-- requires: schema_numerus +-- requires: extension_vat +-- requires: email +-- requires: extension_pg_libphonenumber +-- requires: extension_uri +-- requires: country_code +-- requires: contact +-- requires: tag_name begin; -drop function if exists numerus.add_contact(integer, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[]); +set search_path to numerus, public; + +create or replace function add_contact(company_id integer, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +$$ +declare + cid integer; + cslug uuid; +begin + insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, tags) + values (add_contact.company_id, add_contact.business_name, (add_contact.country_code || add_contact.vatin)::vatin, add_contact.trade_name, parse_packed_phone_number(add_contact.phone, add_contact.country_code), add_contact.email, add_contact.web, add_contact.address, add_contact.city, add_contact.province, add_contact.postal_code, add_contact.country_code, add_contact.tags) + returning contact_id, slug + into cid, cslug; + + return cslug; +end +$$ + language plpgsql +; + +revoke execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; +grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; +grant execute on function add_contact(integer, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; + + +drop function if exists add_contact(integer, text, text, email, uri, tax_details, tag_name[]); commit; diff --git a/revert/add_contact@v0.sql b/revert/add_contact@v0.sql new file mode 100644 index 0000000..9437949 --- /dev/null +++ b/revert/add_contact@v0.sql @@ -0,0 +1,7 @@ +-- Revert numerus:add_contact from pg + +begin; + +drop function if exists numerus.add_contact(integer, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[]); + +commit; diff --git a/revert/contact_email.sql b/revert/contact_email.sql new file mode 100644 index 0000000..9fa2a1a --- /dev/null +++ b/revert/contact_email.sql @@ -0,0 +1,23 @@ +-- Revert numerus:contact_email from pg + +begin; + +set search_path to numerus, public; + +alter table contact + add column email email +; + +update contact +set email = email.email +from contact_email as email +where email.contact_id = email.contact_id +; + +alter table contact + alter column email set not null +; + +drop table if exists contact_email; + +commit; diff --git a/revert/contact_phone.sql b/revert/contact_phone.sql new file mode 100644 index 0000000..59205de --- /dev/null +++ b/revert/contact_phone.sql @@ -0,0 +1,23 @@ +-- Revert numerus:contact_phone from pg + +begin; + +set search_path to numerus, public; + +alter table contact + add column phone packed_phone_number +; + +update contact +set phone = phone.phone +from contact_phone as phone +where phone.contact_id = contact.contact_id +; + +alter table contact + alter column phone set not null +; + +drop table if exists contact_phone; + +commit; diff --git a/revert/contact_tax_details.sql b/revert/contact_tax_details.sql new file mode 100644 index 0000000..30e3adf --- /dev/null +++ b/revert/contact_tax_details.sql @@ -0,0 +1,51 @@ +-- Revert numerus:contact_tax_details from pg + +begin; + +set search_path to numerus, public; + +alter table contact + drop constraint name_not_empty +, add column country_code country_code +, add column postal_code text +, add column province text +, add column city text +, add column address text +, add column vatin vatin +, add column business_name text constraint business_name_not_empty check(length(trim(business_name)) > 1) +; + +alter table contact + rename column name to trade_name +; + +update contact +set business_name = tax.business_name + , vatin = tax.vatin + , address = tax.address + , city = tax.city + , province = tax.province + , postal_code = tax.postal_code + , country_code = tax.country_code +from contact_tax_details as tax +where tax.contact_id = contact.contact_id +; + +alter table contact + alter column business_name set not null +, alter column vatin set not null +, alter column address set not null +, alter column city set not null +, alter column province set not null +, alter column postal_code set not null +, alter column country_code set not null +; + +update contact +set trade_name = '' +where trade_name = business_name +; + +drop table if exists contact_tax_details; + +commit; diff --git a/revert/contact_web.sql b/revert/contact_web.sql new file mode 100644 index 0000000..3a63784 --- /dev/null +++ b/revert/contact_web.sql @@ -0,0 +1,23 @@ +-- Revert numerus:contact_web from pg + +begin; + +set search_path to numerus, public; + +alter table contact + add column web uri +; + +update contact +set web = web.uri +from contact_web as web +where web.contact_id = contact.contact_id +; + +alter table contact + alter column web set not null +; + +drop table if exists numerus.contact_web; + +commit; diff --git a/revert/edit_contact.sql b/revert/edit_contact.sql index e17b325..6b6b9dc 100644 --- a/revert/edit_contact.sql +++ b/revert/edit_contact.sql @@ -1,7 +1,55 @@ --- Revert numerus:edit_contact from pg +-- Deploy numerus:edit_contact to pg +-- requires: schema_numerus +-- requires: email +-- requires: extension_uri +-- requires: country_code +-- requires: tag_name +-- requires: contact +-- requires: extension_vat +-- requires: extension_pg_libphonenumber begin; -drop function if exists numerus.edit_contact(uuid, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[]); +set search_path to numerus, public; + +create or replace function edit_contact(contact_slug uuid, business_name text, vatin text, trade_name text, phone text, email email, web uri, address text, city text, province text, postal_code text, country_code country_code, tags tag_name[]) returns uuid as +$$ +declare + cid integer; + company integer; +begin + update contact + set business_name = edit_contact.business_name + , vatin = (edit_contact.country_code || edit_contact.vatin)::vatin + , trade_name = edit_contact.trade_name + , phone = parse_packed_phone_number( edit_contact.phone, edit_contact.country_code) + , email = edit_contact.email + , web = edit_contact.web + , address = edit_contact.address + , city = edit_contact.city + , province = edit_contact.province + , postal_code = edit_contact.postal_code + , country_code = edit_contact.country_code + , tags = edit_contact.tags + where slug = contact_slug + returning contact_id, company_id + into cid, company + ; + + if cid is null then + return null; + end if; + + return contact_slug; +end +$$ + language plpgsql +; + +revoke execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) from public; +grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to invoicer; +grant execute on function edit_contact(uuid, text, text, text, text, email, uri, text, text, text, text, country_code, tag_name[]) to admin; + +drop function if exists edit_contact(uuid, text, text, email, uri, tax_details, tag_name[]); commit; diff --git a/revert/edit_contact@v0.sql b/revert/edit_contact@v0.sql new file mode 100644 index 0000000..e17b325 --- /dev/null +++ b/revert/edit_contact@v0.sql @@ -0,0 +1,7 @@ +-- Revert numerus:edit_contact from pg + +begin; + +drop function if exists numerus.edit_contact(uuid, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[]); + +commit; diff --git a/revert/invoice_contact_id_fkey.sql b/revert/invoice_contact_id_fkey.sql new file mode 100644 index 0000000..e3c4425 --- /dev/null +++ b/revert/invoice_contact_id_fkey.sql @@ -0,0 +1,10 @@ +-- Revert numerus:invoice_contact_id_fkey from pg + +begin; + +alter table numerus.invoice + drop constraint invoice_contact_id_fkey +, add foreign key (contact_id) references numerus.contact (contact_id) +; + +commit; diff --git a/revert/tax_details.sql b/revert/tax_details.sql new file mode 100644 index 0000000..221f086 --- /dev/null +++ b/revert/tax_details.sql @@ -0,0 +1,7 @@ +-- Revert numerus:tax_details from pg + +begin; + +drop type if exists numerus.tax_details; + +commit; diff --git a/sqitch.plan b/sqitch.plan index b876de2..1b31987 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -98,3 +98,12 @@ compute_new_quote_amount [roles schema_numerus company tax new_quote_product new edited_quote_product [schema_numerus discount_rate] 2023-06-07T13:03:23Z jordi fita mas # Add type for passing products to edit quotations edit_quote [roles schema_numerus quote currency parse_price edited_quote_product tax quote_contact quote_payment_method quote_product quote_product_tax quote_product_product tag_name] 2023-06-07T13:08:10Z jordi fita mas # Add function to edit quotations @v0 2023-06-12T14:05:34Z jordi fita mas # Tag version 0 + +contact_phone [roles schema_numerus extension_pg_libphonenumber] 2023-06-28T11:04:19Z jordi fita mas # Add relation to keep contacts’ phone numbers +contact_email [roles schema_numerus email contact] 2023-06-28T11:47:19Z jordi fita mas # Add relation to keep contacts’ emails +contact_web [roles schema_numerus extension_uri contact] 2023-06-28T12:01:07Z jordi fita mas # Add relation to keep contacts’ websites +contact_tax_details [roles schema_numerus contact extension_vat country_code country] 2023-06-23T09:14:03Z jordi fita mas # Add relation of contact’s tax details +tax_details [schema_numerus extension_vat country_code] 2023-06-29T10:57:57Z jordi fita mas # Add composite type for contacts’ tax details +add_contact [add_contact@v0 tax_details] 2023-06-29T11:10:15Z jordi fita mas # Change add contact to accept a tax_detail parameter and use the new relations +edit_contact [edit_contact@v0 tax_details] 2023-06-29T11:50:41Z jordi fita mas # Change edit_contact to require tax_details parameter and to use new relations for web, email, and phone +invoice_contact_id_fkey [schema_numerus invoice contact_tax_details] 2023-06-30T16:50:45Z jordi fita mas # Update invoice’s contact_id foreign key to point to tax sales diff --git a/test/add_contact.sql b/test/add_contact.sql index 056bd85..88a9fa3 100644 --- a/test/add_contact.sql +++ b/test/add_contact.sql @@ -5,19 +5,19 @@ reset client_min_messages; begin; -select plan(14); +select plan(18); set search_path to auth, numerus, public; -select has_function('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]']); -select function_lang_is('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'plpgsql'); -select function_returns('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'uuid'); -select isnt_definer('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]']); -select volatility_is('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'volatile'); -select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'guest', array []::text[]); -select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'invoicer', array ['EXECUTE']); -select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'admin', array ['EXECUTE']); -select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'authenticator', array []::text[]); +select has_function('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]']); +select function_lang_is('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'plpgsql'); +select function_returns('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'uuid'); +select isnt_definer('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]']); +select volatility_is('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'volatile'); +select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'guest', array []::text[]); +select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'invoicer', array ['EXECUTE']); +select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'admin', array ['EXECUTE']); +select function_privs_are('numerus', 'add_contact', array ['integer', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'authenticator', array []::text[]); set client_min_messages to warning; @@ -41,35 +41,67 @@ values (111, 1, 'cash', 'cash') set constraints "company_default_payment_method_id_fkey" immediate; select lives_ok( - $$ select add_contact(1, 'Contact 2.1', '40404040D', 'Trade Contact 2.1', '777-777-777', 'c@c', 'https://c', 'Fake St., 123', 'City 2.1', 'Province 2.1', '17486', 'ES', '{tag1,tag2}') $$, - 'Should be able to insert a contact for the first company with two tags' + $$ select add_contact(1, 'Contact 2.1', '777-777-777', null, 'https://c', null, '{tag1,tag2}') $$, + 'Should be able to insert a contact for the first company with two tags, no email, and no tax details' ); select lives_ok( - $$ select add_contact(1, 'Contact 2.2', '41414141L', 'Trade Contact 2.2', '888-888-888', 'd@d', 'https://d', 'Another Fake St., 123', 'City 2.2', 'Province 2.2', '17487', 'ES', '{}') $$, - 'Should be able to insert a second contact for the first company with no tag' + $$ select add_contact(1, 'Contact 2.2', null, 'd@d', null, '(Contact 2.2 Ltd,41414141L,"Fake St., 123",City 2.2,Province 2.2,17487,ES)', '{}') $$, + 'Should be able to insert a second contact for the first company with no tag, no phone, and not website' ); select lives_ok( - $$ select add_contact(2, 'Contact 4.1', '42424242Y', '', '999-999-999', 'e@e', '', 'Yet Another Fake St., 123', 'City 4.1', 'Province 4.1', '17488', 'ES', '{tag2}') $$, - 'Should be able to insert a contact for the second company with a tag' + $$ select add_contact(2, 'Contact 4.1', '999-999-999', 'e@e', 'http://e', '(Contact 4.1 Ltd,42424242Y,"Another Fake St., 123",City 4.1,Province 4.1,17488,ES)', '{tag2}') $$, + 'Should be able to insert a contact for the second company with a tag and everything else' ); select lives_ok( - $$ select add_contact(1, 'Contact 2.3', '43434343Q', '', '000-000-000', 'f@f', '', 'The Last Fake St., 123', '', '', '', 'ES', '{tag2}') $$, + $$ select add_contact(1, 'Contact 2.3', null, null, null, null, '{tag2}') $$, 'Should be able to insert another contact with a repeated tag' ); select bag_eq( - $$ select company_id, business_name, vatin::text, trade_name, phone::text, email::text, web::text, address, city, province, postal_code, country_code::text, tags, created_at from contact $$, - $$ values (1, 'Contact 2.1', 'ES40404040D', 'Trade Contact 2.1', '+34 777 77 77 77', 'c@c', 'https://c', 'Fake St., 123', 'City 2.1', 'Province 2.1', '17486', 'ES', '{tag1,tag2}'::tag_name[], CURRENT_TIMESTAMP) - , (1, 'Contact 2.2', 'ES41414141L', 'Trade Contact 2.2', '+34 888 88 88 88', 'd@d', 'https://d', 'Another Fake St., 123', 'City 2.2', 'Province 2.2', '17487', 'ES', '{}'::tag_name[], CURRENT_TIMESTAMP) - , (2, 'Contact 4.1', 'ES42424242Y', '', '+34 999 99 99 99', 'e@e', '', 'Yet Another Fake St., 123', 'City 4.1', 'Province 4.1', '17488', 'ES', '{tag2}'::tag_name[], CURRENT_TIMESTAMP) - , (1, 'Contact 2.3', 'ES43434343Q', '', '+34 000000000', 'f@f', '', 'The Last Fake St., 123', '', '', '', 'ES', '{tag2}'::tag_name[], CURRENT_TIMESTAMP) + $$ select company_id, name, tags, created_at from contact $$, + $$ values (1, 'Contact 2.1', '{tag1,tag2}'::tag_name[], CURRENT_TIMESTAMP) + , (1, 'Contact 2.2', '{}'::tag_name[], CURRENT_TIMESTAMP) + , (2, 'Contact 4.1', '{tag2}'::tag_name[], CURRENT_TIMESTAMP) + , (1, 'Contact 2.3', '{tag2}'::tag_name[], CURRENT_TIMESTAMP) $$, 'Should have created all contacts' ); +select bag_eq( + $$ select name, business_name, vatin::text, address, city, province, postal_code, country_code::text from contact join contact_tax_details using (contact_id) $$, + $$ values ('Contact 2.2', 'Contact 2.2 Ltd', 'ES41414141L', 'Fake St., 123', 'City 2.2', 'Province 2.2', '17487', 'ES') + , ('Contact 4.1', 'Contact 4.1 Ltd', 'ES42424242Y', 'Another Fake St., 123', 'City 4.1', 'Province 4.1', '17488', 'ES') + $$, + 'Should have created all contacts’ tax details' +); + +select bag_eq( + $$ select name, phone::text from contact join contact_phone using (contact_id) $$, + $$ values ('Contact 2.1', '+34 777 77 77 77') + , ('Contact 4.1', '+34 999 99 99 99') + $$, + 'Should have created all contacts’ phone' +); + +select bag_eq( + $$ select name, email::text from contact join contact_email using (contact_id) $$, + $$ values ('Contact 2.2', 'd@d') + , ('Contact 4.1', 'e@e') + $$, + 'Should have created all contacts’ email' +); + +select bag_eq( + $$ select name, uri::text from contact join contact_web using (contact_id) $$, + $$ values ('Contact 2.1', 'https://c') + , ('Contact 4.1', 'http://e') + $$, + 'Should have created all contacts’ web' +); + select * from finish(); diff --git a/test/add_expense.sql b/test/add_expense.sql index 9235cd4..da75fb0 100644 --- a/test/add_expense.sql +++ b/test/add_expense.sql @@ -55,11 +55,11 @@ values (3, 1, 11, 'IRPF -15 %', -0.15) , (6, 2, 22, 'IVA 10 %', 0.10) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') - , (14, 2, 'Contact 4.1', 'XX777', '', '999-999-999', 'e@e', '', '', '', '', '', 'ES') - , (15, 2, 'Contact 4.2', 'XX888', '', '000-000-000', 'f@f', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') + , (14, 2, 'Contact 4.1') + , (15, 2, 'Contact 4.2') ; diff --git a/test/add_invoice.sql b/test/add_invoice.sql index 0cdd983..eae5fd6 100644 --- a/test/add_invoice.sql +++ b/test/add_invoice.sql @@ -25,6 +25,7 @@ truncate invoice_number_counter cascade; truncate invoice_product_tax cascade; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate product cascade; truncate tax cascade; @@ -72,11 +73,18 @@ values ( 7, 1, 'Product 2.1', 1212) , (11, 2, 'Product 4.3', 1010) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') - , (14, 2, 'Contact 4.1', 'XX777', '', '999-999-999', 'e@e', '', '', '', '', '', 'ES') - , (15, 2, 'Contact 4.2', 'XX888', '', '000-000-000', 'f@f', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') + , (14, 2, 'Contact 4.1') + , (15, 2, 'Contact 4.2') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (12, 'Contact 2.1', 'XX555', '', '', '', '', 'ES') + , (13, 'Contact 2.2', 'XX666', '', '', '', '', 'ES') + , (14, 'Contact 4.1', 'XX777', '', '', '', '', 'ES') + , (15, 'Contact 4.2', 'XX888', '', '', '', '', 'ES') ; diff --git a/test/add_quote.sql b/test/add_quote.sql index a2576a9..45500ba 100644 --- a/test/add_quote.sql +++ b/test/add_quote.sql @@ -74,11 +74,11 @@ values ( 7, 1, 'Product 2.1', 1212) , (11, 2, 'Product 4.3', 1010) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') - , (14, 2, 'Contact 4.1', 'XX777', '', '999-999-999', 'e@e', '', '', '', '', '', 'ES') - , (15, 2, 'Contact 4.2', 'XX888', '', '000-000-000', 'f@f', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') + , (14, 2, 'Contact 4.1') + , (15, 2, 'Contact 4.2') ; diff --git a/test/attach_to_expense.sql b/test/attach_to_expense.sql index 8873f61..53ca5a7 100644 --- a/test/attach_to_expense.sql +++ b/test/attach_to_expense.sql @@ -53,9 +53,9 @@ values (3, 1, 11, 'IRPF -15 %', -0.15) , (4, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') ; insert into expense (expense_id, company_id, slug, invoice_number, invoice_date, contact_id, amount, currency_code, tags) diff --git a/test/contact.sql b/test/contact.sql index d8cada8..719e89d 100644 --- a/test/contact.sql +++ b/test/contact.sql @@ -5,7 +5,7 @@ reset client_min_messages; begin; -select plan(90); +select plan(48); set search_path to numerus, auth, public; @@ -43,62 +43,10 @@ select col_not_null('contact', 'slug'); select col_has_default('contact', 'slug'); select col_default_is('contact', 'slug', 'gen_random_uuid()'); -select has_column('contact', 'business_name'); -select col_type_is('contact', 'business_name', 'text'); -select col_not_null('contact', 'business_name'); -select col_hasnt_default('contact', 'business_name'); - -select has_column('contact', 'vatin'); -select col_type_is('contact', 'vatin', 'vatin'); -select col_not_null('contact', 'vatin'); -select col_hasnt_default('contact', 'vatin'); - -select has_column('contact', 'trade_name'); -select col_type_is('contact', 'trade_name', 'text'); -select col_not_null('contact', 'trade_name'); -select col_hasnt_default('contact', 'trade_name'); - -select has_column('contact', 'phone'); -select col_type_is('contact', 'phone', 'packed_phone_number'); -select col_not_null('contact', 'phone'); -select col_hasnt_default('contact', 'phone'); - -select has_column('contact', 'email'); -select col_type_is('contact', 'email', 'email'); -select col_not_null('contact', 'email'); -select col_hasnt_default('contact', 'email'); - -select has_column('contact', 'web'); -select col_type_is('contact', 'web', 'uri'); -select col_not_null('contact', 'web'); -select col_hasnt_default('contact', 'web'); - -select has_column('contact', 'address'); -select col_type_is('contact', 'address', 'text'); -select col_not_null('contact', 'address'); -select col_hasnt_default('contact', 'address'); - -select has_column('contact', 'city'); -select col_type_is('contact', 'city', 'text'); -select col_not_null('contact', 'city'); -select col_hasnt_default('contact', 'city'); - -select has_column('contact', 'province'); -select col_type_is('contact', 'province', 'text'); -select col_not_null('contact', 'province'); -select col_hasnt_default('contact', 'province'); - -select has_column('contact', 'postal_code'); -select col_type_is('contact', 'postal_code', 'text'); -select col_not_null('contact', 'postal_code'); -select col_hasnt_default('contact', 'postal_code'); - -select has_column('contact', 'country_code'); -select col_type_is('contact', 'country_code', 'country_code'); -select col_is_fk('contact', 'country_code'); -select col_type_is('contact', 'country_code', 'country_code'); -select col_not_null('contact', 'country_code'); -select col_hasnt_default('contact', 'country_code'); +select has_column('contact', 'name'); +select col_type_is('contact', 'name', 'text'); +select col_not_null('contact', 'name'); +select col_hasnt_default('contact', 'name'); select has_column('contact', 'tags'); select col_type_is('contact', 'tags', 'tag_name[]'); @@ -144,15 +92,15 @@ values (2, 1) , (4, 5) ; -insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (company_id, name) +values (2, 'Contact 1') + , (4, 'Contact 2') ; prepare contact_data as -select company_id, business_name +select company_id, name from contact -order by company_id, business_name; +order by company_id, name; set role invoicer; select is_empty('contact_data', 'Should show no data when cookie is not set yet'); @@ -185,11 +133,11 @@ select throws_ok( reset role; select throws_ok( $$ - insert into contact (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) - values (2, ' ', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES') + insert into contact (company_id, name) + values (2, ' ') $$, - '23514', 'new row for relation "contact" violates check constraint "business_name_not_empty"', - 'Should not allow contacts with blank business name' + '23514', 'new row for relation "contact" violates check constraint "name_not_empty"', + 'Should not allow contacts with blank trade name' ); diff --git a/test/contact_email.sql b/test/contact_email.sql new file mode 100644 index 0000000..04034ed --- /dev/null +++ b/test/contact_email.sql @@ -0,0 +1,119 @@ +-- Test contact_email +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(21); + +set search_path to numerus, auth, public; + +select has_table('contact_email'); +select has_pk('contact_email' ); +select table_privs_are('contact_email', 'guest', array []::text[]); +select table_privs_are('contact_email', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_email', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_email', 'authenticator', array []::text[]); + +select has_column('contact_email', 'contact_id'); +select col_is_pk('contact_email', 'contact_id'); +select col_is_fk('contact_email', 'contact_id'); +select fk_ok('contact_email', 'contact_id', 'contact', 'contact_id'); +select col_type_is('contact_email', 'contact_id', 'integer'); +select col_not_null('contact_email', 'contact_id'); +select col_hasnt_default('contact_email', 'contact_id'); + +select has_column('contact_email', 'email'); +select col_type_is('contact_email', 'email', 'email'); +select col_not_null('contact_email', 'email'); +select col_hasnt_default('contact_email', 'email'); + + + +set client_min_messages to warning; +truncate contact_email cascade; +truncate contact cascade; +truncate company_user cascade; +truncate payment_method cascade; +truncate company cascade; +truncate auth."user" cascade; +reset client_min_messages; + +set constraints "company_default_payment_method_id_fkey" deferred; + +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, default_payment_method_id) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 222) + , (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 444) +; + +insert into payment_method (payment_method_id, company_id, name, instructions) +values (444, 4, 'cash', 'cash') + , (222, 2, 'cash', 'cash') +; + +set constraints "company_default_payment_method_id_fkey" immediate; + +insert into company_user (company_id, user_id) +values (2, 1) + , (4, 5) +; + +insert into contact (contact_id, company_id, name) +values (6, 2, 'C1') + , (8, 4, 'C2') + , (9, 4, 'C3') +; + +insert into contact_email (contact_id, email) +values (6, 'c@c') + , (8, 'd@d') +; + +prepare contact_data as +select company_id, email +from contact +join contact_email using (contact_id) +order by company_id, email; + +set role invoicer; +select is_empty('contact_data', 'Should show no data when cookie is not set yet'); +reset role; + +select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (2, 'c@c') + $$, + 'Should only list contacts of the companies where demo@tandem.blog is user of' +); +reset role; + +select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (4, 'd@d') + $$, + 'Should only list contacts of the companies where admin@tandem.blog is user of' +); +reset role; + +select set_cookie('not-a-cookie'); +select throws_ok( + 'contact_data', + '42501', 'permission denied for table contact', + 'Should not allow select to guest users' +); +reset role; + + +select * +from finish(); + +rollback; + diff --git a/test/contact_phone.sql b/test/contact_phone.sql new file mode 100644 index 0000000..82765b6 --- /dev/null +++ b/test/contact_phone.sql @@ -0,0 +1,118 @@ +-- Test contact_phone +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(21); + +set search_path to numerus, auth, public; + +select has_table('contact_phone'); +select has_pk('contact_phone' ); +select table_privs_are('contact_phone', 'guest', array []::text[]); +select table_privs_are('contact_phone', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_phone', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_phone', 'authenticator', array []::text[]); + +select has_column('contact_phone', 'contact_id'); +select col_is_pk('contact_phone', 'contact_id'); +select col_is_fk('contact_phone', 'contact_id'); +select fk_ok('contact_phone', 'contact_id', 'contact', 'contact_id'); +select col_type_is('contact_phone', 'contact_id', 'integer'); +select col_not_null('contact_phone', 'contact_id'); +select col_hasnt_default('contact_phone', 'contact_id'); + +select has_column('contact_phone', 'phone'); +select col_type_is('contact_phone', 'phone', 'packed_phone_number'); +select col_not_null('contact_phone', 'phone'); +select col_hasnt_default('contact_phone', 'phone'); + + +set client_min_messages to warning; +truncate contact_phone cascade; +truncate contact cascade; +truncate company_user cascade; +truncate payment_method cascade; +truncate company cascade; +truncate auth."user" cascade; +reset client_min_messages; + +set constraints "company_default_payment_method_id_fkey" deferred; + +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, default_payment_method_id) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 222) + , (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 444) +; + +insert into payment_method (payment_method_id, company_id, name, instructions) +values (444, 4, 'cash', 'cash') + , (222, 2, 'cash', 'cash') +; + +set constraints "company_default_payment_method_id_fkey" immediate; + +insert into company_user (company_id, user_id) +values (2, 1) + , (4, 5) +; + +insert into contact (contact_id, company_id, name) +values (6, 2, 'C1') + , (8, 4, 'C2') + , (9, 4, 'C3') +; + +insert into contact_phone (contact_id, phone) +values (6, '777-777-777') + , (8, '888-888-888') +; + +prepare contact_data as +select company_id, phone +from contact +join contact_phone using (contact_id) +order by company_id, phone; + +set role invoicer; +select is_empty('contact_data', 'Should show no data when cookie is not set yet'); +reset role; + +select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (2, '777-777-777'::packed_phone_number) + $$, + 'Should only list contacts of the companies where demo@tandem.blog is user of' +); +reset role; + +select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (4, '888-888-888'::packed_phone_number) + $$, + 'Should only list contacts of the companies where admin@tandem.blog is user of' +); +reset role; + +select set_cookie('not-a-cookie'); +select throws_ok( + 'contact_data', + '42501', 'permission denied for table contact', + 'Should not allow select to guest users' +); +reset role; + + +select * +from finish(); + +rollback; + diff --git a/test/contact_tax_details.sql b/test/contact_tax_details.sql new file mode 100644 index 0000000..5bc2143 --- /dev/null +++ b/test/contact_tax_details.sql @@ -0,0 +1,157 @@ +-- Test contact_tax_details +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(48); + +set search_path to numerus, auth, public; + +select has_table('contact_tax_details'); +select has_pk('contact_tax_details' ); +select table_privs_are('contact_tax_details', 'guest', array []::text[]); +select table_privs_are('contact_tax_details', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_tax_details', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_tax_details', 'authenticator', array []::text[]); + +select has_column('contact_tax_details', 'contact_id'); +select col_is_pk('contact_tax_details', 'contact_id'); +select col_is_fk('contact_tax_details', 'contact_id'); +select fk_ok('contact_tax_details', 'contact_id', 'contact', 'contact_id'); +select col_type_is('contact_tax_details', 'contact_id', 'integer'); +select col_not_null('contact_tax_details', 'contact_id'); +select col_hasnt_default('contact_tax_details', 'contact_id'); + +select has_column('contact_tax_details', 'business_name'); +select col_type_is('contact_tax_details', 'business_name', 'text'); +select col_not_null('contact_tax_details', 'business_name'); +select col_hasnt_default('contact_tax_details', 'business_name'); + +select has_column('contact_tax_details', 'vatin'); +select col_type_is('contact_tax_details', 'vatin', 'vatin'); +select col_not_null('contact_tax_details', 'vatin'); +select col_hasnt_default('contact_tax_details', 'vatin'); + +select has_column('contact_tax_details', 'address'); +select col_type_is('contact_tax_details', 'address', 'text'); +select col_not_null('contact_tax_details', 'address'); +select col_hasnt_default('contact_tax_details', 'address'); + +select has_column('contact_tax_details', 'city'); +select col_type_is('contact_tax_details', 'city', 'text'); +select col_not_null('contact_tax_details', 'city'); +select col_hasnt_default('contact_tax_details', 'city'); + +select has_column('contact_tax_details', 'province'); +select col_type_is('contact_tax_details', 'province', 'text'); +select col_not_null('contact_tax_details', 'province'); +select col_hasnt_default('contact_tax_details', 'province'); + +select has_column('contact_tax_details', 'postal_code'); +select col_type_is('contact_tax_details', 'postal_code', 'text'); +select col_not_null('contact_tax_details', 'postal_code'); +select col_hasnt_default('contact_tax_details', 'postal_code'); + +select has_column('contact_tax_details', 'country_code'); +select col_is_fk('contact_tax_details', 'country_code'); +select col_type_is('contact_tax_details', 'country_code', 'country_code'); +select col_type_is('contact_tax_details', 'country_code', 'country_code'); +select col_not_null('contact_tax_details', 'country_code'); +select col_hasnt_default('contact_tax_details', 'country_code'); + + +set client_min_messages to warning; +truncate contact_tax_details cascade; +truncate contact cascade; +truncate company_user cascade; +truncate payment_method cascade; +truncate company cascade; +truncate auth."user" cascade; +reset client_min_messages; + +set constraints "company_default_payment_method_id_fkey" deferred; + +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, default_payment_method_id) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 222) + , (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 444) +; + +insert into payment_method (payment_method_id, company_id, name, instructions) +values (444, 4, 'cash', 'cash') + , (222, 2, 'cash', 'cash') +; + +set constraints "company_default_payment_method_id_fkey" immediate; + +insert into company_user (company_id, user_id) +values (2, 1) + , (4, 5) +; + +insert into contact (contact_id, company_id, name) +values (6, 2, 'C1') + , (8, 4, 'C2') + , (9, 4, 'C3') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (6, 'Contact 1', 'XX555', '', '', '', '', 'ES') + , (8, 'Contact 2', 'XX666', '', '', '', '', 'ES') +; + +prepare contact_data as +select company_id, business_name +from contact +join contact_tax_details using (contact_id) +order by company_id, business_name; + +set role invoicer; +select is_empty('contact_data', 'Should show no data when cookie is not set yet'); +reset role; + +select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (2, 'Contact 1') + $$, + 'Should only list contacts of the companies where demo@tandem.blog is user of' +); +reset role; + +select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (4, 'Contact 2') + $$, + 'Should only list contacts of the companies where admin@tandem.blog is user of' +); +reset role; + +select set_cookie('not-a-cookie'); +select throws_ok( + 'contact_data', + '42501', 'permission denied for table contact', + 'Should not allow select to guest users' +); +reset role; + +select throws_ok( $$ + insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) + values (9, ' ', 'XX123', '', '', '', '', 'ES') + $$, + '23514', 'new row for relation "contact_tax_details" violates check constraint "business_name_not_empty"', + 'Should not allow contacts with blank business name' +); + +select * +from finish(); + +rollback; + diff --git a/test/contact_web.sql b/test/contact_web.sql new file mode 100644 index 0000000..00aa735 --- /dev/null +++ b/test/contact_web.sql @@ -0,0 +1,118 @@ +-- Test contact_web +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(21); + +set search_path to numerus, auth, public; + +select has_table('contact_web'); +select has_pk('contact_web' ); +select table_privs_are('contact_web', 'guest', array []::text[]); +select table_privs_are('contact_web', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_web', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']); +select table_privs_are('contact_web', 'authenticator', array []::text[]); + +select has_column('contact_web', 'contact_id'); +select col_is_pk('contact_web', 'contact_id'); +select col_is_fk('contact_web', 'contact_id'); +select fk_ok('contact_web', 'contact_id', 'contact', 'contact_id'); +select col_type_is('contact_web', 'contact_id', 'integer'); +select col_not_null('contact_web', 'contact_id'); +select col_hasnt_default('contact_web', 'contact_id'); + +select has_column('contact_web', 'uri'); +select col_type_is('contact_web', 'uri', 'uri'); +select col_not_null('contact_web', 'uri'); +select col_hasnt_default('contact_web', 'uri'); + + +set client_min_messages to warning; +truncate contact_web cascade; +truncate contact cascade; +truncate company_user cascade; +truncate payment_method cascade; +truncate company cascade; +truncate auth."user" cascade; +reset client_min_messages; + +set constraints "company_default_payment_method_id_fkey" deferred; + +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, default_payment_method_id) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 222) + , (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 444) +; + +insert into payment_method (payment_method_id, company_id, name, instructions) +values (444, 4, 'cash', 'cash') + , (222, 2, 'cash', 'cash') +; + +set constraints "company_default_payment_method_id_fkey" immediate; + +insert into company_user (company_id, user_id) +values (2, 1) + , (4, 5) +; + +insert into contact (contact_id, company_id, name) +values (6, 2, 'C1') + , (8, 4, 'C2') + , (9, 4, 'C3') +; + +insert into contact_web (contact_id, uri) +values (6, 'http://rainforest.com/') + , (8, 'https://kiwi.com/') +; + +prepare contact_data as +select company_id, uri +from contact +join contact_web using (contact_id) +order by company_id, uri; + +set role invoicer; +select is_empty('contact_data', 'Should show no data when cookie is not set yet'); +reset role; + +select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (2, 'http://rainforest.com/'::uri) + $$, + 'Should only list contacts of the companies where demo@tandem.blog is user of' +); +reset role; + +select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'); +select bag_eq( + 'contact_data', + $$ values (4, 'https://kiwi.com/'::uri) + $$, + 'Should only list contacts of the companies where admin@tandem.blog is user of' +); +reset role; + +select set_cookie('not-a-cookie'); +select throws_ok( + 'contact_data', + '42501', 'permission denied for table contact', + 'Should not allow select to guest users' +); +reset role; + + +select * +from finish(); + +rollback; + diff --git a/test/edit_contact.sql b/test/edit_contact.sql index bc86b38..73d61cd 100644 --- a/test/edit_contact.sql +++ b/test/edit_contact.sql @@ -5,19 +5,19 @@ reset client_min_messages; begin; -select plan(12); +select plan(17); set search_path to auth, numerus, public; -select has_function('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]']); -select function_lang_is('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'plpgsql'); -select function_returns('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'uuid'); -select isnt_definer('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]']); -select volatility_is('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'volatile'); -select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'guest', array []::text[]); -select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'invoicer', array ['EXECUTE']); -select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'admin', array ['EXECUTE']); -select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'text', 'text', 'email', 'uri', 'text', 'text', 'text', 'text', 'country_code', 'tag_name[]'], 'authenticator', array []::text[]); +select has_function('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]']); +select function_lang_is('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'plpgsql'); +select function_returns('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'uuid'); +select isnt_definer('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]']); +select volatility_is('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'volatile'); +select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'guest', array []::text[]); +select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'invoicer', array ['EXECUTE']); +select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'admin', array ['EXECUTE']); +select function_privs_are('numerus', 'edit_contact', array ['uuid', 'text', 'text', 'email', 'uri', 'tax_details', 'tag_name[]'], 'authenticator', array []::text[]); set client_min_messages to warning; @@ -40,30 +40,89 @@ values (111, 1, 'cash', 'cash') set constraints "company_default_payment_method_id_fkey" immediate; -insert into contact (contact_id, company_id, slug, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, tags) -values (12, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES', '{tag1}') - , (13, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES', '{tag2}') +insert into contact (contact_id, company_id, slug, name, tags) +values (12, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Contact 1', '{tag1}') + , (13, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Contact 2', '{tag2}') + , (14, 1, '12fd031b-8f4d-4ac1-9dde-7df336dc6d52', 'Contact 3', '{tag3}') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (12, 'Contact 1 Ltd', 'XX555', '', '', '', '', 'ES') + , (13, 'Contact 2 Ltd', 'XX666', '', '', '', '', 'ES') +; + +insert into contact_phone (contact_id, phone) +values (12, '777-777-777') + , (13, '888-888-888') +; + +insert into contact_email (contact_id, email) +values (12, 'c@c') + , (13, 'd@d') +; + +insert into contact_web (contact_id, uri) +values (12, 'https://1/') + , (13, 'https://2/') ; select lives_ok( - $$ select edit_contact('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Contact 2.1', '40404040D', 'Trade Contact 2.1', '999-999-999', 'c1@c1', 'https://c', 'Fake St., 123', 'City 2.1', 'Province 2.1', '19486', 'ES', array['tag1']) $$, + $$ select edit_contact('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Contact 2.1', '999-999-999', 'c1@c1', 'https://c', '(Contact 2.1 Ltd,40404040D,"Fake St., 123",City 2.1,Province 2.1,19486,ES)', array['tag1']) $$, 'Should be able to edit the first contact' ); select lives_ok( - $$ select edit_contact('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Contact 2.2', '41414141L', 'Trade Contact 2.2', '111-111-111', 'd2@d2', 'https://d', 'Another Fake St., 123', 'City 2.2', 'Province 2.2', '17417', 'ES', array['tag1', 'tag3']) $$, + $$ select edit_contact('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Contact 2.2', null, null, null, null, array['tag1', 'tag3']) $$, 'Should be able to edit the second contact' ); +select lives_ok( + $$ select edit_contact('12fd031b-8f4d-4ac1-9dde-7df336dc6d52', 'Contact 2.3', '111-111-111', 'd2@d2', 'https://d', '(Contact 2.3 Ltd,41414141L,"Another Fake St., 123",City 2.2,Province 2.2,17417,ES)', array['tag2']) $$, + 'Should be able to edit the third contact' +); + select bag_eq( - $$ select company_id, business_name, vatin::text, trade_name, phone::text, email::text, web::text, address, city, province, postal_code, country_code::text, tags, created_at from contact $$, - $$ values (1, 'Contact 2.1', 'ES40404040D', 'Trade Contact 2.1', '+34 999 99 99 99', 'c1@c1', 'https://c', 'Fake St., 123', 'City 2.1', 'Province 2.1', '19486', 'ES', '{tag1}'::tag_name[], CURRENT_TIMESTAMP) - , (1, 'Contact 2.2', 'ES41414141L', 'Trade Contact 2.2', '+34 111111111', 'd2@d2', 'https://d', 'Another Fake St., 123', 'City 2.2', 'Province 2.2', '17417', 'ES', '{tag1,tag3}'::tag_name[], CURRENT_TIMESTAMP) + $$ select company_id, name, tags, created_at from contact $$, + $$ values (1, 'Contact 2.1', '{tag1}'::tag_name[], CURRENT_TIMESTAMP) + , (1, 'Contact 2.2', '{tag1,tag3}'::tag_name[], CURRENT_TIMESTAMP) + , (1, 'Contact 2.3', '{tag2}'::tag_name[], CURRENT_TIMESTAMP) $$, 'Should have updated all contacts' ); +select bag_eq( + $$ select name, business_name, vatin::text, address, city, province, postal_code, country_code::text from contact join contact_tax_details using (contact_id) $$, + $$ values ('Contact 2.1', 'Contact 2.1 Ltd', 'ES40404040D', 'Fake St., 123', 'City 2.1', 'Province 2.1', '19486', 'ES') + , ('Contact 2.3', 'Contact 2.3 Ltd', 'ES41414141L', 'Another Fake St., 123', 'City 2.2', 'Province 2.2', '17417', 'ES') + $$, + 'Should have updated all contacts’ tax details' +); + +select bag_eq( + $$ select name, phone::text from contact join contact_phone using (contact_id) $$, + $$ values ('Contact 2.1', '+34 999 99 99 99') + , ('Contact 2.3', '+34 111111111') + $$, + 'Should have updated all contacts’ phone' +); + +select bag_eq( + $$ select name, email::text from contact join contact_email using (contact_id) $$, + $$ values ('Contact 2.1', 'c1@c1') + , ('Contact 2.3', 'd2@d2') + $$, + 'Should have updated all contacts’ email' +); + +select bag_eq( + $$ select name, uri::text from contact join contact_web using (contact_id) $$, + $$ values ('Contact 2.1', 'https://c') + , ('Contact 2.3', 'https://d') + $$, + 'Should have updated all contacts’ web' +); + select * from finish(); diff --git a/test/edit_expense.sql b/test/edit_expense.sql index 9dd02ed..8d33e1e 100644 --- a/test/edit_expense.sql +++ b/test/edit_expense.sql @@ -53,9 +53,9 @@ values (3, 1, 11, 'IRPF -15 %', -0.15) , (4, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') ; insert into expense (expense_id, company_id, slug, invoice_number, invoice_date, contact_id, amount, currency_code, tags) diff --git a/test/edit_invoice.sql b/test/edit_invoice.sql index 538ada9..821bc75 100644 --- a/test/edit_invoice.sql +++ b/test/edit_invoice.sql @@ -24,6 +24,7 @@ set client_min_messages to warning; truncate invoice_product_tax cascade; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate product cascade; truncate tax cascade; @@ -61,9 +62,14 @@ values ( 7, 1, 'Product 1.1', 1212) , ( 9, 1, 'Product 3.3', 3636) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (12, 'Contact 2.1', 'XX555', '', '', '', '', 'ES') + , (13, 'Contact 2.2', 'XX666', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, slug, invoice_number, invoice_date, contact_id, payment_method_id, currency_code, tags) diff --git a/test/edit_quote.sql b/test/edit_quote.sql index 497b408..d004416 100644 --- a/test/edit_quote.sql +++ b/test/edit_quote.sql @@ -63,9 +63,9 @@ values ( 7, 1, 'Product 1.1', 1212) , ( 9, 1, 'Product 3.3', 3636) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (13, 1, 'Contact 2.2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (12, 1, 'Contact 2.1') + , (13, 1, 'Contact 2.2') ; insert into quote (quote_id, company_id, slug, quote_number, quote_date, currency_code, tags) diff --git a/test/expense.sql b/test/expense.sql index e5434f1..f0e5768 100644 --- a/test/expense.sql +++ b/test/expense.sql @@ -118,9 +118,9 @@ values (2, 1) , (4, 5) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (6, 2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (8, 4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (6, 2, 'Contact 1') + , (8, 4, 'Contact 2') ; insert into expense (company_id, invoice_number, contact_id, invoice_date, amount, currency_code) diff --git a/test/expense_attachment.sql b/test/expense_attachment.sql index 8fc2fe3..7e9804b 100644 --- a/test/expense_attachment.sql +++ b/test/expense_attachment.sql @@ -87,9 +87,9 @@ values (3, 2, 22, 'IVA 21 %', 0.21) , (6, 4, 44, 'IVA 10 %', 0.10) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values ( 9, 2, 'Customer 1', 'XX555', '', '777-777-777', 'c1@e', '', '', '', '', '', 'ES') - , (10, 4, 'Customer 2', 'XX666', '', '888-888-888', 'c2@e', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values ( 9, 2, 'Customer 1') + , (10, 4, 'Customer 2') ; insert into expense (expense_id, company_id, invoice_number, contact_id, invoice_date, amount, currency_code) diff --git a/test/expense_tax.sql b/test/expense_tax.sql index 188fdf6..49346ac 100644 --- a/test/expense_tax.sql +++ b/test/expense_tax.sql @@ -84,9 +84,9 @@ values (3, 2, 22, 'IVA 21 %', 0.21) , (6, 4, 44, 'IVA 10 %', 0.10) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values ( 9, 2, 'Customer 1', 'XX555', '', '777-777-777', 'c1@e', '', '', '', '', '', 'ES') - , (10, 4, 'Customer 2', 'XX666', '', '888-888-888', 'c2@e', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values ( 9, 2, 'Customer 1') + , (10, 4, 'Customer 2') ; insert into expense (expense_id, company_id, invoice_number, contact_id, invoice_date, amount, currency_code) diff --git a/test/expense_tax_amount.sql b/test/expense_tax_amount.sql index 8c715a5..234e92f 100644 --- a/test/expense_tax_amount.sql +++ b/test/expense_tax_amount.sql @@ -58,8 +58,8 @@ values (2, 1, 11, 'IRPF -15 %', -0.15) , (5, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (7, 1, 'Contact') ; insert into expense (expense_id, company_id, invoice_number, invoice_date, contact_id, amount, currency_code) diff --git a/test/invoice.sql b/test/invoice.sql index 5471884..5c118d6 100644 --- a/test/invoice.sql +++ b/test/invoice.sql @@ -56,7 +56,7 @@ select col_default_is('invoice', 'invoice_date', 'CURRENT_DATE'); select has_column('invoice', 'contact_id'); select col_is_fk('invoice', 'contact_id'); -select fk_ok('invoice', 'contact_id', 'contact', 'contact_id'); +select fk_ok('invoice', 'contact_id', 'contact_tax_details', 'contact_id'); select col_type_is('invoice', 'contact_id', 'integer'); select col_not_null('invoice', 'contact_id'); select col_hasnt_default('invoice', 'contact_id'); @@ -104,6 +104,7 @@ select col_default_is('invoice', 'created_at', 'CURRENT_TIMESTAMP'); set client_min_messages to warning; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate company_user cascade; truncate company cascade; @@ -135,9 +136,14 @@ values (2, 1) , (4, 5) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (6, 2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (8, 4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (6, 2, 'Contact 1') + , (8, 4, 'Contact 2') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (6, 'Contact 1', 'XX555', '', '', '', '', 'ES') + , (8, 'Contact 2', 'XX666', '', '', '', '', 'ES') ; insert into invoice (company_id, invoice_number, contact_id, currency_code, payment_method_id) diff --git a/test/invoice_amount.sql b/test/invoice_amount.sql index 3c12009..09bec1c 100644 --- a/test/invoice_amount.sql +++ b/test/invoice_amount.sql @@ -29,6 +29,7 @@ set client_min_messages to warning; truncate invoice_product_tax cascade; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate tax cascade; truncate tax_class cascade; @@ -59,8 +60,12 @@ values (2, 1, 11, 'IRPF -15 %', -0.15) , (5, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (7, 1, 'Contact') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (7, 'Contact', 'XX555', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) diff --git a/test/invoice_product.sql b/test/invoice_product.sql index 2d1ba90..0e14d24 100644 --- a/test/invoice_product.sql +++ b/test/invoice_product.sql @@ -68,6 +68,7 @@ select col_default_is('invoice_product', 'discount_rate', '0.0'); set client_min_messages to warning; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate company_user cascade; truncate payment_method cascade; @@ -99,9 +100,14 @@ values (2, 1) , (4, 5) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (6, 2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') - , (8, 4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (6, 2, 'Contact 1') + , (8, 4, 'Contact 2') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (6, 'Contact 1', 'XX555', '', '', '', '', 'ES') + , (8, 'Contact 2', 'XX666', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, invoice_number, contact_id, currency_code, payment_method_id) diff --git a/test/invoice_product_amount.sql b/test/invoice_product_amount.sql index 75d5e91..12a97bd 100644 --- a/test/invoice_product_amount.sql +++ b/test/invoice_product_amount.sql @@ -29,6 +29,7 @@ set client_min_messages to warning; truncate invoice_product_tax cascade; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate tax cascade; truncate tax_class cascade; @@ -59,8 +60,12 @@ values (2, 1, 11, 'IRPF -15 %', -0.15) , (5, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (7, 1, 'Contact') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (7, 'Contact', 'XX555', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) diff --git a/test/invoice_product_tax.sql b/test/invoice_product_tax.sql index c5018a0..0fb34f5 100644 --- a/test/invoice_product_tax.sql +++ b/test/invoice_product_tax.sql @@ -43,6 +43,7 @@ truncate invoice_product cascade; truncate invoice cascade; truncate tax cascade; truncate tax_class cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate company_user cascade; truncate payment_method cascade; @@ -84,9 +85,14 @@ values (3, 2, 22, 'IVA 21 %', 0.21) , (6, 4, 44, 'IVA 10 %', 0.10) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values ( 9, 2, 'Customer 1', 'XX555', '', '777-777-777', 'c1@e', '', '', '', '', '', 'ES') - , (10, 4, 'Customer 2', 'XX666', '', '888-888-888', 'c2@e', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values ( 9, 2, 'Customer 1') + , (10, 4, 'Customer 2') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values ( 9, 'Customer 1', 'XX555', '', '', '', '', 'ES') + , (10, 'Customer 2', 'XX666', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, invoice_number, contact_id, currency_code, payment_method_id) diff --git a/test/invoice_tax_amount.sql b/test/invoice_tax_amount.sql index 3dd6d6c..7c385c6 100644 --- a/test/invoice_tax_amount.sql +++ b/test/invoice_tax_amount.sql @@ -29,6 +29,7 @@ set client_min_messages to warning; truncate invoice_product_tax cascade; truncate invoice_product cascade; truncate invoice cascade; +truncate contact_tax_details cascade; truncate contact cascade; truncate tax cascade; truncate tax_class cascade; @@ -59,8 +60,12 @@ values (2, 1, 11, 'IRPF -15 %', -0.15) , (5, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (7, 1, 'Contact') +; + +insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code) +values (7, 'Contact', 'XX555', '', '', '', '', 'ES') ; insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) diff --git a/test/quote_amount.sql b/test/quote_amount.sql index 81624cd..a75e448 100644 --- a/test/quote_amount.sql +++ b/test/quote_amount.sql @@ -59,8 +59,8 @@ values (2, 1, 11, 'IRPF -15 %', -0.15) , (5, 1, 11, 'IVA 21 %', 0.21) ; -insert into contact (contact_id, company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code) -values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') +insert into contact (contact_id, company_id, name) +values (7, 1, 'Contact') ; insert into quote (quote_id, company_id, quote_number, quote_date, currency_code) diff --git a/test/tax_details.sql b/test/tax_details.sql new file mode 100644 index 0000000..d7246a2 --- /dev/null +++ b/test/tax_details.sql @@ -0,0 +1,26 @@ +-- Test tax_details +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(9); + +set search_path to numerus, public; + +select has_composite('numerus', 'tax_details', 'Composite type numerus.tax_details should exist'); +select columns_are('numerus', 'tax_details', array['business_name', 'vatin', 'address', 'city', 'province', 'postal_code', 'country_code']); +select col_type_is('numerus'::name, 'tax_details'::name, 'business_name'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'vatin'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'address'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'city'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'province'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'postal_code'::name, 'text'); +select col_type_is('numerus'::name, 'tax_details'::name, 'country_code'::name, 'country_code'); + + +select * +from finish(); + +rollback; diff --git a/verify/add_contact.sql b/verify/add_contact.sql index 2e71a67..55c87af 100644 --- a/verify/add_contact.sql +++ b/verify/add_contact.sql @@ -2,6 +2,6 @@ begin; -select has_function_privilege('numerus.add_contact(integer, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[])', 'execute'); +select has_function_privilege('numerus.add_contact(integer, text, text, numerus.email, uri, numerus.tax_details, numerus.tag_name[])', 'execute'); rollback; diff --git a/verify/add_contact@v0.sql b/verify/add_contact@v0.sql new file mode 100644 index 0000000..2e71a67 --- /dev/null +++ b/verify/add_contact@v0.sql @@ -0,0 +1,7 @@ +-- Verify numerus:add_contact on pg + +begin; + +select has_function_privilege('numerus.add_contact(integer, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[])', 'execute'); + +rollback; diff --git a/verify/contact_email.sql b/verify/contact_email.sql new file mode 100644 index 0000000..5bdacc4 --- /dev/null +++ b/verify/contact_email.sql @@ -0,0 +1,13 @@ +-- Verify numerus:contact_email on pg + +begin; + +select contact_id + , email +from numerus.contact_email +where false; + +select 1 / count(*) from pg_class where oid = 'numerus.contact_email'::regclass and relrowsecurity; +select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.contact_email'::regclass; + +rollback; diff --git a/verify/contact_phone.sql b/verify/contact_phone.sql new file mode 100644 index 0000000..574e718 --- /dev/null +++ b/verify/contact_phone.sql @@ -0,0 +1,13 @@ +-- Verify numerus:contact_phone on pg + +begin; + +select contact_id + , phone +from numerus.contact_phone +where false; + +select 1 / count(*) from pg_class where oid = 'numerus.contact_phone'::regclass and relrowsecurity; +select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.contact_phone'::regclass; + +rollback; diff --git a/verify/contact_tax_details.sql b/verify/contact_tax_details.sql new file mode 100644 index 0000000..6a053e5 --- /dev/null +++ b/verify/contact_tax_details.sql @@ -0,0 +1,19 @@ +-- Verify numerus:contact_tax_details on pg + +begin; + +select contact_id + , business_name + , vatin + , address + , city + , province + , postal_code + , country_code +from numerus.contact_tax_details +where false; + +select 1 / count(*) from pg_class where oid = 'numerus.contact_tax_details'::regclass and relrowsecurity; +select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.contact_tax_details'::regclass; + +rollback; diff --git a/verify/contact_web.sql b/verify/contact_web.sql new file mode 100644 index 0000000..bb177f9 --- /dev/null +++ b/verify/contact_web.sql @@ -0,0 +1,13 @@ +-- Verify numerus:contact_web on pg + +begin; + +select contact_id + , uri +from numerus.contact_web +where false; + +select 1 / count(*) from pg_class where oid = 'numerus.contact_web'::regclass and relrowsecurity; +select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.contact_web'::regclass; + +rollback; diff --git a/verify/edit_contact.sql b/verify/edit_contact.sql index e145ad3..1fa9360 100644 --- a/verify/edit_contact.sql +++ b/verify/edit_contact.sql @@ -2,6 +2,6 @@ begin; -select has_function_privilege('numerus.edit_contact(uuid, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[])', 'execute'); +select has_function_privilege('numerus.edit_contact(uuid, text, text, numerus.email, uri, numerus.tax_details, numerus.tag_name[])', 'execute'); rollback; diff --git a/verify/edit_contact@v0.sql b/verify/edit_contact@v0.sql new file mode 100644 index 0000000..e145ad3 --- /dev/null +++ b/verify/edit_contact@v0.sql @@ -0,0 +1,7 @@ +-- Verify numerus:edit_contact on pg + +begin; + +select has_function_privilege('numerus.edit_contact(uuid, text, text, text, text, numerus.email, uri, text, text, text, text, numerus.country_code, numerus.tag_name[])', 'execute'); + +rollback; diff --git a/verify/invoice_contact_id_fkey.sql b/verify/invoice_contact_id_fkey.sql new file mode 100644 index 0000000..32a70c2 --- /dev/null +++ b/verify/invoice_contact_id_fkey.sql @@ -0,0 +1,13 @@ +-- Verify numerus:invoice_contact_id_fkey.sql on pg + +begin; + +select 1/count(*) +from pg_catalog.pg_constraint +where conrelid = 'numerus.invoice'::regclass +and contype = 'f' +and conname = 'invoice_contact_id_fkey' +and pg_catalog.pg_get_constraintdef(oid, true) = 'FOREIGN KEY (contact_id) REFERENCES numerus.contact_tax_details(contact_id)' +; + +rollback; diff --git a/verify/tax_details.sql b/verify/tax_details.sql new file mode 100644 index 0000000..b55b99d --- /dev/null +++ b/verify/tax_details.sql @@ -0,0 +1,7 @@ +-- Verify numerus:tax_details on pg + +begin; + +select pg_catalog.has_type_privilege('numerus.tax_details', 'usage'); + +rollback; diff --git a/web/static/numerus.css b/web/static/numerus.css index 4f1bdb6..b1264f4 100644 --- a/web/static/numerus.css +++ b/web/static/numerus.css @@ -717,6 +717,14 @@ main > nav { grid-template-columns: repeat(3, 1fr); } +.contact-tax-details { + display: none; +} + +input:checked ~ .contact-tax-details { + display: grid; +} + /* Multiselect, tags */ .tag { diff --git a/web/template/contacts/edit.gohtml b/web/template/contacts/edit.gohtml index f0b2ef2..0f4a7ed 100644 --- a/web/template/contacts/edit.gohtml +++ b/web/template/contacts/edit.gohtml @@ -24,23 +24,28 @@ {{ with .Form }}
- {{ template "input-field" .BusinessName }} - {{ template "input-field" .VATIN }} - {{ template "input-field" .TradeName }} + {{ template "input-field" .Name }} {{ template "input-field" .Phone }} {{ template "input-field" .Email }} {{ template "input-field" .Web }} + {{ template "tags-field" .Tags }} +
+ + {{ template "check-field" .HasTaxDetails }} + +
+ {{ template "input-field" .BusinessName }} + {{ template "input-field" .VATIN }} {{ template "input-field" .Address }} {{ template "input-field" .City }} {{ template "input-field" .Province }} {{ template "input-field" .PostalCode }} {{ template "select-field" .Country }} - {{ template "tags-field" .Tags }}
{{ end }}
- +
diff --git a/web/template/contacts/new.gohtml b/web/template/contacts/new.gohtml index 1d273f9..0bdf05d 100644 --- a/web/template/contacts/new.gohtml +++ b/web/template/contacts/new.gohtml @@ -21,22 +21,27 @@ {{ csrfToken }}
- {{ template "input-field" .BusinessName | addInputAttr "autofocus" }} - {{ template "input-field" .VATIN }} - {{ template "input-field" .TradeName }} - {{ template "input-field" .Phone }} - {{ template "input-field" .Email }} - {{ template "input-field" .Web }} - {{ template "input-field" .Address }} - {{ template "input-field" .City }} - {{ template "input-field" .Province }} - {{ template "input-field" .PostalCode }} - {{ template "select-field" .Country }} - {{ template "tags-field" .Tags }} + {{ template "input-field" .Name | addInputAttr "autofocus" }} + {{ template "input-field" .Phone }} + {{ template "input-field" .Email }} + {{ template "input-field" .Web }} + {{ template "tags-field" .Tags }} +
+ + {{ template "check-field" .HasTaxDetails }} + +
+ {{ template "input-field" .BusinessName }} + {{ template "input-field" .VATIN }} + {{ template "input-field" .Address }} + {{ template "input-field" .City }} + {{ template "input-field" .Province }} + {{ template "input-field" .PostalCode }} + {{ template "select-field" .Country }}
- +
diff --git a/web/template/form.gohtml b/web/template/form.gohtml index 0461c0f..7e6a5b7 100644 --- a/web/template/form.gohtml +++ b/web/template/form.gohtml @@ -142,6 +142,12 @@ {{- end }} +{{ define "check-field" -}} + {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.CheckField*/ -}} + + +{{- end }} + {{ define "invoice-product-form" -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.invoiceProductForm*/ -}}
{{ if .HasQuotee -}} -
- {{ .Quotee.Name }}
- {{ .Quotee.VATIN }}
- {{ .Quotee.Address }}
- {{ .Quotee.City }} ({{ .Quotee.PostalCode}}), {{ .Quotee.Province }}
-
+
+ {{ .Quotee.Name }}
+ {{ if .HasTaxDetails -}} + {{ .Quotee.VATIN }}
+ {{ .Quotee.Address }}
+ {{ .Quotee.City }} ({{ .Quotee.PostalCode}}), {{ .Quotee.Province }}
+ {{- end }} +
{{- end }} {{ if .TermsAndConditions -}}

{{(gettext "Terms and Conditions:")}} {{ .TermsAndConditions }}

{{- end }} - + {{- $columns := 5 | add (len .TaxClasses) | add (boolToInt .HasDiscounts) -}}