Remove the number field from new invoice form

Initially, this field was meant to be left almost always blank, except
for when we deleted invoiced and had to “replace” its number with a new
invoice; using the automatic numbering in this cas would not “fill in”
the missing number in the sequence.

However, we decide to not allow removing invoicer not edit their
numbers, therefore, if everything goes as planned, there should not be
any gap in the sequence, and that field is rendered useless.

Oriol suggested making it a read-only field, both for new and edit
forms, but i do not think it makes sense to have a field if you can not
edit it at all, specially in the new invoice dialog, where it would
always be blank.  In the edit form we already show the number in the
title and breadcrumbs, thus no need for the read-only field as
reference.

I still keep a Number member to the form struct, but is now a string
(kind of “a read-only field”, in a way) and just to be written in the
title or breadcrumbs.  I did not like the idea of adding a new SQL
query just for that value.
This commit is contained in:
jordi fita mas 2023-04-01 15:57:56 +02:00
parent 5717a5b9ed
commit c453715ee1
9 changed files with 43 additions and 77 deletions

View File

@ -61,12 +61,12 @@ select add_product(1, 'Teia', 'Fusta resinosa de pi i daltres arbres, provine
alter sequence invoice_invoice_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, '{producte}','{"(1,Teia,\"Fusta resinosa de pi i daltres arbres, provinent sobretot del cor de larbre, 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, '{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, '{producte,higiene}','{"(1,\"Paper higiènic (pack de 32 U)\",Paper que susa 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, '{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, '{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, '{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 laigua 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 - '28 days'::interval)::date, 6, 'Vol esmorzar!', 1, '{producte}','{"(1,Teia,\"Fusta resinosa de pi i daltres arbres, provinent sobretot del cor de larbre, 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, '{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, '{producte,higiene}','{"(1,\"Paper higiènic (pack de 32 U)\",Paper que susa 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, '{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, '{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, '{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 laigua 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 = 'unpaid' where invoice_id = 3;

View File

@ -16,7 +16,7 @@ begin;
set search_path to numerus, public;
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
create or replace function add_invoice(company integer, invoice_date date, contact_id integer, notes text, payment_method_id integer, tags tag_name[], products new_invoice_product[]) returns uuid as
$$
declare
iid integer;
@ -25,13 +25,9 @@ declare
ccode text;
ipid integer;
begin
if invoice_number is null or length(trim(invoice_number)) = 0 then
invoice_number = next_invoice_number(company, invoice_date);
end if;
insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code, payment_method_id)
select company_id
, invoice_number
, next_invoice_number(add_invoice.company, invoice_date)
, invoice_date
, contact_id
, notes
@ -70,8 +66,8 @@ end;
$$
language plpgsql;
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, tag_name[], new_invoice_product[]) to invoicer;
grant execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) to admin;
revoke execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) from public;
grant execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) to invoicer;
grant execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) to admin;
commit;

View File

@ -217,7 +217,6 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
form.InvoiceStatus.Selected = []string{"created"}
form.Number.Val = ""
}
form.Date.Val = time.Now().Format("2006-01-02")
w.WriteHeader(http.StatusOK)
@ -456,7 +455,7 @@ func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Param
mustRenderNewInvoiceForm(w, r, form)
return
}
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, form.Tags, NewInvoiceProductArray(form.Products))
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7)", company.Id, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.Tags, NewInvoiceProductArray(form.Products))
if IsHTMxRequest(r) {
w.Header().Set("HX-Trigger", "closeModal")
w.Header().Set("HX-Refresh", "true")
@ -529,9 +528,9 @@ func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
type invoiceForm struct {
locale *Locale
company *Company
Number string
InvoiceStatus *SelectField
Customer *SelectField
Number *InputField
Date *InputField
Notes *InputField
PaymentMethod *SelectField
@ -556,11 +555,6 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
Required: true,
Options: MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id),
},
Number: &InputField{
Name: "number",
Label: pgettext("input", "Number", locale),
Type: "text",
},
Date: &InputField{
Name: "date",
Label: pgettext("input", "Invoice Date", locale),
@ -592,7 +586,6 @@ func (form *invoiceForm) Parse(r *http.Request) error {
}
form.InvoiceStatus.FillValue(r)
form.Customer.FillValue(r)
form.Number.FillValue(r)
form.Date.FillValue(r)
form.Notes.FillValue(r)
form.Tags.FillValue(r)
@ -689,7 +682,7 @@ func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
, invoice_date
, notes
, payment_method_id
`, slug).Scan(&invoiceId, form.InvoiceStatus, form.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
`, slug).Scan(&invoiceId, form.InvoiceStatus, form.Customer, &form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
form.PaymentMethod.Selected = selectedPaymentMethod
form.InvoiceStatus.Selected = selectedInvoiceStatus
return false
@ -890,7 +883,7 @@ func newEditInvoicePage(slug string, form *invoiceForm, r *http.Request) *editIn
return &editInvoicePage{
newNewInvoicePage(form, r),
slug,
form.Number.String(),
form.Number,
}
}

View File

@ -2,6 +2,6 @@
begin;
drop function if exists numerus.add_invoice(integer, text, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[]);
drop function if exists numerus.add_invoice(integer, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[]);
commit;

View File

