Add invoice tags
I followed the same restrictions as Gitea’s topics, arbitrarily, because if it is enough for repositories it should be for invoices too, apparently.
This commit is contained in:
parent
5dedaefc22
commit
2bc05e948c
|
@ -60,14 +60,15 @@ select add_product(1, 'Cavall Fort', 'Revista quinzenal en llengua catalana i de
|
||||||
select add_product(1, 'Palla', 'Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.', '25.00', array[3]);
|
select add_product(1, 'Palla', 'Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.', '25.00', array[3]);
|
||||||
select add_product(1, 'Teia', 'Fusta resinosa de pi i d’altres arbres, provinent sobretot del cor de l’arbre, que crema amb molta facilitat.', '7.00', array[2]);
|
select add_product(1, 'Teia', 'Fusta resinosa de pi i d’altres arbres, provinent sobretot del cor de l’arbre, que crema amb molta facilitat.', '7.00', array[2]);
|
||||||
|
|
||||||
|
alter sequence tag_tag_id_seq restart;
|
||||||
alter sequence invoice_invoice_id_seq restart;
|
alter sequence invoice_invoice_id_seq restart;
|
||||||
alter sequence invoice_product_invoice_product_id_seq restart;
|
alter sequence invoice_product_invoice_product_id_seq restart;
|
||||||
select add_invoice(1, '', (current_date - '28 days'::interval)::date, 6, 'Vol esmorzar!', 1, '{"(1,Teia,\"Fusta resinosa de pi i d’altres arbres, provinent sobretot del cor de l’arbre, que crema amb molta facilitat.\",7.00,1,0.0,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '28 days'::interval)::date, 6, 'Vol esmorzar!', 1, '{producte}','{"(1,Teia,\"Fusta resinosa de pi i d’altres arbres, provinent sobretot del cor de l’arbre, que crema amb molta facilitat.\",7.00,1,0.0,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
||||||
select add_invoice(1, '', (current_date - '24 days'::interval)::date, 5, '', 1, '{"(1,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{3})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '24 days'::interval)::date, 5, '', 1, '{producte,bestia}','{"(1,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{3})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
||||||
select add_invoice(1, '', (current_date - '17 days'::interval)::date, 4, '', 1, '{"(1,\"Paper higiènic (pack de 32 U)\",Paper que s’usa per mantenir la higiene personal després de defecar o orinar.,7.99,10,0.0,{4})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '17 days'::interval)::date, 4, '', 1, '{producte,higiene}','{"(1,\"Paper higiènic (pack de 32 U)\",Paper que s’usa per mantenir la higiene personal després de defecar o orinar.,7.99,10,0.0,{4})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
|
||||||
select add_invoice(1, '', (current_date - '7 days'::interval)::date, 3, '', 1, '{"(1,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '7 days'::interval)::date, 3, '', 1, '{producte,mag}','{"(1,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{2})"}');
|
||||||
select add_invoice(1, '', (current_date - '4 days'::interval)::date, 2, '', 1, '{"(1,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '4 days'::interval)::date, 2, '', 1, '{producte,mag}','{"(1,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{2})"}');
|
||||||
select add_invoice(1, '', (current_date - '1 days'::interval)::date, 1, '', 1, '{"(1,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.\",57.82,18,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{2})"}');
|
select add_invoice(1, '', (current_date - '1 days'::interval)::date, 1, '', 1, '{producte,mag}','{"(1,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.\",57.82,18,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{2})"}');
|
||||||
|
|
||||||
update invoice set invoice_status = 'paid' where invoice_id in (1, 5);
|
update invoice set invoice_status = 'paid' where invoice_id in (1, 5);
|
||||||
update invoice set invoice_status = 'unpaid' where invoice_id = 3;
|
update invoice set invoice_status = 'unpaid' where invoice_id = 3;
|
||||||
|
|
|
@ -9,12 +9,15 @@
|
||||||
-- requires: invoice_product
|
-- requires: invoice_product
|
||||||
-- requires: invoice_product_tax
|
-- requires: invoice_product_tax
|
||||||
-- requires: next_invoice_number
|
-- requires: next_invoice_number
|
||||||
|
-- requires: tag_name
|
||||||
|
-- requires: tag
|
||||||
|
-- requires: invoice_tag
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
set search_path to numerus, public;
|
set search_path to numerus, public;
|
||||||
|
|
||||||
create or replace function add_invoice(company_id integer, invoice_number text, invoice_date date, contact_id integer, notes text, payment_method_id integer, products new_invoice_product[]) returns uuid as
|
create or replace function add_invoice(company integer, invoice_number text, invoice_date date, contact_id integer, notes text, payment_method_id integer, tags tag_name[], products new_invoice_product[]) returns uuid as
|
||||||
$$
|
$$
|
||||||
declare
|
declare
|
||||||
iid integer;
|
iid integer;
|
||||||
|
@ -24,11 +27,11 @@ declare
|
||||||
ipid integer;
|
ipid integer;
|
||||||
begin
|
begin
|
||||||
if invoice_number is null or length(trim(invoice_number)) = 0 then
|
if invoice_number is null or length(trim(invoice_number)) = 0 then
|
||||||
invoice_number = next_invoice_number(company_id, invoice_date);
|
invoice_number = next_invoice_number(company, invoice_date);
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code, payment_method_id)
|
insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code, payment_method_id)
|
||||||
select company.company_id
|
select company_id
|
||||||
, invoice_number
|
, invoice_number
|
||||||
, invoice_date
|
, invoice_date
|
||||||
, contact_id
|
, contact_id
|
||||||
|
@ -36,7 +39,7 @@ begin
|
||||||
, currency_code
|
, currency_code
|
||||||
, add_invoice.payment_method_id
|
, add_invoice.payment_method_id
|
||||||
from company
|
from company
|
||||||
where company.company_id = add_invoice.company_id
|
where company.company_id = add_invoice.company
|
||||||
returning invoice_id, slug, currency_code
|
returning invoice_id, slug, currency_code
|
||||||
into iid, pslug, ccode;
|
into iid, pslug, ccode;
|
||||||
|
|
||||||
|
@ -61,13 +64,27 @@ begin
|
||||||
join unnest(product.tax) as ptax(tax_id) using (tax_id);
|
join unnest(product.tax) as ptax(tax_id) using (tax_id);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
|
if array_length(tags, 1) > 0 then
|
||||||
|
insert into tag (company_id, name)
|
||||||
|
select add_invoice.company, new_tag.name
|
||||||
|
from unnest (tags) as new_tag(name)
|
||||||
|
on conflict (company_id, name) do nothing
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into invoice_tag (invoice_id, tag_id)
|
||||||
|
select iid, tag_id
|
||||||
|
from tag
|
||||||
|
join unnest (tags) as new_tag(name) on company_id = add_invoice.company and tag.name = new_tag.name
|
||||||
|
;
|
||||||
|
end if;
|
||||||
|
|
||||||
return pslug;
|
return pslug;
|
||||||
end;
|
end;
|
||||||
$$
|
$$
|
||||||
language plpgsql;
|
language plpgsql;
|
||||||
|
|
||||||
revoke execute on function add_invoice(integer, text, date, integer, text, integer, new_invoice_product[]) from public;
|
revoke execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) from public;
|
||||||
grant execute on function add_invoice(integer, text, date, integer, text, integer, new_invoice_product[]) to invoicer;
|
grant execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) to invoicer;
|
||||||
grant execute on function add_invoice(integer, text, date, integer, text, integer, new_invoice_product[]) to admin;
|
grant execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) to admin;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
-- Deploy numerus:invoice_tag to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
-- requires: tag
|
||||||
|
-- requires: invoice
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create table invoice_tag (
|
||||||
|
invoice_id integer not null references invoice,
|
||||||
|
tag_id integer not null references tag,
|
||||||
|
primary key (invoice_id, tag_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
grant select, insert, update, delete on table invoice_tag to invoicer;
|
||||||
|
grant select, insert, update, delete on table invoice_tag to admin;
|
||||||
|
|
||||||
|
alter table invoice_tag enable row level security;
|
||||||
|
|
||||||
|
create policy company_policy
|
||||||
|
on invoice_tag
|
||||||
|
using (
|
||||||
|
exists(
|
||||||
|
select 1
|
||||||
|
from invoice
|
||||||
|
where invoice.invoice_id = invoice_tag.invoice_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,35 @@
|
||||||
|
-- Deploy numerus:tag to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
-- requires: tag_name
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
create table tag (
|
||||||
|
tag_id serial primary key,
|
||||||
|
company_id integer not null references company,
|
||||||
|
name tag_name not null,
|
||||||
|
unique (company_id, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
grant select, insert, update, delete on table tag to invoicer;
|
||||||
|
grant select, insert, update, delete on table tag to admin;
|
||||||
|
|
||||||
|
grant usage on sequence tag_tag_id_seq to invoicer;
|
||||||
|
grant usage on sequence tag_tag_id_seq to admin;
|
||||||
|
|
||||||
|
alter table tag enable row level security;
|
||||||
|
|
||||||
|
create policy company_policy
|
||||||
|
on tag
|
||||||
|
using (
|
||||||
|
exists(
|
||||||
|
select 1
|
||||||
|
from company_user
|
||||||
|
join user_profile using (user_id)
|
||||||
|
where company_user.company_id = tag.company_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- Deploy numerus:tag_name to pg
|
||||||
|
-- requires: schema_numerus
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
-- The same as a topic on gitea
|
||||||
|
create domain tag_name as text
|
||||||
|
check ( value ~ '^[a-z0-9][a-z0-9-]{0,34}$' );
|
||||||
|
|
||||||
|
commit;
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -27,6 +28,7 @@ type InvoiceEntry struct {
|
||||||
Total string
|
Total string
|
||||||
CustomerName string
|
CustomerName string
|
||||||
CustomerSlug string
|
CustomerSlug string
|
||||||
|
Tags []string
|
||||||
Status string
|
Status string
|
||||||
StatusLabel string
|
StatusLabel string
|
||||||
}
|
}
|
||||||
|
@ -39,21 +41,51 @@ type InvoicesIndexPage struct {
|
||||||
func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
|
tag := r.URL.Query().Get("tag")
|
||||||
page := &InvoicesIndexPage{
|
page := &InvoicesIndexPage{
|
||||||
Invoices: mustCollectInvoiceEntries(r.Context(), conn, mustGetCompany(r), locale),
|
Invoices: mustCollectInvoiceEntries(r.Context(), conn, mustGetCompany(r), locale, tag),
|
||||||
InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, locale),
|
InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, locale),
|
||||||
}
|
}
|
||||||
mustRenderAppTemplate(w, r, "invoices/index.gohtml", page)
|
mustRenderAppTemplate(w, r, "invoices/index.gohtml", page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale) []*InvoiceEntry {
|
func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale, tag string) []*InvoiceEntry {
|
||||||
rows := conn.MustQuery(ctx, "select invoice.slug, invoice_date, invoice_number, contact.business_name, contact.slug, invoice.invoice_status, isi18n.name, to_price(total, decimal_digits) from invoice join contact using (contact_id) join invoice_status_i18n isi18n on invoice.invoice_status = isi18n.invoice_status and isi18n.lang_tag = $2 join invoice_amount using (invoice_id) join currency using (currency_code) where invoice.company_id = $1 order by invoice_date desc, invoice_number desc", company.Id, locale.Language.String())
|
rows := conn.MustQuery(ctx, `
|
||||||
|
select invoice.slug
|
||||||
|
, invoice_date
|
||||||
|
, invoice_number
|
||||||
|
, contact.business_name
|
||||||
|
, contact.slug
|
||||||
|
, array_agg(tag.name::text)
|
||||||
|
, invoice.invoice_status
|
||||||
|
, isi18n.name
|
||||||
|
, to_price(total, decimal_digits)
|
||||||
|
from invoice
|
||||||
|
left join invoice_tag using (invoice_id)
|
||||||
|
left join tag using(tag_id)
|
||||||
|
join contact using (contact_id)
|
||||||
|
join invoice_status_i18n isi18n on invoice.invoice_status = isi18n.invoice_status and isi18n.lang_tag = $2
|
||||||
|
join invoice_amount using (invoice_id)
|
||||||
|
join currency using (currency_code)
|
||||||
|
where invoice.company_id = $1 and (($3 = '') or (tag.name = $3))
|
||||||
|
group by invoice.slug
|
||||||
|
, invoice_date
|
||||||
|
, invoice_number
|
||||||
|
, contact.business_name
|
||||||
|
, contact.slug
|
||||||
|
, invoice.invoice_status
|
||||||
|
, isi18n.name
|
||||||
|
, total
|
||||||
|
, decimal_digits
|
||||||
|
order by invoice_date desc
|
||||||
|
, invoice_number desc
|
||||||
|
`, company.Id, locale.Language.String(), tag)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var entries []*InvoiceEntry
|
var entries []*InvoiceEntry
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
entry := &InvoiceEntry{}
|
entry := &InvoiceEntry{}
|
||||||
if err := rows.Scan(&entry.Slug, &entry.Date, &entry.Number, &entry.CustomerName, &entry.CustomerSlug, &entry.Status, &entry.StatusLabel, &entry.Total); err != nil {
|
if err := rows.Scan(&entry.Slug, &entry.Date, &entry.Number, &entry.CustomerName, &entry.CustomerSlug, &entry.Tags, &entry.Status, &entry.StatusLabel, &entry.Total); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
entries = append(entries, entry)
|
entries = append(entries, entry)
|
||||||
|
@ -330,7 +362,9 @@ func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
||||||
mustRenderNewInvoiceForm(w, r, form)
|
mustRenderNewInvoiceForm(w, r, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, NewInvoiceProductArray(form.Products))
|
reg := regexp.MustCompile("[^a-z0-9-]+")
|
||||||
|
tags := strings.Split(reg.ReplaceAllString(form.Tags.Val, " "), " ")
|
||||||
|
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7, $8)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, tags, NewInvoiceProductArray(form.Products))
|
||||||
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
|
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,6 +455,7 @@ type invoiceForm struct {
|
||||||
Date *InputField
|
Date *InputField
|
||||||
Notes *InputField
|
Notes *InputField
|
||||||
PaymentMethod *SelectField
|
PaymentMethod *SelectField
|
||||||
|
Tags *InputField
|
||||||
Products []*invoiceProductForm
|
Products []*invoiceProductForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,7 +473,6 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
||||||
Name: "number",
|
Name: "number",
|
||||||
Label: pgettext("input", "Number", locale),
|
Label: pgettext("input", "Number", locale),
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Required: false,
|
|
||||||
},
|
},
|
||||||
Date: &InputField{
|
Date: &InputField{
|
||||||
Name: "date",
|
Name: "date",
|
||||||
|
@ -451,6 +485,11 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
||||||
Label: pgettext("input", "Notes", locale),
|
Label: pgettext("input", "Notes", locale),
|
||||||
Type: "textarea",
|
Type: "textarea",
|
||||||
},
|
},
|
||||||
|
Tags: &InputField{
|
||||||
|
Name: "tags",
|
||||||
|
Label: pgettext("input", "Tags", locale),
|
||||||
|
Type: "text",
|
||||||
|
},
|
||||||
PaymentMethod: &SelectField{
|
PaymentMethod: &SelectField{
|
||||||
Name: "payment_method",
|
Name: "payment_method",
|
||||||
Required: true,
|
Required: true,
|
||||||
|
@ -469,6 +508,7 @@ func (form *invoiceForm) Parse(r *http.Request) error {
|
||||||
form.Number.FillValue(r)
|
form.Number.FillValue(r)
|
||||||
form.Date.FillValue(r)
|
form.Date.FillValue(r)
|
||||||
form.Notes.FillValue(r)
|
form.Notes.FillValue(r)
|
||||||
|
form.Tags.FillValue(r)
|
||||||
form.PaymentMethod.FillValue(r)
|
form.PaymentMethod.FillValue(r)
|
||||||
if _, ok := r.Form["product.id.0"]; ok {
|
if _, ok := r.Form["product.id.0"]; ok {
|
||||||
taxOptions := mustGetTaxOptions(r.Context(), getConn(r), form.company)
|
taxOptions := mustGetTaxOptions(r.Context(), getConn(r), form.company)
|
||||||
|
@ -541,7 +581,24 @@ func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
|
||||||
var invoiceId int
|
var invoiceId int
|
||||||
selectedPaymentMethod := form.PaymentMethod.Selected
|
selectedPaymentMethod := form.PaymentMethod.Selected
|
||||||
form.PaymentMethod.Clear()
|
form.PaymentMethod.Clear()
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, contact_id, invoice_number, invoice_date, notes, payment_method_id from invoice where slug = $1", slug).Scan(&invoiceId, form.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod)) {
|
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||||
|
select invoice_id
|
||||||
|
, contact_id
|
||||||
|
, invoice_number
|
||||||
|
, invoice_date
|
||||||
|
, notes
|
||||||
|
, payment_method_id
|
||||||
|
, string_agg(tag.name, ', ')
|
||||||
|
from invoice
|
||||||
|
left join invoice_tag using (invoice_id)
|
||||||
|
left join tag using(tag_id) where slug = $1
|
||||||
|
group by invoice_id
|
||||||
|
, contact_id
|
||||||
|
, invoice_number
|
||||||
|
, invoice_date
|
||||||
|
, notes
|
||||||
|
, payment_method_id
|
||||||
|
`, slug).Scan(&invoiceId, form.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
|
||||||
form.PaymentMethod.Selected = selectedPaymentMethod
|
form.PaymentMethod.Selected = selectedPaymentMethod
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
93
po/ca.po
93
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-03-09 12:08+0100\n"
|
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||||
|
@ -46,51 +46,51 @@ msgctxt "title"
|
||||||
msgid "New Invoice"
|
msgid "New Invoice"
|
||||||
msgstr "Nova factura"
|
msgstr "Nova factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:41
|
#: web/template/invoices/products.gohtml:42
|
||||||
#: web/template/products/index.gohtml:21
|
#: web/template/products/index.gohtml:21
|
||||||
msgctxt "product"
|
msgctxt "product"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tots"
|
msgstr "Tots"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:42
|
#: web/template/invoices/products.gohtml:43
|
||||||
#: web/template/products/index.gohtml:22
|
#: web/template/products/index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:43
|
#: web/template/invoices/products.gohtml:44
|
||||||
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Preu"
|
msgstr "Preu"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:57
|
#: web/template/invoices/products.gohtml:58
|
||||||
#: web/template/products/index.gohtml:37
|
#: web/template/products/index.gohtml:37
|
||||||
msgid "No products added yet."
|
msgid "No products added yet."
|
||||||
msgstr "No hi ha cap producte."
|
msgstr "No hi ha cap producte."
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:65 web/template/invoices/new.gohtml:61
|
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add products"
|
msgid "Add products"
|
||||||
msgstr "Afegeix productes"
|
msgstr "Afegeix productes"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:42 web/template/invoices/view.gohtml:59
|
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Subtotal"
|
msgstr "Subtotal"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
|
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
|
||||||
#: web/template/invoices/view.gohtml:103
|
#: web/template/invoices/view.gohtml:103
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr "Total"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:64
|
#: web/template/invoices/new.gohtml:65
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr "Actualitza"
|
msgstr "Actualitza"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:19
|
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "New invoice"
|
msgid "New invoice"
|
||||||
msgstr "Nova factura"
|
msgstr "Nova factura"
|
||||||
|
@ -127,8 +127,8 @@ msgstr "Estat"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:33
|
#: web/template/invoices/index.gohtml:33
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Label"
|
msgid "Tags"
|
||||||
msgstr "Etiqueta"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:34
|
#: web/template/invoices/index.gohtml:34
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
|
@ -150,12 +150,12 @@ msgctxt "action"
|
||||||
msgid "Select invoice %v"
|
msgid "Select invoice %v"
|
||||||
msgstr "Selecciona factura %v"
|
msgstr "Selecciona factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:86 web/template/invoices/view.gohtml:14
|
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Duplicate"
|
msgid "Duplicate"
|
||||||
msgstr "Duplica"
|
msgstr "Duplica"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:96
|
#: web/template/invoices/index.gohtml:101
|
||||||
msgid "No invoices added yet."
|
msgid "No invoices added yet."
|
||||||
msgstr "No hi ha cap factura."
|
msgstr "No hi ha cap factura."
|
||||||
|
|
||||||
|
@ -428,44 +428,44 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||||
|
|
||||||
#: pkg/products.go:165 pkg/invoices.go:578
|
#: pkg/products.go:165 pkg/invoices.go:635
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: pkg/products.go:171 pkg/invoices.go:583
|
#: pkg/products.go:171 pkg/invoices.go:640
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: pkg/products.go:176 pkg/invoices.go:587
|
#: pkg/products.go:176 pkg/invoices.go:644
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Preu"
|
msgstr "Preu"
|
||||||
|
|
||||||
#: pkg/products.go:186 pkg/invoices.go:613
|
#: pkg/products.go:186 pkg/invoices.go:670
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Taxes"
|
msgid "Taxes"
|
||||||
msgstr "Imposts"
|
msgstr "Imposts"
|
||||||
|
|
||||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:492
|
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
|
||||||
#: pkg/invoices.go:649
|
#: pkg/invoices.go:706
|
||||||
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."
|
||||||
|
|
||||||
#: pkg/products.go:207 pkg/invoices.go:650
|
#: pkg/products.go:207 pkg/invoices.go:707
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podeu deixar el preu en blanc."
|
msgstr "No podeu deixar el preu en blanc."
|
||||||
|
|
||||||
#: pkg/products.go:208 pkg/invoices.go:651
|
#: pkg/products.go:208 pkg/invoices.go:708
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El preu ha de ser un número major a zero."
|
msgstr "El preu ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/products.go:210 pkg/invoices.go:659
|
#: pkg/products.go:210 pkg/invoices.go:716
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||||
|
|
||||||
#: pkg/products.go:211 pkg/invoices.go:660
|
#: pkg/products.go:211 pkg/invoices.go:717
|
||||||
msgid "You can only select a tax of each class."
|
msgid "You can only select a tax of each class."
|
||||||
msgstr "Només podeu seleccionar un impost de cada classe."
|
msgstr "Només podeu seleccionar un impost de cada classe."
|
||||||
|
|
||||||
|
@ -573,83 +573,88 @@ msgstr "La confirmació no és igual a la contrasenya."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||||
|
|
||||||
#: pkg/invoices.go:270
|
#: pkg/invoices.go:302
|
||||||
msgid "Select a customer to bill."
|
msgid "Select a customer to bill."
|
||||||
msgstr "Escolliu un client a facturar."
|
msgstr "Escolliu un client a facturar."
|
||||||
|
|
||||||
#: pkg/invoices.go:363 pkg/invoices.go:392
|
#: pkg/invoices.go:397 pkg/invoices.go:426
|
||||||
msgid "Invalid action"
|
msgid "Invalid action"
|
||||||
msgstr "Acció invàlida."
|
msgstr "Acció invàlida."
|
||||||
|
|
||||||
#: pkg/invoices.go:386
|
#: pkg/invoices.go:420
|
||||||
msgid "invoices.zip"
|
msgid "invoices.zip"
|
||||||
msgstr "factures.zip"
|
msgstr "factures.zip"
|
||||||
|
|
||||||
#: pkg/invoices.go:433
|
#: pkg/invoices.go:468
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Client"
|
msgstr "Client"
|
||||||
|
|
||||||
#: pkg/invoices.go:439
|
#: pkg/invoices.go:474
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Número"
|
msgstr "Número"
|
||||||
|
|
||||||
#: pkg/invoices.go:445
|
#: pkg/invoices.go:479
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Invoice Date"
|
msgid "Invoice Date"
|
||||||
msgstr "Data de factura"
|
msgstr "Data de factura"
|
||||||
|
|
||||||
#: pkg/invoices.go:451
|
#: pkg/invoices.go:485
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr "Notes"
|
msgstr "Notes"
|
||||||
|
|
||||||
#: pkg/invoices.go:457
|
#: pkg/invoices.go:490
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tags"
|
||||||
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
|
#: pkg/invoices.go:496
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Payment Method"
|
msgid "Payment Method"
|
||||||
msgstr "Mètode de pagament"
|
msgstr "Mètode de pagament"
|
||||||
|
|
||||||
#: pkg/invoices.go:493
|
#: pkg/invoices.go:533
|
||||||
msgid "Invoice date can not be empty."
|
msgid "Invoice date can not be empty."
|
||||||
msgstr "No podeu deixar la data de la factura en blanc."
|
msgstr "No podeu deixar la data de la factura en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:494
|
#: pkg/invoices.go:534
|
||||||
msgid "Invoice date must be a valid date."
|
msgid "Invoice date must be a valid date."
|
||||||
msgstr "La data de facturació ha de ser vàlida."
|
msgstr "La data de facturació ha de ser vàlida."
|
||||||
|
|
||||||
#: pkg/invoices.go:496
|
#: pkg/invoices.go:536
|
||||||
msgid "Selected payment method is not valid."
|
msgid "Selected payment method is not valid."
|
||||||
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
|
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
|
||||||
|
|
||||||
#: pkg/invoices.go:573
|
#: pkg/invoices.go:630
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr "Identificador"
|
msgstr "Identificador"
|
||||||
|
|
||||||
#: pkg/invoices.go:596
|
#: pkg/invoices.go:653
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Quantitat"
|
msgstr "Quantitat"
|
||||||
|
|
||||||
#: pkg/invoices.go:604
|
#: pkg/invoices.go:661
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Discount (%)"
|
msgid "Discount (%)"
|
||||||
msgstr "Descompte (%)"
|
msgstr "Descompte (%)"
|
||||||
|
|
||||||
#: pkg/invoices.go:653
|
#: pkg/invoices.go:710
|
||||||
msgid "Quantity can not be empty."
|
msgid "Quantity can not be empty."
|
||||||
msgstr "No podeu deixar la quantitat en blanc."
|
msgstr "No podeu deixar la quantitat en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:654
|
#: pkg/invoices.go:711
|
||||||
msgid "Quantity must be a number greater than zero."
|
msgid "Quantity must be a number greater than zero."
|
||||||
msgstr "La quantitat ha de ser un número major a zero."
|
msgstr "La quantitat ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/invoices.go:656
|
#: pkg/invoices.go:713
|
||||||
msgid "Discount can not be empty."
|
msgid "Discount can not be empty."
|
||||||
msgstr "No podeu deixar el descompte en blanc."
|
msgstr "No podeu deixar el descompte en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:657
|
#: pkg/invoices.go:714
|
||||||
msgid "Discount must be a percentage between 0 and 100."
|
msgid "Discount must be a percentage between 0 and 100."
|
||||||
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
||||||
|
|
||||||
|
@ -751,6 +756,10 @@ msgstr "No podeu deixar el codi postal en blanc."
|
||||||
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."
|
||||||
|
|
||||||
|
#~ msgctxt "title"
|
||||||
|
#~ msgid "Label"
|
||||||
|
#~ msgstr "Etiqueta"
|
||||||
|
|
||||||
#~ msgid "Select a tax for this product."
|
#~ msgid "Select a tax for this product."
|
||||||
#~ msgstr "Escolliu un impost per aquest producte."
|
#~ msgstr "Escolliu un impost per aquest producte."
|
||||||
|
|
||||||
|
|
93
po/es.po
93
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-03-09 12:08+0100\n"
|
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
@ -46,51 +46,51 @@ msgctxt "title"
|
||||||
msgid "New Invoice"
|
msgid "New Invoice"
|
||||||
msgstr "Nueva factura"
|
msgstr "Nueva factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:41
|
#: web/template/invoices/products.gohtml:42
|
||||||
#: web/template/products/index.gohtml:21
|
#: web/template/products/index.gohtml:21
|
||||||
msgctxt "product"
|
msgctxt "product"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todos"
|
msgstr "Todos"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:42
|
#: web/template/invoices/products.gohtml:43
|
||||||
#: web/template/products/index.gohtml:22
|
#: web/template/products/index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:43
|
#: web/template/invoices/products.gohtml:44
|
||||||
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Precio"
|
msgstr "Precio"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:57
|
#: web/template/invoices/products.gohtml:58
|
||||||
#: web/template/products/index.gohtml:37
|
#: web/template/products/index.gohtml:37
|
||||||
msgid "No products added yet."
|
msgid "No products added yet."
|
||||||
msgstr "No hay productos."
|
msgstr "No hay productos."
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:65 web/template/invoices/new.gohtml:61
|
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add products"
|
msgid "Add products"
|
||||||
msgstr "Añadir productos"
|
msgstr "Añadir productos"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:42 web/template/invoices/view.gohtml:59
|
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Subtotal"
|
msgstr "Subtotal"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:52 web/template/invoices/view.gohtml:63
|
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
|
||||||
#: web/template/invoices/view.gohtml:103
|
#: web/template/invoices/view.gohtml:103
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr "Total"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:64
|
#: web/template/invoices/new.gohtml:65
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr "Actualizar"
|
msgstr "Actualizar"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:66 web/template/invoices/index.gohtml:19
|
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "New invoice"
|
msgid "New invoice"
|
||||||
msgstr "Nueva factura"
|
msgstr "Nueva factura"
|
||||||
|
@ -127,8 +127,8 @@ msgstr "Estado"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:33
|
#: web/template/invoices/index.gohtml:33
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Label"
|
msgid "Tags"
|
||||||
msgstr "Etiqueta"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:34
|
#: web/template/invoices/index.gohtml:34
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
|
@ -150,12 +150,12 @@ msgctxt "action"
|
||||||
msgid "Select invoice %v"
|
msgid "Select invoice %v"
|
||||||
msgstr "Seleccionar factura %v"
|
msgstr "Seleccionar factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:86 web/template/invoices/view.gohtml:14
|
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Duplicate"
|
msgid "Duplicate"
|
||||||
msgstr "Duplicar"
|
msgstr "Duplicar"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:96
|
#: web/template/invoices/index.gohtml:101
|
||||||
msgid "No invoices added yet."
|
msgid "No invoices added yet."
|
||||||
msgstr "No hay facturas."
|
msgstr "No hay facturas."
|
||||||
|
|
||||||
|
@ -428,44 +428,44 @@ msgstr "No podéis dejar la contraseña en blanco."
|
||||||
msgid "Invalid user or password."
|
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:165 pkg/invoices.go:578
|
#: pkg/products.go:165 pkg/invoices.go:635
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: pkg/products.go:171 pkg/invoices.go:583
|
#: pkg/products.go:171 pkg/invoices.go:640
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripción"
|
msgstr "Descripción"
|
||||||
|
|
||||||
#: pkg/products.go:176 pkg/invoices.go:587
|
#: pkg/products.go:176 pkg/invoices.go:644
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Precio"
|
msgstr "Precio"
|
||||||
|
|
||||||
#: pkg/products.go:186 pkg/invoices.go:613
|
#: pkg/products.go:186 pkg/invoices.go:670
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Taxes"
|
msgid "Taxes"
|
||||||
msgstr "Impuestos"
|
msgstr "Impuestos"
|
||||||
|
|
||||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:492
|
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
|
||||||
#: pkg/invoices.go:649
|
#: pkg/invoices.go:706
|
||||||
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."
|
||||||
|
|
||||||
#: pkg/products.go:207 pkg/invoices.go:650
|
#: pkg/products.go:207 pkg/invoices.go:707
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podéis dejar el precio en blanco."
|
msgstr "No podéis dejar el precio en blanco."
|
||||||
|
|
||||||
#: pkg/products.go:208 pkg/invoices.go:651
|
#: pkg/products.go:208 pkg/invoices.go:708
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El precio tiene que ser un número mayor a cero."
|
msgstr "El precio tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/products.go:210 pkg/invoices.go:659
|
#: pkg/products.go:210 pkg/invoices.go:716
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Habéis escogido un impuesto que no es válido."
|
msgstr "Habéis escogido un impuesto que no es válido."
|
||||||
|
|
||||||
#: pkg/products.go:211 pkg/invoices.go:660
|
#: pkg/products.go:211 pkg/invoices.go:717
|
||||||
msgid "You can only select a tax of each class."
|
msgid "You can only select a tax of each class."
|
||||||
msgstr "Solo podéis escoger un impuesto de cada clase."
|
msgstr "Solo podéis escoger un impuesto de cada clase."
|
||||||
|
|
||||||
|
@ -573,83 +573,88 @@ msgstr "La confirmación no corresponde con la contraseña."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Habéis escogido un idioma que no es válido."
|
msgstr "Habéis escogido un idioma que no es válido."
|
||||||
|
|
||||||
#: pkg/invoices.go:270
|
#: pkg/invoices.go:302
|
||||||
msgid "Select a customer to bill."
|
msgid "Select a customer to bill."
|
||||||
msgstr "Escoged un cliente a facturar."
|
msgstr "Escoged un cliente a facturar."
|
||||||
|
|
||||||
#: pkg/invoices.go:363 pkg/invoices.go:392
|
#: pkg/invoices.go:397 pkg/invoices.go:426
|
||||||
msgid "Invalid action"
|
msgid "Invalid action"
|
||||||
msgstr "Acción inválida."
|
msgstr "Acción inválida."
|
||||||
|
|
||||||
#: pkg/invoices.go:386
|
#: pkg/invoices.go:420
|
||||||
msgid "invoices.zip"
|
msgid "invoices.zip"
|
||||||
msgstr "facturas.zip"
|
msgstr "facturas.zip"
|
||||||
|
|
||||||
#: pkg/invoices.go:433
|
#: pkg/invoices.go:468
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Cliente"
|
msgstr "Cliente"
|
||||||
|
|
||||||
#: pkg/invoices.go:439
|
#: pkg/invoices.go:474
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Número"
|
msgstr "Número"
|
||||||
|
|
||||||
#: pkg/invoices.go:445
|
#: pkg/invoices.go:479
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Invoice Date"
|
msgid "Invoice Date"
|
||||||
msgstr "Fecha de factura"
|
msgstr "Fecha de factura"
|
||||||
|
|
||||||
#: pkg/invoices.go:451
|
#: pkg/invoices.go:485
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr "Notas"
|
msgstr "Notas"
|
||||||
|
|
||||||
#: pkg/invoices.go:457
|
#: pkg/invoices.go:490
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Tags"
|
||||||
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
|
#: pkg/invoices.go:496
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Payment Method"
|
msgid "Payment Method"
|
||||||
msgstr "Método de pago"
|
msgstr "Método de pago"
|
||||||
|
|
||||||
#: pkg/invoices.go:493
|
#: pkg/invoices.go:533
|
||||||
msgid "Invoice date can not be empty."
|
msgid "Invoice date can not be empty."
|
||||||
msgstr "No podéis dejar la fecha de la factura en blanco."
|
msgstr "No podéis dejar la fecha de la factura en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:494
|
#: pkg/invoices.go:534
|
||||||
msgid "Invoice date must be a valid date."
|
msgid "Invoice date must be a valid date."
|
||||||
msgstr "La fecha de factura debe ser válida."
|
msgstr "La fecha de factura debe ser válida."
|
||||||
|
|
||||||
#: pkg/invoices.go:496
|
#: pkg/invoices.go:536
|
||||||
msgid "Selected payment method is not valid."
|
msgid "Selected payment method is not valid."
|
||||||
msgstr "Habéis escogido un método de pago que no es válido."
|
msgstr "Habéis escogido un método de pago que no es válido."
|
||||||
|
|
||||||
#: pkg/invoices.go:573
|
#: pkg/invoices.go:630
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr "Identificador"
|
msgstr "Identificador"
|
||||||
|
|
||||||
#: pkg/invoices.go:596
|
#: pkg/invoices.go:653
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Cantidad"
|
msgstr "Cantidad"
|
||||||
|
|
||||||
#: pkg/invoices.go:604
|
#: pkg/invoices.go:661
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Discount (%)"
|
msgid "Discount (%)"
|
||||||
msgstr "Descuento (%)"
|
msgstr "Descuento (%)"
|
||||||
|
|
||||||
#: pkg/invoices.go:653
|
#: pkg/invoices.go:710
|
||||||
msgid "Quantity can not be empty."
|
msgid "Quantity can not be empty."
|
||||||
msgstr "No podéis dejar la cantidad en blanco."
|
msgstr "No podéis dejar la cantidad en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:654
|
#: pkg/invoices.go:711
|
||||||
msgid "Quantity must be a number greater than zero."
|
msgid "Quantity must be a number greater than zero."
|
||||||
msgstr "La cantidad tiene que ser un número mayor a cero."
|
msgstr "La cantidad tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/invoices.go:656
|
#: pkg/invoices.go:713
|
||||||
msgid "Discount can not be empty."
|
msgid "Discount can not be empty."
|
||||||
msgstr "No podéis dejar el descuento en blanco."
|
msgstr "No podéis dejar el descuento en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:657
|
#: pkg/invoices.go:714
|
||||||
msgid "Discount must be a percentage between 0 and 100."
|
msgid "Discount must be a percentage between 0 and 100."
|
||||||
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
|
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
|
||||||
|
|
||||||
|
@ -751,6 +756,10 @@ msgstr "No podéis dejar el código postal en blanco."
|
||||||
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."
|
||||||
|
|
||||||
|
#~ msgctxt "title"
|
||||||
|
#~ msgid "Label"
|
||||||
|
#~ msgstr "Etiqueta"
|
||||||
|
|
||||||
#~ msgid "Select a tax for this product."
|
#~ msgid "Select a tax for this product."
|
||||||
#~ msgstr "Escoged un impuesto para este producto."
|
#~ msgstr "Escoged un impuesto para este producto."
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
drop function if exists numerus.add_invoice(integer, text, date, integer, text, integer, numerus.new_invoice_product[]);
|
drop function if exists numerus.add_invoice(integer, text, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[]);
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:invoice_tag from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop table if exists numerus.invoice_tag;
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:tag from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop table if exists numerus.tag;
|
||||||
|
|
||||||
|
commit;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert numerus:tag_name from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop domain if exists numerus.tag_name;
|
||||||
|
|
||||||
|
commit;
|
|
@ -58,7 +58,10 @@ invoice_product_tax [schema_numerus invoice_product tax tax_rate] 2023-02-15T13:
|
||||||
new_invoice_product [schema_numerus] 2023-02-16T21:06:01Z jordi fita mas <jordi@tandem.blog> # Add type for passing products to new invoices
|
new_invoice_product [schema_numerus] 2023-02-16T21:06:01Z jordi fita mas <jordi@tandem.blog> # Add type for passing products to new invoices
|
||||||
invoice_number_counter [schema_numerus company] 2023-02-17T13:04:48Z jordi fita mas <jordi@tandem.blog> # Add relation to count invoice numbers
|
invoice_number_counter [schema_numerus company] 2023-02-17T13:04:48Z jordi fita mas <jordi@tandem.blog> # Add relation to count invoice numbers
|
||||||
next_invoice_number [schema_numerus invoice_number_counter] 2023-02-17T13:21:48Z jordi fita mas <jordi@tandem.blog> # Add function to retrieve the next invoice number
|
next_invoice_number [schema_numerus invoice_number_counter] 2023-02-17T13:21:48Z jordi fita mas <jordi@tandem.blog> # Add function to retrieve the next invoice number
|
||||||
add_invoice [schema_numerus invoice company currency parse_price new_invoice_product tax invoice_product invoice_product_tax next_invoice_number] 2023-02-16T21:12:46Z jordi fita mas <jordi@tandem.blog> # Add function to create new invoices
|
tag_name [schema_numerus] 2023-03-10T11:06:11Z jordi fita mas <jordi@tandem.blog> # Add domain for tag names
|
||||||
|
tag [schema_numerus tag_name] 2023-03-10T11:04:24Z jordi fita mas <jordi@tandem.blog> # Add relation for tags
|
||||||
|
invoice_tag [schema_numerus tag invoice] 2023-03-10T11:37:43Z jordi fita mas <jordi@tandem.blog> # Add relation for invoice tag
|
||||||
|
add_invoice [schema_numerus invoice company currency parse_price new_invoice_product tax invoice_product invoice_product_tax next_invoice_number tag_name tag invoice_tag] 2023-02-16T21:12:46Z jordi fita mas <jordi@tandem.blog> # Add function to create new invoices
|
||||||
invoice_tax_amount [schema_numerus invoice_product invoice_product_tax] 2023-02-22T12:08:35Z jordi fita mas <jordi@tandem.blog> # Add view for invoice tax amount
|
invoice_tax_amount [schema_numerus invoice_product invoice_product_tax] 2023-02-22T12:08:35Z jordi fita mas <jordi@tandem.blog> # Add view for invoice tax amount
|
||||||
invoice_product_amount [schema_numerus invoice_product invoice_product_tax] 2023-03-01T11:18:05Z jordi fita mas <jordi@tandem.blog> # Add view for invoice product subtotal and total
|
invoice_product_amount [schema_numerus invoice_product invoice_product_tax] 2023-03-01T11:18:05Z jordi fita mas <jordi@tandem.blog> # Add view for invoice product subtotal and total
|
||||||
invoice_amount [schema_numerus invoice_product invoice_product_amount] 2023-02-22T12:58:46Z jordi fita mas <jordi@tandem.blog> # Add view to compute subtotal and total for invoices
|
invoice_amount [schema_numerus invoice_product invoice_product_amount] 2023-02-22T12:58:46Z jordi fita mas <jordi@tandem.blog> # Add view to compute subtotal and total for invoices
|
||||||
|
|
|
@ -5,22 +5,24 @@ reset client_min_messages;
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
select plan(17);
|
select plan(19);
|
||||||
|
|
||||||
set search_path to auth, numerus, public;
|
set search_path to auth, numerus, public;
|
||||||
|
|
||||||
select has_function('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]']);
|
select has_function('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
|
||||||
select function_lang_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'plpgsql');
|
select function_lang_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'plpgsql');
|
||||||
select function_returns('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'uuid');
|
select function_returns('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'uuid');
|
||||||
select isnt_definer('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]']);
|
select isnt_definer('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
|
||||||
select volatility_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'volatile');
|
select volatility_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'volatile');
|
||||||
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'guest', array []::text[]);
|
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'guest', array []::text[]);
|
||||||
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']);
|
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']);
|
||||||
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'admin', array ['EXECUTE']);
|
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'admin', array ['EXECUTE']);
|
||||||
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'authenticator', array []::text[]);
|
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
|
||||||
set client_min_messages to warning;
|
set client_min_messages to warning;
|
||||||
|
truncate invoice_tag cascade;
|
||||||
|
truncate tag cascade;
|
||||||
truncate invoice_number_counter cascade;
|
truncate invoice_number_counter cascade;
|
||||||
truncate invoice_product_tax cascade;
|
truncate invoice_product_tax cascade;
|
||||||
truncate invoice_product cascade;
|
truncate invoice_product cascade;
|
||||||
|
@ -81,27 +83,27 @@ values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '',
|
||||||
|
|
||||||
|
|
||||||
select lives_ok(
|
select lives_ok(
|
||||||
$$ select add_invoice(1, 'INV001', '2023-02-15', 12, 'Notes 1', 111, '{"(7,Product 1,Description 1,12.24,2,0.0,{4})"}') $$,
|
$$ select add_invoice(1, 'INV001', '2023-02-15', 12, 'Notes 1', 111, '{tag1,tag2}','{"(7,Product 1,Description 1,12.24,2,0.0,{4})"}') $$,
|
||||||
'Should be able to insert an invoice for the first company with a product'
|
'Should be able to insert an invoice for the first company with a product'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
select lives_ok(
|
||||||
$$ select add_invoice(1, 'INV002', '2023-02-16', 13, 'Notes 2', 111, '{"(7,Product 1 bis,Description 1 bis,33.33,1,0.50,\"{4,3}\")","(8,Product 2,Description 2,24.00,3,0.75,{})"}') $$,
|
$$ select add_invoice(1, 'INV002', '2023-02-16', 13, 'Notes 2', 111, '{}', '{"(7,Product 1 bis,Description 1 bis,33.33,1,0.50,\"{4,3}\")","(8,Product 2,Description 2,24.00,3,0.75,{})"}') $$,
|
||||||
'Should be able to insert a second invoice for the first company with two product'
|
'Should be able to insert a second invoice for the first company with two product'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
select lives_ok(
|
||||||
$$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', 222, '{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$,
|
$$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', 222, '{tag3}','{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$,
|
||||||
'Should be able to insert an invoice for the second company with a product'
|
'Should be able to insert an invoice for the second company with a product'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
select lives_ok(
|
||||||
$$ select add_invoice(1, NULL, '2023-03-15', 13, '', 111, '{"(7,PA1,DA1,44.33,1,0.50,{})"}') $$,
|
$$ select add_invoice(1, NULL, '2023-03-15', 13, '', 111, '{tag2}', '{"(7,PA1,DA1,44.33,1,0.50,{})"}') $$,
|
||||||
'Should be able to insert an invoice with an autogenerated number'
|
'Should be able to insert an invoice with an autogenerated number'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
select lives_ok(
|
||||||
$$ select add_invoice(2, ' ', '2023-04-16', 14, '', 222, '{"(11,PA2,DA2,55.33,10,0.75,{})"}') $$,
|
$$ select add_invoice(2, ' ', '2023-04-16', 14, '', 222, '{tag2,tag3,tag4}','{"(11,PA2,DA2,55.33,10,0.75,{})"}') $$,
|
||||||
'Should consider non-null, but otherwise empty numbers the same as null and autogenerate it'
|
'Should consider non-null, but otherwise empty numbers the same as null and autogenerate it'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -138,6 +140,30 @@ select bag_eq(
|
||||||
'Should have created all invoice product taxes'
|
'Should have created all invoice product taxes'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select company_id, name from tag $$,
|
||||||
|
$$ values (1, 'tag1')
|
||||||
|
, (1, 'tag2')
|
||||||
|
, (2, 'tag2')
|
||||||
|
, (2, 'tag3')
|
||||||
|
, (2, 'tag4')
|
||||||
|
$$,
|
||||||
|
'Should have added all new tags once'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select invoice_number, tag.name from invoice_tag join invoice using (invoice_id) join tag using (tag_id) $$,
|
||||||
|
$$ values ('INV001', 'tag1')
|
||||||
|
, ('INV001', 'tag2')
|
||||||
|
, ('INV101', 'tag3')
|
||||||
|
, ('F20230006', 'tag2')
|
||||||
|
, ('INV056-23', 'tag2')
|
||||||
|
, ('INV056-23', 'tag3')
|
||||||
|
, ('INV056-23', 'tag4')
|
||||||
|
$$,
|
||||||
|
'Should have assigned the tags to invoices'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
select *
|
select *
|
||||||
from finish();
|
from finish();
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
-- Test invoice_tag
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(23);
|
||||||
|
|
||||||
|
set search_path to numerus, auth, public;
|
||||||
|
|
||||||
|
select has_table('invoice_tag');
|
||||||
|
select has_pk('invoice_tag' );
|
||||||
|
select col_is_pk('invoice_tag', array['invoice_id', 'tag_id']);
|
||||||
|
select table_privs_are('invoice_tag', 'guest', array []::text[]);
|
||||||
|
select table_privs_are('invoice_tag', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('invoice_tag', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('invoice_tag', 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
select has_column('invoice_tag', 'invoice_id');
|
||||||
|
select col_is_fk('invoice_tag', 'invoice_id');
|
||||||
|
select fk_ok('invoice_tag', 'invoice_id', 'invoice', 'invoice_id');
|
||||||
|
select col_type_is('invoice_tag', 'invoice_id', 'integer');
|
||||||
|
select col_not_null('invoice_tag', 'invoice_id');
|
||||||
|
select col_hasnt_default('invoice_tag', 'invoice_id');
|
||||||
|
|
||||||
|
select has_column('invoice_tag', 'tag_id');
|
||||||
|
select col_is_fk('invoice_tag', 'tag_id');
|
||||||
|
select fk_ok('invoice_tag', 'tag_id', 'tag', 'tag_id');
|
||||||
|
select col_type_is('invoice_tag', 'tag_id', 'integer');
|
||||||
|
select col_not_null('invoice_tag', 'tag_id');
|
||||||
|
select col_hasnt_default('invoice_tag', 'tag_id');
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate invoice_tag cascade;
|
||||||
|
truncate invoice cascade;
|
||||||
|
truncate tag cascade;
|
||||||
|
truncate contact cascade;
|
||||||
|
truncate company_user cascade;
|
||||||
|
truncate company cascade;
|
||||||
|
truncate payment_method cascade;
|
||||||
|
truncate auth."user" cascade;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
|
||||||
|
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
|
||||||
|
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
|
||||||
|
;
|
||||||
|
|
||||||
|
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 (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, 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 invoice (invoice_id, company_id, invoice_number, contact_id, currency_code, payment_method_id)
|
||||||
|
values (10, 2, 'INV020001', 6, 'EUR', 222)
|
||||||
|
, (12, 4, 'INV040001', 8, 'EUR', 444)
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into tag (tag_id, company_id, name)
|
||||||
|
values (14, 2, 'web')
|
||||||
|
, (15, 2, 'design')
|
||||||
|
, (16, 4, 'product')
|
||||||
|
, (17, 4, 'development')
|
||||||
|
, (18, 4, 'something-else')
|
||||||
|
, (19, 4, 'design')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into invoice_tag (invoice_id, tag_id)
|
||||||
|
values (10, 14)
|
||||||
|
, (10, 15)
|
||||||
|
, (12, 18)
|
||||||
|
;
|
||||||
|
|
||||||
|
prepare invoice_tag_data as
|
||||||
|
select invoice_id, tag_id
|
||||||
|
from invoice_tag
|
||||||
|
;
|
||||||
|
|
||||||
|
set role invoicer;
|
||||||
|
select is_empty('invoice_tag_data', 'Should show no data when cookie is not set yet');
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||||
|
select bag_eq(
|
||||||
|
'invoice_tag_data',
|
||||||
|
$$ values ( 10, 14 )
|
||||||
|
, ( 10, 15 )
|
||||||
|
$$,
|
||||||
|
'Should only list invoice tags of the companies where demo@tandem.blog is user of'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||||
|
select bag_eq(
|
||||||
|
'invoice_tag_data',
|
||||||
|
$$ values ( 12, 18 )
|
||||||
|
$$,
|
||||||
|
'Should only list invoice tags of the companies where admin@tandem.blog is user of'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('not-a-cookie');
|
||||||
|
select throws_ok(
|
||||||
|
'invoice_tag_data',
|
||||||
|
'42501', 'permission denied for table invoice_tag',
|
||||||
|
'Should not allow select to guest users'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
reset role;
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
-- Test tag
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(33);
|
||||||
|
|
||||||
|
set search_path to numerus, auth, public;
|
||||||
|
|
||||||
|
select has_table('tag');
|
||||||
|
select has_pk('tag' );
|
||||||
|
select table_privs_are('tag', 'guest', array []::text[]);
|
||||||
|
select table_privs_are('tag', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('tag', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('tag', 'authenticator', array []::text[]);
|
||||||
|
|
||||||
|
select has_sequence('tag_tag_id_seq');
|
||||||
|
select sequence_privs_are('tag_tag_id_seq', 'guest', array[]::text[]);
|
||||||
|
select sequence_privs_are('tag_tag_id_seq', 'invoicer', array['USAGE']);
|
||||||
|
select sequence_privs_are('tag_tag_id_seq', 'admin', array['USAGE']);
|
||||||
|
select sequence_privs_are('tag_tag_id_seq', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
|
select has_column('tag', 'tag_id');
|
||||||
|
select col_is_pk('tag', 'tag_id');
|
||||||
|
select col_type_is('tag', 'tag_id', 'integer');
|
||||||
|
select col_not_null('tag', 'tag_id');
|
||||||
|
select col_has_default('tag', 'tag_id');
|
||||||
|
select col_default_is('tag', 'tag_id', 'nextval(''tag_tag_id_seq''::regclass)');
|
||||||
|
|
||||||
|
select has_column('tag', 'company_id');
|
||||||
|
select col_is_fk('tag', 'company_id');
|
||||||
|
select fk_ok('tag', 'company_id', 'company', 'company_id');
|
||||||
|
select col_type_is('tag', 'company_id', 'integer');
|
||||||
|
select col_not_null('tag', 'company_id');
|
||||||
|
select col_hasnt_default('tag', 'company_id');
|
||||||
|
|
||||||
|
select has_column('tag', 'name');
|
||||||
|
select col_type_is('tag', 'name', 'tag_name');
|
||||||
|
select col_not_null('tag', 'name');
|
||||||
|
select col_hasnt_default('tag', 'name');
|
||||||
|
select col_is_unique('tag', array['company_id', 'name']);
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate tag cascade;
|
||||||
|
truncate company_user cascade;
|
||||||
|
truncate company cascade;
|
||||||
|
truncate payment_method cascade;
|
||||||
|
truncate auth."user" cascade;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
|
||||||
|
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
|
||||||
|
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
|
||||||
|
;
|
||||||
|
|
||||||
|
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 (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 tag (company_id, name)
|
||||||
|
values (2, 'web')
|
||||||
|
, (2, 'design')
|
||||||
|
, (4, 'product')
|
||||||
|
, (4, 'development')
|
||||||
|
, (4, 'something-else')
|
||||||
|
, (4, 'design')
|
||||||
|
;
|
||||||
|
|
||||||
|
prepare tag_data as
|
||||||
|
select company_id, name
|
||||||
|
from tag
|
||||||
|
;
|
||||||
|
|
||||||
|
set role invoicer;
|
||||||
|
select is_empty('tag_data', 'Should show no data when cookie is not set yet');
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||||
|
select bag_eq(
|
||||||
|
'tag_data',
|
||||||
|
$$ values ( 2, 'web' )
|
||||||
|
, ( 2, 'design' )
|
||||||
|
$$,
|
||||||
|
'Should only list tags of the companies where demo@tandem.blog is user of'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||||
|
select bag_eq(
|
||||||
|
'tag_data',
|
||||||
|
$$ values (4, 'product' )
|
||||||
|
, (4, 'development' )
|
||||||
|
, (4, 'something-else' )
|
||||||
|
, (4, 'design' )
|
||||||
|
$$,
|
||||||
|
'Should only list tags of the companies where admin@tandem.blog is user of'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select set_cookie('not-a-cookie');
|
||||||
|
select throws_ok(
|
||||||
|
'tag_data',
|
||||||
|
'42501', 'permission denied for table tag',
|
||||||
|
'Should not allow select to guest users'
|
||||||
|
);
|
||||||
|
reset role;
|
||||||
|
|
||||||
|
select throws_ok( $$
|
||||||
|
insert into tag (company_id, name)
|
||||||
|
values (2, 'web')
|
||||||
|
$$,
|
||||||
|
'23505', 'duplicate key value violates unique constraint "tag_company_id_name_key"',
|
||||||
|
'Should not allow repeated tag names within the same company'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
-- Test tag_name
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(12);
|
||||||
|
|
||||||
|
set search_path to numerus, public;
|
||||||
|
|
||||||
|
select has_domain('tag_name');
|
||||||
|
select domain_type_is('tag_name', 'text');
|
||||||
|
|
||||||
|
select lives_ok($$ select 'abcdefghijklmnopqrstuvwxyz012345678'::tag_name $$, 'Should be able to cast strings with up to 35 lowercase alphanumeric characters to tag_name');
|
||||||
|
select lives_ok($$ select 'a'::tag_name $$, 'Should be able to cast strings with a single letter to tag_name');
|
||||||
|
select lives_ok($$ select '1'::tag_name $$, 'Should be able to cast strings with a single number to tag_name');
|
||||||
|
select lives_ok($$ select 'a-long-tag'::tag_name $$, 'Should be able to cast strings with dashes to tag_name');
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT 'abcdefghijklmnopqrstuvwxyz0123456789'::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject tag names with more than 35 characters'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT 'aB'::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject tag names with uppercase characters'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT 'a$'::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject tag names with symbols'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT 'a a'::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject tag names with spaces'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT '-aa'::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject tag names starting with a dash'
|
||||||
|
);
|
||||||
|
|
||||||
|
select throws_ok(
|
||||||
|
$$ SELECT ''::tag_name $$,
|
||||||
|
23514, null,
|
||||||
|
'Should reject empty tag names'
|
||||||
|
);
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
select has_function_privilege('numerus.add_invoice(integer, text, date, integer, text, integer, numerus.new_invoice_product[])', 'execute');
|
select has_function_privilege('numerus.add_invoice(integer, text, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[])', 'execute');
|
||||||
|
|
||||||
rollback;
|
rollback;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- Verify numerus:invoice_tag on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select invoice_id
|
||||||
|
, tag_id
|
||||||
|
from numerus.invoice_tag
|
||||||
|
where false;
|
||||||
|
|
||||||
|
select 1 / count(*) from pg_class where oid = 'numerus.invoice_tag'::regclass and relrowsecurity;
|
||||||
|
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.invoice_tag'::regclass;
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,14 @@
|
||||||
|
-- Verify numerus:tag on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select tag_id
|
||||||
|
, company_id
|
||||||
|
, name
|
||||||
|
from numerus.tag
|
||||||
|
where false;
|
||||||
|
|
||||||
|
select 1 / count(*) from pg_class where oid = 'numerus.tag'::regclass and relrowsecurity;
|
||||||
|
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.tag'::regclass;
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify numerus:tag_name on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select pg_catalog.has_type_privilege('numerus.tag_name', 'usage');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -30,7 +30,7 @@
|
||||||
<th>{{( pgettext "Invoice Num." "title" )}}</th>
|
<th>{{( pgettext "Invoice Num." "title" )}}</th>
|
||||||
<th>{{( pgettext "Customer" "title" )}}</th>
|
<th>{{( pgettext "Customer" "title" )}}</th>
|
||||||
<th>{{( pgettext "Status" "title" )}}</th>
|
<th>{{( pgettext "Status" "title" )}}</th>
|
||||||
<th>{{( pgettext "Label" "title" )}}</th>
|
<th>{{( pgettext "Tags" "title" )}}</th>
|
||||||
<th>{{( pgettext "Amount" "title" )}}</th>
|
<th>{{( pgettext "Amount" "title" )}}</th>
|
||||||
<th>{{( pgettext "Download" "title" )}}</th>
|
<th>{{( pgettext "Download" "title" )}}</th>
|
||||||
<th>{{( pgettext "Actions" "title" )}}</th>
|
<th>{{( pgettext "Actions" "title" )}}</th>
|
||||||
|
@ -69,7 +69,12 @@
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td>
|
||||||
|
{{- range $index, $tag := .Tags }}
|
||||||
|
{{- if gt $index 0 }}, {{ end -}}
|
||||||
|
<a href="?tag={{ . }}">{{ . }}</a>
|
||||||
|
{{- end }}
|
||||||
|
</td>
|
||||||
<td class="numeric">{{ .Total|formatPrice }}</td>
|
<td class="numeric">{{ .Total|formatPrice }}</td>
|
||||||
<td class="invoice-download"><a href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf"
|
<td class="invoice-download"><a href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf"
|
||||||
download="{{ .Number}}.pdf"
|
download="{{ .Number}}.pdf"
|
||||||
|
|
|
@ -20,8 +20,9 @@
|
||||||
{{ template "select-field" .Customer }}
|
{{ template "select-field" .Customer }}
|
||||||
{{ template "input-field" .Number }}
|
{{ template "input-field" .Number }}
|
||||||
{{ template "input-field" .Date }}
|
{{ template "input-field" .Date }}
|
||||||
{{ template "input-field" .Notes }}
|
{{ template "input-field" .Tags }}
|
||||||
{{ template "select-field" .PaymentMethod }}
|
{{ template "select-field" .PaymentMethod }}
|
||||||
|
{{ template "input-field" .Notes }}
|
||||||
|
|
||||||
{{- range $product := .Products }}
|
{{- range $product := .Products }}
|
||||||
<fieldset class="new-invoice-product">
|
<fieldset class="new-invoice-product">
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
{{ template "hidden-field" .Number }}
|
{{ template "hidden-field" .Number }}
|
||||||
{{ template "hidden-field" .Date }}
|
{{ template "hidden-field" .Date }}
|
||||||
{{ template "hidden-field" .Notes }}
|
{{ template "hidden-field" .Notes }}
|
||||||
|
{{ template "hidden-field" .Tags }}
|
||||||
|
|
||||||
{{- range $product := .Products }}
|
{{- range $product := .Products }}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
Loading…
Reference in New Issue