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
|
|
|
-- Deploy numerus:parse_price to pg
|
|
|
|
-- requires: schema_public
|
Convert from cents to “price” and back
I do not want to use floats in the Go lang application, because it is
not supposed to do anything with these values other than to print and
retrieve them from the user; all computations will be performed by
PostgreSQL in cents.
That means i have to “convert” from the price format that users expect
to see (e.g., 1.234,56) to cents (e.g., 123456) and back when passing
data between Go and PostgreSQL, and that conversion depends on the
currency’s decimal places.
At first i did everything in Go, but saw that i would need to do it in
a loop when retrieving the list of products, and immediately knew it was
a mistake—i needed a PL/pgSQL function for that.
I still need to convert from string to float, however, when printing the
value to the user. Because the string representation is in C, but i
need to format it according to the locale with golang/x/text. That
package has the information of how to correctly format numbers, but it
is in an internal package that i can not use, and numbers.Digit only
accepts numeric types, not a string.
2023-02-05 12:55:12 +00:00
|
|
|
|
|
|
|
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
|
|
|
set search_path to numerus, public;
|
|
|
|
|
|
|
|
create or replace function parse_price(price text, decimal_digits integer) returns integer as
|
|
|
|
$$
|
|
|
|
declare
|
|
|
|
parts text[];
|
|
|
|
result int;
|
|
|
|
frac_part text;
|
|
|
|
sign int := 1;
|
|
|
|
begin
|
|
|
|
if price like '-%' Then
|
|
|
|
sign := -1;
|
|
|
|
price := substring(price from 2);
|
|
|
|
end if;
|
|
|
|
|
|
|
|
parts := string_to_array(price, '.');
|
|
|
|
if array_length(parts, 1) > 2 then
|
|
|
|
raise invalid_parameter_value using message = price || ' is not a valid price representation.';
|
|
|
|
end if;
|
|
|
|
|
|
|
|
result := parts[1]::integer;
|
|
|
|
for d in 1..decimal_digits loop
|
|
|
|
result := result * 10;
|
|
|
|
end loop;
|
|
|
|
|
|
|
|
if array_length(parts, 1) = 2 then
|
|
|
|
frac_part := rtrim(parts[2], '0');
|
|
|
|
if length(frac_part) > decimal_digits then
|
|
|
|
raise invalid_parameter_value using message = price || ' has too many digits in the fraction part.';
|
|
|
|
end if;
|
|
|
|
frac_part := rpad(frac_part, decimal_digits, '0');
|
|
|
|
result := result + frac_part::integer;
|
|
|
|
end if;
|
|
|
|
|
|
|
|
return sign * result;
|
|
|
|
end;
|
|
|
|
$$
|
|
|
|
language plpgsql
|
|
|
|
immutable;
|
|
|
|
|
|
|
|
comment on function parse_price(text, integer) is
|
|
|
|
'Converts the string representation of a price in decimal form to cents, according to the number of decimal digits passed.';
|
|
|
|
|
|
|
|
revoke execute on function parse_price(text, integer) from public;
|
|
|
|
grant execute on function parse_price(text, integer) to invoicer;
|
|
|
|
grant execute on function parse_price(text, integer) to admin;
|
Convert from cents to “price” and back
I do not want to use floats in the Go lang application, because it is
not supposed to do anything with these values other than to print and
retrieve them from the user; all computations will be performed by
PostgreSQL in cents.
That means i have to “convert” from the price format that users expect
to see (e.g., 1.234,56) to cents (e.g., 123456) and back when passing
data between Go and PostgreSQL, and that conversion depends on the
currency’s decimal places.
At first i did everything in Go, but saw that i would need to do it in
a loop when retrieving the list of products, and immediately knew it was
a mistake—i needed a PL/pgSQL function for that.
I still need to convert from string to float, however, when printing the
value to the user. Because the string representation is in C, but i
need to format it according to the locale with golang/x/text. That
package has the information of how to correctly format numbers, but it
is in an internal package that i can not use, and numbers.Digit only
accepts numeric types, not a string.
2023-02-05 12:55:12 +00:00
|
|
|
|
|
|
|
commit;
|