I was repeating myself a lot for this use case, because each one needed
a different URL and SQL query, however they were kind of structurally
similar and could be refactored into common functions.
Since most of PL/pgSQL functions accept a `uuid` domain, we get an error
if the value is not valid, forcing us to return an HTTP 500, as we
can not detect that the error was due to that.
Instead, i now validate that the slug is indeed a valid UUID before
attempting to send it to the database, returning the correct HTTP error
code and avoiding useless calls to the database.
I based the validation function of Parse() from Google’s uuid package[0]
because this function is an order or magnitude faster in benchmarks:
goos: linux
goarch: amd64
pkg: dev.tandem.ws/tandem/numerus/pkg
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkValidUuid-4 36946050 29.37 ns/op
BenchmarkValidUuid_Re-4 3633169 306.70 ns/op
The regular expression used for the benchmark was:
var re = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
And the input parameter for both functions was the following valid UUID,
because most of the time the passed UUID will be valid:
"f47ac10b-58cc-0372-8567-0e02b2c3d479"
I did not use the uuid package, even though it is in Debian’s
repository, because i only need to check whether the value is valid,
not convert it to a byte array. As far as i know, that package can not
do that.
[0]: https://github.com/google/uuid
I want this button, as well as the submit button, to be on a row below
the filters’ input, especially for quotes and invoices, that have the
most filters and looks weird with the button wedged in. Thus, i added
a <fieldset> around all the filters.
Closes#69
With Oriol agreed that adding or editing invoices, products, and
contacts is not just a “user interruption” but the main flow of the
program, and, as such, it is not correct to use dialogs for these.
More importantly, it was harder to concentrate, specially with the more
involved form of invoices, because of all the “noise” behind the dialog.
I actually find more comfortable to select the product from the list
presented up until now, but this is mostly because i have very few
products and the list is not too long, so the idea is that with
JavaScript we will dynamically add an empty product row to the invoice
and then use the name field to search the product by name.
I have the feeling that i am doing something wrong because i ended up
with a lot of HTMx attribute for what i feel is not that much work,
but for now it will work.
I have added the `Is` field to `InputField` in order to include the `id`
attribute to the HTML element, because the HTMLAttributes are attached
to the `input`, not the `div`, and i felt like this one should also be
a custom element based on div, like all the others.
These is not yet any keyboard control to select the search results.
I am not happy with having the search of products in a different URL
than the index, specially since they use the exact same SQL query and
ProductFilter struct, but i did not know how else ask for a different
representation without resorting to the more complicated MIME types.
I need a way to search products by name in the invoice form, when the
user adds or changes a product. Since this is something that i have to
add too to the product list, i added it now so the function will already
be ready.
It all started when i wanted to try to filter invoices by multiple tags
using an “AND”, instead of “OR” as it was doing until now. But
something felt off and seemed to me that i was doing thing much more
complex than needed, all to be able to list the tags as a suggestion
in the input field—which i am not doing yet.
I found this article series[0] exploring different approaches for
tagging, which includes the one i was using, and comparing their
performance. I have not actually tested it, but it seems that i have
chosen the worst option, in both query time and storage.
I attempted to try using an array attribute to each table, which is more
or less the same they did in the articles but without using a separate
relation for tags, and i found out that all the queries were way easier
to write, and needed two joins less, so it was a no-brainer.
[0]: http://www.databasesoup.com/2015/01/tag-all-things.html
Had to add the editProductPage because now i need to know the slug in
order to build the form’s action link. I also added the `ProductName`
field because it was less awkward than using `.Form.Name` everywhere.
With Oriol we agreed that products should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices and
contacts.
Had to add the `company_id` attribute in the `using` clause for `tag` in
`MustFillFromDatabase`, even though it’s not strictly necessary, because
then PostgreSQL does not know which `company_id` attribute use for the
join with `company`—the one from `product` or the one from `tag`.
I am not sure if, at the end, all pages that now use
mustRenderAppTemplate will be replaced with mustRenderMainTemplate,
but for now i keep them separate to know which routes are already
“boosted”.
We only allow a single tax of each class in products and invoices,
because it does not make sense to add two different VAT to the same
product, for instance.
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.
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.
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.
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.