diff --git a/deploy/add_invoice.sql b/deploy/add_invoice.sql index 01a21a2..e709ed5 100644 --- a/deploy/add_invoice.sql +++ b/deploy/add_invoice.sql @@ -14,7 +14,7 @@ begin; 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, products new_invoice_product[]) returns uuid as +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 $$ declare iid integer; @@ -27,13 +27,14 @@ begin invoice_number = next_invoice_number(company_id, invoice_date); end if; - insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code) + insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code, payment_method_id) select company.company_id , invoice_number , invoice_date , contact_id , notes , currency_code + , add_invoice.payment_method_id from company where company.company_id = add_invoice.company_id returning invoice_id, slug, currency_code @@ -65,8 +66,8 @@ end; $$ language plpgsql; -revoke execute on function add_invoice(integer, text, date, integer, text, new_invoice_product[]) from public; -grant execute on function add_invoice(integer, text, date, integer, text, new_invoice_product[]) to invoicer; -grant execute on function add_invoice(integer, text, date, integer, text, new_invoice_product[]) to admin; +revoke execute on function add_invoice(integer, text, date, integer, text, integer, 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, new_invoice_product[]) to admin; commit; diff --git a/deploy/invoice.sql b/deploy/invoice.sql index 9c5f4e2..dc25313 100644 --- a/deploy/invoice.sql +++ b/deploy/invoice.sql @@ -18,6 +18,7 @@ create table invoice ( contact_id integer not null references contact, invoice_status text not null default 'created' references invoice_status, notes text not null default '', + payment_method_id integer not null references payment_method, currency_code text not null references currency, created_at timestamptz not null default current_timestamp ); diff --git a/pkg/invoices.go b/pkg/invoices.go index 9850776..27c7744 100644 --- a/pkg/invoices.go +++ b/pkg/invoices.go @@ -121,19 +121,20 @@ func mustClose(closer io.Closer) { } type invoice struct { - Number string - Slug string - Date time.Time - Invoicer taxDetails - Invoicee taxDetails - Notes string - Products []*invoiceProduct - Subtotal string - Taxes [][]string - TaxClasses []string - HasDiscounts bool - Total string - LegalDisclaimer string + Number string + Slug string + Date time.Time + Invoicer taxDetails + Invoicee taxDetails + Notes string + PaymentInstructions string + Products []*invoiceProduct + Subtotal string + Taxes [][]string + TaxClasses []string + HasDiscounts bool + Total string + LegalDisclaimer string } type taxDetails struct { @@ -164,7 +165,7 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri } var invoiceId int var decimalDigits int - if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, decimal_digits, invoice_number, invoice_date, notes, business_name, vatin, phone, email, address, city, province, postal_code, to_price(subtotal, decimal_digits), to_price(total, decimal_digits) from invoice join contact using (contact_id) join invoice_amount using (invoice_id) join currency using (currency_code) where invoice.slug = $1", slug).Scan(&invoiceId, &decimalDigits, &inv.Number, &inv.Date, &inv.Notes, &inv.Invoicee.Name, &inv.Invoicee.VATIN, &inv.Invoicee.Phone, &inv.Invoicee.Email, &inv.Invoicee.Address, &inv.Invoicee.City, &inv.Invoicee.Province, &inv.Invoicee.PostalCode, &inv.Subtotal, &inv.Total)) { + if notFoundErrorOrPanic(conn.QueryRow(ctx, "select invoice_id, decimal_digits, invoice_number, invoice_date, notes, instructions, business_name, vatin, phone, email, address, city, province, postal_code, to_price(subtotal, decimal_digits), to_price(total, decimal_digits) from invoice join payment_method using (payment_method_id) join contact using (contact_id) join invoice_amount using (invoice_id) join currency using (currency_code) where invoice.slug = $1", slug).Scan(&invoiceId, &decimalDigits, &inv.Number, &inv.Date, &inv.Notes, &inv.PaymentInstructions, &inv.Invoicee.Name, &inv.Invoicee.VATIN, &inv.Invoicee.Phone, &inv.Invoicee.Email, &inv.Invoicee.Address, &inv.Invoicee.City, &inv.Invoicee.Province, &inv.Invoicee.PostalCode, &inv.Subtotal, &inv.Total)) { return nil } if err := conn.QueryRow(ctx, "select business_name, vatin, phone, email, address, city, province, postal_code, legal_disclaimer from company where company_id = $1", company.Id).Scan(&inv.Invoicer.Name, &inv.Invoicer.VATIN, &inv.Invoicer.Phone, &inv.Invoicer.Email, &inv.Invoicer.Address, &inv.Invoicer.City, &inv.Invoicer.Province, &inv.Invoicer.PostalCode, &inv.LegalDisclaimer); err != nil { @@ -290,7 +291,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)", company.Id, form.Number, form.Date, form.Customer, form.Notes, NewInvoiceProductArray(form.Products)) + 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)) http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther) } diff --git a/revert/add_invoice.sql b/revert/add_invoice.sql index 414abb9..612cc0d 100644 --- a/revert/add_invoice.sql +++ b/revert/add_invoice.sql @@ -2,6 +2,6 @@ begin; -drop function if exists numerus.add_invoice(integer, text, date, integer, text, numerus.new_invoice_product[]); +drop function if exists numerus.add_invoice(integer, text, date, integer, text, integer, numerus.new_invoice_product[]); commit; diff --git a/test/add_invoice.sql b/test/add_invoice.sql index b7b0ab2..e95ccd6 100644 --- a/test/add_invoice.sql +++ b/test/add_invoice.sql @@ -9,15 +9,15 @@ select plan(17); set search_path to auth, numerus, public; -select has_function('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]']); -select function_lang_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'plpgsql'); -select function_returns('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'uuid'); -select isnt_definer('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]']); -select volatility_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'volatile'); -select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'guest', array []::text[]); -select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']); -select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'admin', array ['EXECUTE']); -select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'new_invoice_product[]'], 'authenticator', array []::text[]); +select has_function('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]']); +select function_lang_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'plpgsql'); +select function_returns('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]'], 'uuid'); +select isnt_definer('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'new_invoice_product[]']); +select volatility_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', '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', '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', 'new_invoice_product[]'], 'authenticator', array []::text[]); set client_min_messages to warning; @@ -81,37 +81,37 @@ 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', '{"(7,Product 1,Description 1,12.24,2,0.0,{4})"}') $$, + $$ select add_invoice(1, 'INV001', '2023-02-15', 12, 'Notes 1', 111, '{"(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', '{"(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' ); select lives_ok( - $$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', '{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$, + $$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', 222, '{"(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, '', '{"(7,PA1,DA1,44.33,1,0.50,{})"}') $$, + $$ select add_invoice(1, NULL, '2023-03-15', 13, '', 111, '{"(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, '', '{"(11,PA2,DA2,55.33,10,0.75,{})"}') $$, + $$ select add_invoice(2, ' ', '2023-04-16', 14, '', 222, '{"(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, currency_code, created_at from invoice $$, - $$ values (1, 'INV001', '2023-02-15'::date, 12, 'created', 'Notes 1', 'EUR', current_timestamp) - , (1, 'INV002', '2023-02-16'::date, 13, 'created', 'Notes 2', 'EUR', current_timestamp) - , (2, 'INV101', '2023-02-14'::date, 15, 'created', 'Notes 3', 'USD', current_timestamp) - , (1, 'F20230006', '2023-03-15'::date, 13, 'created', '', 'EUR', current_timestamp) - , (2, 'INV056-23', '2023-04-16'::date, 14, 'created', '', 'USD', current_timestamp) + $$ 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) $$, 'Should have created all invoices' ); diff --git a/test/invoice.sql b/test/invoice.sql index 9e2b409..4793be5 100644 --- a/test/invoice.sql +++ b/test/invoice.sql @@ -5,7 +5,7 @@ reset client_min_messages; begin; -select plan(72); +select plan(78); set search_path to numerus, auth, public; @@ -75,6 +75,13 @@ select col_not_null('invoice', 'notes'); select col_has_default('invoice', 'notes'); select col_default_is('invoice', 'notes', ''); +select has_column('invoice', 'payment_method_id'); +select col_is_fk('invoice', 'payment_method_id'); +select fk_ok('invoice', 'payment_method_id', 'payment_method', 'payment_method_id'); +select col_type_is('invoice', 'payment_method_id', 'integer'); +select col_not_null('invoice', 'payment_method_id'); +select col_hasnt_default('invoice', 'payment_method_id'); + select has_column('invoice', 'currency_code'); select col_is_fk('invoice', 'currency_code'); select fk_ok('invoice', 'currency_code', 'currency', 'currency_code'); @@ -127,9 +134,9 @@ values (6, 2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '' , (8, 4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') ; -insert into invoice (company_id, invoice_number, contact_id, currency_code) -values (2, 'INV020001', 6, 'EUR') - , (4, 'INV040001', 8, 'EUR') +insert into invoice (company_id, invoice_number, contact_id, currency_code, payment_method_id) +values (2, 'INV020001', 6, 'EUR', 222) + , (4, 'INV040001', 8, 'EUR', 444) ; @@ -170,8 +177,8 @@ reset role; select throws_ok( $$ - insert into invoice (company_id, invoice_number, contact_id, currency_code) - values (2, ' ', 6, 'EUR') + insert into invoice (company_id, invoice_number, contact_id, currency_code, payment_method_id) + values (2, ' ', 6, 'EUR', 222) $$, '23514', 'new row for relation "invoice" violates check constraint "invoice_number_not_empty"', 'Should not allow invoice with blank number' diff --git a/test/invoice_amount.sql b/test/invoice_amount.sql index 8ca21b1..d979b2b 100644 --- a/test/invoice_amount.sql +++ b/test/invoice_amount.sql @@ -68,11 +68,11 @@ insert into contact (contact_id, company_id, business_name, vatin, trade_name, p values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') ; -insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code) -values ( 8, 1, 'I1', current_date, 7, 'EUR') - , ( 9, 1, 'I2', current_date, 7, 'EUR') - , (10, 1, 'I3', current_date, 7, 'EUR') - , (11, 1, 'I4', current_date, 7, 'EUR') +insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) +values ( 8, 1, 'I1', current_date, 7, 'EUR', '111') + , ( 9, 1, 'I2', current_date, 7, 'EUR', '111') + , (10, 1, 'I3', current_date, 7, 'EUR', '111') + , (11, 1, 'I4', current_date, 7, 'EUR', '111') ; insert into invoice_product (invoice_product_id, invoice_id, product_id, name, price, quantity, discount_rate) diff --git a/test/invoice_product.sql b/test/invoice_product.sql index 6dabb59..08b6f6d 100644 --- a/test/invoice_product.sql +++ b/test/invoice_product.sql @@ -112,9 +112,9 @@ values (6, 2, 'Contact 1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '' , (8, 4, 'Contact 2', 'XX666', '', '888-888-888', 'd@d', '', '', '', '', '', 'ES') ; -insert into invoice (invoice_id, company_id, invoice_number, contact_id, currency_code) -values (10, 2, 'INV020001', 6, 'EUR') - , (12, 4, 'INV040001', 8, 'EUR') +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 product (product_id, company_id, name, description, price) diff --git a/test/invoice_product_amount.sql b/test/invoice_product_amount.sql index 916d3d2..63fbb3e 100644 --- a/test/invoice_product_amount.sql +++ b/test/invoice_product_amount.sql @@ -68,11 +68,11 @@ insert into contact (contact_id, company_id, business_name, vatin, trade_name, p values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') ; -insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code) -values ( 8, 1, 'I1', current_date, 7, 'EUR') - , ( 9, 1, 'I2', current_date, 7, 'EUR') - , (10, 1, 'I3', current_date, 7, 'EUR') - , (11, 1, 'I4', current_date, 7, 'EUR') +insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) +values ( 8, 1, 'I1', current_date, 7, 'EUR', 111) + , ( 9, 1, 'I2', current_date, 7, 'EUR', 111) + , (10, 1, 'I3', current_date, 7, 'EUR', 111) + , (11, 1, 'I4', current_date, 7, 'EUR', 111) ; insert into invoice_product (invoice_product_id, invoice_id, product_id, name, price, quantity, discount_rate) diff --git a/test/invoice_product_tax.sql b/test/invoice_product_tax.sql index c754692..b953727 100644 --- a/test/invoice_product_tax.sql +++ b/test/invoice_product_tax.sql @@ -95,9 +95,9 @@ values ( 9, 2, 'Customer 1', 'XX555', '', '777-777-777', 'c1@e', '', '', '', '', , (10, 4, 'Customer 2', 'XX666', '', '888-888-888', 'c2@e', '', '', '', '', '', 'ES') ; -insert into invoice (invoice_id, company_id, invoice_number, contact_id, currency_code) -values (11, 2, 'INV001', 9, 'EUR') - , (12, 4, 'INV002', 10, 'EUR') +insert into invoice (invoice_id, company_id, invoice_number, contact_id, currency_code, payment_method_id) +values (11, 2, 'INV001', 9, 'EUR', 222) + , (12, 4, 'INV002', 10, 'EUR', 444) ; insert into invoice_product (invoice_product_id, invoice_id, product_id, name, price) diff --git a/test/invoice_tax_amount.sql b/test/invoice_tax_amount.sql index 6e52de2..ece8e26 100644 --- a/test/invoice_tax_amount.sql +++ b/test/invoice_tax_amount.sql @@ -68,11 +68,11 @@ insert into contact (contact_id, company_id, business_name, vatin, trade_name, p values (7, 1, 'Contact', 'XX555', '', '777-777-777', 'c@c', '', '', '', '', '', 'ES') ; -insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code) -values ( 8, 1, 'I1', current_date, 7, 'EUR') - , ( 9, 1, 'I2', current_date, 7, 'EUR') - , (10, 1, 'I3', current_date, 7, 'EUR') - , (11, 1, 'I4', current_date, 7, 'EUR') +insert into invoice (invoice_id, company_id, invoice_number, invoice_date, contact_id, currency_code, payment_method_id) +values ( 8, 1, 'I1', current_date, 7, 'EUR', 111) + , ( 9, 1, 'I2', current_date, 7, 'EUR', 111) + , (10, 1, 'I3', current_date, 7, 'EUR', 111) + , (11, 1, 'I4', current_date, 7, 'EUR', 111) ; insert into invoice_product (invoice_product_id, invoice_id, product_id, name, price, quantity, discount_rate) diff --git a/verify/add_invoice.sql b/verify/add_invoice.sql index e632a8b..4662ff7 100644 --- a/verify/add_invoice.sql +++ b/verify/add_invoice.sql @@ -2,6 +2,6 @@ begin; -select has_function_privilege('numerus.add_invoice(integer, text, date, integer, text, numerus.new_invoice_product[])', 'execute'); +select has_function_privilege('numerus.add_invoice(integer, text, date, integer, text, integer, numerus.new_invoice_product[])', 'execute'); rollback; diff --git a/verify/invoice.sql b/verify/invoice.sql index ef53c76..88efecc 100644 --- a/verify/invoice.sql +++ b/verify/invoice.sql @@ -10,6 +10,7 @@ select invoice_id , contact_id , invoice_status , notes + , payment_method_id , currency_code , created_at from numerus.invoice diff --git a/web/static/invoice.css b/web/static/invoice.css index c9efeb4..a5fb716 100644 --- a/web/static/invoice.css +++ b/web/static/invoice.css @@ -55,11 +55,15 @@ text-align: right; } -.invoice .notes { +.invoice .notes, .invoice .payment-instructions { white-space: pre; text-align: right; } +.invoice .notes + .payment-instructions { + margin-top: 5rem; +} + .invoice td { vertical-align: top; } diff --git a/web/template/invoices/view.gohtml b/web/template/invoices/view.gohtml index d9c29a6..f693f43 100644 --- a/web/template/invoices/view.gohtml +++ b/web/template/invoices/view.gohtml @@ -110,6 +110,7 @@ {{ if .Notes -}}

{{ .Notes }}

{{- end }} +

{{ .PaymentInstructions }}