Commit Graph

145 Commits

Author SHA1 Message Date
jordi fita mas 0d4fb124b4 Keep all “new invoice actions” on the same /new URI 2023-02-27 13:13:28 +01:00
jordi fita mas bc7ed0f06f Tell Goland to go fuck itself with the unhandled errors in close 2023-02-27 12:55:18 +01:00
jordi fita mas 2ef75efda8 Sort invoices so that the first is the most recent 2023-02-27 12:48:56 +01:00
jordi fita mas c7ac82d6ea Redirect to the newly created invoice on add
It makes more sense to see the invoice once created than to return to
the list of invoices and having to look for it.
2023-02-27 12:45:32 +01:00
jordi fita mas c1a64dd51d Add --format parameter to WeasyPrint
Apparently, in older version of WeasyPrint it was possible to output
either PNG or PDF, so they had that parameter.  In more recent versions,
the only acceptable is PDF, but they removed the fucking parameter
between versions 51 and 52, to it _will_ fail in recent version of
WeasyPrint.

For now, i am using the same version that Debian 11 does, and let’s
hope it stays like this a long time. (It won’t, of course, but well….)
2023-02-26 17:42:38 +01:00
jordi fita mas 4d2379555e Convert invoices to PDF with WeasyPrint
Although it is possible to just print the invoice from the browser, many
people will not even try an assume that they can not create a PDF for
the invoice.

I thought of using Groff or TeX to create the PDF, but it would mean
maintaining two templates in two different systems (HTML and whatever i
would use), and would probably look very different, because i do not
know Groff or TeX that well.

I wish there was a way to tell the browser to print to PDF, and it can
be done, but only with the Chrome Protocol to a server-side running
Chrome instance.   This works, but i would need a Chrome running as a
daemon.

I also wrote a Qt application that uses QWebEngine to print the PDF,
much like wkhtmltopdf, but with support for more recent HTML and CSS
standards.  Unfortunately, Qt 6.4’s embedded Chromium does not follow
break-page-inside as well as WeasyPrint does.

To use WeasyPrint, at first i wanted to reach the same URL as the user,
passing the cookie to WeasyPrint so that i can access the same invoice
as the user, something that can be done with wkhtmltopdf, but WeasyPrint
does not have such option.  I did it with a custom Python script, but
then i need to package and install that script, that is not that much
work, but using the Debian-provided script is even less work, and less
likely to drift when WeasyPrint changes API.

Also, it is unnecessary to do a network round-trip from Go to Python
back to Go, because i can already write the invoice HTML as is to
WeasyPrint’s stdin.
2023-02-26 17:26:09 +01:00
jordi fita mas 47d3e1940c Fix typo in Debian’s package description 2023-02-26 17:10:26 +01:00
jordi fita mas 843f7746cf Adapt the invoice design to Oriol’s 2023-02-25 13:48:57 +01:00
jordi fita mas fffab62135 Use left join between products and taxes
A product can have no tax.
2023-02-25 03:20:34 +01:00
jordi fita mas 419ac3ed46 Adjust invoice.css to work with WeasyPrint too
I am planning to use WeasyPrint to “generate PDF” from the same HTML
that the user view, but it seems that it does not support flex’s gap
and some other properties that i had to change to work in both user
agents.

I also moved the invoice’s “footer” inside the last product’s body
because i do not want the footer to be a “widow”.
2023-02-25 03:16:20 +01:00
jordi fita mas 191401abe9 Temporarily switch to admin role to register types
The authenticator role does not have access to the numerus schema, so
it can not see the oid of discount_rate, for instance.
2023-02-24 13:17:37 +01:00
jordi fita mas 18fba2964f Add invoice view, with print CSS
Had to group name and description rows in tbody because i do not want
to break them on pagination.

I also could not use tfoot for subtotal, taxes, and total because then
they appear on every page.

The disclaimer should appear only at the very bottom of the last page,
but i do not know how to do that; using position fixed shows it on
every page.
2023-02-24 12:22:15 +01:00
jordi fita mas 985f843e8e Show the invoice subtotal, taxes, and total when creating it 2023-02-23 15:31:57 +01:00
jordi fita mas 980db529f1 Fix to_price and parse_price for negative values 2023-02-23 14:42:18 +01:00
jordi fita mas 8dbf8ef2d0 Add currency_pattern to language relation
The design calls for rendering all amounts with their currency symbol,
but golang.org/x/text’s currency package always render the symbol in
front, which is wrong in Catalan and Spanish, and a lot of other
languages.