@ -5,19 +5,19 @@ reset client_min_messages;
begin;
select plan(19);
select plan(17);
set search_path to auth, numerus, public;
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', 'tag_name[]', 'new_invoice_product[]'], 'plpgsql');
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', 'tag_name[]', 'new_invoice_product[]']);
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', 'tag_name[]', '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[]'], 'invoicer', 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', 'tag_name[]', 'new_invoice_product[]'], 'authenticator', array []::text[]);
select has_function('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select function_lang_is('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'plpgsql');
select function_returns('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'uuid');
select isnt_definer('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select volatility_is('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'volatile');
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'guest', array []::text[]);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'authenticator', array []::text[]);
set client_min_messages to warning;
@ -83,59 +83,45 @@ values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '',
select lives_ok(
$$ 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})"}') $$,
$$ select add_invoice(1, '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'
);
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, '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'
);
select lives_ok(
$$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', 222, '{tag3}','{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$,
$$ select add_invoice(2, '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'
);
select lives_ok(
$$ 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'
);
select lives_ok(
$$ 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'
);
select bag_eq(
$$ select company_id, invoice_number, invoice_date, contact_id, invoice_status, notes, payment_method_id, currency_code, created_at from invoice $$,
$$ values (1, 'INV001', '2023-02-15'::date, 12, 'created', 'Notes 1', 111, 'EUR', current_timestamp)
, (1, 'INV002', '2023-02-16'::date, 13, 'created', 'Notes 2', 111, 'EUR', current_timestamp)
, (2, 'INV101', '2023-02-14'::date, 15, 'created', 'Notes 3', 222, 'USD', current_timestamp)
, (1, 'F20230006', '2023-03-15'::date, 13, 'created', '', 111, 'EUR', current_timestamp)
, (2, 'INV056-23', '2023-04-16'::date, 14, 'created', '', 222, 'USD', current_timestamp)
$$ values (1, 'F20230006', '2023-02-15'::date, 12, 'created', 'Notes 1', 111, 'EUR', current_timestamp)
, (1, 'F20230007', '2023-02-16'::date, 13, 'created', 'Notes 2', 111, 'EUR', current_timestamp)
, (2, 'INV056-23', '2023-02-14'::date, 15, 'created', 'Notes 3', 222, 'USD', current_timestamp)
$$,
'Should have created all invoices'
);
select bag_eq(
$$ select invoice_number, product_id, name, description, price, quantity, discount_rate from invoice_product join invoice using (invoice_id) $$,
$$ values ('INV001', 7, 'Product 1', 'Description 1', 1224, 2, 0.00)
, ('INV002', 7, 'Product 1 bis', 'Description 1 bis', 3333, 1, 0.50)
, ('INV002', 8, 'Product 2', 'Description 2', 2400, 3, 0.75)
, ('INV101', 11, 'Product 4.3', '', 1111, 1, 0.0)
, ('F20230006', 7, 'PA1', 'DA1', 4433, 1, 0.50)
, ('INV056-23', 11, 'PA2', 'DA2', 5533, 10, 0.75)
$$ values ('F20230006', 7, 'Product 1', 'Description 1', 1224, 2, 0.00)
, ('F20230007', 7, 'Product 1 bis', 'Description 1 bis', 3333, 1, 0.50)
, ('F20230007', 8, 'Product 2', 'Description 2', 2400, 3, 0.75)
, ('INV056-23', 11, 'Product 4.3', '', 1111, 1, 0.0)
$$,
'Should have created all invoice products'
);
select bag_eq(
$$ select invoice_number, product_id, tax_id, tax_rate from invoice_product_tax join invoice_product using (invoice_product_id) join invoice using (invoice_id) $$,
$$ values ('INV001', 7, 4, 0.21)
, ('INV002', 7, 4, 0.21)
, ('INV002', 7, 3, -0.15)
, ('INV101', 11, 6, 0.10)
$$ values ('F20230006', 7, 4, 0.21)
, ('F20230007', 7, 4, 0.21)
, ('F20230007', 7, 3, -0.15)
, ('INV056-23', 11, 6, 0.10)
$$,
'Should have created all invoice product taxes'
);
@ -144,22 +130,16 @@ 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')
$$ values ('F20230006', 'tag1')
, ('F20230006', 'tag2')
, ('INV056-23', 'tag2')
, ('INV056-23', 'tag3')
, ('INV056-23', 'tag4')
$$,
'Should have assigned the tags to invoices'
);

View File

@ -2,6 +2,6 @@
begin;
select has_function_privilege('numerus.add_invoice(integer, text, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[])', 'execute');
select has_function_privilege('numerus.add_invoice(integer, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[])', 'execute');
rollback;

View File

@ -22,7 +22,6 @@
{{ with .Form -}}
{{ template "select-field" .Customer }}
{{ template "hidden-field" .Number }}
{{ template "hidden-field" .Date }}
{{ template "tags-field" .Tags }}
{{ template "select-field" .PaymentMethod }}

View File

@ -23,7 +23,6 @@
{{ with .Form -}}
{{ template "hidden-select-field" .InvoiceStatus }}
{{ template "select-field" .Customer }}
{{ template "input-field" .Number }}
{{ template "input-field" .Date }}
{{ template "tags-field" .Tags }}
{{ template "select-field" .PaymentMethod }}

View File

@ -8,7 +8,7 @@
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
{{ if eq .Form.Number.Val "" }}
{{ if eq .Form.Number "" }}
<a>{{( pgettext "New Invoice" "title" )}}</a>
{{ else }}
<a>{{ .Form.Number }}</a>
@ -26,7 +26,6 @@
{{- with .Form }}
{{ template "hidden-select-field" .Customer }}
{{ template "hidden-field" .Number }}
{{ template "hidden-field" .Date }}
{{ template "hidden-field" .Notes }}
{{ template "hidden-field" .Tags }}