Compute the total amount, base plus taxes, of all expenses
This works mostly like invoices: i have to “update” the expense form
to compute its total based on the subtotal and the selected taxes,
although in this case i do no need to compute the subtotal because that
is given by the user.
Nevertheless, i added a new function to compute that total because it
was already hairy enough for the dashboard, that also needs to compute
the tota, not just the base, and i wanted to test that function.
There is no need for a custom input type for that function as it only
needs a couple of simple domains. I have created the output type,
though, because otherwise i would need to have records or “reuse” any
other “amount” output type, which would be confusing.\
Part of #68.
2023-07-13 18:50:26 +00:00
|
|
|
|
-- Test compute_new_expense_amount
|
|
|
|
|
set client_min_messages to warning;
|
|
|
|
|
create extension if not exists pgtap;
|
|
|
|
|
reset client_min_messages;
|
|
|
|
|
|
|
|
|
|
begin;
|
|
|
|
|
|
Fix compute_new_expense_amount to set 0.00 to taxes when subtotal is ''
The problem is that parse_price('', 2) returned NULL instead of throwing
and exception: it seems that accessing var[1] of a text[] variable set
to the empty array, {}, returns NULL, and NULL::integer is, of course,
still NULL.
Apparently, this is the only case, until now, that i had an empty
subtotal, and i did not know what to do: should i keep the function as
is and just handle its NULL return, change it to return 0 in that case,
or raise an exception?
The argument for the first two options, to leave it as is or to
return zero, was that it was convenient for me to allow empty strings as
input values, because that’s what i get from an empty <input>; returning
zero would avoid an extra coalesce everywhere the function was used.
The argument in favor to the last option, an exception, was that the
empty string does not represent an integer, nor a “unknown” (NULL)
integer, therefore the function should do the same when i pass in any
other string that does not represent an integer, just as “a.b”.
At the end i went for option three, because it is the one that breaks
fewer expectatives: casting an empty string to integer, or passing
an empty string as the first value to to_number() throw and exception in
PostgreSQL; my function should do the same. Heck, that what **i**
expected it to do because of the casting inside the function.
To still allow empty strings as parameter to compute_new_expense_amount,
the only case so far, i only had to check for that empty string and
convert it to the string representation of zero, so that parse_price
returns the value i want for that function. This, of course, breaks
the same expectatives as returning NULL for to_price, but i think it is
OK in this case because to_price is more general—used in many more
cases—than compute_new_expense_amount, which is only intended for that
HTML form.
Closes #77.
2023-08-25 12:19:27 +00:00
|
|
|
|
select plan(15);
|
Compute the total amount, base plus taxes, of all expenses
This works mostly like invoices: i have to “update” the expense form
to compute its total based on the subtotal and the selected taxes,
although in this case i do no need to compute the subtotal because that
is given by the user.
Nevertheless, i added a new function to compute that total because it
was already hairy enough for the dashboard, that also needs to compute
the tota, not just the base, and i wanted to test that function.
There is no need for a custom input type for that function as it only
needs a couple of simple domains. I have created the output type,
though, because otherwise i would need to have records or “reuse” any
other “amount” output type, which would be confusing.\
Part of #68.
2023-07-13 18:50:26 +00:00
|
|
|
|
|
|
|
|
|
set search_path to numerus, auth, public;
|
|
|
|
|
|
|
|
|
|
select has_function('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]']);
|
|
|
|
|
select function_lang_is('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'plpgsql');
|
|
|
|
|
select function_returns('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'new_expense_amount');
|
|
|
|
|
select isnt_definer('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]']);
|
|
|
|
|
select volatility_is('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'stable');
|
|
|
|
|
select function_privs_are('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'guest', array []::text[]);
|
|
|
|
|
select function_privs_are('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'invoicer', array ['EXECUTE']);
|
|
|
|
|
select function_privs_are('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'admin', array ['EXECUTE']);
|
|
|
|
|
select function_privs_are('numerus', 'compute_new_expense_amount', array ['integer', 'text', 'integer[]'], 'authenticator', array []::text[]);
|
|
|
|
|
|
|
|
|
|
set client_min_messages to warning;
|
|
|
|
|
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 1', '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)
|
|
|
|
|
, (5, 1, 11, 'IVA 21 %', 0.21)
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '', array[]::integer[]),
|
|
|
|
|
'("{}",0.00)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
Fix compute_new_expense_amount to set 0.00 to taxes when subtotal is ''
The problem is that parse_price('', 2) returned NULL instead of throwing
and exception: it seems that accessing var[1] of a text[] variable set
to the empty array, {}, returns NULL, and NULL::integer is, of course,
still NULL.
Apparently, this is the only case, until now, that i had an empty
subtotal, and i did not know what to do: should i keep the function as
is and just handle its NULL return, change it to return 0 in that case,
or raise an exception?
The argument for the first two options, to leave it as is or to
return zero, was that it was convenient for me to allow empty strings as
input values, because that’s what i get from an empty <input>; returning
zero would avoid an extra coalesce everywhere the function was used.
The argument in favor to the last option, an exception, was that the
empty string does not represent an integer, nor a “unknown” (NULL)
integer, therefore the function should do the same when i pass in any
other string that does not represent an integer, just as “a.b”.
At the end i went for option three, because it is the one that breaks
fewer expectatives: casting an empty string to integer, or passing
an empty string as the first value to to_number() throw and exception in
PostgreSQL; my function should do the same. Heck, that what **i**
expected it to do because of the casting inside the function.
To still allow empty strings as parameter to compute_new_expense_amount,
the only case so far, i only had to check for that empty string and
convert it to the string representation of zero, so that parse_price
returns the value i want for that function. This, of course, breaks
the same expectatives as returning NULL for to_price, but i think it is
OK in this case because to_price is more general—used in many more
cases—than compute_new_expense_amount, which is only intended for that
HTML form.
Closes #77.
2023-08-25 12:19:27 +00:00
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '', array[2,5,3]::integer[]),
|
|
|
|
|
'("{{IRPF -15 %,0.00},{IVA 4 %,0.00},{IVA 21 %,0.00}}",0.00)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
Compute the total amount, base plus taxes, of all expenses
This works mostly like invoices: i have to “update” the expense form
to compute its total based on the subtotal and the selected taxes,
although in this case i do no need to compute the subtotal because that
is given by the user.
Nevertheless, i added a new function to compute that total because it
was already hairy enough for the dashboard, that also needs to compute
the tota, not just the base, and i wanted to test that function.
There is no need for a custom input type for that function as it only
needs a couple of simple domains. I have created the output type,
though, because otherwise i would need to have records or “reuse” any
other “amount” output type, which would be confusing.\
Part of #68.
2023-07-13 18:50:26 +00:00
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '4.60', array[2,5,3]),
|
|
|
|
|
'("{{IRPF -15 %,-0.69},{IVA 4 %,0.18},{IVA 21 %,0.97}}",5.06)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '17.32', array[2,4,5]),
|
|
|
|
|
'("{{IRPF -15 %,-2.60},{IVA 10 %,1.73},{IVA 21 %,3.64}}",20.09)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '52.17', array[3,4,5]),
|
|
|
|
|
'("{{IVA 4 %,2.09},{IVA 10 %,5.22},{IVA 21 %,10.96}}",70.44)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
select is(
|
|
|
|
|
compute_new_expense_amount(1, '62.16', array[]::integer[]),
|
|
|
|
|
'("{}",62.16)'::new_expense_amount
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
select *
|
|
|
|
|
from finish();
|
|
|
|
|
|
|
|
|
|
rollback;
|