Consulting the Internet, the most popular package for that is
accounting[0], which is almost as useless because they confuse locale
with the currency’s country of origin’s “usual locale” (e.g., en-US for
USD), which is also wrong: in Catalan i need to write USD prices as
"1.234,56 $" regardless of what Americans do.

With accounting i have the recourse of initializing the struct that
holds all the “locale” information, which is also wrong because i have
to define the decimal and thousands separators, something that depends
only on the locale, next to the currency’s precision, that is
locale-independent.  But, since all CLDR data from golang.org/x/text
is inside an internal package, i can not access it and would need to
define all that information myself, which defeats the purpose of using
an external package.

Since for now i only need the format pattern for currency, i just saved
it into the database of available languages, that i do not expect to
grow too much.

[0]: https://github.com/leekchan/accounting
2023-02-23 12:12:33 +01:00
jordi fita mas 97ef02b0f9 Add views to compute taxes and total amount of invoices
They are not functions because i need to join them with the main
invoice relation, and although possible is a bit more awkward with
functions.

The taxes have their own relation because i will need them grouped by
their name in the PDF, so it will probably be a select for that
relation.
2023-02-22 14:39:38 +01:00
jordi fita mas 32fdab4217 Reindex product indices when removing
When updating the product list, i forgot to change the index in the
product field’s names and, therefore, i created invoices with only the
products until the first gap.
2023-02-21 13:57:40 +01:00
jordi fita mas c1e443e3bc Move the new product description before the taxes
Because in the “default view” that position has a lot of space that is
best used by a large text area than a simple number input.
2023-02-21 13:55:59 +01:00
jordi fita mas b3004486f0 Properly register array and composite PostgreSQL types with pgtype
I have moved everything into a different file, even though it is related
to “db”, because it was starting to get a bit ugly.

Apparently i was doing too much work and had to read the code to
understand what i was supposed to do, because pgtypes’ documentation,
as all other projects from the same author, is almost non-existent.
2023-02-20 11:42:21 +01:00
jordi fita mas 98ac17a129 Call add_invoice when adding invoice from Go
I needed to add binary encoding function for the new_invoice_product
type, and currently it is not correct: i hardcoded the OID values, but
they are going to be different on a different database.
2023-02-19 23:53:06 +01:00
jordi fita mas 697f821310 Call next_invoice_number from add_invoice 2023-02-18 14:49:02 +01:00
jordi fita mas 880c4f53b2 Add the function to get the next invoice number
I can not use a PostgreSQL sequence because invoices need to be gapless,
and sequences are designed to not rollback, for performance reasons.  In
this case, the performance is secondary because the law does not care.
2023-02-17 14:48:24 +01:00
jordi fita mas de7d167e65 Add constraints to reject empty product and companies names 2023-02-17 12:39:32 +01:00
jordi fita mas 245cccd85a Add PL/pgSQL function to add invoices 2023-02-16 23:09:10 +01:00
jordi fita mas 9bddb548a2 Add invoice product tax relation 2023-02-15 14:49:06 +01:00
jordi fita mas 045bf7ff6a Add the formnovalidate attribute to update and add products buttons
They are to complete the invoice, so it can be in an invalid date, but
we do not want to force people to finish all required inputs before they
can add products or update quantities, do we?

Now had to add the empty option label for customer in all cases, because
it could be empty, although that should be done regardless in case
someone has a browser that does not validate fields.
2023-02-14 12:55:19 +01:00
jordi fita mas 4463c7ee0b Use array_agg to get the taxes for the product’s form 2023-02-14 12:49:29 +01:00
jordi fita mas 4db0a8fb5a Refactor checking for pgx.ErrNoRows in a function 2023-02-14 12:46:11 +01:00
jordi fita mas 13fa1d6b89 Add PL/pgSQL functions to add and edit products
I am going to add similar functions for invoices, as i will need to
add the taxes for their products and their own taxes, thus the Go code
will begin to be “too much work” and i feel better if that is in
PL/pgSQL.

