Allow importing contacts from Holded
This allows to import an Excel file exported from Holded, because it is our own user case. When we have more customers, we will give out an Excel template file to fill out. Why XLSX files instead of CSV, for instance? First, because this is the output from Holded, but even then we would have more trouble with CSV than with XLSX because of Microsoft: they royally fucked up interoperability when decided that CSV files, the files that only other applications or programmers see, should be “localized”, and use a comma or a **semicolon** to separate a **comma** separated file depending on the locale’s decimal separator. This is ridiculous because it means that CSV files created with an Excel in USA uses comma while the same Excel but with a French locale expects the fields to be separated by semicolon. And for no good reason, either. Since they fucked up so bad, decided to add a non-standard “meta” field to specify the separator, writing a `sep=,` in the first line, but this only works for reading, because saving the same file changes the separator back to the locale-dependent character and removes the “meta” field. And since everyone expects to open spreadsheet with Excel, i can not use CSV if i do not want a bunch of support tickets telling me that the template is all in a single line. I use an extremely old version of a xlsx reading library for golang[0] because it is already available in Debian repositories, and the only thing i want from it is to convert the convoluted XML file into a string array. Go is only responsible to read the file and dump its contents into a temporary table, so that it can execute the PL/pgSQL function that will actually move that data to the correct relations, much like add_contact does but in batch. In PostgreSQL version 16 they added a pg_input_is_valid function that i would use to test whether input values really conform to domains, but i will have to wait for Debian to pick up the new version. Meanwhile, i use a couple of temporary functions, in lieu of nested functions support in PostgreSQL. Part of #45 [0]: https://github.com/tealeg/xlsx
This commit is contained in:
parent
a068784a22
commit
183b8d3ed9
|
@ -11,6 +11,7 @@ Build-Depends:
|
||||||
golang-github-julienschmidt-httprouter-dev,
|
golang-github-julienschmidt-httprouter-dev,
|
||||||
golang-github-leonelquinteros-gotext-dev,
|
golang-github-leonelquinteros-gotext-dev,
|
||||||
golang-golang-x-text-dev,
|
golang-golang-x-text-dev,
|
||||||
|
golang-github-tealeg-xlsx-dev,
|
||||||
postgresql-all (>= 217~),
|
postgresql-all (>= 217~),
|
||||||
sqitch,
|
sqitch,
|
||||||
pgtap,
|
pgtap,
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
-- Deploy numerus:import_contact to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
-- requires: roles
|
||||||
|
-- requires: contact
|
||||||
|
-- requires: contact_web
|
||||||
|
-- requires: contact_phone
|
||||||
|
-- requires: contact_email
|
||||||
|
-- requires: contact_iban
|
||||||
|
-- requires: contact_swift
|
||||||
|
-- requires: contact_tax_details
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create or replace function begin_import_contacts() returns name as
|
||||||
|
$$
|
||||||
|
create temporary table imported_contact (
|
||||||
|
contact_id integer
|
||||||
|
, name text not null default ''
|
||||||
|
, vatin text not null default ''
|
||||||
|
, email text not null default ''
|
||||||
|
, phone text not null default ''
|
||||||
|
, web text not null default ''
|
||||||
|
, address text not null default ''
|
||||||
|
, city text not null default ''
|
||||||
|
, province text not null default ''
|
||||||
|
, postal_code text not null default ''
|
||||||
|
, country_code text not null default ''
|
||||||
|
, iban text not null default ''
|
||||||
|
, bic text not null default ''
|
||||||
|
, tags text not null default ''
|
||||||
|
);
|
||||||
|
select 'imported_contact'::name;
|
||||||
|
$$
|
||||||
|
language sql;
|
||||||
|
|
||||||
|
revoke execute on function begin_import_contacts() from public;
|
||||||
|
grant execute on function begin_import_contacts() to invoicer;
|
||||||
|
grant execute on function begin_import_contacts() to admin;
|
||||||
|
|
||||||
|
create or replace function end_import_contacts(company_id integer) returns integer as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
imported integer;
|
||||||
|
begin
|
||||||
|
update imported_contact
|
||||||
|
set country_code = upper(trim(country_code))
|
||||||
|
, name = trim(name)
|
||||||
|
, vatin = trim(vatin)
|
||||||
|
, email = trim(email)
|
||||||
|
, phone = trim(phone)
|
||||||
|
, web = trim(web)
|
||||||
|
, address = trim(address)
|
||||||
|
, city = trim(city)
|
||||||
|
, province = trim(province)
|
||||||
|
, postal_code = trim(postal_code)
|
||||||
|
, iban = trim(iban)
|
||||||
|
, bic = trim(bic)
|
||||||
|
, tags = lower(trim(regexp_replace(regexp_replace(tags, '[^\sA-Za-z0-9-]', '', 'g'), '\s\s+', ' ', 'g')))
|
||||||
|
;
|
||||||
|
|
||||||
|
update imported_contact
|
||||||
|
set contact_id = tax_details.contact_id
|
||||||
|
from contact_tax_details as tax_details
|
||||||
|
join contact using (contact_id)
|
||||||
|
where contact.company_id = end_import_contacts.company_id
|
||||||
|
and tax_details.vatin::text = imported_contact.country_code || imported_contact.vatin
|
||||||
|
;
|
||||||
|
|
||||||
|
update imported_contact
|
||||||
|
set contact_id = nextval('contact_contact_id_seq'::regclass)
|
||||||
|
where length(trim(name)) > 1
|
||||||
|
and contact_id is null
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact (contact_id, company_id, name, tags)
|
||||||
|
select contact_id, end_import_contacts.company_id, name, string_to_array(tags, ' ')::tag_name[]
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set tags = array_cat(contact.tags, excluded.tags)
|
||||||
|
;
|
||||||
|
|
||||||
|
-- TODO: use pg_input_is_valid with PostgreSQL 16
|
||||||
|
create or replace function pg_temp.input_is_valid(input text, typename text) returns bool as
|
||||||
|
$func$
|
||||||
|
begin
|
||||||
|
begin
|
||||||
|
execute format('select %L::%s', input, typename);
|
||||||
|
return true;
|
||||||
|
exception when others then
|
||||||
|
return false;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
$func$
|
||||||
|
language plpgsql
|
||||||
|
immutable;
|
||||||
|
|
||||||
|
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||||
|
select contact_id, imported_contact.name, (country_code || vatin)::vatin, address, city, province, postal_code, country_code
|
||||||
|
from imported_contact
|
||||||
|
join country using (country_code)
|
||||||
|
where contact_id is not null
|
||||||
|
and length(address) > 1
|
||||||
|
and length(city) > 1
|
||||||
|
and length(province) > 1
|
||||||
|
and postal_code ~ postal_code_regex
|
||||||
|
and pg_temp.input_is_valid(country_code || vatin, 'vatin')
|
||||||
|
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
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_email (contact_id, email)
|
||||||
|
select contact_id, email::email
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
and pg_temp.input_is_valid(email, 'email')
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set email = excluded.email
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_web (contact_id, uri)
|
||||||
|
select contact_id, web::uri
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
and pg_temp.input_is_valid(web, 'uri')
|
||||||
|
and length(web) > 1
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set uri = excluded.uri
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_iban (contact_id, iban)
|
||||||
|
select contact_id, iban::iban
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
and pg_temp.input_is_valid(iban, 'iban')
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set iban = excluded.iban
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_swift (contact_id, bic)
|
||||||
|
select contact_id, bic::bic
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
and pg_temp.input_is_valid(bic, 'bic')
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set bic = excluded.bic
|
||||||
|
;
|
||||||
|
|
||||||
|
-- TODO: use pg_input_is_valid with PostgreSQL 16
|
||||||
|
create or replace function pg_temp.phone_is_valid(phone text, country text) returns bool as
|
||||||
|
$func$
|
||||||
|
begin
|
||||||
|
begin
|
||||||
|
perform parse_packed_phone_number(phone, country);
|
||||||
|
return true;
|
||||||
|
exception when others then
|
||||||
|
return false;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
$func$
|
||||||
|
language plpgsql
|
||||||
|
immutable;
|
||||||
|
|
||||||
|
insert into contact_phone (contact_id, phone)
|
||||||
|
select contact_id, parse_packed_phone_number(phone, case when country_code = '' then 'ES' else country_code end)
|
||||||
|
from imported_contact
|
||||||
|
where contact_id is not null
|
||||||
|
and pg_temp.phone_is_valid(phone, case when country_code = '' then 'ES' else country_code end)
|
||||||
|
on conflict (contact_id) do update
|
||||||
|
set phone = excluded.phone
|
||||||
|
;
|
||||||
|
|
||||||
|
select count(*) from imported_contact where contact_id is not null into imported;
|
||||||
|
return imported;
|
||||||
|
|
||||||
|
drop table imported_contact;
|
||||||
|
end
|
||||||
|
$$
|
||||||
|
language plpgsql;
|
||||||
|
|
||||||
|
revoke execute on function end_import_contacts(integer) from public;
|
||||||
|
grant execute on function end_import_contacts(integer) to invoicer;
|
||||||
|
grant execute on function end_import_contacts(integer) to admin;
|
||||||
|
|
||||||
|
commit;
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/jackc/pgx/v4 v4.15.0
|
github.com/jackc/pgx/v4 v4.15.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/leonelquinteros/gotext v1.5.0
|
github.com/leonelquinteros/gotext v1.5.0
|
||||||
|
github.com/tealeg/xlsx v0.0.0-20181024002044-dbf71b6a931e
|
||||||
golang.org/x/text v0.7.0
|
golang.org/x/text v0.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -70,9 +70,11 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914FOauSkBM=
|
github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914FOauSkBM=
|
||||||
github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
|
github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
|
||||||
|
@ -112,6 +114,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/tealeg/xlsx v0.0.0-20181024002044-dbf71b6a931e h1:0AoAjM/7iqEZwTsWhk3nm9+H5mocFnh6dCGUaIOSTDQ=
|
||||||
|
github.com/tealeg/xlsx v0.0.0-20181024002044-dbf71b6a931e/go.mod h1:uxu5UY2ovkuRPWKQ8Q7JG0JbSivrISjdPzZQKeo74mA=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
@ -184,6 +188,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"github.com/tealeg/xlsx"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -41,8 +42,12 @@ func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
|
||||||
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
form := newContactForm(r.Context(), conn, locale)
|
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if slug == "import" {
|
||||||
|
ServeImportPage(w, r, params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form := newContactForm(r.Context(), conn, locale)
|
||||||
if slug == "new" {
|
if slug == "new" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
mustRenderNewContactForm(w, r, form)
|
mustRenderNewContactForm(w, r, form)
|
||||||
|
@ -501,3 +506,92 @@ func HandleUpdateContactTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
}
|
}
|
||||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ServeImportPage(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
form := newContactImportForm(getLocale(r), getCompany(r))
|
||||||
|
mustRenderMainTemplate(w, r, "contacts/import.gohtml", form)
|
||||||
|
}
|
||||||
|
|
||||||
|
type contactImportForm struct {
|
||||||
|
locale *Locale
|
||||||
|
company *Company
|
||||||
|
File *FileField
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContactImportForm(locale *Locale, company *Company) *contactImportForm {
|
||||||
|
return &contactImportForm{
|
||||||
|
locale: locale,
|
||||||
|
company: company,
|
||||||
|
File: &FileField{
|
||||||
|
Name: "file",
|
||||||
|
Label: pgettext("input", "Holded Excel file", locale),
|
||||||
|
MaxSize: 1 << 20,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (form *contactImportForm) Parse(r *http.Request) error {
|
||||||
|
if err := r.ParseMultipartForm(form.File.MaxSize); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := form.File.FillValue(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleImportContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
locale := getLocale(r)
|
||||||
|
company := mustGetCompany(r)
|
||||||
|
form := newContactImportForm(locale, company)
|
||||||
|
if err := form.Parse(r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := verifyCsrfTokenValid(r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workbook, err := xlsx.OpenBinary(form.File.Content)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
conn := getConn(r)
|
||||||
|
tx := conn.MustBegin(r.Context())
|
||||||
|
defer tx.MustRollback(r.Context())
|
||||||
|
relation := tx.MustGetText(r.Context(), "select begin_import_contacts()")
|
||||||
|
columns := []string{"name", "vatin", "email", "phone", "address", "city", "postal_code", "province", "country_code", "iban", "bic", "tags"}
|
||||||
|
for _, sheet := range workbook.Sheets {
|
||||||
|
tx.MustCopyFrom(r.Context(), relation, columns, len(sheet.Rows)-4, func(idx int) ([]interface{}, error) {
|
||||||
|
row := sheet.Rows[idx+4]
|
||||||
|
var values []interface{}
|
||||||
|
if len(row.Cells) < 23 {
|
||||||
|
values = []interface{}{"", "", "", "", "", "", "", "", "", "", "", ""}
|
||||||
|
} else {
|
||||||
|
phone := row.Cells[5].String() // mobile
|
||||||
|
if phone == "" {
|
||||||
|
phone = row.Cells[4].String() // landline
|
||||||
|
}
|
||||||
|
values = []interface{}{
|
||||||
|
row.Cells[1].String(),
|
||||||
|
row.Cells[2].String(),
|
||||||
|
row.Cells[3].String(),
|
||||||
|
phone,
|
||||||
|
row.Cells[6].String(),
|
||||||
|
row.Cells[7].String(),
|
||||||
|
row.Cells[8].String(),
|
||||||
|
row.Cells[9].String(),
|
||||||
|
row.Cells[11].String(),
|
||||||
|
row.Cells[19].String(),
|
||||||
|
row.Cells[20].String(),
|
||||||
|
row.Cells[22].String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tx.MustExec(r.Context(), "select end_import_contacts($1)", company.Id)
|
||||||
|
tx.MustCommit(r.Context())
|
||||||
|
htmxRedirect(w, r, companyURI(company, "/contacts"))
|
||||||
|
}
|
||||||
|
|
12
pkg/db.go
12
pkg/db.go
|
@ -143,6 +143,14 @@ func (tx *Tx) MustExec(ctx context.Context, sql string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) MustGetText(ctx context.Context, sql string, args ...interface{}) string {
|
||||||
|
var result string
|
||||||
|
if err := tx.QueryRow(ctx, sql, args...).Scan(&result); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *Tx) MustGetInteger(ctx context.Context, sql string, args ...interface{}) int {
|
func (tx *Tx) MustGetInteger(ctx context.Context, sql string, args ...interface{}) int {
|
||||||
var result int
|
var result int
|
||||||
if err := tx.QueryRow(ctx, sql, args...).Scan(&result); err != nil {
|
if err := tx.QueryRow(ctx, sql, args...).Scan(&result); err != nil {
|
||||||
|
@ -159,8 +167,8 @@ func (tx *Tx) MustGetIntegerOrDefault(ctx context.Context, def int, sql string,
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *Tx) MustCopyFrom(ctx context.Context, tableName string, columns []string, rows [][]interface{}) int64 {
|
func (tx *Tx) MustCopyFrom(ctx context.Context, tableName string, columns []string, length int, next func(int) ([]interface{}, error)) int64 {
|
||||||
copied, err := tx.CopyFrom(ctx, pgx.Identifier{tableName}, columns, pgx.CopyFromRows(rows))
|
copied, err := tx.CopyFrom(ctx, pgx.Identifier{tableName}, columns, pgx.CopyFromSlice(length, next))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,6 +325,7 @@ type FileField struct {
|
||||||
OriginalFileName string
|
OriginalFileName string
|
||||||
ContentType string
|
ContentType string
|
||||||
Content []byte
|
Content []byte
|
||||||
|
Required bool
|
||||||
Errors []error
|
Errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ func NewRouter(db *Db) http.Handler {
|
||||||
companyRouter.DELETE("/payment-method/:paymentMethodId", HandleDeletePaymentMethod)
|
companyRouter.DELETE("/payment-method/:paymentMethodId", HandleDeletePaymentMethod)
|
||||||
companyRouter.GET("/contacts", IndexContacts)
|
companyRouter.GET("/contacts", IndexContacts)
|
||||||
companyRouter.POST("/contacts", HandleAddContact)
|
companyRouter.POST("/contacts", HandleAddContact)
|
||||||
|
companyRouter.POST("/contacts/import", HandleImportContacts)
|
||||||
companyRouter.GET("/contacts/:slug", GetContactForm)
|
companyRouter.GET("/contacts/:slug", GetContactForm)
|
||||||
companyRouter.PUT("/contacts/:slug", HandleUpdateContact)
|
companyRouter.PUT("/contacts/:slug", HandleUpdateContact)
|
||||||
companyRouter.PUT("/contacts/:slug/tags", HandleUpdateContactTags)
|
companyRouter.PUT("/contacts/:slug/tags", HandleUpdateContactTags)
|
||||||
|
|
119
po/ca.po
119
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-07-02 01:58+0200\n"
|
"POT-Creation-Date: 2023-07-02 23:47+0200\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||||
|
@ -30,10 +30,11 @@ msgstr "Afegeix productes a la factura"
|
||||||
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
|
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
|
||||||
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||||
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
|
#: web/template/contacts/import.gohtml:8 web/template/profile.gohtml:9
|
||||||
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
|
#: web/template/expenses/new.gohtml:10 web/template/expenses/index.gohtml:10
|
||||||
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
|
#: web/template/expenses/edit.gohtml:10 web/template/tax-details.gohtml:9
|
||||||
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
|
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||||
|
#: web/template/products/edit.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inici"
|
msgstr "Inici"
|
||||||
|
@ -134,7 +135,7 @@ msgid "New invoice"
|
||||||
msgstr "Nova factura"
|
msgstr "Nova factura"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:43 web/template/dashboard.gohtml:23
|
#: web/template/invoices/index.gohtml:43 web/template/dashboard.gohtml:23
|
||||||
#: web/template/quotes/index.gohtml:43 web/template/contacts/index.gohtml:34
|
#: web/template/quotes/index.gohtml:43 web/template/contacts/index.gohtml:36
|
||||||
#: web/template/expenses/index.gohtml:36 web/template/products/index.gohtml:34
|
#: web/template/expenses/index.gohtml:36 web/template/products/index.gohtml:34
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
|
@ -167,7 +168,7 @@ msgid "Status"
|
||||||
msgstr "Estat"
|
msgstr "Estat"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:54 web/template/quotes/index.gohtml:54
|
#: web/template/invoices/index.gohtml:54 web/template/quotes/index.gohtml:54
|
||||||
#: web/template/contacts/index.gohtml:43 web/template/expenses/index.gohtml:46
|
#: web/template/contacts/index.gohtml:45 web/template/expenses/index.gohtml:46
|
||||||
#: web/template/products/index.gohtml:41
|
#: web/template/products/index.gohtml:41
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
|
@ -186,7 +187,7 @@ msgid "Download"
|
||||||
msgstr "Descàrrega"
|
msgstr "Descàrrega"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:57 web/template/quotes/index.gohtml:57
|
#: web/template/invoices/index.gohtml:57 web/template/quotes/index.gohtml:57
|
||||||
#: web/template/contacts/index.gohtml:44 web/template/expenses/index.gohtml:49
|
#: web/template/contacts/index.gohtml:46 web/template/expenses/index.gohtml:49
|
||||||
#: web/template/products/index.gohtml:43
|
#: web/template/products/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
|
@ -199,7 +200,7 @@ msgstr "Selecciona factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:119 web/template/invoices/view.gohtml:19
|
#: web/template/invoices/index.gohtml:119 web/template/invoices/view.gohtml:19
|
||||||
#: web/template/quotes/index.gohtml:119 web/template/quotes/view.gohtml:22
|
#: web/template/quotes/index.gohtml:119 web/template/quotes/view.gohtml:22
|
||||||
#: web/template/contacts/index.gohtml:74 web/template/expenses/index.gohtml:88
|
#: web/template/contacts/index.gohtml:76 web/template/expenses/index.gohtml:88
|
||||||
#: web/template/products/index.gohtml:72
|
#: web/template/products/index.gohtml:72
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
|
@ -444,31 +445,37 @@ msgstr "Nou contacte"
|
||||||
|
|
||||||
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
||||||
#: web/template/contacts/index.gohtml:10 web/template/contacts/edit.gohtml:11
|
#: web/template/contacts/index.gohtml:10 web/template/contacts/edit.gohtml:11
|
||||||
|
#: web/template/contacts/import.gohtml:9
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactes"
|
msgstr "Contactes"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:15
|
#: web/template/contacts/index.gohtml:15
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
|
msgid "Import"
|
||||||
|
msgstr "Importa"
|
||||||
|
|
||||||
|
#: web/template/contacts/index.gohtml:17
|
||||||
|
msgctxt "action"
|
||||||
msgid "New contact"
|
msgid "New contact"
|
||||||
msgstr "Nou contacte"
|
msgstr "Nou contacte"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
|
#: web/template/contacts/index.gohtml:42 web/template/expenses/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr "Contacte"
|
msgstr "Contacte"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:41
|
#: web/template/contacts/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correu-e"
|
msgstr "Correu-e"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:42
|
#: web/template/contacts/index.gohtml:44
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Telèfon"
|
msgstr "Telèfon"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:84
|
#: web/template/contacts/index.gohtml:86
|
||||||
msgid "No contacts added yet."
|
msgid "No contacts added yet."
|
||||||
msgstr "No hi ha cap contacte."
|
msgstr "No hi ha cap contacte."
|
||||||
|
|
||||||
|
@ -477,6 +484,11 @@ msgctxt "title"
|
||||||
msgid "Edit Contact “%s”"
|
msgid "Edit Contact “%s”"
|
||||||
msgstr "Edició del contacte «%s»"
|
msgstr "Edició del contacte «%s»"
|
||||||
|
|
||||||
|
#: web/template/contacts/import.gohtml:2 web/template/contacts/import.gohtml:10
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Import Contacts"
|
||||||
|
msgstr "Importació de contactes"
|
||||||
|
|
||||||
#: web/template/login.gohtml:2 web/template/login.gohtml:15
|
#: web/template/login.gohtml:2 web/template/login.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
|
@ -648,7 +660,7 @@ msgctxt "title"
|
||||||
msgid "Edit Product “%s”"
|
msgid "Edit Product “%s”"
|
||||||
msgstr "Edició del producte «%s»"
|
msgstr "Edició del producte «%s»"
|
||||||
|
|
||||||
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:257
|
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:262
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correu-e"
|
msgstr "Correu-e"
|
||||||
|
@ -662,7 +674,7 @@ msgstr "Contrasenya"
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podeu deixar el correu-e en blanc."
|
msgstr "No podeu deixar el correu-e en blanc."
|
||||||
|
|
||||||
#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:399
|
#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:404
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
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."
|
msgstr "Aquest valor no és un correu-e vàlid. Hauria de ser similar a nom@domini.cat."
|
||||||
|
|
||||||
|
@ -675,44 +687,44 @@ msgid "Invalid user or password."
|
||||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||||
|
|
||||||
#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909
|
#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909
|
||||||
#: pkg/contacts.go:135 pkg/contacts.go:243
|
#: pkg/contacts.go:140 pkg/contacts.go:248
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630
|
#: 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/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174
|
||||||
#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:140
|
#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:145
|
||||||
#: pkg/contacts.go:343
|
#: pkg/contacts.go:348
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetes"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
|
#: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
|
||||||
#: pkg/contacts.go:144
|
#: pkg/contacts.go:149
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags Condition"
|
msgid "Tags Condition"
|
||||||
msgstr "Condició de les etiquetes"
|
msgstr "Condició de les etiquetes"
|
||||||
|
|
||||||
#: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
|
#: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
|
||||||
#: pkg/contacts.go:148
|
#: pkg/contacts.go:153
|
||||||
msgctxt "tag condition"
|
msgctxt "tag condition"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Totes"
|
msgstr "Totes"
|
||||||
|
|
||||||
#: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
|
#: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
|
||||||
#: pkg/contacts.go:149
|
#: pkg/contacts.go:154
|
||||||
msgid "Invoices must have all the specified labels."
|
msgid "Invoices must have all the specified labels."
|
||||||
msgstr "Les factures han de tenir totes les etiquetes."
|
msgstr "Les factures han de tenir totes les etiquetes."
|
||||||
|
|
||||||
#: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
|
#: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
|
||||||
#: pkg/contacts.go:153
|
#: pkg/contacts.go:158
|
||||||
msgctxt "tag condition"
|
msgctxt "tag condition"
|
||||||
msgid "Any"
|
msgid "Any"
|
||||||
msgstr "Qualsevol"
|
msgstr "Qualsevol"
|
||||||
|
|
||||||
#: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
|
#: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
|
||||||
#: pkg/contacts.go:154
|
#: pkg/contacts.go:159
|
||||||
msgid "Invoices must have at least one of the specified labels."
|
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."
|
msgstr "Les factures han de tenir com a mínim una de les etiquetes."
|
||||||
|
|
||||||
|
@ -732,7 +744,7 @@ msgid "Taxes"
|
||||||
msgstr "Imposts"
|
msgstr "Imposts"
|
||||||
|
|
||||||
#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005
|
#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005
|
||||||
#: pkg/contacts.go:392
|
#: pkg/contacts.go:397
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podeu deixar el nom en blanc."
|
msgstr "No podeu deixar el nom en blanc."
|
||||||
|
|
||||||
|
@ -759,47 +771,47 @@ msgctxt "input"
|
||||||
msgid "Trade name"
|
msgid "Trade name"
|
||||||
msgstr "Nom comercial"
|
msgstr "Nom comercial"
|
||||||
|
|
||||||
#: pkg/company.go:118 pkg/contacts.go:249
|
#: pkg/company.go:118 pkg/contacts.go:254
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Telèfon"
|
msgstr "Telèfon"
|
||||||
|
|
||||||
#: pkg/company.go:136 pkg/contacts.go:265
|
#: pkg/company.go:136 pkg/contacts.go:270
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Web"
|
msgid "Web"
|
||||||
msgstr "Web"
|
msgstr "Web"
|
||||||
|
|
||||||
#: pkg/company.go:144 pkg/contacts.go:277
|
#: pkg/company.go:144 pkg/contacts.go:282
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Business name"
|
msgid "Business name"
|
||||||
msgstr "Nom i cognoms"
|
msgstr "Nom i cognoms"
|
||||||
|
|
||||||
#: pkg/company.go:154 pkg/contacts.go:287
|
#: pkg/company.go:154 pkg/contacts.go:292
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "DNI / NIF"
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
#: pkg/company.go:160 pkg/contacts.go:293
|
#: pkg/company.go:160 pkg/contacts.go:298
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Address"
|
msgid "Address"
|
||||||
msgstr "Adreça"
|
msgstr "Adreça"
|
||||||
|
|
||||||
#: pkg/company.go:169 pkg/contacts.go:302
|
#: pkg/company.go:169 pkg/contacts.go:307
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr "Població"
|
msgstr "Població"
|
||||||
|
|
||||||
#: pkg/company.go:175 pkg/contacts.go:308
|
#: pkg/company.go:175 pkg/contacts.go:313
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Province"
|
msgid "Province"
|
||||||
msgstr "Província"
|
msgstr "Província"
|
||||||
|
|
||||||
#: pkg/company.go:181 pkg/contacts.go:314
|
#: pkg/company.go:181 pkg/contacts.go:319
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Postal code"
|
msgid "Postal code"
|
||||||
msgstr "Codi postal"
|
msgstr "Codi postal"
|
||||||
|
|
||||||
#: pkg/company.go:190 pkg/contacts.go:323
|
#: pkg/company.go:190 pkg/contacts.go:328
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Country"
|
msgid "Country"
|
||||||
msgstr "País"
|
msgstr "País"
|
||||||
|
@ -834,23 +846,23 @@ msgctxt "input"
|
||||||
msgid "Legal disclaimer"
|
msgid "Legal disclaimer"
|
||||||
msgstr "Nota legal"
|
msgstr "Nota legal"
|
||||||
|
|
||||||
#: pkg/company.go:271 pkg/contacts.go:375
|
#: pkg/company.go:271 pkg/contacts.go:380
|
||||||
msgid "Selected country is not valid."
|
msgid "Selected country is not valid."
|
||||||
msgstr "Heu seleccionat un país que no és vàlid."
|
msgstr "Heu seleccionat un país que no és vàlid."
|
||||||
|
|
||||||
#: pkg/company.go:275 pkg/contacts.go:378
|
#: pkg/company.go:275 pkg/contacts.go:383
|
||||||
msgid "Business name can not be empty."
|
msgid "Business name can not be empty."
|
||||||
msgstr "No podeu deixar el nom i els cognoms en blanc."
|
msgstr "No podeu deixar el nom i els cognoms en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:276 pkg/contacts.go:379
|
#: pkg/company.go:276 pkg/contacts.go:384
|
||||||
msgid "Business name must have at least two letters."
|
msgid "Business name must have at least two letters."
|
||||||
msgstr "Nom i cognoms han de tenir com a mínim dues lletres."
|
msgstr "Nom i cognoms han de tenir com a mínim dues lletres."
|
||||||
|
|
||||||
#: pkg/company.go:277 pkg/contacts.go:380
|
#: pkg/company.go:277 pkg/contacts.go:385
|
||||||
msgid "VAT number can not be empty."
|
msgid "VAT number can not be empty."
|
||||||
msgstr "No podeu deixar el DNI o NIF en blanc."
|
msgstr "No podeu deixar el DNI o NIF en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:278 pkg/contacts.go:381
|
#: pkg/company.go:278 pkg/contacts.go:386
|
||||||
msgid "This value is not a valid VAT number."
|
msgid "This value is not a valid VAT number."
|
||||||
msgstr "Aquest valor no és un DNI o NIF vàlid."
|
msgstr "Aquest valor no és un DNI o NIF vàlid."
|
||||||
|
|
||||||
|
@ -858,31 +870,31 @@ msgstr "Aquest valor no és un DNI o NIF vàlid."
|
||||||
msgid "Phone can not be empty."
|
msgid "Phone can not be empty."
|
||||||
msgstr "No podeu deixar el telèfon en blanc."
|
msgstr "No podeu deixar el telèfon en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:281 pkg/contacts.go:396
|
#: pkg/company.go:281 pkg/contacts.go:401
|
||||||
msgid "This value is not a valid phone number."
|
msgid "This value is not a valid phone number."
|
||||||
msgstr "Aquest valor no és un telèfon vàlid."
|
msgstr "Aquest valor no és un telèfon vàlid."
|
||||||
|
|
||||||
#: pkg/company.go:287 pkg/contacts.go:402
|
#: pkg/company.go:287 pkg/contacts.go:407
|
||||||
msgid "This value is not a valid web address. It should be like https://domain.com/."
|
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/."
|
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:383
|
#: pkg/company.go:289 pkg/contacts.go:388
|
||||||
msgid "Address can not be empty."
|
msgid "Address can not be empty."
|
||||||
msgstr "No podeu deixar l’adreça en blanc."
|
msgstr "No podeu deixar l’adreça en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:290 pkg/contacts.go:384
|
#: pkg/company.go:290 pkg/contacts.go:389
|
||||||
msgid "City can not be empty."
|
msgid "City can not be empty."
|
||||||
msgstr "No podeu deixar la població en blanc."
|
msgstr "No podeu deixar la població en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:291 pkg/contacts.go:385
|
#: pkg/company.go:291 pkg/contacts.go:390
|
||||||
msgid "Province can not be empty."
|
msgid "Province can not be empty."
|
||||||
msgstr "No podeu deixar la província en blanc."
|
msgstr "No podeu deixar la província en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:292 pkg/contacts.go:387
|
#: pkg/company.go:292 pkg/contacts.go:392
|
||||||
msgid "Postal code can not be empty."
|
msgid "Postal code can not be empty."
|
||||||
msgstr "No podeu deixar el codi postal en blanc."
|
msgstr "No podeu deixar el codi postal en blanc."
|
||||||
|
|
||||||
#: pkg/company.go:293 pkg/contacts.go:388
|
#: pkg/company.go:293 pkg/contacts.go:393
|
||||||
msgid "This value is not a valid postal code."
|
msgid "This value is not a valid postal code."
|
||||||
msgstr "Aquest valor no és un codi postal vàlid."
|
msgstr "Aquest valor no és un codi postal vàlid."
|
||||||
|
|
||||||
|
@ -1248,33 +1260,38 @@ msgstr "DD/MM/YYYY"
|
||||||
msgid "Invoice product ID must be a number greater than zero."
|
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."
|
msgstr "L’ID del producte de factura ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/contacts.go:273
|
#: pkg/contacts.go:278
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Need to input tax details"
|
msgid "Need to input tax details"
|
||||||
msgstr "Necessito poder facturar aquest contacte"
|
msgstr "Necessito poder facturar aquest contacte"
|
||||||
|
|
||||||
#: pkg/contacts.go:333
|
#: pkg/contacts.go:338
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "IBAN"
|
msgid "IBAN"
|
||||||
msgstr "IBAN"
|
msgstr "IBAN"
|
||||||
|
|
||||||
#: pkg/contacts.go:338
|
#: pkg/contacts.go:343
|
||||||
msgctxt "bic"
|
msgctxt "bic"
|
||||||
msgid "BIC"
|
msgid "BIC"
|
||||||
msgstr "BIC"
|
msgstr "BIC"
|
||||||
|
|
||||||
#: pkg/contacts.go:393
|
#: pkg/contacts.go:398
|
||||||
msgid "Name must have at least two letters."
|
msgid "Name must have at least two letters."
|
||||||
msgstr "El nom ha de tenir com a mínim dues lletres."
|
msgstr "El nom ha de tenir com a mínim dues lletres."
|
||||||
|
|
||||||
#: pkg/contacts.go:405
|
#: pkg/contacts.go:410
|
||||||
msgid "This values is not a valid IBAN."
|
msgid "This values is not a valid IBAN."
|
||||||
msgstr "Aquest valor no és un IBAN vàlid."
|
msgstr "Aquest valor no és un IBAN vàlid."
|
||||||
|
|
||||||
#: pkg/contacts.go:408
|
#: pkg/contacts.go:413
|
||||||
msgid "This values is not a valid BIC."
|
msgid "This values is not a valid BIC."
|
||||||
msgstr "Aquest valor no és un BIC vàlid."
|
msgstr "Aquest valor no és un BIC vàlid."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:527
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Holded Excel file"
|
||||||
|
msgstr "Fitxer Excel del Holded"
|
||||||
|
|
||||||
#~ msgctxt "action"
|
#~ msgctxt "action"
|
||||||
#~ msgid "Update contact"
|
#~ msgid "Update contact"
|
||||||
#~ msgstr "Actualitza contacte"
|
#~ msgstr "Actualitza contacte"
|
||||||
|
|
119
po/es.po
119
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-07-02 01:58+0200\n"
|
"POT-Creation-Date: 2023-07-02 23:47+0200\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
@ -30,10 +30,11 @@ msgstr "Añadir productos a la factura"
|
||||||
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
|
#: web/template/quotes/index.gohtml:9 web/template/quotes/view.gohtml:9
|
||||||
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
#: web/template/quotes/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||||
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
#: web/template/contacts/index.gohtml:9 web/template/contacts/edit.gohtml:10
|
||||||
#: web/template/profile.gohtml:9 web/template/expenses/new.gohtml:10
|
#: web/template/contacts/import.gohtml:8 web/template/profile.gohtml:9
|
||||||
#: web/template/expenses/index.gohtml:10 web/template/expenses/edit.gohtml:10
|
#: web/template/expenses/new.gohtml:10 web/template/expenses/index.gohtml:10
|
||||||
#: web/template/tax-details.gohtml:9 web/template/products/new.gohtml:9
|
#: web/template/expenses/edit.gohtml:10 web/template/tax-details.gohtml:9
|
||||||
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
|
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||||
|
#: web/template/products/edit.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inicio"
|
msgstr "Inicio"
|
||||||
|
@ -134,7 +135,7 @@ msgid "New invoice"
|
||||||
msgstr "Nueva factura"
|
msgstr "Nueva factura"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:43 web/template/dashboard.gohtml:23
|
#: web/template/invoices/index.gohtml:43 web/template/dashboard.gohtml:23
|
||||||
#: web/template/quotes/index.gohtml:43 web/template/contacts/index.gohtml:34
|
#: web/template/quotes/index.gohtml:43 web/template/contacts/index.gohtml:36
|
||||||
#: web/template/expenses/index.gohtml:36 web/template/products/index.gohtml:34
|
#: web/template/expenses/index.gohtml:36 web/template/products/index.gohtml:34
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
|
@ -167,7 +168,7 @@ msgid "Status"
|
||||||
msgstr "Estado"
|
msgstr "Estado"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:54 web/template/quotes/index.gohtml:54
|
#: web/template/invoices/index.gohtml:54 web/template/quotes/index.gohtml:54
|
||||||
#: web/template/contacts/index.gohtml:43 web/template/expenses/index.gohtml:46
|
#: web/template/contacts/index.gohtml:45 web/template/expenses/index.gohtml:46
|
||||||
#: web/template/products/index.gohtml:41
|
#: web/template/products/index.gohtml:41
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
|
@ -186,7 +187,7 @@ msgid "Download"
|
||||||
msgstr "Descargar"
|
msgstr "Descargar"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:57 web/template/quotes/index.gohtml:57
|
#: web/template/invoices/index.gohtml:57 web/template/quotes/index.gohtml:57
|
||||||
#: web/template/contacts/index.gohtml:44 web/template/expenses/index.gohtml:49
|
#: web/template/contacts/index.gohtml:46 web/template/expenses/index.gohtml:49
|
||||||
#: web/template/products/index.gohtml:43
|
#: web/template/products/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
|
@ -199,7 +200,7 @@ msgstr "Seleccionar factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:119 web/template/invoices/view.gohtml:19
|
#: web/template/invoices/index.gohtml:119 web/template/invoices/view.gohtml:19
|
||||||
#: web/template/quotes/index.gohtml:119 web/template/quotes/view.gohtml:22
|
#: web/template/quotes/index.gohtml:119 web/template/quotes/view.gohtml:22
|
||||||
#: web/template/contacts/index.gohtml:74 web/template/expenses/index.gohtml:88
|
#: web/template/contacts/index.gohtml:76 web/template/expenses/index.gohtml:88
|
||||||
#: web/template/products/index.gohtml:72
|
#: web/template/products/index.gohtml:72
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
|
@ -444,31 +445,37 @@ msgstr "Nuevo contacto"
|
||||||
|
|
||||||
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
#: web/template/contacts/new.gohtml:10 web/template/contacts/index.gohtml:2
|
||||||
#: web/template/contacts/index.gohtml:10 web/template/contacts/edit.gohtml:11
|
#: web/template/contacts/index.gohtml:10 web/template/contacts/edit.gohtml:11
|
||||||
|
#: web/template/contacts/import.gohtml:9
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Contacts"
|
msgid "Contacts"
|
||||||
msgstr "Contactos"
|
msgstr "Contactos"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:15
|
#: web/template/contacts/index.gohtml:15
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
|
msgid "Import"
|
||||||
|
msgstr "Importar"
|
||||||
|
|
||||||
|
#: web/template/contacts/index.gohtml:17
|
||||||
|
msgctxt "action"
|
||||||
msgid "New contact"
|
msgid "New contact"
|
||||||
msgstr "Nuevo contacto"
|
msgstr "Nuevo contacto"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:40 web/template/expenses/index.gohtml:43
|
#: web/template/contacts/index.gohtml:42 web/template/expenses/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr "Contacto"
|
msgstr "Contacto"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:41
|
#: web/template/contacts/index.gohtml:43
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correo-e"
|
msgstr "Correo-e"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:42
|
#: web/template/contacts/index.gohtml:44
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Teléfono"
|
msgstr "Teléfono"
|
||||||
|
|
||||||
#: web/template/contacts/index.gohtml:84
|
#: web/template/contacts/index.gohtml:86
|
||||||
msgid "No contacts added yet."
|
msgid "No contacts added yet."
|
||||||
msgstr "No hay contactos."
|
msgstr "No hay contactos."
|
||||||
|
|
||||||
|
@ -477,6 +484,11 @@ msgctxt "title"
|
||||||
msgid "Edit Contact “%s”"
|
msgid "Edit Contact “%s”"
|
||||||
msgstr "Edición del contacto «%s»"
|
msgstr "Edición del contacto «%s»"
|
||||||
|
|
||||||
|
#: web/template/contacts/import.gohtml:2 web/template/contacts/import.gohtml:10
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Import Contacts"
|
||||||
|
msgstr "Importación de contactos"
|
||||||
|
|
||||||
#: web/template/login.gohtml:2 web/template/login.gohtml:15
|
#: web/template/login.gohtml:2 web/template/login.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
|
@ -648,7 +660,7 @@ msgctxt "title"
|
||||||
msgid "Edit Product “%s”"
|
msgid "Edit Product “%s”"
|
||||||
msgstr "Edición del producto «%s»"
|
msgstr "Edición del producto «%s»"
|
||||||
|
|
||||||
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:257
|
#: pkg/login.go:37 pkg/company.go:127 pkg/profile.go:40 pkg/contacts.go:262
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correo-e"
|
msgstr "Correo-e"
|
||||||
|
@ -662,7 +674,7 @@ msgstr "Contraseña"
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podéis dejar el correo-e en blanco."
|
msgstr "No podéis dejar el correo-e en blanco."
|
||||||
|
|
||||||
#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:399
|
#: pkg/login.go:71 pkg/company.go:284 pkg/profile.go:90 pkg/contacts.go:404
|
||||||
msgid "This value is not a valid email. It should be like name@domain.com."
|
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."
|
msgstr "Este valor no es un correo-e válido. Tiene que ser parecido a nombre@dominio.es."
|
||||||
|
|
||||||
|
@ -675,44 +687,44 @@ msgid "Invalid user or password."
|
||||||
msgstr "Nombre de usuario o contraseña inválido."
|
msgstr "Nombre de usuario o contraseña inválido."
|
||||||
|
|
||||||
#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909
|
#: pkg/products.go:164 pkg/products.go:263 pkg/quote.go:823 pkg/invoices.go:909
|
||||||
#: pkg/contacts.go:135 pkg/contacts.go:243
|
#: pkg/contacts.go:140 pkg/contacts.go:248
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: pkg/products.go:169 pkg/products.go:290 pkg/quote.go:174 pkg/quote.go:630
|
#: 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/expenses.go:188 pkg/expenses.go:347 pkg/invoices.go:174
|
||||||
#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:140
|
#: pkg/invoices.go:657 pkg/invoices.go:1208 pkg/contacts.go:145
|
||||||
#: pkg/contacts.go:343
|
#: pkg/contacts.go:348
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetes"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
|
#: pkg/products.go:173 pkg/quote.go:178 pkg/expenses.go:351 pkg/invoices.go:178
|
||||||
#: pkg/contacts.go:144
|
#: pkg/contacts.go:149
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags Condition"
|
msgid "Tags Condition"
|
||||||
msgstr "Condición de las etiquetas"
|
msgstr "Condición de las etiquetas"
|
||||||
|
|
||||||
#: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
|
#: pkg/products.go:177 pkg/quote.go:182 pkg/expenses.go:355 pkg/invoices.go:182
|
||||||
#: pkg/contacts.go:148
|
#: pkg/contacts.go:153
|
||||||
msgctxt "tag condition"
|
msgctxt "tag condition"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todas"
|
msgstr "Todas"
|
||||||
|
|
||||||
#: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
|
#: pkg/products.go:178 pkg/expenses.go:356 pkg/invoices.go:183
|
||||||
#: pkg/contacts.go:149
|
#: pkg/contacts.go:154
|
||||||
msgid "Invoices must have all the specified labels."
|
msgid "Invoices must have all the specified labels."
|
||||||
msgstr "Las facturas deben tener todas las etiquetas."
|
msgstr "Las facturas deben tener todas las etiquetas."
|
||||||
|
|
||||||
#: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
|
#: pkg/products.go:182 pkg/quote.go:187 pkg/expenses.go:360 pkg/invoices.go:187
|
||||||
#: pkg/contacts.go:153
|
#: pkg/contacts.go:158
|
||||||
msgctxt "tag condition"
|
msgctxt "tag condition"
|
||||||
msgid "Any"
|
msgid "Any"
|
||||||
msgstr "Cualquiera"
|
msgstr "Cualquiera"
|
||||||
|
|
||||||
#: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
|
#: pkg/products.go:183 pkg/expenses.go:361 pkg/invoices.go:188
|
||||||
#: pkg/contacts.go:154
|
#: pkg/contacts.go:159
|
||||||
msgid "Invoices must have at least one of the specified labels."
|
msgid "Invoices must have at least one of the specified labels."
|
||||||
msgstr "Las facturas deben tener como mínimo una de las etiquetas."
|
msgstr "Las facturas deben tener como mínimo una de las etiquetas."
|
||||||
|
|
||||||
|
@ -732,7 +744,7 @@ msgid "Taxes"
|
||||||
msgstr "Impuestos"
|
msgstr "Impuestos"
|
||||||
|
|
||||||
#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005
|
#: pkg/products.go:309 pkg/quote.go:919 pkg/profile.go:92 pkg/invoices.go:1005
|
||||||
#: pkg/contacts.go:392
|
#: pkg/contacts.go:397
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre en blanco."
|
msgstr "No podéis dejar el nombre en blanco."
|
||||||
|
|
||||||
|
@ -759,47 +771,47 @@ msgctxt "input"
|
||||||
msgid "Trade name"
|
msgid "Trade name"
|
||||||
msgstr "Nombre comercial"
|
msgstr "Nombre comercial"
|
||||||
|
|
||||||
#: pkg/company.go:118 pkg/contacts.go:249
|
#: pkg/company.go:118 pkg/contacts.go:254
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "Teléfono"
|
msgstr "Teléfono"
|
||||||
|
|
||||||
#: pkg/company.go:136 pkg/contacts.go:265
|
#: pkg/company.go:136 pkg/contacts.go:270
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Web"
|
msgid "Web"
|
||||||
msgstr "Web"
|
msgstr "Web"
|
||||||
|
|
||||||
#: pkg/company.go:144 pkg/contacts.go:277
|
#: pkg/company.go:144 pkg/contacts.go:282
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Business name"
|
msgid "Business name"
|
||||||
msgstr "Nombre y apellidos"
|
msgstr "Nombre y apellidos"
|
||||||
|
|
||||||
#: pkg/company.go:154 pkg/contacts.go:287
|
#: pkg/company.go:154 pkg/contacts.go:292
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "DNI / NIF"
|
msgstr "DNI / NIF"
|
||||||
|
|
||||||
#: pkg/company.go:160 pkg/contacts.go:293
|
#: pkg/company.go:160 pkg/contacts.go:298
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Address"
|
msgid "Address"
|
||||||
msgstr "Dirección"
|
msgstr "Dirección"
|
||||||
|
|
||||||
#: pkg/company.go:169 pkg/contacts.go:302
|
#: pkg/company.go:169 pkg/contacts.go:307
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr "Población"
|
msgstr "Población"
|
||||||
|
|
||||||
#: pkg/company.go:175 pkg/contacts.go:308
|
#: pkg/company.go:175 pkg/contacts.go:313
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Province"
|
msgid "Province"
|
||||||
msgstr "Provincia"
|
msgstr "Provincia"
|
||||||
|
|
||||||
#: pkg/company.go:181 pkg/contacts.go:314
|
#: pkg/company.go:181 pkg/contacts.go:319
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Postal code"
|
msgid "Postal code"
|
||||||
msgstr "Código postal"
|
msgstr "Código postal"
|
||||||
|
|
||||||
#: pkg/company.go:190 pkg/contacts.go:323
|
#: pkg/company.go:190 pkg/contacts.go:328
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Country"
|
msgid "Country"
|
||||||
msgstr "País"
|
msgstr "País"
|
||||||
|
@ -834,23 +846,23 @@ msgctxt "input"
|
||||||
msgid "Legal disclaimer"
|
msgid "Legal disclaimer"
|
||||||
msgstr "Nota legal"
|
msgstr "Nota legal"
|
||||||
|
|
||||||
#: pkg/company.go:271 pkg/contacts.go:375
|
#: pkg/company.go:271 pkg/contacts.go:380
|
||||||
msgid "Selected country is not valid."
|
msgid "Selected country is not valid."
|
||||||
msgstr "Habéis escogido un país que no es válido."
|
msgstr "Habéis escogido un país que no es válido."
|
||||||
|
|
||||||
#: pkg/company.go:275 pkg/contacts.go:378
|
#: pkg/company.go:275 pkg/contacts.go:383
|
||||||
msgid "Business name can not be empty."
|
msgid "Business name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre y los apellidos en blanco."
|
msgstr "No podéis dejar el nombre y los apellidos en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:276 pkg/contacts.go:379
|
#: pkg/company.go:276 pkg/contacts.go:384
|
||||||
msgid "Business name must have at least two letters."
|
msgid "Business name must have at least two letters."
|
||||||
msgstr "El nombre y los apellidos deben contener como mínimo dos letras."
|
msgstr "El nombre y los apellidos deben contener como mínimo dos letras."
|
||||||
|
|
||||||
#: pkg/company.go:277 pkg/contacts.go:380
|
#: pkg/company.go:277 pkg/contacts.go:385
|
||||||
msgid "VAT number can not be empty."
|
msgid "VAT number can not be empty."
|
||||||
msgstr "No podéis dejar el DNI o NIF en blanco."
|
msgstr "No podéis dejar el DNI o NIF en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:278 pkg/contacts.go:381
|
#: pkg/company.go:278 pkg/contacts.go:386
|
||||||
msgid "This value is not a valid VAT number."
|
msgid "This value is not a valid VAT number."
|
||||||
msgstr "Este valor no es un DNI o NIF válido."
|
msgstr "Este valor no es un DNI o NIF válido."
|
||||||
|
|
||||||
|
@ -858,31 +870,31 @@ msgstr "Este valor no es un DNI o NIF válido."
|
||||||
msgid "Phone can not be empty."
|
msgid "Phone can not be empty."
|
||||||
msgstr "No podéis dejar el teléfono en blanco."
|
msgstr "No podéis dejar el teléfono en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:281 pkg/contacts.go:396
|
#: pkg/company.go:281 pkg/contacts.go:401
|
||||||
msgid "This value is not a valid phone number."
|
msgid "This value is not a valid phone number."
|
||||||
msgstr "Este valor no es un teléfono válido."
|
msgstr "Este valor no es un teléfono válido."
|
||||||
|
|
||||||
#: pkg/company.go:287 pkg/contacts.go:402
|
#: pkg/company.go:287 pkg/contacts.go:407
|
||||||
msgid "This value is not a valid web address. It should be like https://domain.com/."
|
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/."
|
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:383
|
#: pkg/company.go:289 pkg/contacts.go:388
|
||||||
msgid "Address can not be empty."
|
msgid "Address can not be empty."
|
||||||
msgstr "No podéis dejar la dirección en blanco."
|
msgstr "No podéis dejar la dirección en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:290 pkg/contacts.go:384
|
#: pkg/company.go:290 pkg/contacts.go:389
|
||||||
msgid "City can not be empty."
|
msgid "City can not be empty."
|
||||||
msgstr "No podéis dejar la población en blanco."
|
msgstr "No podéis dejar la población en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:291 pkg/contacts.go:385
|
#: pkg/company.go:291 pkg/contacts.go:390
|
||||||
msgid "Province can not be empty."
|
msgid "Province can not be empty."
|
||||||
msgstr "No podéis dejar la provincia en blanco."
|
msgstr "No podéis dejar la provincia en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:292 pkg/contacts.go:387
|
#: pkg/company.go:292 pkg/contacts.go:392
|
||||||
msgid "Postal code can not be empty."
|
msgid "Postal code can not be empty."
|
||||||
msgstr "No podéis dejar el código postal en blanco."
|
msgstr "No podéis dejar el código postal en blanco."
|
||||||
|
|
||||||
#: pkg/company.go:293 pkg/contacts.go:388
|
#: pkg/company.go:293 pkg/contacts.go:393
|
||||||
msgid "This value is not a valid postal code."
|
msgid "This value is not a valid postal code."
|
||||||
msgstr "Este valor no es un código postal válido válido."
|
msgstr "Este valor no es un código postal válido válido."
|
||||||
|
|
||||||
|
@ -1248,33 +1260,38 @@ msgstr "DD/MM/YYYY"
|
||||||
msgid "Invoice product ID must be a number greater than zero."
|
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."
|
msgstr "El ID de producto de factura tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/contacts.go:273
|
#: pkg/contacts.go:278
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Need to input tax details"
|
msgid "Need to input tax details"
|
||||||
msgstr "Necesito facturar este contacto"
|
msgstr "Necesito facturar este contacto"
|
||||||
|
|
||||||
#: pkg/contacts.go:333
|
#: pkg/contacts.go:338
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "IBAN"
|
msgid "IBAN"
|
||||||
msgstr "IBAN"
|
msgstr "IBAN"
|
||||||
|
|
||||||
#: pkg/contacts.go:338
|
#: pkg/contacts.go:343
|
||||||
msgctxt "bic"
|
msgctxt "bic"
|
||||||
msgid "BIC"
|
msgid "BIC"
|
||||||
msgstr "BIC"
|
msgstr "BIC"
|
||||||
|
|
||||||
#: pkg/contacts.go:393
|
#: pkg/contacts.go:398
|
||||||
msgid "Name must have at least two letters."
|
msgid "Name must have at least two letters."
|
||||||
msgstr "El nombre debe contener como mínimo dos letras."
|
msgstr "El nombre debe contener como mínimo dos letras."
|
||||||
|
|
||||||
#: pkg/contacts.go:405
|
#: pkg/contacts.go:410
|
||||||
msgid "This values is not a valid IBAN."
|
msgid "This values is not a valid IBAN."
|
||||||
msgstr "Este valor no es un IBAN válido."
|
msgstr "Este valor no es un IBAN válido."
|
||||||
|
|
||||||
#: pkg/contacts.go:408
|
#: pkg/contacts.go:413
|
||||||
msgid "This values is not a valid BIC."
|
msgid "This values is not a valid BIC."
|
||||||
msgstr "Este valor no es un BIC válido."
|
msgstr "Este valor no es un BIC válido."
|
||||||
|
|
||||||
|
#: pkg/contacts.go:527
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Holded Excel file"
|
||||||
|
msgstr "Archivo Excel de Holded"
|
||||||
|
|
||||||
#~ msgctxt "action"
|
#~ msgctxt "action"
|
||||||
#~ msgid "Update contact"
|
#~ msgid "Update contact"
|
||||||
#~ msgstr "Actualizar contacto"
|
#~ msgstr "Actualizar contacto"
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Revert numerus:import_contact from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop function if exists numerus.end_import_contacts(integer);
|
||||||
|
drop function if exists numerus.begin_import_contacts();
|
||||||
|
|
||||||
|
commit;
|
|
@ -111,3 +111,4 @@ bic [schema_numerus] 2023-07-01T22:46:30Z jordi fita mas <jordi@tandem.blog> # A
|
||||||
contact_swift [schema_numerus roles contact bic] 2023-07-01T23:03:13Z jordi fita mas <jordi@tandem.blog> # Add relation for contact’s SWIFT-BIC
|
contact_swift [schema_numerus roles contact bic] 2023-07-01T23:03:13Z jordi fita mas <jordi@tandem.blog> # Add relation for contact’s SWIFT-BIC
|
||||||
add_contact [add_contact@v0 tax_details contact_web contact_email contact_phone contact_iban contact_swift] 2023-06-29T11:10:15Z jordi fita mas <jordi@tandem.blog> # Change add contact to accept a tax_detail parameter and use the new relations for web, email, phone, iban, and swift
|
add_contact [add_contact@v0 tax_details contact_web contact_email contact_phone contact_iban contact_swift] 2023-06-29T11:10:15Z jordi fita mas <jordi@tandem.blog> # Change add contact to accept a tax_detail parameter and use the new relations for web, email, phone, iban, and swift
|
||||||
edit_contact [edit_contact@v0 tax_details contact_web contact_email contact_phone contact_iban contact_swift] 2023-06-29T11:50:41Z jordi fita mas <jordi@tandem.blog> # Change edit_contact to require tax_details parameter and to use new relations for web, email, phone, iban, and swift
|
edit_contact [edit_contact@v0 tax_details contact_web contact_email contact_phone contact_iban contact_swift] 2023-06-29T11:50:41Z jordi fita mas <jordi@tandem.blog> # Change edit_contact to require tax_details parameter and to use new relations for web, email, phone, iban, and swift
|
||||||
|
import_contact [schema_numerus roles contact contact_web contact_phone contact_email contact_iban contact_swift contact_tax_details] 2023-07-02T18:50:22Z jordi fita mas <jordi@tandem.blog> # Add functions to massively import customer data
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
-- Test import_contact
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(27);
|
||||||
|
|
||||||
|
set search_path to numerus, auth, public;
|
||||||
|
|
||||||
|
select has_function('numerus', 'begin_import_contacts', array []::name[]);
|
||||||
|
select function_lang_is('numerus', 'begin_import_contacts', array []::name[], 'sql');
|
||||||
|
select function_returns('numerus', 'begin_import_contacts', array []::name[], 'name');
|
||||||
|
select isnt_definer('numerus', 'begin_import_contacts', array []::name[]);
|
||||||
|
select volatility_is('numerus', 'begin_import_contacts', array []::name[], 'volatile');
|
||||||
|
select function_privs_are('numerus', 'begin_import_contacts', array []::name[], 'guest', array []::text[]);
|
||||||
|
select function_privs_are('numerus', 'begin_import_contacts', array []::name[], 'invoicer', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'begin_import_contacts', array []::name[], 'admin', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'begin_import_contacts', array []::name[], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
select has_function('numerus', 'end_import_contacts', array ['integer']);
|
||||||
|
select function_lang_is('numerus', 'end_import_contacts', array ['integer'], 'plpgsql');
|
||||||
|
select function_returns('numerus', 'end_import_contacts', array ['integer'], 'integer');
|
||||||
|
select isnt_definer('numerus', 'end_import_contacts', array ['integer']);
|
||||||
|
select volatility_is('numerus', 'end_import_contacts', array ['integer'], 'volatile');
|
||||||
|
select function_privs_are('numerus', 'end_import_contacts', array ['integer'], 'guest', array []::text[]);
|
||||||
|
select function_privs_are('numerus', 'end_import_contacts', array ['integer'], 'invoicer', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'end_import_contacts', array ['integer'], 'admin', array ['EXECUTE']);
|
||||||
|
select function_privs_are('numerus', 'end_import_contacts', array ['integer'], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate contact_swift cascade;
|
||||||
|
truncate contact_iban cascade;
|
||||||
|
truncate contact_web cascade;
|
||||||
|
truncate contact_email cascade;
|
||||||
|
truncate contact_phone cascade;
|
||||||
|
truncate contact_tax_details cascade;
|
||||||
|
truncate contact cascade;
|
||||||
|
truncate payment_method cascade;
|
||||||
|
truncate company cascade;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||||
|
|
||||||
|
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 (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||||
|
values (111, 1, 'cash', 'cash')
|
||||||
|
, (112, 1, 'bank', 'send money to my bank account')
|
||||||
|
;
|
||||||
|
|
||||||
|
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||||
|
|
||||||
|
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}')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||||
|
values (12, 'Contact 1 Ltd', 'ES41414141L', 'One Road', 'One City', 'One Province', '17001', 'ES')
|
||||||
|
, (13, 'Contact 2 Ltd', 'ES42424242Y', 'Two Road', 'Two City', 'Two Province', '17002', '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/')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_iban (contact_id, iban)
|
||||||
|
values (12, 'NL04RABO9373475770')
|
||||||
|
, (13, 'NL17RABO4416709382')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into contact_swift (contact_id, bic)
|
||||||
|
values (12, 'ABNANL2A')
|
||||||
|
, (13, 'ARBNNL22')
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
select is( begin_import_contacts(), 'imported_contact', 'Should return the name of the relation to import' );
|
||||||
|
|
||||||
|
insert into imported_contact (name, vatin, email, phone, web, address, city, province, postal_code, country_code, iban, bic, tags)
|
||||||
|
values ('Contact 1 S.L.', '41414141L', 'a@a', '111-111-111', 'https://a/', 'Fake St., 123', 'Fake City', 'Fake province', '17000', 'ES', 'NL73INGB9691012820', 'EMCFNLKEX30', '#updated') -- valid updated contact
|
||||||
|
, ('Contact 2 Ltd', '42424242Y', 'd@d', '888-888-888', 'https://2/', '', '', '', '', 'ES', 'NL17RABO4416709382', 'ARBNNL22', '') -- valid existing contact, with same data but missing taxt details; leave what we already had
|
||||||
|
, ('Contact 3', '43434343Q', 'e@e', '999-999-999', 'invalid uri', 'Three Road', 'Three City', 'Three Province', '17003', 'FR', 'NL77INGB8674905641', 'EMCFNLKEXXX', '#new') -- valid new contact
|
||||||
|
, ('Contact 4.1', '44444444B', 'invalid email', '000-000-000', '', 'Four Road', 'Four City', 'Four Province', '17004', 'ES', 'invalid iban', 'EMCFNLKEX20', '#missing #details #vatin') -- invalid vatin: no tax details added
|
||||||
|
, ('Contact 4.2', '44444444A', 'f@f', 'invalid phone', '', '', 'Four City', 'Four Province', '17004', 'ES', 'NL50RABO9661117578', 'invalid bic', '#missing #details #street') -- invalid street: no tax details added
|
||||||
|
, ('Contact 4.3', '44444444A', '', '', 'https://4/', 'Four Road', '', 'Four Province', '17004', 'ES', '', '' , '#missing #details #city #$$$$') -- invalid city: no tax details added
|
||||||
|
, ('Contact 4.4', '44444444A', '', '', '', 'Four Road', 'Four City', '', '17004', 'ES', '', '' , '#missing #details #Pro$vince') -- invalid province: no tax details added
|
||||||
|
, ('Contact 4.5', '44444444A', '', '', '', 'Four Road', 'Four City', 'Four Province', '', 'ES', '', '' , '#missing #det/ails #postal code') -- invalid postal code: no tax details added
|
||||||
|
, ('Contact 4.6', '44444444A', '', '', '', 'Four Road', 'Four City', 'Four Province', '17004', '', '', '' , '#mis-sing #details #country') -- invalid country code: no tax details added
|
||||||
|
, ('Contact 5', '', '', '', '', '', '', '', '', '', '', '', 'just name') -- valid new contact with just a name
|
||||||
|
, (' ', '44444444A', 'valid@email.com', '111 111 111', 'https://3/', 'Fake St., 123', 'City', 'Province', '17486', 'ES', 'NL04RABO9373475770', 'ARBNNL22', 'tag1 tag2') -- contact with invalid name — not added
|
||||||
|
;
|
||||||
|
|
||||||
|
select is( end_import_contacts(1), 10, 'Should have imported all contacts with mostly correct data' );
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select company_id, name, tags from contact $$,
|
||||||
|
$$ values (1, 'Contact 1', '{tag1,updated}'::tag_name[])
|
||||||
|
, (1, 'Contact 2', '{tag2}'::tag_name[])
|
||||||
|
, (1, 'Contact 3', '{new}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.1', '{missing,details,vatin}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.2', '{missing,details,street}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.3', '{missing,details,city}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.4', '{missing,details,province}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.5', '{missing,details,postal,code}'::tag_name[])
|
||||||
|
, (1, 'Contact 4.6', '{mis-sing,details,country}'::tag_name[])
|
||||||
|
, (1, 'Contact 5', '{just,name}'::tag_name[])
|
||||||
|
$$,
|
||||||
|
'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 1', 'Contact 1 S.L.', 'ES41414141L', 'Fake St., 123', 'Fake City', 'Fake province', '17000', 'ES')
|
||||||
|
, ('Contact 2', 'Contact 2 Ltd', 'ES42424242Y', 'Two Road', 'Two City', 'Two Province', '17002', 'ES')
|
||||||
|
, ('Contact 3', 'Contact 3', 'FR43434343Q', 'Three Road', 'Three City', 'Three Province', '17003', 'FR')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ tax details'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select name, phone::text from contact join contact_phone using (contact_id) $$,
|
||||||
|
$$ values ('Contact 1', '+34 111111111')
|
||||||
|
, ('Contact 2', '+34 888 88 88 88')
|
||||||
|
, ('Contact 3', '+33 9 99 99 99 99')
|
||||||
|
, ('Contact 4.1', '+34 000000000')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ phone'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select name, email::text from contact join contact_email using (contact_id) $$,
|
||||||
|
$$ values ('Contact 1', 'a@a')
|
||||||
|
, ('Contact 2', 'd@d')
|
||||||
|
, ('Contact 3', 'e@e')
|
||||||
|
, ('Contact 4.2', 'f@f')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ email'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select name, uri::text from contact join contact_web using (contact_id) $$,
|
||||||
|
$$ values ('Contact 1', 'https://a/')
|
||||||
|
, ('Contact 2', 'https://2/')
|
||||||
|
, ('Contact 4.3', 'https://4/')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ web'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select name, iban::text from contact join contact_iban using (contact_id) $$,
|
||||||
|
$$ values ('Contact 1', 'NL73INGB9691012820')
|
||||||
|
, ('Contact 2', 'NL17RABO4416709382')
|
||||||
|
, ('Contact 3', 'NL77INGB8674905641')
|
||||||
|
, ('Contact 4.2', 'NL50RABO9661117578')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ IBAN'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select name, bic::text from contact join contact_swift using (contact_id) $$,
|
||||||
|
$$ values ('Contact 1', 'EMCFNLKEX30')
|
||||||
|
, ('Contact 2', 'ARBNNL22')
|
||||||
|
, ('Contact 3', 'EMCFNLKEXXX')
|
||||||
|
, ('Contact 4.1', 'EMCFNLKEX20')
|
||||||
|
$$,
|
||||||
|
'Should have created all contacts’ BIC'
|
||||||
|
);
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Verify numerus:import_contact on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select has_function_privilege('numerus.begin_import_contacts()', 'execute');
|
||||||
|
select has_function_privilege('numerus.end_import_contacts(integer)', 'execute');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,26 @@
|
||||||
|
{{ define "title" -}}
|
||||||
|
{{( pgettext "Import Contacts" "title" )}}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ define "breadcrumbs" -}}
|
||||||
|
<nav>
|
||||||
|
<p data-hx-target="main" data-hx-boost="true">
|
||||||
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
<a href="{{ companyURI "/contacts"}}">{{( pgettext "Contacts" "title" )}}</a> /
|
||||||
|
<a>{{( pgettext "Import Contacts" "title" )}}</a>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.contactImportForm*/ -}}
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{{ csrfToken }}
|
||||||
|
|
||||||
|
{{ template "file-field" .File }}
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<button class="primary" type="submit">Import</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{{- end }}
|
|
@ -11,6 +11,8 @@
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ template "filters-toggle" }}
|
{{ template "filters-toggle" }}
|
||||||
|
<a class="button"
|
||||||
|
href="{{ companyURI "/contacts/import" }}">{{( pgettext "Import" "action" )}}</a>
|
||||||
<a class="primary button"
|
<a class="primary button"
|
||||||
href="{{ companyURI "/contacts/new" }}">{{( pgettext "New contact" "action" )}}</a>
|
href="{{ companyURI "/contacts/new" }}">{{( pgettext "New contact" "action" )}}</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{{ define "file-field" -}}
|
{{ define "file-field" -}}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.FileField*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.FileField*/ -}}
|
||||||
<div class="input{{ if .Errors }} has-errors{{ end }}">
|
<div class="input{{ if .Errors }} has-errors{{ end }}">
|
||||||
<input type="file" name="{{ .Name }}" id="{{ .Name }}-field" placeholder="{{ .Label }}">
|
<input type="file" name="{{ .Name }}" id="{{ .Name }}-field" placeholder="{{ .Label }}"{{ if .Required}} required="required"{{ end }}>
|
||||||
<label for="{{ .Name }}-field">{{ .Label }}{{ if gt .MaxSize 0 }} {{printf (pgettext "(Max. %s)" "input") (.MaxSize|humanizeBytes) }}{{ end }}</label>
|
<label for="{{ .Name }}-field">{{ .Label }}{{ if gt .MaxSize 0 }} {{printf (pgettext "(Max. %s)" "input") (.MaxSize|humanizeBytes) }}{{ end }}</label>
|
||||||
{{- if .Errors }}
|
{{- if .Errors }}
|
||||||
<ul>
|
<ul>
|
||||||
|
|
Loading…
Reference in New Issue