Add payment collection
This is the same as a payment, but the user is the payee instead of the payer. I used a different relation than payment because i do not know any other way to encode the constraint that only invoices can have a collection, while expenses have only payments. Besides the name and the fact that they are related to invoices, a collection is pretty much the same as a payment.
This commit is contained in:
parent
c6c550a036
commit
7f31b10cce
|
@ -78,53 +78,84 @@ select add_product(123, 'Cavall Fort', 'Revista quinzenal en llengua catalana i
|
|||
select add_product(123, 'Palla', 'Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.', '25.00', array[125], array['necessitat']);
|
||||
select add_product(123, 'Teia', 'Fusta resinosa de pi i d’altres arbres, provinent sobretot del cor de l’arbre, que crema amb molta facilitat.', '7.00', array[124], array['obsolet']);
|
||||
|
||||
alter table payment_account alter column payment_account_id restart with 123;
|
||||
select add_payment_account_bank(123, 'Guardiola', 'ES2820958297603648596978');
|
||||
select add_payment_account_cash(123, 'Matalàs');
|
||||
|
||||
alter sequence invoice_invoice_id_seq restart with 123;
|
||||
alter sequence invoice_product_invoice_product_id_seq restart with 123;
|
||||
select add_invoice(123, (current_date - '338 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 123, (current_date - '337 days'::interval)::date, 123, 'Cobrament de FRA123', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '334 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 124, (current_date - '330 days'::interval)::date, 123, 'Cobrament de FRA124', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '327 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
select add_collection(123, 125, (current_date - '317 days'::interval)::date, 123, 'Cobrament de FRA125', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '317 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 126, (current_date - '316 days'::interval)::date, 124, 'Cobrament de FRA126', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '314 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
select add_collection(123, 127, (current_date - '310 days'::interval)::date, 123, 'Cobrament de FRA127', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '311 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 128, (current_date - '311 days'::interval)::date, 124, 'Cobrament de FRA128', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '278 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_collection(123, 129, (current_date - '270 days'::interval)::date, 123, 'Cobrament de FRA129', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '274 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 130, (current_date - '272 days'::interval)::date, 123, 'Cobrament de FRA130', '691.90', '{}');
|
||||
select add_invoice(123, (current_date - '267 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 131, (current_date - '265 days'::interval)::date, 123, 'Cobrament de FRA131', '87.50', '{}');
|
||||
select add_invoice(123, (current_date - '257 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 132, (current_date - '250 days'::interval)::date, 124, 'Cobrament de FRA132', '87.50', '{}');
|
||||
select add_invoice(123, (current_date - '254 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 133, (current_date - '220 days'::interval)::date, 123, 'Cobrament de FRA133', '691.90', '{}');
|
||||
select add_invoice(123, (current_date - '251 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_collection(123, 134, (current_date - '245 days'::interval)::date, 123, 'Cobrament de FRA134', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '208 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 135, (current_date - '190 days'::interval)::date, 123, 'Cobrament de FRA135', '691.90', '{}');
|
||||
select add_invoice(123, (current_date - '204 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 136, (current_date - '200 days'::interval)::date, 123, 'Cobrament de FRA136', '691.90', '{}');
|
||||
select add_invoice(123, (current_date - '197 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_collection(123, 137, (current_date - '190 days'::interval)::date, 123, 'Cobrament de FRA137', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '187 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 138, (current_date - '186 days'::interval)::date, 124, 'Cobrament de FRA138', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '184 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
select add_collection(123, 139, (current_date - '181 days'::interval)::date, 123, 'Cobrament de FRA139', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '181 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_collection(123, 140, (current_date - '177 days'::interval)::date, 123, 'Cobrament de FRA140', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '148 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 141, (current_date - '140 days'::interval)::date, 123, 'Cobrament de FRA141', '87.50', '{}');
|
||||
select add_invoice(123, (current_date - '144 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 142, (current_date - '143 days'::interval)::date, 123, 'Cobrament de FRA142', '87.50', '{}');
|
||||
select add_invoice(123, (current_date - '137 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
select add_collection(123, 143, (current_date - '137 days'::interval)::date, 123, 'Cobrament de FRA143', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '127 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 144, (current_date - '120 days'::interval)::date, 123, 'Cobrament de FRA144', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '124 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 145, (current_date - '123 days'::interval)::date, 123, 'Cobrament de FRA145', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '121 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 146, (current_date - '101 days'::interval)::date, 124, 'Cobrament de FRA146', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '78 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 147, (current_date - '60 days'::interval)::date, 123, 'Cobrament de FRA147', '87.50', '{}');
|
||||
select add_invoice(123, (current_date - '74 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 148, (current_date - '61 days'::interval)::date, 123, 'Cobrament de FRA148', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '67 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 149, (current_date - '66 days'::interval)::date, 124, 'Cobrament de FRA149', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '57 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
select add_collection(123, 150, (current_date - '55 days'::interval)::date, 123, 'Cobrament de FRA150', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '54 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 151, (current_date - '50 days'::interval)::date, 124, 'Cobrament de FRA151', '691.90', '{}');
|
||||
select add_invoice(123, (current_date - '51 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_collection(123, 152, (current_date - '44 days'::interval)::date, 123, 'Cobrament de FRA152', '1200.50', '{}');
|
||||
select add_invoice(123, (current_date - '28 days'::interval)::date, 128, 'Vol esmorzar!', 123, '{producte}','{"(129,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,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_collection(123, 153, (current_date - '28 days'::interval)::date, 124, 'Cobrament de FRA153', '12.87', '{}');
|
||||
select add_invoice(123, (current_date - '24 days'::interval)::date, 127, '', 123, '{producte,bestia}','{"(128,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{125})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_invoice(123, (current_date - '17 days'::interval)::date, 126, '', 123, '{producte,higiene}','{"(126,\"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,{126})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{124})"}');
|
||||
select add_invoice(123, (current_date - '7 days'::interval)::date, 125, '', 123, '{producte,mag}','{"(125,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{124})"}');
|
||||
select add_invoice(123, (current_date - '4 days'::interval)::date, 124, '', 123, '{producte,mag}','{"(124,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{124})"}');
|
||||
select add_collection(123, 157, (current_date - '2 days'::interval)::date, 123, 'Primer cobrament de FRA157', '1000.00', '{}');
|
||||
select add_invoice(123, (current_date - '1 days'::interval)::date, 123, '', 123, '{producte,mag}','{"(123,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a l’aigua règia.\",57.82,18,0.05,{124})","(127,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{124})"}');
|
||||
|
||||
update invoice set invoice_status = 'paid' where invoice_id not in (154, 155, 156, 158);
|
||||
update invoice set invoice_status = 'unpaid' where invoice_id = 155;
|
||||
update invoice set invoice_status = 'sent' where invoice_id = 156;
|
||||
|
||||
alter table payment_account alter column payment_account_id restart with 123;
|
||||
select add_payment_account_bank(123, 'Guardiola', 'ES2820958297603648596978');
|
||||
select add_payment_account_cash(123, 'Matalàs');
|
||||
|
||||
alter sequence expense_expense_id_seq restart with 123;
|
||||
select add_expense(123, (date_trunc('month', current_date) - '11 months + 14 day'::interval)::date, 130, 'ABC123', '256.12', '{124}', '{}');
|
||||
select add_payment(123, 123, (date_trunc('month', current_date) - '11 months + 04 day'::interval)::date, 123, 'Pagament d’ABC123', '256.12', '{}');
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
-- Deploy numerus:add_collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: collection
|
||||
-- requires: invoice_collection
|
||||
-- requires: company
|
||||
-- requires: currency
|
||||
-- requires: parse_price
|
||||
-- requires: tag_name
|
||||
-- requires: update_invoice_collection_status
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function add_collection(company integer, invoice_id integer, collection_date date, payment_account_id integer, description text, amount text, tags tag_name[]) returns uuid as
|
||||
$$
|
||||
declare
|
||||
cid integer;
|
||||
cslug uuid;
|
||||
amount_cents integer;
|
||||
begin
|
||||
insert into collection
|
||||
( company_id
|
||||
, payment_account_id
|
||||
, description
|
||||
, collection_date
|
||||
, amount
|
||||
, currency_code
|
||||
, payment_status
|
||||
, tags
|
||||
)
|
||||
select company_id
|
||||
, payment_account_id
|
||||
, description
|
||||
, collection_date
|
||||
, parse_price(amount, currency.decimal_digits)
|
||||
, currency_code
|
||||
, 'complete'
|
||||
, tags
|
||||
from company
|
||||
join currency using (currency_code)
|
||||
where company.company_id = add_collection.company
|
||||
returning collection_id, slug, collection.amount
|
||||
into cid, cslug, amount_cents
|
||||
;
|
||||
|
||||
if invoice_id is not null then
|
||||
-- must be inserted before updating statuses, so that it can see this
|
||||
-- collection’s amount too.
|
||||
insert into invoice_collection (invoice_id, collection_id)
|
||||
values (invoice_id, cid)
|
||||
;
|
||||
|
||||
perform update_invoice_collection_status(cid, invoice_id, amount_cents);
|
||||
end if;
|
||||
|
||||
return cslug;
|
||||
end
|
||||
$$
|
||||
language plpgsql
|
||||
;
|
||||
|
||||
revoke execute on function add_collection(integer, integer, date, integer, text, text, tag_name[]) from public;
|
||||
grant execute on function add_collection(integer, integer, date, integer, text, text, tag_name[]) to invoicer;
|
||||
grant execute on function add_collection(integer, integer, date, integer, text, text, tag_name[]) to admin;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,30 @@
|
|||
-- Deploy numerus:attach_to_collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: collection
|
||||
-- requires: collection_attachment
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function attach_to_collection(collection_slug uuid, original_filename text, mime_type text, content bytea) returns void as
|
||||
$$
|
||||
insert into collection_attachment (collection_id, original_filename, mime_type, content)
|
||||
select collection_id, original_filename, mime_type, content
|
||||
from collection
|
||||
where slug = collection_slug
|
||||
on conflict (collection_id) do update
|
||||
set original_filename = excluded.original_filename
|
||||
, mime_type = excluded.mime_type
|
||||
, content = excluded.content
|
||||
;
|
||||
$$
|
||||
language sql
|
||||
;
|
||||
|
||||
revoke execute on function attach_to_collection(uuid, text, text, bytea) from public;
|
||||
grant execute on function attach_to_collection(uuid, text, text, bytea) to invoicer;
|
||||
grant execute on function attach_to_collection(uuid, text, text, bytea) to admin;
|
||||
|
||||
commit;
|
|
@ -10,19 +10,24 @@ set search_path to numerus;
|
|||
insert into invoice_status (invoice_status, name)
|
||||
values ('created', 'Created')
|
||||
, ('sent', 'Sent')
|
||||
, ('partial', 'Partial')
|
||||
, ('paid', 'Paid')
|
||||
, ('unpaid', 'Unpaid')
|
||||
on conflict (invoice_status) do nothing
|
||||
;
|
||||
|
||||
insert into invoice_status_i18n (invoice_status, lang_tag, name)
|
||||
values ('created', 'ca', 'Creada')
|
||||
, ('sent', 'ca', 'Enviada')
|
||||
, ('partial', 'ca', 'Parcial')
|
||||
, ('paid', 'ca', 'Cobrada')
|
||||
, ('unpaid', 'ca', 'No cobrada')
|
||||
, ('created', 'es', 'Creada')
|
||||
, ('sent', 'es', 'Enviada')
|
||||
, ('partial', 'es', 'Parcial')
|
||||
, ('paid', 'es', 'Cobrada')
|
||||
, ('unpaid', 'es', 'No cobrada')
|
||||
on conflict (invoice_status, lang_tag) do nothing
|
||||
;
|
||||
|
||||
commit;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
-- Deploy numerus:available_invoice_status to pg
|
||||
-- requires: schema_numerus
|
||||
-- requires: invoice_status
|
||||
-- requires: invoice_status_i18n
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus;
|
||||
|
||||
insert into invoice_status (invoice_status, name)
|
||||
values ('created', 'Created')
|
||||
, ('sent', 'Sent')
|
||||
, ('paid', 'Paid')
|
||||
, ('unpaid', 'Unpaid')
|
||||
;
|
||||
|
||||
insert into invoice_status_i18n (invoice_status, lang_tag, name)
|
||||
values ('created', 'ca', 'Creada')
|
||||
, ('sent', 'ca', 'Enviada')
|
||||
, ('paid', 'ca', 'Cobrada')
|
||||
, ('unpaid', 'ca', 'No cobrada')
|
||||
, ('created', 'es', 'Creada')
|
||||
, ('sent', 'es', 'Enviada')
|
||||
, ('paid', 'es', 'Cobrada')
|
||||
, ('unpaid', 'es', 'No cobrada')
|
||||
;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,45 @@
|
|||
-- Deploy numerus:collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: company
|
||||
-- requires: payment_account
|
||||
-- requires: currency
|
||||
-- requires: tag_name
|
||||
-- requires: payment_status
|
||||
-- requires: extension_pgcrypto
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create table collection (
|
||||
collection_id integer generated by default as identity primary key,
|
||||
company_id integer not null references company,
|
||||
slug uuid not null unique default gen_random_uuid(),
|
||||
description text not null,
|
||||
collection_date date not null default current_date,
|
||||
payment_account_id integer not null references payment_account,
|
||||
amount integer not null constraint collection_amount_positive check (amount > 0),
|
||||
currency_code text not null references currency,
|
||||
tags tag_name[] not null default '{}',
|
||||
payment_status text not null default 'complete' references payment_status,
|
||||
created_at timestamptz not null default current_timestamp
|
||||
);
|
||||
|
||||
grant select, insert, update, delete on table collection to invoicer;
|
||||
grant select, insert, update, delete on table collection to admin;
|
||||
|
||||
alter table collection enable row level security;
|
||||
|
||||
create policy company_policy
|
||||
on collection
|
||||
using (
|
||||
exists(
|
||||
select 1
|
||||
from company_user
|
||||
join user_profile using (user_id)
|
||||
where company_user.company_id = collection.company_id
|
||||
)
|
||||
);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,32 @@
|
|||
-- Deploy numerus:collection_attachment to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: collection
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create table collection_attachment (
|
||||
collection_id integer primary key references collection,
|
||||
original_filename text not null,
|
||||
mime_type text not null,
|
||||
content bytea not null
|
||||
);
|
||||
|
||||
grant select, insert, update, delete on table collection_attachment to invoicer;
|
||||
grant select, insert, update, delete on table collection_attachment to admin;
|
||||
|
||||
alter table collection_attachment enable row level security;
|
||||
|
||||
create policy company_policy
|
||||
on collection_attachment
|
||||
using (
|
||||
exists(
|
||||
select 1
|
||||
from collection
|
||||
where collection.collection_id = collection_attachment.collection_id
|
||||
)
|
||||
);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,53 @@
|
|||
-- Deploy numerus:edit_collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: collection
|
||||
-- requires: invoice_collection
|
||||
-- requires: currency
|
||||
-- requires: parse_price
|
||||
-- requires: tag_name
|
||||
-- requires: update_invoice_collection_status
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function edit_collection(collection_slug uuid, collection_date date, payment_account_id integer, description text, amount text, tags tag_name[]) returns uuid as
|
||||
$$
|
||||
declare
|
||||
cid integer;
|
||||
iid integer;
|
||||
amount_cents integer;
|
||||
begin
|
||||
update collection
|
||||
set collection_date = edit_collection.collection_date
|
||||
, payment_account_id = edit_collection.payment_account_id
|
||||
, description = edit_collection.description
|
||||
, amount = parse_price(edit_collection.amount, decimal_digits)
|
||||
, tags = edit_collection.tags
|
||||
from currency
|
||||
where slug = collection_slug
|
||||
and currency.currency_code = collection.currency_code
|
||||
returning collection_id, collection.amount
|
||||
into cid, amount_cents
|
||||
;
|
||||
|
||||
select invoice_id into iid
|
||||
from invoice_collection
|
||||
where collection_id = cid;
|
||||
|
||||
if iid is not null then
|
||||
perform update_invoice_collection_status(cid, iid, amount_cents);
|
||||
end if;
|
||||
|
||||
return collection_slug;
|
||||
end
|
||||
$$
|
||||
language plpgsql
|
||||
;
|
||||
|
||||
revoke execute on function edit_collection(uuid, date, integer, text, text, tag_name[]) from public;
|
||||
grant execute on function edit_collection(uuid, date, integer, text, text, tag_name[]) to invoicer;
|
||||
grant execute on function edit_collection(uuid, date, integer, text, text, tag_name[]) to admin;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,32 @@
|
|||
-- Deploy numerus:invoice_collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: invoice
|
||||
-- requires: collection
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create table invoice_collection (
|
||||
invoice_id integer not null references invoice,
|
||||
collection_id integer not null references collection,
|
||||
primary key (invoice_id, collection_id)
|
||||
);
|
||||
|
||||
grant select, insert, update, delete on table invoice_collection to invoicer;
|
||||
grant select, insert, update, delete on table invoice_collection to admin;
|
||||
|
||||
alter table invoice_collection enable row level security;
|
||||
|
||||
create policy company_policy
|
||||
on invoice_collection
|
||||
using (
|
||||
exists(
|
||||
select 1
|
||||
from invoice
|
||||
where invoice.invoice_id = invoice_collection.invoice_id
|
||||
)
|
||||
);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,40 @@
|
|||
-- Deploy numerus:remove_collection to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: invoice_collection
|
||||
-- requires: collection
|
||||
-- requires: collection_attachment
|
||||
-- requires: update_invoice_collection_status
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function remove_collection(collection_slug uuid) returns void as
|
||||
$$
|
||||
declare
|
||||
cid integer;
|
||||
iid integer;
|
||||
begin
|
||||
select collection_id into cid from collection where slug = collection_slug;
|
||||
if not found then
|
||||
return;
|
||||
end if;
|
||||
|
||||
delete from invoice_collection where collection_id = cid returning invoice_id into iid;
|
||||
if iid is not null then
|
||||
perform update_invoice_collection_status(null, iid, 0);
|
||||
end if;
|
||||
|
||||
delete from collection_attachment where collection_id = cid;
|
||||
delete from collection where collection_id = cid;
|
||||
end
|
||||
$$
|
||||
language plpgsql
|
||||
;
|
||||
|
||||
revoke execute on function remove_collection(uuid) from public;
|
||||
grant execute on function remove_collection(uuid) to invoicer;
|
||||
grant execute on function remove_collection(uuid) to admin;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,49 @@
|
|||
-- Deploy numerus:update_invoice_collection_status to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_numerus
|
||||
-- requires: invoice
|
||||
-- requires: collection
|
||||
-- requires: invoice_collection
|
||||
-- requires: invoice_amount
|
||||
-- requires: available_invoice_status
|
||||
-- requires: available_payment_status
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
create or replace function update_invoice_collection_status(cid integer, iid integer, amount_cents integer) returns void as
|
||||
$$
|
||||
update collection
|
||||
set payment_status = case when invoice_amount.total > amount_cents or exists (select 1 from invoice_collection as ic where ic.collection_id = invoice_amount.invoice_id and collection_id <> cid) then 'partial' else 'complete' end
|
||||
from invoice_amount
|
||||
where invoice_id = iid
|
||||
and collection_id = cid
|
||||
;
|
||||
|
||||
update invoice
|
||||
set invoice_status = case
|
||||
when collected_amount >= total_amount then 'paid'
|
||||
when collected_amount = 0 then 'created'
|
||||
else 'partial' end
|
||||
from (
|
||||
select coalesce(sum(collection.amount), 0) as collected_amount
|
||||
from invoice_collection
|
||||
join collection using (collection_id)
|
||||
where invoice_collection.invoice_id = iid
|
||||
) as collection,
|
||||
(
|
||||
select total as total_amount
|
||||
from invoice_amount
|
||||
where invoice_id = iid
|
||||
) as amount
|
||||
where invoice.invoice_id = iid;
|
||||
$$
|
||||
language sql
|
||||
;
|
||||
|
||||
revoke execute on function update_invoice_collection_status(integer, integer, integer) from public;
|
||||
grant execute on function update_invoice_collection_status(integer, integer, integer) to invoicer;
|
||||
grant execute on function update_invoice_collection_status(integer, integer, integer) to admin;
|
||||
|
||||
commit;
|
185
pkg/payments.go
185
pkg/payments.go
|
@ -10,6 +10,11 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentTypePayment = "P"
|
||||
PaymentTypeCollection = "C"
|
||||
)
|
||||
|
||||
func servePaymentIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
|
@ -42,14 +47,14 @@ type PaymentIndexPage struct {
|
|||
|
||||
func NewPaymentIndexPage(ctx context.Context, conn *Conn, company *Company, locale *Locale) *PaymentIndexPage {
|
||||
return &PaymentIndexPage{
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, company, locale, 0),
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, company, locale, "", 0),
|
||||
BaseURI: companyURI(company, "/payments"),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPaymentIndexPageForExpense(ctx context.Context, conn *Conn, company *Company, locale *Locale, expense *PaymentExpense) *PaymentIndexPage {
|
||||
return &PaymentIndexPage{
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, company, locale, expense.Id),
|
||||
Payments: mustCollectPaymentEntries(ctx, conn, company, locale, PaymentTypePayment, expense.Id),
|
||||
BaseURI: companyURI(company, "/expenses/"+expense.Slug+"/payments"),
|
||||
Expense: expense,
|
||||
}
|
||||
|
@ -111,10 +116,11 @@ func (expense *PaymentExpense) calcRemainingPaymentAmount(ctx context.Context, c
|
|||
|
||||
type PaymentEntry struct {
|
||||
ID int
|
||||
Type string
|
||||
Slug string
|
||||
PaymentDate time.Time
|
||||
Description string
|
||||
ExpenseSlug string
|
||||
DocumentSlug string
|
||||
InvoiceNumber string
|
||||
Total string
|
||||
OriginalFileName string
|
||||
|
@ -123,35 +129,57 @@ type PaymentEntry struct {
|
|||
StatusLabel string
|
||||
}
|
||||
|
||||
func mustCollectPaymentEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale, expenseId int) []*PaymentEntry {
|
||||
func mustCollectPaymentEntries(ctx context.Context, conn *Conn, company *Company, locale *Locale, paymentType string, documentId int) []*PaymentEntry {
|
||||
rows := conn.MustQuery(ctx, `
|
||||
select payment_id
|
||||
, payment.slug
|
||||
, payment_date
|
||||
, description
|
||||
, to_price(payment.amount, decimal_digits) as total
|
||||
, payment.tags
|
||||
, payment.payment_status
|
||||
, psi18n.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
, coalesce(expense.slug::text, '')
|
||||
, coalesce(expense.invoice_number, '')
|
||||
from payment
|
||||
join payment_status_i18n psi18n on payment.payment_status = psi18n.payment_status and psi18n.lang_tag = $1
|
||||
join currency using (currency_code)
|
||||
left join payment_attachment as attachment using (payment_id)
|
||||
left join expense_payment using (payment_id)
|
||||
left join expense using (expense_id)
|
||||
where payment.company_id = $2
|
||||
and ($3 = 0 or expense_id = $3)
|
||||
select $5 as type
|
||||
, payment_id
|
||||
, payment.slug
|
||||
, payment_date
|
||||
, description
|
||||
, to_price(payment.amount, decimal_digits) as total
|
||||
, payment.tags
|
||||
, payment.payment_status
|
||||
, psi18n.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
, coalesce(expense.slug::text, '')
|
||||
, coalesce(expense.invoice_number, '')
|
||||
from payment
|
||||
join payment_status_i18n psi18n on payment.payment_status = psi18n.payment_status and psi18n.lang_tag = $1
|
||||
join currency using (currency_code)
|
||||
left join payment_attachment as attachment using (payment_id)
|
||||
left join expense_payment using (payment_id)
|
||||
left join expense using (expense_id)
|
||||
where payment.company_id = $2
|
||||
and ($3 = '' or ($3 = $5 and expense_id = $4))
|
||||
union all
|
||||
select $6 as type
|
||||
, collection_id
|
||||
, collection.slug
|
||||
, collection_date as payment_date
|
||||
, description
|
||||
, to_price(collection.amount, decimal_digits) as total
|
||||
, collection.tags
|
||||
, collection.payment_status
|
||||
, psi18n.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
, coalesce(invoice.slug::text, '')
|
||||
, coalesce(invoice.invoice_number, '')
|
||||
from collection
|
||||
join payment_status_i18n psi18n on collection.payment_status = psi18n.payment_status and psi18n.lang_tag = $1
|
||||
join currency using (currency_code)
|
||||
left join collection_attachment as attachment using (collection_id)
|
||||
left join invoice_collection using (collection_id)
|
||||
left join invoice using (invoice_id)
|
||||
where collection.company_id = $2
|
||||
and ($3 = '' or ($3 = $6 and invoice_id = $4))
|
||||
order by payment_date desc, total desc
|
||||
`, locale.Language, company.Id, expenseId)
|
||||
`, locale.Language, company.Id, paymentType, documentId, PaymentTypePayment, PaymentTypeCollection)
|
||||
defer rows.Close()
|
||||
|
||||
var entries []*PaymentEntry
|
||||
for rows.Next() {
|
||||
entry := &PaymentEntry{}
|
||||
if err := rows.Scan(&entry.ID, &entry.Slug, &entry.PaymentDate, &entry.Description, &entry.Total, &entry.Tags, &entry.Status, &entry.StatusLabel, &entry.OriginalFileName, &entry.ExpenseSlug, &entry.InvoiceNumber); err != nil {
|
||||
if err := rows.Scan(&entry.Type, &entry.ID, &entry.Slug, &entry.PaymentDate, &entry.Description, &entry.Total, &entry.Tags, &entry.Status, &entry.StatusLabel, &entry.OriginalFileName, &entry.DocumentSlug, &entry.InvoiceNumber); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
|
@ -221,6 +249,7 @@ type PaymentForm struct {
|
|||
Slug string
|
||||
BaseURI string
|
||||
Expense *PaymentExpense
|
||||
Type *SelectField
|
||||
Description *InputField
|
||||
PaymentDate *InputField
|
||||
PaymentAccount *SelectField
|
||||
|
@ -234,6 +263,15 @@ func newPaymentForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
locale: locale,
|
||||
company: company,
|
||||
BaseURI: companyURI(company, "/payments"),
|
||||
Type: &SelectField{
|
||||
Name: "type",
|
||||
Label: pgettext("input", "Type", locale),
|
||||
Required: true,
|
||||
Options: []*SelectOption{
|
||||
{Value: PaymentTypePayment, Label: pgettext("payment type", "Payment", locale)},
|
||||
{Value: PaymentTypeCollection, Label: pgettext("payment type", "Collection", locale)},
|
||||
},
|
||||
},
|
||||
Description: &InputField{
|
||||
Name: "description",
|
||||
Label: pgettext("input", "Description", locale),
|
||||
|
@ -276,6 +314,7 @@ func newPaymentForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
|
||||
func newPaymentFormForExpense(ctx context.Context, conn *Conn, locale *Locale, company *Company, expense *PaymentExpense) *PaymentForm {
|
||||
form := newPaymentForm(ctx, conn, locale, company)
|
||||
form.Type.Selected = []string{PaymentTypePayment}
|
||||
form.BaseURI = companyURI(company, "/expenses/"+expense.Slug+"/payments")
|
||||
form.Expense = expense
|
||||
return form
|
||||
|
@ -283,6 +322,7 @@ func newPaymentFormForExpense(ctx context.Context, conn *Conn, locale *Locale, c
|
|||
|
||||
func (f *PaymentForm) MustRender(w http.ResponseWriter, r *http.Request) {
|
||||
if f.Slug == "" {
|
||||
f.Type.EmptyLabel = gettext("Select a type.", f.locale)
|
||||
f.PaymentAccount.EmptyLabel = gettext("Select an account.", f.locale)
|
||||
mustRenderMainTemplate(w, r, "payments/new.gohtml", f)
|
||||
} else {
|
||||
|
@ -291,23 +331,37 @@ func (f *PaymentForm) MustRender(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (f *PaymentForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
|
||||
selectedType := f.Type.Selected
|
||||
selectedPaymentAccount := f.PaymentAccount.Selected
|
||||
f.PaymentAccount.Clear()
|
||||
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||
select description
|
||||
, payment_date
|
||||
, payment_account_id::text
|
||||
, to_price(amount, decimal_digits)
|
||||
, tags
|
||||
from payment
|
||||
join currency using (currency_code)
|
||||
where payment.slug = $1
|
||||
`, slug).Scan(
|
||||
select $2 as type
|
||||
, description
|
||||
, payment_date
|
||||
, payment_account_id::text
|
||||
, to_price(amount, decimal_digits)
|
||||
, tags
|
||||
from payment
|
||||
join currency using (currency_code)
|
||||
where payment.slug = $1
|
||||
union all
|
||||
select $3 as type
|
||||
, description
|
||||
, collection_date
|
||||
, payment_account_id::text
|
||||
, to_price(amount, decimal_digits)
|
||||
, tags
|
||||
from collection
|
||||
join currency using (currency_code)
|
||||
where collection.slug = $1
|
||||
`, slug, PaymentTypePayment, PaymentTypeCollection).Scan(
|
||||
f.Type,
|
||||
f.Description,
|
||||
f.PaymentDate,
|
||||
f.PaymentAccount,
|
||||
f.Amount,
|
||||
f.Tags)) {
|
||||
f.Type.Selected = selectedType
|
||||
f.PaymentAccount.Selected = selectedPaymentAccount
|
||||
return false
|
||||
}
|
||||
|
@ -319,6 +373,7 @@ func (f *PaymentForm) Parse(r *http.Request) error {
|
|||
if err := r.ParseMultipartForm(f.File.MaxSize); err != nil {
|
||||
return err
|
||||
}
|
||||
f.Type.FillValue(r)
|
||||
f.Description.FillValue(r)
|
||||
f.PaymentDate.FillValue(r)
|
||||
f.PaymentAccount.FillValue(r)
|
||||
|
@ -332,6 +387,7 @@ func (f *PaymentForm) Parse(r *http.Request) error {
|
|||
|
||||
func (f *PaymentForm) Validate() bool {
|
||||
validator := newFormValidator()
|
||||
validator.CheckValidSelectOption(f.Type, gettext("Selected payment type is not valid.", f.locale))
|
||||
validator.CheckRequiredInput(f.Description, gettext("Description can not be empty.", f.locale))
|
||||
validator.CheckValidSelectOption(f.PaymentAccount, gettext("Selected payment account is not valid.", f.locale))
|
||||
validator.CheckValidDate(f.PaymentDate, gettext("Payment date must be a valid date.", f.locale))
|
||||
|
@ -365,13 +421,20 @@ func handleAddPaymentForm(w http.ResponseWriter, r *http.Request, conn *Conn, co
|
|||
form.MustRender(w, r)
|
||||
return
|
||||
}
|
||||
var paymentSlug any
|
||||
var documentId any
|
||||
if form.Expense != nil {
|
||||
paymentSlug = form.Expense.Id
|
||||
documentId = form.Expense.Id
|
||||
}
|
||||
slug := conn.MustGetText(r.Context(), "", "select add_payment($1, $2, $3, $4, $5, $6, $7)", company.Id, paymentSlug, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags)
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
if form.Type.String() == PaymentTypePayment {
|
||||
slug := conn.MustGetText(r.Context(), "", "select add_payment($1, $2, $3, $4, $5, $6, $7)", company.Id, documentId, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags)
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
} else {
|
||||
slug := conn.MustGetText(r.Context(), "", "select add_collection($1, $2, $3, $4, $5, $6, $7)", company.Id, documentId, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags)
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_collection($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
}
|
||||
htmxRedirect(w, r, form.BaseURI)
|
||||
}
|
||||
|
@ -420,12 +483,22 @@ func handleEditPaymentForm(w http.ResponseWriter, r *http.Request, conn *Conn, f
|
|||
form.MustRender(w, r)
|
||||
return
|
||||
}
|
||||
if found := conn.MustGetText(r.Context(), "", "select edit_payment($1, $2, $3, $4, $5, $6)", form.Slug, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags); found == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", form.Slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
if form.Type.String() == PaymentTypePayment {
|
||||
if found := conn.MustGetText(r.Context(), "", "select edit_payment($1, $2, $3, $4, $5, $6)", form.Slug, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags); found == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_payment($1, $2, $3, $4)", form.Slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
} else {
|
||||
if found := conn.MustGetText(r.Context(), "", "select edit_collection($1, $2, $3, $4, $5, $6)", form.Slug, form.PaymentDate, form.PaymentAccount, form.Description, form.Amount, form.Tags); found == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if len(form.File.Content) > 0 {
|
||||
conn.MustQuery(r.Context(), "select attach_to_collection($1, $2, $3, $4)", form.Slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
|
||||
}
|
||||
}
|
||||
htmxRedirect(w, r, form.BaseURI)
|
||||
}
|
||||
|
@ -462,7 +535,7 @@ func removePayment(w http.ResponseWriter, r *http.Request, slug string, backURI
|
|||
}
|
||||
|
||||
conn := getConn(r)
|
||||
conn.MustExec(r.Context(), "select remove_payment($1)", slug)
|
||||
conn.MustExec(r.Context(), "select remove_payment($1), remove_collection($1)", slug)
|
||||
|
||||
htmxRedirect(w, r, backURI)
|
||||
}
|
||||
|
@ -482,18 +555,24 @@ func handleRemoveExpensePayment(w http.ResponseWriter, r *http.Request, params h
|
|||
|
||||
func servePaymentAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveAttachment(w, r, params, `
|
||||
select mime_type
|
||||
, content
|
||||
from payment
|
||||
join payment_attachment using (payment_id)
|
||||
where slug = $1
|
||||
select mime_type
|
||||
, content
|
||||
from payment
|
||||
join payment_attachment using (payment_id)
|
||||
where slug = $1
|
||||
union all
|
||||
select mime_type
|
||||
, content
|
||||
from collection
|
||||
join collection_attachment using (collection_id)
|
||||
where slug = $1
|
||||
`)
|
||||
}
|
||||
|
||||
func servePaymentTagsEditForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
serveTagsEditForm(w, r, params, "/payments/", "select tags from payment where slug = $1")
|
||||
serveTagsEditForm(w, r, params, "/payments/", "select tags from payment where slug = $1 union all select tags from collection where slug = $1")
|
||||
}
|
||||
|
||||
func handleUpdatePaymentTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handleUpdateTags(w, r, params, "/payments/", "update payment set tags = $1 where slug = $2 returning slug")
|
||||
handleUpdateTags(w, r, params, "/payments/", "with p as (update payment set tags = $1 where slug = $2 returning slug), c as (update collection set tags = $1 where slug = $2 returning slug) select p.slug from p union all select c.slug from c")
|
||||
}
|
||||
|
|
81
po/ca.po
81
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-08-17 05:26+0200\n"
|
||||
"POT-Creation-Date: 2024-08-21 03:28+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -39,7 +39,7 @@ msgstr "Afegeix productes a la factura"
|
|||
#: web/template/company/payment_methods.gohtml:10
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:10
|
||||
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:10
|
||||
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:14
|
||||
#: web/template/payments/accounts/new.gohtml:10
|
||||
#: web/template/payments/accounts/index.gohtml:10
|
||||
#: web/template/payments/accounts/edit.gohtml:10
|
||||
|
@ -124,7 +124,7 @@ msgstr "Total"
|
|||
#: web/template/invoices/new.gohtml:92 web/template/invoices/edit.gohtml:93
|
||||
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
|
||||
#: web/template/expenses/new.gohtml:56 web/template/expenses/edit.gohtml:58
|
||||
#: web/template/payments/edit.gohtml:41
|
||||
#: web/template/payments/edit.gohtml:46
|
||||
#: web/template/payments/accounts/edit.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
|
@ -135,7 +135,7 @@ msgstr "Actualitza"
|
|||
#: web/template/contacts/new.gohtml:49 web/template/contacts/edit.gohtml:53
|
||||
#: web/template/expenses/new.gohtml:59 web/template/expenses/edit.gohtml:61
|
||||
#: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36
|
||||
#: web/template/payments/new.gohtml:39
|
||||
#: web/template/payments/new.gohtml:44
|
||||
#: web/template/payments/accounts/new.gohtml:41
|
||||
msgctxt "action"
|
||||
msgid "Save"
|
||||
|
@ -242,7 +242,7 @@ msgstr "Accions per la factura %s"
|
|||
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
||||
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
||||
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:125
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:81
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:85
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Edita"
|
||||
|
@ -634,7 +634,7 @@ msgstr "Nova despesa"
|
|||
#: web/template/expenses/new.gohtml:11 web/template/expenses/index.gohtml:3
|
||||
#: web/template/expenses/index.gohtml:11 web/template/expenses/edit.gohtml:11
|
||||
#: web/template/payments/new.gohtml:12 web/template/payments/index.gohtml:12
|
||||
#: web/template/payments/edit.gohtml:12
|
||||
#: web/template/payments/edit.gohtml:16
|
||||
msgctxt "title"
|
||||
msgid "Expenses"
|
||||
msgstr "Despeses"
|
||||
|
@ -781,7 +781,7 @@ msgid "New Payment"
|
|||
msgstr "Nou pagament"
|
||||
|
||||
#: web/template/payments/new.gohtml:15 web/template/payments/index.gohtml:3
|
||||
#: web/template/payments/index.gohtml:15 web/template/payments/edit.gohtml:15
|
||||
#: web/template/payments/index.gohtml:15 web/template/payments/edit.gohtml:19
|
||||
msgctxt "title"
|
||||
msgid "Payments"
|
||||
msgstr "Pagaments"
|
||||
|
@ -810,24 +810,29 @@ msgstr "Document"
|
|||
msgid "Are you sure you wish to delete this payment?"
|
||||
msgstr "Esteu segur de voler esborrar aquest pagament?"
|
||||
|
||||
#: web/template/payments/index.gohtml:73
|
||||
#: web/template/payments/index.gohtml:77
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Accions pel pagament %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:92
|
||||
#: web/template/payments/index.gohtml:96
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Esborra"
|
||||
|
||||
#: web/template/payments/index.gohtml:102
|
||||
#: web/template/payments/index.gohtml:106
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hi ha cap pagament."
|
||||
|
||||
#: web/template/payments/edit.gohtml:3
|
||||
#: web/template/payments/edit.gohtml:4
|
||||
msgctxt "title"
|
||||
msgid "Edit Payment “%s”"
|
||||
msgstr "Edició del pagament «%s»"
|
||||
|
||||
#: web/template/payments/edit.gohtml:6
|
||||
msgctxt "title"
|
||||
msgid "Edit Collection “%s”"
|
||||
msgstr "Edició del cobrament «%s»"
|
||||
|
||||
#: web/template/payments/accounts/new.gohtml:3
|
||||
#: web/template/payments/accounts/new.gohtml:12
|
||||
msgctxt "title"
|
||||
|
@ -901,7 +906,7 @@ msgid "Name"
|
|||
msgstr "Nom"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/tags.go:37 pkg/quote.go:174
|
||||
#: pkg/quote.go:708 pkg/payments.go:272 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/quote.go:708 pkg/payments.go:310 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/contacts.go:154
|
||||
#: pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -936,7 +941,7 @@ msgstr "Qualsevol"
|
|||
msgid "Invoices must have at least one of the specified labels."
|
||||
msgstr "Les factures han de tenir com a mínim una de les etiquetes."
|
||||
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:239
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:277
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1351,60 +1356,78 @@ msgstr "La confirmació no és igual a la contrasenya."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||
|
||||
#: pkg/payments.go:202
|
||||
#: pkg/payments.go:230
|
||||
#, c-format
|
||||
msgid "Payment of %s"
|
||||
msgstr "Pagament de %s"
|
||||
|
||||
#: pkg/payments.go:245
|
||||
#: pkg/payments.go:268 pkg/accounts.go:131
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipus"
|
||||
|
||||
#: pkg/payments.go:271
|
||||
msgctxt "payment type"
|
||||
msgid "Payment"
|
||||
msgstr "Pagament"
|
||||
|
||||
#: pkg/payments.go:272
|
||||
msgctxt "payment type"
|
||||
msgid "Collection"
|
||||
msgstr "Cobrament"
|
||||
|
||||
#: pkg/payments.go:283
|
||||
msgctxt "input"
|
||||
msgid "Payment Date"
|
||||
msgstr "Data del pagament"
|
||||
|
||||
#: pkg/payments.go:251
|
||||
#: pkg/payments.go:289
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: pkg/payments.go:257 pkg/expenses.go:319
|
||||
#: pkg/payments.go:295 pkg/expenses.go:319
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
||||
#: pkg/payments.go:267 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
#: pkg/payments.go:305 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Fitxer"
|
||||
|
||||
#: pkg/payments.go:286
|
||||
#: pkg/payments.go:325
|
||||
msgid "Select a type."
|
||||
msgstr "Escolliu un tipus."
|
||||
|
||||
#: pkg/payments.go:326
|
||||
msgid "Select an account."
|
||||
msgstr "Escolliu un compte."
|
||||
|
||||
#: pkg/payments.go:335
|
||||
#: pkg/payments.go:390
|
||||
msgid "Selected payment type is not valid."
|
||||
msgstr "Heu seleccionat un tipus de pagament que no és vàlid."
|
||||
|
||||
#: pkg/payments.go:391
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podeu deixar la descripció en blanc."
|
||||
|
||||
#: pkg/payments.go:336
|
||||
#: pkg/payments.go:392
|
||||
msgid "Selected payment account is not valid."
|
||||
msgstr "Heu seleccionat un compte de pagament que no és vàlid."
|
||||
|
||||
#: pkg/payments.go:337
|
||||
#: pkg/payments.go:393
|
||||
msgid "Payment date must be a valid date."
|
||||
msgstr "La data de pagament ha de ser vàlida."
|
||||
|
||||
#: pkg/payments.go:338 pkg/expenses.go:372
|
||||
#: pkg/payments.go:394 pkg/expenses.go:372
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podeu deixar l’import en blanc."
|
||||
|
||||
#: pkg/payments.go:339 pkg/expenses.go:373
|
||||
#: pkg/payments.go:395 pkg/expenses.go:373
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "L’import ha de ser un número major a zero."
|
||||
|
||||
#: pkg/accounts.go:131
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipus"
|
||||
|
||||
#: pkg/accounts.go:146 pkg/contacts.go:352
|
||||
msgctxt "input"
|
||||
msgid "IBAN"
|
||||
|
|
81
po/es.po
81
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: numerus\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-08-17 05:26+0200\n"
|
||||
"POT-Creation-Date: 2024-08-21 03:28+0200\n"
|
||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -39,7 +39,7 @@ msgstr "Añadir productos a la factura"
|
|||
#: web/template/company/payment_methods.gohtml:10
|
||||
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
|
||||
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:10
|
||||
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:10
|
||||
#: web/template/payments/index.gohtml:10 web/template/payments/edit.gohtml:14
|
||||
#: web/template/payments/accounts/new.gohtml:10
|
||||
#: web/template/payments/accounts/index.gohtml:10
|
||||
#: web/template/payments/accounts/edit.gohtml:10
|
||||
|
@ -124,7 +124,7 @@ msgstr "Total"
|
|||
#: web/template/invoices/new.gohtml:92 web/template/invoices/edit.gohtml:93
|
||||
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
|
||||
#: web/template/expenses/new.gohtml:56 web/template/expenses/edit.gohtml:58
|
||||
#: web/template/payments/edit.gohtml:41
|
||||
#: web/template/payments/edit.gohtml:46
|
||||
#: web/template/payments/accounts/edit.gohtml:38
|
||||
msgctxt "action"
|
||||
msgid "Update"
|
||||
|
@ -135,7 +135,7 @@ msgstr "Actualizar"
|
|||
#: web/template/contacts/new.gohtml:49 web/template/contacts/edit.gohtml:53
|
||||
#: web/template/expenses/new.gohtml:59 web/template/expenses/edit.gohtml:61
|
||||
#: web/template/products/new.gohtml:30 web/template/products/edit.gohtml:36
|
||||
#: web/template/payments/new.gohtml:39
|
||||
#: web/template/payments/new.gohtml:44
|
||||
#: web/template/payments/accounts/new.gohtml:41
|
||||
msgctxt "action"
|
||||
msgid "Save"
|
||||
|
@ -242,7 +242,7 @@ msgstr "Acciones para la factura %s"
|
|||
#: web/template/invoices/index.gohtml:139 web/template/invoices/view.gohtml:19
|
||||
#: web/template/quotes/index.gohtml:137 web/template/quotes/view.gohtml:22
|
||||
#: web/template/contacts/index.gohtml:82 web/template/expenses/index.gohtml:125
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:81
|
||||
#: web/template/products/index.gohtml:78 web/template/payments/index.gohtml:85
|
||||
msgctxt "action"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
@ -634,7 +634,7 @@ msgstr "Nuevo gasto"
|
|||
#: web/template/expenses/new.gohtml:11 web/template/expenses/index.gohtml:3
|
||||
#: web/template/expenses/index.gohtml:11 web/template/expenses/edit.gohtml:11
|
||||
#: web/template/payments/new.gohtml:12 web/template/payments/index.gohtml:12
|
||||
#: web/template/payments/edit.gohtml:12
|
||||
#: web/template/payments/edit.gohtml:16
|
||||
msgctxt "title"
|
||||
msgid "Expenses"
|
||||
msgstr "Gastos"
|
||||
|
@ -781,7 +781,7 @@ msgid "New Payment"
|
|||
msgstr "Nuevo pago"
|
||||
|
||||
#: web/template/payments/new.gohtml:15 web/template/payments/index.gohtml:3
|
||||
#: web/template/payments/index.gohtml:15 web/template/payments/edit.gohtml:15
|
||||
#: web/template/payments/index.gohtml:15 web/template/payments/edit.gohtml:19
|
||||
msgctxt "title"
|
||||
msgid "Payments"
|
||||
msgstr "Pagos"
|
||||
|
@ -810,24 +810,29 @@ msgstr "Documento"
|
|||
msgid "Are you sure you wish to delete this payment?"
|
||||
msgstr "¿Estáis seguro de querer borrar este pago?"
|
||||
|
||||
#: web/template/payments/index.gohtml:73
|
||||
#: web/template/payments/index.gohtml:77
|
||||
msgid "Actions for payment %s"
|
||||
msgstr "Acciones para el pago %s"
|
||||
|
||||
#: web/template/payments/index.gohtml:92
|
||||
#: web/template/payments/index.gohtml:96
|
||||
msgctxt "action"
|
||||
msgid "Remove"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: web/template/payments/index.gohtml:102
|
||||
#: web/template/payments/index.gohtml:106
|
||||
msgid "No payments added yet."
|
||||
msgstr "No hay pagos."
|
||||
|
||||
#: web/template/payments/edit.gohtml:3
|
||||
#: web/template/payments/edit.gohtml:4
|
||||
msgctxt "title"
|
||||
msgid "Edit Payment “%s”"
|
||||
msgstr "Edición del pago «%s»"
|
||||
|
||||
#: web/template/payments/edit.gohtml:6
|
||||
msgctxt "title"
|
||||
msgid "Edit Collection “%s”"
|
||||
msgstr "Edición del cobro «%s»"
|
||||
|
||||
#: web/template/payments/accounts/new.gohtml:3
|
||||
#: web/template/payments/accounts/new.gohtml:12
|
||||
msgctxt "title"
|
||||
|
@ -901,7 +906,7 @@ msgid "Name"
|
|||
msgstr "Nombre"
|
||||
|
||||
#: pkg/products.go:177 pkg/products.go:303 pkg/tags.go:37 pkg/quote.go:174
|
||||
#: pkg/quote.go:708 pkg/payments.go:272 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/quote.go:708 pkg/payments.go:310 pkg/expenses.go:335 pkg/expenses.go:485
|
||||
#: pkg/invoices.go:177 pkg/invoices.go:877 pkg/contacts.go:154
|
||||
#: pkg/contacts.go:362
|
||||
msgctxt "input"
|
||||
|
@ -936,7 +941,7 @@ msgstr "Cualquiera"
|
|||
msgid "Invoices must have at least one of the specified labels."
|
||||
msgstr "Las facturas deben tener como mínimo una de las etiquetas."
|
||||
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:239
|
||||
#: pkg/products.go:282 pkg/quote.go:915 pkg/payments.go:277
|
||||
#: pkg/invoices.go:1161
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
|
@ -1351,60 +1356,78 @@ msgstr "La confirmación no corresponde con la contraseña."
|
|||
msgid "Selected language is not valid."
|
||||
msgstr "Habéis escogido un idioma que no es válido."
|
||||
|
||||
#: pkg/payments.go:202
|
||||
#: pkg/payments.go:230
|
||||
#, c-format
|
||||
msgid "Payment of %s"
|
||||
msgstr "Pago de %s"
|
||||
|
||||
#: pkg/payments.go:245
|
||||
#: pkg/payments.go:268 pkg/accounts.go:131
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: pkg/payments.go:271
|
||||
msgctxt "payment type"
|
||||
msgid "Payment"
|
||||
msgstr "Pago"
|
||||
|
||||
#: pkg/payments.go:272
|
||||
msgctxt "payment type"
|
||||
msgid "Collection"
|
||||
msgstr "Cobro"
|
||||
|
||||
#: pkg/payments.go:283
|
||||
msgctxt "input"
|
||||
msgid "Payment Date"
|
||||
msgstr "Fecha del pago"
|
||||
|
||||
#: pkg/payments.go:251
|
||||
#: pkg/payments.go:289
|
||||
msgctxt "input"
|
||||
msgid "Account"
|
||||
msgstr "Cuenta"
|
||||
|
||||
#: pkg/payments.go:257 pkg/expenses.go:319
|
||||
#: pkg/payments.go:295 pkg/expenses.go:319
|
||||
msgctxt "input"
|
||||
msgid "Amount"
|
||||
msgstr "Importe"
|
||||
|
||||
#: pkg/payments.go:267 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
#: pkg/payments.go:305 pkg/expenses.go:330 pkg/invoices.go:888
|
||||
msgctxt "input"
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
#: pkg/payments.go:286
|
||||
#: pkg/payments.go:325
|
||||
msgid "Select a type."
|
||||
msgstr "Escoged un tipo."
|
||||
|
||||
#: pkg/payments.go:326
|
||||
msgid "Select an account."
|
||||
msgstr "Escoged una cuenta."
|
||||
|
||||
#: pkg/payments.go:335
|
||||
#: pkg/payments.go:390
|
||||
msgid "Selected payment type is not valid."
|
||||
msgstr "Habéis escogido un tipo de pago que no es válido."
|
||||
|
||||
#: pkg/payments.go:391
|
||||
msgid "Description can not be empty."
|
||||
msgstr "No podéis dejar la descripción en blanco."
|
||||
|
||||
#: pkg/payments.go:336
|
||||
#: pkg/payments.go:392
|
||||
msgid "Selected payment account is not valid."
|
||||
msgstr "Habéis escogido una cuenta de pago que no es válida."
|
||||
|
||||
#: pkg/payments.go:337
|
||||
#: pkg/payments.go:393
|
||||
msgid "Payment date must be a valid date."
|
||||
msgstr "La fecha de pago debe ser válida."
|
||||
|
||||
#: pkg/payments.go:338 pkg/expenses.go:372
|
||||
#: pkg/payments.go:394 pkg/expenses.go:372
|
||||
msgid "Amount can not be empty."
|
||||
msgstr "No podéis dejar el importe en blanco."
|
||||
|
||||
#: pkg/payments.go:339 pkg/expenses.go:373
|
||||
#: pkg/payments.go:395 pkg/expenses.go:373
|
||||
msgid "Amount must be a number greater than zero."
|
||||
msgstr "El importe tiene que ser un número mayor a cero."
|
||||
|
||||
#: pkg/accounts.go:131
|
||||
msgctxt "input"
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: pkg/accounts.go:146 pkg/contacts.go:352
|
||||
msgctxt "input"
|
||||
msgid "IBAN"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:add_collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.add_collection(integer, integer, date, integer, text, text, numerus.tag_name[]);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:attach_to_collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.attach_to_collection(uuid, text, text, bytea);
|
||||
|
||||
commit;
|
|
@ -1,10 +1,14 @@
|
|||
-- Revert numerus:available_invoice_status from pg
|
||||
-- Deploy numerus:available_invoice_status to pg
|
||||
-- requires: schema_numerus
|
||||
-- requires: invoice_status
|
||||
-- requires: invoice_status_i18n
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus;
|
||||
|
||||
delete from invoice_status_i18n;
|
||||
delete from invoice_status;
|
||||
update invoice set invoice_status = 'created' where invoice_status = 'partial';
|
||||
delete from invoice_status_i18n where invoice_status = 'partial';
|
||||
delete from invoice_status where invoice_status = 'partial';
|
||||
|
||||
commit;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
-- Revert numerus:available_invoice_status from pg
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus;
|
||||
|
||||
delete from invoice_status_i18n;
|
||||
delete from invoice_status;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.collection;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:collection_attachment from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.collection_attachment;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:edit_collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.edit_collection(uuid, date, integer, text, text, numerus.tag_name[]);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:invoice_collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.invoice_collection;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:remove_collection from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.remove_collection(uuid);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:update_invoice_collection_status from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.update_invoice_collection_status(integer, integer, integer);
|
||||
|
||||
commit;
|
|
@ -157,3 +157,12 @@ attach_to_payment [roles schema_numerus payment payment_attachment] 2024-08-11T2
|
|||
remove_payment [roles schema_numerus expense_payment payment payment_attachment update_expense_payment_status] 2024-08-11T00:30:58Z jordi fita mas <jordi@tandem.blog> # Add function to remove payments
|
||||
add_expense [add_expense@v2] 2024-08-12T23:48:07Z jordi fita mas <jordi@tandem.blog> # Remove the status parameter from add_expense
|
||||
edit_expense [edit_expense@v2] 2024-08-12T23:53:48Z jordi fita mas <jordi@tandem.blog> # Remove the expense_status from edit_expense
|
||||
collection [roles schema_numerus company payment_account currency tag_name payment_status extension_pgcrypto] 2024-08-18T02:54:28Z jordi fita mas <jordi@tandem.blog> # Add relation of cash (payment) collection
|
||||
invoice_collection [roles schema_numerus invoice collection] 2024-08-18T03:22:09Z jordi fita mas <jordi@tandem.blog> # Add relation of invoice collections
|
||||
available_invoice_status [available_invoice_status@v2] 2024-08-18T03:34:13Z jordi fita mas <jordi@tandem.blog> # Add “partial” invoice status
|
||||
update_invoice_collection_status [roles schema_numerus invoice collection invoice_collection invoice_amount available_invoice_status available_payment_status] 2024-08-19T01:20:56Z jordi fita mas <jordi@tandem.blog> # Add function to update invoice and collection status
|
||||
add_collection [roles schema_numerus collection invoice_collection company currency parse_price tag_name update_invoice_collection_status] 2024-08-19T01:26:01Z jordi fita mas <jordi@tandem.blog> # Add function to insert new collections
|
||||
edit_collection [roles schema_numerus collection invoice_collection currency parse_price tag_name update_invoice_collection_status] 2024-08-19T23:58:36Z jordi fita mas <jordi@tandem.blog> # Add function to update collections
|
||||
collection_attachment [roles schema_numerus collection] 2024-08-20T00:34:09Z jordi fita mas <jordi@tandem.blog> # Add relation of collection attachments
|
||||
attach_to_collection [roles schema_numerus collection collection_attachment] 2024-08-20T00:41:53Z jordi fita mas <jordi@tandem.blog> # Add function to attach files to collections
|
||||
remove_collection [roles schema_numerus invoice_collection collection collection_attachment update_invoice_collection_status] 2024-08-20T00:47:27Z jordi fita mas <jordi@tandem.blog> # Add function to remove collections
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
-- Test add_collection
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(20);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
select has_function('numerus', 'add_collection', array['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]']);
|
||||
select function_lang_is('numerus', 'add_collection', array['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'plpgsql');
|
||||
select function_returns('numerus', 'add_collection', array['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'uuid');
|
||||
select isnt_definer('numerus', 'add_collection', array['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]']);
|
||||
select volatility_is('numerus', 'add_collection', array['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'volatile');
|
||||
|
||||
select function_privs_are('numerus', 'add_collection', array ['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'add_collection', array ['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'add_collection', array ['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'add_collection', array ['integer', 'integer', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate invoice_collection cascade;
|
||||
truncate collection cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate invoice_product_tax cascade;
|
||||
truncate invoice_product cascade;
|
||||
truncate invoice cascade;
|
||||
truncate contact_tax_details cascade;
|
||||
truncate contact cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||
|
||||
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_payment_method_id)
|
||||
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||
, (2, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 222)
|
||||
;
|
||||
|
||||
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||
values (111, 1, 'cash', 'cash')
|
||||
, (222, 2, 'cash', 'cash')
|
||||
;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, tax_class_id, name, rate)
|
||||
values (2, 1, 11, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 11, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 11, 'IVA 10 %', 0.10)
|
||||
;
|
||||
|
||||
insert into contact (contact_id, company_id, name)
|
||||
values ( 9, 1, 'Customer 1')
|
||||
, (10, 2, 'Customer 2')
|
||||
;
|
||||
|
||||
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||
values (9, 'Customer 1', 'XX555', '', '', '', '', 'ES')
|
||||
, (10, 'Customer 2', 'XX666', '', '', '', '', 'ES')
|
||||
;
|
||||
|
||||
insert into invoice (invoice_id, company_id, invoice_number, contact_id, invoice_date, payment_method_id, currency_code)
|
||||
values (12, 1, 'REF123', 9, '2011-01-11', 111, 'EUR')
|
||||
, (13, 2, 'INV001', 10, '2011-01-11', 111, 'USD')
|
||||
, (14, 2, 'INV002', 10, '2022-02-22', 222, 'USD')
|
||||
, (15, 2, 'INV003', 10, '2022-02-22', 222, 'USD')
|
||||
, (16, 1, 'REF001', 9, '2023-03-03', 111, 'EUR')
|
||||
, (17, 1, 'REF002', 9, '2023-03-03', 111, 'EUR')
|
||||
, (18, 1, 'REF003', 9, '2023-03-03', 111, 'EUR')
|
||||
;
|
||||
|
||||
insert into invoice_product (invoice_product_id, invoice_id, name, price)
|
||||
values (19, 12, 'P1', 100)
|
||||
, (20, 12, 'P2', 11)
|
||||
, (21, 13, 'P1', 50)
|
||||
, (22, 13, 'P2', 61)
|
||||
, (23, 14, 'P1', 100)
|
||||
, (24, 14, 'P2', 100)
|
||||
, (25, 14, 'P3', 11)
|
||||
, (26, 14, 'P4', 11)
|
||||
, (27, 15, 'P1', 222)
|
||||
, (28, 16, 'P*', 10000)
|
||||
, (29, 17, 'P*', 10000)
|
||||
, (30, 18, 'P*', 10000)
|
||||
;
|
||||
|
||||
insert into invoice_product_tax (invoice_product_id, tax_id, tax_rate)
|
||||
values (28, 3, 0.04)
|
||||
, (29, 2, -0.15)
|
||||
, (30, 2, -0.15)
|
||||
, (30, 4, 0.10)
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (11, 1, 'other', 'Other 1')
|
||||
, (22, 2, 'cash', 'Cash 2')
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(1, null, '2023-05-02', 11, '“Protection”', '11.11', array['tag1', 'tag2']) $$,
|
||||
'Should be able to insert a collection, unrelated to any invoice, for the first company'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(2, 13, '2023-05-03', 22, 'Collection of INV001', '1.11', array[]::tag_name[]) $$,
|
||||
'Should be able to insert a complete collection for the first invoice'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(2, 14, '2023-05-04', 22, 'First collection of INV002', '1.00', array[]::tag_name[]) $$,
|
||||
'Should be able to insert a partial collection for the second invoice'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(2, 14, '2023-05-05', 22, 'Second collection of INV002', '1.22', array[]::tag_name[]) $$,
|
||||
'Should be able to insert a partial, and final, collection for the second invoice'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(2, 15, '2023-05-06', 22, 'Partial collection of INV003', '1.11', array[]::tag_name[]) $$,
|
||||
'Should be able to insert a partial collection for the third invoice'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(1, 16, '2023-03-06', 11, 'Re: REF001', '103.99', array[]::tag_name[]) $$,
|
||||
'Should be able to collect an invoice with taxes'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(1, 17, '2023-03-06', 11, 'Re: REF002', '85', array[]::tag_name[]) $$,
|
||||
'Should be able to collect an invoice with negative taxes'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_collection(1, 18, '2023-03-06', 11, 'Re: REF003', '95', array[]::tag_name[]) $$,
|
||||
'Should be able to collect an invoice with multiple taxes'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select company_id, description, collection_date::text, payment_account_id, amount, currency_code, payment_status, tags::text, created_at from collection $$,
|
||||
$$ values (1, '“Protection”', '2023-05-02', 11, 1111, 'EUR', 'complete', '{tag1,tag2}', current_timestamp)
|
||||
, (2, 'Collection of INV001', '2023-05-03', 22, 111, 'USD', 'complete', '{}', current_timestamp)
|
||||
, (2, 'First collection of INV002', '2023-05-04', 22, 100, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (2, 'Second collection of INV002', '2023-05-05', 22, 122, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (2, 'Partial collection of INV003', '2023-05-06', 22, 111, 'USD', 'partial', '{}', current_timestamp)
|
||||
, (1, 'Re: REF001', '2023-03-06', 11, 10399, 'EUR', 'partial', '{}', current_timestamp)
|
||||
, (1, 'Re: REF002', '2023-03-06', 11, 8500, 'EUR', 'complete', '{}', current_timestamp)
|
||||
, (1, 'Re: REF003', '2023-03-06', 11, 9500, 'EUR', 'complete', '{}', current_timestamp)
|
||||
$$,
|
||||
'Should have created all collections'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_id, description from invoice_collection join collection using (collection_id) $$,
|
||||
$$ values (13, 'Collection of INV001')
|
||||
, (14, 'First collection of INV002')
|
||||
, (14, 'Second collection of INV002')
|
||||
, (15, 'Partial collection of INV003')
|
||||
, (16, 'Re: REF001')
|
||||
, (17, 'Re: REF002')
|
||||
, (18, 'Re: REF003')
|
||||
$$,
|
||||
'Should have linked all invoices to collections'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_id, invoice_status from invoice $$,
|
||||
$$ values (12, 'created')
|
||||
, (13, 'paid')
|
||||
, (14, 'paid')
|
||||
, (15, 'partial')
|
||||
, (16, 'partial')
|
||||
, (17, 'paid')
|
||||
, (18, 'paid')
|
||||
$$,
|
||||
'Should have updated the status of invoices'
|
||||
);
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,80 @@
|
|||
-- Test attach_to_collection
|
||||
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_function('numerus', 'attach_to_collection', array['uuid', 'text', 'text', 'bytea']);
|
||||
select function_lang_is('numerus', 'attach_to_collection', array['uuid', 'text', 'text', 'bytea'], 'sql');
|
||||
select function_returns('numerus', 'attach_to_collection', array['uuid', 'text', 'text', 'bytea'], 'void');
|
||||
select isnt_definer('numerus', 'attach_to_collection', array['uuid', 'text', 'text', 'bytea']);
|
||||
select volatility_is('numerus', 'attach_to_collection', array['uuid', 'text', 'text', 'bytea'], 'volatile');
|
||||
select function_privs_are('numerus', 'attach_to_collection', array ['uuid', 'text', 'text', 'bytea'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'attach_to_collection', array ['uuid', 'text', 'text', 'bytea'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'attach_to_collection', array ['uuid', 'text', 'text', 'bytea'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'attach_to_collection', array ['uuid', 'text', 'text', 'bytea'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate collection_attachment cascade;
|
||||
truncate collection cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||
|
||||
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_payment_method_id)
|
||||
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||
;
|
||||
|
||||
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||
values (111, 1, 'cash', 'cash')
|
||||
;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (11, 1, 'cash', 'Cash 1')
|
||||
, (12, 1, 'cash', 'Cash 2')
|
||||
, (13, 1, 'other', 'Other')
|
||||
;
|
||||
|
||||
insert into collection (collection_id, company_id, slug, description, collection_date, payment_account_id, amount, currency_code, payment_status)
|
||||
values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Collection 1', '2023-05-04', 12, 111, 'EUR', 'complete')
|
||||
, (17, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'Collection 2', '2023-05-05', 13, 100, 'EUR', 'partial')
|
||||
;
|
||||
|
||||
insert into collection_attachment (collection_id, original_filename, mime_type, content)
|
||||
values (17, 'something.txt', 'text/plain', convert_to('Once upon a time…', 'UTF-8'))
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select attach_to_collection('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'collection.txt', 'text/plain', convert_to('To receive 42 €', 'UTF-8')) $$,
|
||||
'Should be able to attach a document to the first collection'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select attach_to_collection('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'collection.html', 'text/html', convert_to('<html><p>To receive 42 €</p></html>', 'UTF-8')) $$,
|
||||
'Should be able to replate the second collection’s attachment with a new document'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select collection_id, original_filename, mime_type, convert_from(content, 'UTF-8') from collection_attachment $$,
|
||||
$$ values (16, 'collection.txt', 'text/plain', 'To receive 42 €')
|
||||
, (17, 'collection.html', 'text/html', '<html><p>To receive 42 €</p></html>')
|
||||
$$,
|
||||
'Should have attached all documents'
|
||||
);
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,187 @@
|
|||
-- Test collection
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(71);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
select has_table('collection');
|
||||
select has_pk('collection');
|
||||
select table_privs_are('collection', 'guest', array []::text[]);
|
||||
select table_privs_are('collection', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('collection', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('collection', 'authenticator', array []::text[]);
|
||||
|
||||
select has_column('collection', 'collection_id');
|
||||
select col_is_pk('collection', 'collection_id');
|
||||
select col_type_is('collection', 'collection_id', 'integer');
|
||||
select col_not_null('collection', 'collection_id');
|
||||
select col_hasnt_default('collection', 'collection_id');
|
||||
|
||||
select has_column('collection', 'company_id');
|
||||
select col_is_fk('collection', 'company_id');
|
||||
select fk_ok('collection', 'company_id', 'company', 'company_id');
|
||||
select col_type_is('collection', 'company_id', 'integer');
|
||||
select col_not_null('collection', 'company_id');
|
||||
select col_hasnt_default('collection', 'company_id');
|
||||
|
||||
select has_column('collection', 'slug');
|
||||
select col_is_unique('collection', 'slug');
|
||||
select col_type_is('collection', 'slug', 'uuid');
|
||||
select col_not_null('collection', 'slug');
|
||||
select col_has_default('collection', 'slug');
|
||||
select col_default_is('collection', 'slug', 'gen_random_uuid()');
|
||||
|
||||
select has_column('collection', 'description');
|
||||
select col_type_is('collection', 'description', 'text');
|
||||
select col_not_null('collection', 'description');
|
||||
select col_hasnt_default('collection', 'description');
|
||||
|
||||
select has_column('collection', 'collection_date');
|
||||
select col_type_is('collection', 'collection_date', 'date');
|
||||
select col_not_null('collection', 'collection_date');
|
||||
select col_has_default('collection', 'collection_date');
|
||||
select col_default_is('collection', 'collection_date', 'CURRENT_DATE');
|
||||
|
||||
select has_column('collection', 'payment_account_id');
|
||||
select col_is_fk('collection', 'payment_account_id');
|
||||
select fk_ok('collection', 'payment_account_id', 'payment_account', 'payment_account_id');
|
||||
select col_type_is('collection', 'payment_account_id', 'integer');
|
||||
select col_not_null('collection', 'payment_account_id');
|
||||
select col_hasnt_default('collection', 'payment_account_id');
|
||||
|
||||
select has_column('collection', 'amount');
|
||||
select col_type_is('collection', 'amount', 'integer');
|
||||
select col_not_null('collection', 'amount');
|
||||
select col_hasnt_default('collection', 'amount');
|
||||
|
||||
select has_column('collection', 'currency_code');
|
||||
select col_is_fk('collection', 'currency_code');
|
||||
select fk_ok('collection', 'currency_code', 'currency', 'currency_code');
|
||||
select col_type_is('collection', 'currency_code', 'text');
|
||||
select col_not_null('collection', 'currency_code');
|
||||
select col_hasnt_default('collection', 'currency_code');
|
||||
|
||||
select has_column('collection', 'tags');
|
||||
select col_type_is('collection', 'tags', 'tag_name[]');
|
||||
select col_not_null('collection', 'tags');
|
||||
select col_has_default('collection', 'tags');
|
||||
select col_default_is('collection', 'tags', '{}');
|
||||
|
||||
select has_column('collection', 'payment_status');
|
||||
select col_is_fk('collection', 'payment_status');
|
||||
select fk_ok('collection', 'payment_status', 'payment_status', 'payment_status');
|
||||
select col_type_is('collection', 'payment_status', 'text');
|
||||
select col_not_null('collection', 'payment_status');
|
||||
select col_has_default('collection', 'payment_status');
|
||||
select col_default_is('collection', 'payment_status', 'complete');
|
||||
|
||||
select has_column('collection', 'created_at');
|
||||
select col_type_is('collection', 'created_at', 'timestamp with time zone');
|
||||
select col_not_null('collection', 'created_at');
|
||||
select col_has_default('collection', 'created_at');
|
||||
select col_default_is('collection', 'created_at', 'CURRENT_TIMESTAMP');
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate collection cascade;
|
||||
truncate payment_account 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 payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (221, 2, 'other', 'Other 2')
|
||||
, (441, 4, 'other', 'Other 4')
|
||||
;
|
||||
|
||||
insert into collection (company_id, description, payment_account_id, amount, currency_code)
|
||||
values (2, 'Collection 20001', 221, 333, 'EUR')
|
||||
, (4, 'Collection 40001', 441, 555, 'EUR')
|
||||
;
|
||||
|
||||
|
||||
prepare collection_data as
|
||||
select company_id, description
|
||||
from collection
|
||||
order by company_id, description;
|
||||
|
||||
set role invoicer;
|
||||
select is_empty('collection_data', 'Should show no data when cookie is not set yet');
|
||||
reset role;
|
||||
|
||||
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||
select bag_eq(
|
||||
'collection_data',
|
||||
$$ values (2, 'Collection 20001')
|
||||
$$,
|
||||
'Should only list collections from the companies where demo@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||
select bag_eq(
|
||||
'collection_data',
|
||||
$$ values (4, 'Collection 40001')
|
||||
$$,
|
||||
'Should only list collections from the companies where admin@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('not-a-cookie');
|
||||
select throws_ok(
|
||||
'collection_data',
|
||||
'42501', 'permission denied for table collection',
|
||||
'Should not allow select to guest users'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select throws_ok(
|
||||
$$ insert into collection (company_id, description, payment_account_id, amount, currency_code) values (2, 'Nope', 221, 0, 'EUR') $$,
|
||||
'23514', 'new row for relation "collection" violates check constraint "collection_amount_positive"',
|
||||
'Should not allow empty collections'
|
||||
);
|
||||
|
||||
select throws_ok(
|
||||
$$ insert into collection (company_id, description, payment_account_id, amount, currency_code) values (2, 'Nope', 221, -1, 'EUR') $$,
|
||||
'23514', 'new row for relation "collection" violates check constraint "collection_amount_positive"',
|
||||
'Should not allow negative collections'
|
||||
);
|
||||
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
-- Test collection_attachment
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(29);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
select has_table('collection_attachment');
|
||||
select has_pk('collection_attachment');
|
||||
select table_privs_are('collection_attachment', 'guest', array []::text[]);
|
||||
select table_privs_are('collection_attachment', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('collection_attachment', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('collection_attachment', 'authenticator', array []::text[]);
|
||||
|
||||
select has_column('collection_attachment', 'collection_id');
|
||||
select col_is_pk('collection_attachment', 'collection_id');
|
||||
select col_is_fk('collection_attachment', 'collection_id');
|
||||
select fk_ok('collection_attachment', 'collection_id', 'collection', 'collection_id');
|
||||
select col_type_is('collection_attachment', 'collection_id', 'integer');
|
||||
select col_not_null('collection_attachment', 'collection_id');
|
||||
select col_hasnt_default('collection_attachment', 'collection_id');
|
||||
|
||||
select has_column('collection_attachment', 'original_filename');
|
||||
select col_type_is('collection_attachment', 'original_filename', 'text');
|
||||
select col_not_null('collection_attachment', 'original_filename');
|
||||
select col_hasnt_default('collection_attachment', 'original_filename');
|
||||
|
||||
select has_column('collection_attachment', 'mime_type');
|
||||
select col_type_is('collection_attachment', 'mime_type', 'text');
|
||||
select col_not_null('collection_attachment', 'mime_type');
|
||||
select col_hasnt_default('collection_attachment', 'mime_type');
|
||||
|
||||
select has_column('collection_attachment', 'content');
|
||||
select col_type_is('collection_attachment', 'content', 'bytea');
|
||||
select col_not_null('collection_attachment', 'content');
|
||||
select col_hasnt_default('collection_attachment', 'content');
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate collection_attachment cascade;
|
||||
truncate collection cascade;
|
||||
truncate company_user cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company 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 payment_account(payment_account_id, company_id, payment_account_type, name)
|
||||
values (8, 2, 'other', 'Other 2')
|
||||
, (9, 4, 'other', 'Other 4')
|
||||
;
|
||||
|
||||
insert into collection (collection_id, company_id, description, payment_account_id, collection_date, amount, currency_code)
|
||||
values (13, 2, 'Payment 2', 8, '2011-01-11', 111, 'EUR')
|
||||
, (14, 4, 'Payment 4', 9, '2022-02-22', 222, 'EUR')
|
||||
;
|
||||
|
||||
insert into collection_attachment (collection_id, original_filename, mime_type, content)
|
||||
values (13, 'collection.txt', 'text/plain', convert_to('Collection 42', 'UTF8'))
|
||||
, (14, 'collection.html', 'text/html', convert_to('<html>Collection <em>42</em></html>', 'UTF8'))
|
||||
;
|
||||
|
||||
prepare collection_attachment_data as
|
||||
select collection_id, original_filename
|
||||
from collection_attachment
|
||||
order by collection_id, original_filename;
|
||||
|
||||
set role invoicer;
|
||||
select is_empty('collection_attachment_data', 'Should show no data when cookie is not set yet');
|
||||
reset role;
|
||||
|
||||
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||
select bag_eq(
|
||||
'collection_attachment_data',
|
||||
$$ values (13, 'collection.txt')
|
||||
$$,
|
||||
'Should only list collection attachmements of the companies where demo@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||
select bag_eq(
|
||||
'collection_attachment_data',
|
||||
$$ values (14, 'collection.html')
|
||||
$$,
|
||||
'Should only list collection attachmements of the companies where admin@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('not-a-cookie');
|
||||
select throws_ok(
|
||||
'collection_attachment_data',
|
||||
'42501', 'permission denied for table collection_attachment',
|
||||
'Should not allow select to guest users'
|
||||
);
|
||||
reset role;
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
-- Test edit_collection
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(17);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
select has_function('numerus', 'edit_collection', array['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]']);
|
||||
select function_lang_is('numerus', 'edit_collection', array['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'plpgsql');
|
||||
select function_returns('numerus', 'edit_collection', array['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'uuid');
|
||||
select isnt_definer('numerus', 'edit_collection', array['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]']);
|
||||
select volatility_is('numerus', 'edit_collection', array['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'volatile');
|
||||
|
||||
select function_privs_are('numerus', 'edit_collection', array ['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'edit_collection', array ['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'edit_collection', array ['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'edit_collection', array ['uuid', 'date', 'integer', 'text', 'text', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate invoice_collection cascade;
|
||||
truncate collection cascade;
|
||||
truncate invoice_product_tax cascade;
|
||||
truncate invoice_product cascade;
|
||||
truncate invoice cascade;
|
||||
truncate contact_tax_details cascade;
|
||||
truncate contact cascade;
|
||||
truncate tax cascade;
|
||||
truncate tax_class cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||
|
||||
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_payment_method_id)
|
||||
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||
;
|
||||
|
||||
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||
values (111, 1, 'cash', 'cash')
|
||||
;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into tax_class (tax_class_id, company_id, name)
|
||||
values (11, 1, 'tax')
|
||||
;
|
||||
|
||||
insert into tax (tax_id, company_id, tax_class_id, name, rate)
|
||||
values (2, 1, 11, 'IRPF -15 %', -0.15)
|
||||
, (3, 1, 11, 'IVA 4 %', 0.04)
|
||||
, (4, 1, 11, 'IVA 10 %', 0.10)
|
||||
;
|
||||
|
||||
insert into contact (contact_id, company_id, name)
|
||||
values ( 9, 1, 'Customer 1')
|
||||
;
|
||||
|
||||
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||
values (9, 'Customer 1', 'XX555', '', '', '', '', 'ES')
|
||||
;
|
||||
|
||||
|
||||
insert into invoice (invoice_id, company_id, invoice_number, contact_id, invoice_date, payment_method_id, currency_code, invoice_status)
|
||||
values (13, 1, 'INV001', 9, '2011-01-11', 111, 'EUR', 'paid')
|
||||
, (14, 1, 'INV002', 9, '2022-02-22', 111, 'EUR', 'paid')
|
||||
, (15, 1, 'INV003', 9, '2022-02-22', 111, 'EUR', 'partial')
|
||||
, (16, 1, 'REF001', 9, '2023-03-03', 111, 'EUR', 'paid')
|
||||
, (17, 1, 'REF002', 9, '2023-03-03', 111, 'EUR', 'paid')
|
||||
, (18, 1, 'REF003', 9, '2023-03-03', 111, 'EUR', 'paid')
|
||||
;
|
||||
|
||||
insert into invoice_product (invoice_product_id, invoice_id, name, price)
|
||||
values (19, 13, 'P1', 111)
|
||||
, (20, 14, 'P1', 111)
|
||||
, (21, 14, 'P2', 111)
|
||||
, (22, 15, 'P2', 111)
|
||||
, (23, 15, 'P2', 111)
|
||||
, (24, 15, 'P2', 111)
|
||||
, (25, 16, 'P1', 10000)
|
||||
, (26, 17, 'P1', 10000)
|
||||
, (27, 18, 'P1', 10000)
|
||||
;
|
||||
|
||||
insert into invoice_product_tax (invoice_product_id, tax_id, tax_rate)
|
||||
values (25, 3, 0.04)
|
||||
, (26, 2, -0.15)
|
||||
, (27, 2, -0.15)
|
||||
, (27, 4, 0.10)
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (11, 1, 'cash', 'Cash 1')
|
||||
, (12, 1, 'cash', 'Cash 2')
|
||||
, (13, 1, 'other', 'Other')
|
||||
;
|
||||
|
||||
insert into collection (collection_id, company_id, slug, description, collection_date, payment_account_id, amount, currency_code, payment_status, tags)
|
||||
values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Payment INV001', '2023-05-04', 12, 111, 'EUR', 'complete', '{tag1}')
|
||||
, (17, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'First INV002', '2023-05-05', 13, 100, 'EUR', 'partial', '{tag2}')
|
||||
, (18, 1, '3bdad7a8-4a1e-4ae0-b5c6-015e51ee0502', 'Second INV002', '2023-05-06', 13, 122, 'EUR', 'partial', '{tag1,tag3}')
|
||||
, (19, 1, '5a524bee-8311-4d13-9adf-ef6310b26990', 'Partial INV003', '2023-05-07', 11, 123, 'EUR', 'partial', '{}')
|
||||
, (20, 1, '65222c3b-4faa-4be4-b39c-5bd170a943cf', 'Re: REF001', '2023-03-07', 11, 10400, 'EUR', 'complete', '{}')
|
||||
, (21, 1, 'dbb699cf-d1f4-40ff-96cb-8f29e238d51d', 'Re: REF002', '2023-03-07', 11, 8500, 'EUR', 'complete', '{}')
|
||||
, (22, 1, '0756a50f-2957-4661-abd2-e422a848af4e', 'Re: REF003', '2023-03-07', 11, 9500, 'EUR', 'complete', '{}')
|
||||
;
|
||||
|
||||
insert into invoice_collection (invoice_id, collection_id)
|
||||
values (13, 16)
|
||||
, (14, 17)
|
||||
, (14, 18)
|
||||
, (15, 19)
|
||||
, (16, 20)
|
||||
, (17, 21)
|
||||
, (18, 22)
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', '2023-05-06', 13, 'Partial INV001', '1.00', array['tag1']) $$,
|
||||
'Should be able to change a complete collection to partial'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('b57b980b-247b-4be4-a0b7-03a7819c53ae', '2023-05-07', 12, 'First INV002', '0.50', array['tag1', 'tag3']) $$,
|
||||
'Should be able to adjust a partial collection, that is still partial, and the invoice now becomes partial'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('5a524bee-8311-4d13-9adf-ef6310b26990', '2023-05-01', 11, 'Complete INV003', '3.33', array[]::tag_name[]) $$,
|
||||
'Should be able to complete a previously partial collection'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('65222c3b-4faa-4be4-b39c-5bd170a943cf', '2023-03-10', 11, 'Re: REF001', '103.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a collection with tax.'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('dbb699cf-d1f4-40ff-96cb-8f29e238d51d', '2023-03-10', 11, 'Re: REF002', '84.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a collection with negative tax.'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_collection('0756a50f-2957-4661-abd2-e422a848af4e', '2023-03-10', 11, 'Re: REF003', '94.99', array[]::tag_name[]) $$,
|
||||
'Should be able to make partial a collection with multiple taxe.'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select description, collection_date::text, payment_account_id, amount, payment_status, tags::text from collection $$,
|
||||
$$ values ('Partial INV001', '2023-05-06', 13, 100, 'partial', '{tag1}')
|
||||
, ('First INV002', '2023-05-07', 12, 50, 'partial', '{tag1,tag3}')
|
||||
, ('Second INV002', '2023-05-06', 13, 122, 'partial', '{tag1,tag3}')
|
||||
, ('Complete INV003', '2023-05-01', 11, 333, 'complete', '{}')
|
||||
, ('Re: REF001', '2023-03-10', 11, 10399, 'partial', '{}')
|
||||
, ('Re: REF002', '2023-03-10', 11, 8499, 'partial', '{}')
|
||||
, ('Re: REF003', '2023-03-10', 11, 9499, 'partial', '{}')
|
||||
$$,
|
||||
'Should have updated all collections'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_id, invoice_status from invoice $$,
|
||||
$$ values (13, 'partial')
|
||||
, (14, 'partial')
|
||||
, (15, 'paid')
|
||||
, (16, 'partial')
|
||||
, (17, 'partial')
|
||||
, (18, 'partial')
|
||||
$$,
|
||||
'Should have updated invoices too'
|
||||
);
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,142 @@
|
|||
-- Test invoice_collection
|
||||
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_collection');
|
||||
select has_pk('invoice_collection');
|
||||
select col_is_pk('invoice_collection', array['invoice_id', 'collection_id']);
|
||||
select table_privs_are('invoice_collection', 'guest', array []::text[]);
|
||||
select table_privs_are('invoice_collection', 'invoicer', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('invoice_collection', 'admin', array ['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('invoice_collection', 'authenticator', array []::text[]);
|
||||
|
||||
select has_column('invoice_collection', 'invoice_id');
|
||||
select col_is_fk('invoice_collection', 'invoice_id');
|
||||
select fk_ok('invoice_collection', 'invoice_id', 'invoice', 'invoice_id');
|
||||
select col_type_is('invoice_collection', 'invoice_id', 'integer');
|
||||
select col_not_null('invoice_collection', 'invoice_id');
|
||||
select col_hasnt_default('invoice_collection', 'invoice_id');
|
||||
|
||||
select has_column('invoice_collection', 'collection_id');
|
||||
select col_is_fk('invoice_collection', 'collection_id');
|
||||
select fk_ok('invoice_collection', 'collection_id', 'collection', 'collection_id');
|
||||
select col_type_is('invoice_collection', 'collection_id', 'integer');
|
||||
select col_not_null('invoice_collection', 'collection_id');
|
||||
select col_hasnt_default('invoice_collection', 'collection_id');
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate invoice_collection cascade;
|
||||
truncate collection cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate invoice cascade;
|
||||
truncate contact_tax_details cascade;
|
||||
truncate contact cascade;
|
||||
truncate company_user cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
truncate auth."user" cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
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, name)
|
||||
values ( 9, 2, 'Customer 1')
|
||||
, (10, 4, 'Customer 2')
|
||||
;
|
||||
|
||||
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||
values (9, 'Customer 1', 'XX555', '', '', '', '', 'ES')
|
||||
, (10, 'Customer 2', 'XX666', '', '', '', '', 'ES')
|
||||
;
|
||||
|
||||
insert into invoice (invoice_id, company_id, invoice_number, contact_id, invoice_date, payment_method_id, currency_code)
|
||||
values (13, 2, 'INV001', 9, '2011-01-11', 222, 'EUR')
|
||||
, (14, 4, 'INV002', 10, '2022-02-22', 444, 'EUR')
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (17, 2, 'cash', 'Cash 2')
|
||||
, (18, 4, 'cash', 'Cash 4')
|
||||
;
|
||||
|
||||
insert into collection (collection_id, company_id, description, collection_date, payment_account_id, amount, currency_code)
|
||||
values (21, 2, 'Collection INV001', '2022-01-11', 17, 111, 'EUR')
|
||||
, (22, 4, 'Collection INV002', '2022-02-23', 18, 222, 'EUR')
|
||||
;
|
||||
|
||||
insert into invoice_collection (invoice_id, collection_id)
|
||||
values (13, 21)
|
||||
, (14, 22)
|
||||
;
|
||||
|
||||
prepare invoice_collection_data as
|
||||
select invoice_id, collection_id
|
||||
from invoice_collection
|
||||
order by invoice_id, collection_id;
|
||||
|
||||
set role invoicer;
|
||||
select is_empty('invoice_collection_data', 'Should show no data when cookie is not set yet');
|
||||
reset role;
|
||||
|
||||
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
|
||||
select bag_eq(
|
||||
'invoice_collection_data',
|
||||
$$ values (13, 21)
|
||||
$$,
|
||||
'Should only list tax of products of the companies where demo@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
|
||||
select bag_eq(
|
||||
'invoice_collection_data',
|
||||
$$ values (14, 22)
|
||||
$$,
|
||||
'Should only list tax of products of the companies where admin@tandem.blog is user of'
|
||||
);
|
||||
reset role;
|
||||
|
||||
select set_cookie('not-a-cookie');
|
||||
select throws_ok(
|
||||
'invoice_collection_data',
|
||||
'42501', 'permission denied for table invoice_collection',
|
||||
'Should not allow select to guest users'
|
||||
);
|
||||
reset role;
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
-- Test remove_collection
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(16);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
select has_function('numerus', 'remove_collection', array['uuid']);
|
||||
select function_lang_is('numerus', 'remove_collection', array['uuid'], 'plpgsql');
|
||||
select function_returns('numerus', 'remove_collection', array['uuid'], 'void');
|
||||
select isnt_definer('numerus', 'remove_collection', array['uuid']);
|
||||
select volatility_is('numerus', 'remove_collection', array['uuid'], 'volatile');
|
||||
|
||||
select function_privs_are('numerus', 'remove_collection', array ['uuid'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'remove_collection', array ['uuid'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'remove_collection', array ['uuid'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'remove_collection', array ['uuid'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate invoice_collection;
|
||||
truncate collection_attachment;
|
||||
truncate collection cascade;
|
||||
truncate invoice_product cascade;
|
||||
truncate invoice cascade;
|
||||
truncate contact_tax_details cascade;
|
||||
truncate contact cascade;
|
||||
truncate payment_account cascade;
|
||||
truncate payment_method cascade;
|
||||
truncate company cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" deferred;
|
||||
|
||||
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_payment_method_id)
|
||||
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 111)
|
||||
;
|
||||
|
||||
insert into payment_method (payment_method_id, company_id, name, instructions)
|
||||
values (111, 1, 'cash', 'cash')
|
||||
;
|
||||
|
||||
set constraints "company_default_payment_method_id_fkey" immediate;
|
||||
|
||||
insert into contact (contact_id, company_id, name)
|
||||
values ( 9, 1, 'Customer 1')
|
||||
;
|
||||
|
||||
insert into contact_tax_details (contact_id, business_name, vatin, address, city, province, postal_code, country_code)
|
||||
values (9, 'Customer 1', 'XX555', '', '', '', '', 'ES')
|
||||
;
|
||||
|
||||
insert into invoice (invoice_id, company_id, invoice_number, contact_id, invoice_date, payment_method_id, currency_code, invoice_status)
|
||||
values (13, 1, 'INV001', 9, '2011-01-11', 111, 'EUR', 'paid')
|
||||
, (14, 1, 'INV002', 9, '2022-02-22', 111, 'EUR', 'paid')
|
||||
, (15, 1, 'INV003', 9, '2022-02-22', 111, 'EUR', 'partial')
|
||||
;
|
||||
|
||||
insert into invoice_product (invoice_product_id, invoice_id, name, price)
|
||||
values (16, 13, 'P1', 111)
|
||||
, (17, 14, 'P1', 111)
|
||||
, (18, 14, 'P2', 111)
|
||||
, (19, 15, 'P1', 111)
|
||||
, (20, 15, 'P2', 111)
|
||||
, (21, 15, 'P3', 111)
|
||||
;
|
||||
|
||||
insert into payment_account (payment_account_id, company_id, payment_account_type, name)
|
||||
values (11, 1, 'cash', 'Cash 1')
|
||||
, (12, 1, 'cash', 'Cash 2')
|
||||
, (13, 1, 'other', 'Other')
|
||||
;
|
||||
|
||||
insert into collection (collection_id, company_id, slug, description, collection_date, payment_account_id, amount, currency_code, payment_status, tags)
|
||||
values (16, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'Collection INV001', '2023-05-04', 12, 111, 'EUR', 'complete', '{tag1}')
|
||||
, (17, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'First INV002', '2023-05-05', 13, 100, 'EUR', 'partial', '{tag2}')
|
||||
, (18, 1, '3bdad7a8-4a1e-4ae0-b5c6-015e51ee0502', 'Second INV002', '2023-05-06', 13, 122, 'EUR', 'partial', '{tag1,tag3}')
|
||||
, (19, 1, '5a524bee-8311-4d13-9adf-ef6310b26990', 'Partial INV003', '2023-05-07', 11, 123, 'EUR', 'partial', '{}')
|
||||
;
|
||||
|
||||
insert into invoice_collection (invoice_id, collection_id)
|
||||
values (13, 16)
|
||||
, (14, 17)
|
||||
, (14, 18)
|
||||
, (15, 19)
|
||||
;
|
||||
|
||||
insert into collection_attachment (collection_id, original_fileName, mime_type, content)
|
||||
values (16, 'collection.txt', 'text/plain', convert_to('Pay 42', 'UTF-8'))
|
||||
, (18, 'empty.html', 'text/html', convert_to('empty', 'UTF-8'))
|
||||
, (19, 'collection.html', 'text/html', convert_to('<html> PAY <em>42</em></html>', 'UTF-8'))
|
||||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select remove_collection('7ac3ae0e-b0c1-4206-a19b-0be20835edd4') $$,
|
||||
'Should be able to remove a complete collection'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select remove_collection('5a524bee-8311-4d13-9adf-ef6310b26990') $$,
|
||||
'Should be able to remove a partial collection, '
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select remove_collection('b57b980b-247b-4be4-a0b7-03a7819c53ae') $$,
|
||||
'Should be able to remove a partial collection, leaving the invoice’s other partial collection'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select description, collection_date::text, payment_account_id, amount, payment_status, tags::text from collection $$,
|
||||
$$ values ('Second INV002', '2023-05-06', 13, 122, 'partial', '{tag1,tag3}')
|
||||
$$,
|
||||
'Should have deleted all given collections'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_id, collection_id from invoice_collection$$,
|
||||
$$ values (14, 18)
|
||||
$$,
|
||||
'Should have deleted all related invoices’ collections'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select collection_id, original_filename from collection_attachment $$,
|
||||
$$ values (18, 'empty.html') $$,
|
||||
'Should have deleted all related attachments'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_id, invoice_status from invoice $$,
|
||||
$$ values (13, 'created')
|
||||
, (14, 'partial')
|
||||
, (15, 'created')
|
||||
$$,
|
||||
'Should have updated invoices too'
|
||||
);
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,27 @@
|
|||
-- Test update_invoice_collection_status
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(9);
|
||||
|
||||
set search_path to numerus, public;
|
||||
|
||||
select has_function('numerus', 'update_invoice_collection_status', array['integer', 'integer', 'integer']);
|
||||
select function_lang_is('numerus', 'update_invoice_collection_status', array['integer', 'integer', 'integer'], 'sql');
|
||||
select function_returns('numerus', 'update_invoice_collection_status', array['integer', 'integer', 'integer'], 'void');
|
||||
select isnt_definer('numerus', 'update_invoice_collection_status', array['integer', 'integer', 'integer']);
|
||||
select volatility_is('numerus', 'update_invoice_collection_status', array['integer', 'integer', 'integer'], 'volatile');
|
||||
|
||||
select function_privs_are('numerus', 'update_invoice_collection_status', array ['integer', 'integer', 'integer'], 'guest', array []::text[]);
|
||||
select function_privs_are('numerus', 'update_invoice_collection_status', array ['integer', 'integer', 'integer'], 'invoicer', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'update_invoice_collection_status', array ['integer', 'integer', 'integer'], 'admin', array ['EXECUTE']);
|
||||
select function_privs_are('numerus', 'update_invoice_collection_status', array ['integer', 'integer', 'integer'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:add_collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.add_collection(integer, integer, date, integer, text, text, numerus.tag_name[])', 'execute');
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:attach_to_collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.attach_to_collection(uuid, text, text, bytea)', 'execute');
|
||||
|
||||
rollback;
|
|
@ -6,6 +6,7 @@ set search_path to numerus;
|
|||
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'created' and name ='Created';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'sent' and name ='Sent';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'partial' and name ='Partial';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'paid' and name ='Paid';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'unpaid' and name ='Unpaid';
|
||||
|
||||
|
@ -13,6 +14,8 @@ select 1 / count(*) from invoice_status_i18n where invoice_status = 'created' an
|
|||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'created' and name ='Creada' and lang_tag = 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'sent' and name ='Enviada' and lang_tag= 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'sent' and name ='Enviada' and lang_tag= 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'partial' and name ='Parcial' and lang_tag = 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'partial' and name ='Parcial' and lang_tag = 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'paid' and name ='Cobrada' and lang_tag= 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'paid' and name ='Cobrada' and lang_tag= 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'unpaid' and name ='No cobrada' and lang_tag= 'ca';
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
-- Verify numerus:available_invoice_status on pg
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus;
|
||||
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'created' and name ='Created';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'sent' and name ='Sent';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'paid' and name ='Paid';
|
||||
select 1 / count(*) from invoice_status where invoice_status = 'unpaid' and name ='Unpaid';
|
||||
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'created' and name ='Creada' and lang_tag = 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'created' and name ='Creada' and lang_tag = 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'sent' and name ='Enviada' and lang_tag= 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'sent' and name ='Enviada' and lang_tag= 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'paid' and name ='Cobrada' and lang_tag= 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'paid' and name ='Cobrada' and lang_tag= 'es';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'unpaid' and name ='No cobrada' and lang_tag= 'ca';
|
||||
select 1 / count(*) from invoice_status_i18n where invoice_status = 'unpaid' and name ='No cobrada' and lang_tag= 'es';
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,22 @@
|
|||
-- Verify numerus:collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select collection_id
|
||||
, company_id
|
||||
, slug
|
||||
, description
|
||||
, collection_date
|
||||
, payment_account_id
|
||||
, amount
|
||||
, currency_code
|
||||
, tags
|
||||
, payment_status
|
||||
, created_at
|
||||
from numerus.collection
|
||||
where false;
|
||||
|
||||
select 1 / count(*) from pg_class where oid = 'numerus.collection'::regclass and relrowsecurity;
|
||||
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.collection'::regclass;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,15 @@
|
|||
-- Verify numerus:collection_attachment on pg
|
||||
|
||||
begin;
|
||||
|
||||
select collection_id
|
||||
, original_filename
|
||||
, mime_type
|
||||
, content
|
||||
from numerus.collection_attachment
|
||||
where false;
|
||||
|
||||
select 1 / count(*) from pg_class where oid = 'numerus.collection_attachment'::regclass and relrowsecurity;
|
||||
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.collection_attachment'::regclass;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:edit_collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.edit_collection(uuid, date, integer, text, text, numerus.tag_name[])', 'execute');
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,13 @@
|
|||
-- Verify numerus:invoice_collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select invoice_id
|
||||
, collection_id
|
||||
from numerus.invoice_collection
|
||||
where false;
|
||||
|
||||
select 1 / count(*) from pg_class where oid = 'numerus.invoice_collection'::regclass and relrowsecurity;
|
||||
select 1 / count(*) from pg_policy where polname = 'company_policy' and polrelid = 'numerus.invoice_collection'::regclass;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:remove_collection on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.remove_collection(uuid)', 'execute');
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify numerus:update_invoice_collection_status on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.update_invoice_collection_status(integer, integer, integer)', 'execute');
|
||||
|
||||
rollback;
|
|
@ -696,6 +696,7 @@ main > nav {
|
|||
.payment-status-partial,
|
||||
.expense-status-partial,
|
||||
.quote-status-sent,
|
||||
.invoice-status-partial,
|
||||
.invoice-status-sent {
|
||||
background-color: var(--numerus--color--hay);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{{ define "title" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.PaymentForm*/ -}}
|
||||
{{ printf ( pgettext "Edit Payment “%s”" "title" ) .Description }}
|
||||
{{- if eq .Type.Value "P" -}}
|
||||
{{ printf ( pgettext "Edit Payment “%s”" "title" ) .Description }}
|
||||
{{- else -}}
|
||||
{{ printf ( pgettext "Edit Collection “%s”" "title" ) .Description }}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "breadcrumbs" -}}
|
||||
|
@ -30,6 +34,7 @@
|
|||
{{ csrfToken }}
|
||||
{{ putMethod }}
|
||||
|
||||
{{ template "hidden-select-field" .Type }}
|
||||
{{ template "select-field" .PaymentAccount }}
|
||||
{{ template "input-field" .Description }}
|
||||
{{ template "input-field" .PaymentDate }}
|
||||
|
|
|
@ -45,7 +45,11 @@
|
|||
<td><a href="{{ $.BaseURI }}/{{ .Slug }}">{{ .Description }}</a></td>
|
||||
<td>
|
||||
{{- if .InvoiceNumber -}}
|
||||
<a href="{{ companyURI "/expenses/"}}{{ .ExpenseSlug }}">{{ .InvoiceNumber }}</a>
|
||||
{{- if eq .Type "P" -}}
|
||||
<a href="{{ companyURI "/expenses/"}}{{ .DocumentSlug }}">{{ .InvoiceNumber }}</a>
|
||||
{{- else -}}
|
||||
<a href="{{ companyURI "/invoice/"}}{{ .DocumentSlug }}">{{ .InvoiceNumber }}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</td>
|
||||
<td class="payment-status-{{ .Status }}">{{ .StatusLabel }}</td>
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
data-hx-boost="true">
|
||||
{{ csrfToken }}
|
||||
|
||||
{{- if .Expense -}}
|
||||
{{ template "hidden-select-field" .Type }}
|
||||
{{- else -}}
|
||||
{{ template "select-field" .Type }}
|
||||
{{- end -}}
|
||||
{{ template "select-field" .PaymentAccount }}
|
||||
{{ template "input-field" .Description }}
|
||||
{{ template "input-field" .PaymentDate }}
|
||||
|
|
Loading…
Reference in New Issue