If i have these functions for invoices, there is no point on having to
do almost the same work, albeit less, for products.
2023-02-14 12:39:54 +01:00
jordi fita mas 989c1717e5 Rename mustGetInvoiceEntries to mustCollectInvoiceEntries
I have seen that pgx has the CollectRows function to do the same job as
that function.  I can not use CollectRows because it uses generics and
requires Go 1.18, but i have adopted the same nomenclature they use.
2023-02-14 12:34:50 +01:00
jordi fita mas e94e3f6ebc Fix time layout in FormValidator.CheckValidDate 2023-02-13 10:34:55 +01:00
jordi fita mas 3891a01fd5 Fix use of API methods not available in Go 1.16
Apparently, url.Values.Has and math.MaxInt was added to Go 1.17,
but on Debian Bullseye there is only Go 1.16.  I do not want to
install a new version of Go to the server unless there is an
overwhelming reason, and a couple of methods are not.  Thus, now i use
Go 1.16 too on my development machine, to avoid situations like this.
2023-02-13 10:32:26 +01:00
jordi fita mas 4903c8a3b9 Add the form to add products to an invoice and create invoices too
Still missing: the invoice number, that requires more tables and
possibly a PL/pgSQL function to do it properly.
2023-02-12 21:06:48 +01:00
jordi fita mas 72fbed68ac Add a missing StatusUnprocessableEntity HTTP response code 2023-02-12 21:03:46 +01:00
jordi fita mas c2d8006748 Make FormValidator.CheckValidDate method public 2023-02-12 21:01:20 +01:00
jordi fita mas 5c15b9de20 Add the bare-bones form for invoices 2023-02-11 22:16:48 +01:00
jordi fita mas 6bf51d5eeb Add discount_rate domain and invoice_product relation
I store again the product’s name, description, and prices, because they
are bound to change, but the invoice should remain the same always.
That makes me wonder if i should do the same for seller’s and buyer’s
data, but that should be a different commit.

I’ve added the discount_rate domain because then i can test it
independently of the invoice_product relation, moreover i am sure i will
need it for simplified invoices too.
2023-02-10 19:02:04 +01:00
jordi fita mas 96cfee6f56 Correctly test negative tax_rate
It turns out that -0.15::tax_rate means -(0.15::tax_rate), but i want
(-0.15)::tax_rate, because otherwise i only check the domain [0, 1).
2023-02-10 19:00:46 +01:00
jordi fita mas fa3ffdf19b Truncate relation invoice in its test suite 2023-02-10 19:00:22 +01:00
jordi fita mas d5fb5a4326 Fix comment on domain tax_rate 2023-02-10 19:00:08 +01:00
jordi fita mas aad0d33c47 Add the invoice relation 2023-02-09 11:42:31 +01:00
jordi fita mas b2971e3a09 Fix product taxes in demo SQL 2023-02-08 14:12:45 +01:00
jordi fita mas 4be2597a86 Allow multiple taxes, and even not tax, for products
It seems that we do not agree en whether the IRPF tax should be
something of the product or the contact, so we decided to make the
product have multiple taxes, just in case, and if only one is needed,
then users can just select one; no need to limit to one.
2023-02-08 13:47:36 +01:00
jordi fita mas 2a98b9c0af Restart sequences for the demo
I was using explicit IDs because i need them to satisfy foreign key
constraints, and also to look them up within the file, but then i had
the problem that the sequences would be left at 1, preventing me to
add new contacts or products, for instance.

Now i use the sequence exactly how the application will (i.e., with
default values), but i have to reset them to 1 to make the ID stable
even when i make tests with pgTAP on the same database.
2023-02-07 16:59:00 +01:00
jordi fita mas 44e8f030b3 Add the invoice_status relation and its i18n 2023-02-07 16:45:27 +01:00
jordi fita mas 608ea16152 Document the requirement between product and tax 2023-02-07 15:34:41 +01:00
jordi fita mas 73ca559209 Add template for InputField of type textarea 2023-02-07 15:28:22 +01:00
jordi fita mas 043bd9cc3f Add demo products 2023-02-06 13:35:56 +01:00
jordi fita mas ae1949024b Allow optional select with empty label
This is not yet necessary, but the empty label is because i do not want
to select a default tax for products—at least, not without a setting for
it.

Since i need to add the required attribute now to select, because
otherwise the browser would allow sending that empty value, i did not
want to do it unconditionally, just in case.
2023-02-05 14:06:33 +01:00
jordi fita mas 60f9792e58 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 13:55:12 +01:00