Commit Graph

228 Commits

Author SHA1 Message Date
jordi fita mas f917ce84dd Replace call to deprecated ioutil.ReadAll with io.ReadAll
Starting from Go 1.16, ioutil.ReadAll simply calls io.ReadAll.
2023-07-02 20:04:45 +02:00
jordi fita mas 20827b2cfb Add IBAN and BIC fields to contacts
These two fields are just for information purposes, as Numerus does not
have any way to wire transfer using these, but people might want to keep
these in the contact’s info as a convenience.

Since not every contact should have an IBAN, e.g., customers, and inside
SEPA (European Union and some more countries) the BIC is not required,
they are in two different relations in order to be optional without
using NULL.

For the IBAN i found an already made PostgreSQL module, but for BIC i
had to write a regular expression based on the information i gathered
from Wikipedia, because the ISO standard is not free.

These two parameters for the add_contact and edit_contact functions are
TEXT because i realized that these functions are intended to be used
from the web application, that only deals with texts, so the
ValueOrNil() function was unnecessarily complex and PostreSQL’s
functions were better suited to “convert” from TEXT to IBAN or BIC.
The same is true for EMAIL and URI domains, so i changed their parameter
types to TEXT too.

Closes #54.
2023-07-02 02:08:45 +02:00
jordi fita mas 1c0f126c58 Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.

It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.

Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices.  Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.

We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.

The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.

Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.

I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked.  My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 21:32:48 +02:00
jordi fita mas f40e4fdb2e Fix passing company ID to expenses chart query
By mistake, i was using 1 instead of $1, but i all was OK in testing
because there is only a single company with ID = 1.
2023-06-21 09:47:23 +02:00
jordi fita mas ee0b5d0bdc Rename Contact to Customer in quotes and invoices’ fields
In this case, the invoicee or quotee _is_ a (potential) customer, so
there is no point on calling them “contact”.
2023-06-20 11:37:02 +02:00
jordi fita mas 07c1071975 Add total amount for quotes, invoices, and expenses tables
We have shown the application to a potential user, and they told us that
it would be very useful to have a total in the table’s footer, so that
they can verify the amount with the bank’s extracts.
2023-06-20 11:33:28 +02:00
jordi fita mas 8a4f80783d Rename Customer expense filter to Contact
It would be very unusual to have an expense from a customer, and we do
not have (yet) a name for supplier or whatever it should be here, so i
used the same name we use for the column in the table.
2023-06-20 11:17:07 +02:00
jordi fita mas 055e92fb23 Internationalize and localize the home template
Had to add an `unsafe` function to be able to translate text with HTML
fragments in it, although the fragments are added back with printf
because the login link is actually not translatable.
2023-06-16 10:58:40 +02:00
jordi fita mas 3af40cc7bc Update weasyprint parameters for version 57.2
Debian 12 (bookworm) has upgraded its weasyprint version and it no
longer includes the --format parameter, because now it only can output
to PDF.
2023-06-15 23:16:53 +02:00
jordi fita mas 6732d654a4 Reduce the amount of useless data in request’s log
I tried to have a log line that uses the “common” format from Apache,
because i thought that it would help me reuse regexps i have defined for
fail2ban filters and such.

However, it makes no much sense.

For once, i was repeating the date and time: log.Printf already does
that for me.

And, second, i do not need that data in Numerus’ log because i always
run it behind a proxy that _has_ a “common”-formatted log file, so
there is no need for me to repeat all that data again.

What i need is the IP, to know whether remotedAdd() function works as
expected; the method, to check that the override does its job; the path,
to know what resource the browser requested; the response status code,
so that i do not need to open the browser console for that; the response
size, to keep on eye that i do not return a lot of data; and the
total response time, to realize how long my unoptimized SQL queries
slows the application down.

The rest, Apache should do its job and record it in its log file for
fail2ban and whatever i need the logs for in the future.
2023-06-13 16:05:40 +02:00
jordi fita mas eb207a01fc Log requests to stdout
This is so that i can have two output files: the one with accesses
(stdout) and the one with errors (stderr).
2023-06-13 15:08:23 +02:00
jordi fita mas d79ddc6731 Log the remote address, and only trust localhost proxies
I need the actual remote address to add fail2ban rules for it, but i
also to not want everyone to be able to fake X-Forward-For HTTP headers.
Which can contain multiple ip addresses, by the way, so i have to get
only the first one, as the others will be the proxies that the request
has been (re)forwarded to.
2023-06-13 14:21:54 +02:00
jordi fita mas dde4395888 Add the most minimal home page design
This is so that Oriol can start working on it.
2023-06-11 22:24:25 +02:00
jordi fita mas a16f696be5 Allow to create an invoice from the data of a quotation 2023-06-10 20:46:03 +02:00
jordi fita mas f43949dd43 Add quote number formatting and next number field to tax details
The same as for invoices: to allow people to have their own numbering
scheme, and for these that start using the program in the middle of the
current year.
2023-06-09 12:43:50 +02:00
jordi fita mas 6c3a3ff232 Allow empty contact and payment method for quotes
I have to use a value to be used as “none” for payment method and
contact.  In PL/pgSQL add_quote and edit_quote functions, that value is
NULL, while in forms it is the empty string.  I can not simply pass the
empty string for either of these fields because PL/pgSQL expects
(nullable) integers, and "" is not a valid integer and is not NULL
either.  A conversion is necessary.

Apparently, Go’s nil is not a valid representation for SQL’s NULL with
pgx, and had to use sql.NullString instead.

I also needed to coalesce contact’s VATIN and phone, because null values
can not be scanned to *string.  I did not do that before because
`coalesce(vatin, '')` throws an error that '' is not a valid VATIN and
just left as is, wrongly expecting that pgx would do the job of leaving
the string blank for me.  It does not.

Lastly, i can not blindly write Quotee’s tax details in the quote’s view
page, or we would see the (), characters for the empty address info.
2023-06-08 13:05:41 +02:00
jordi fita mas ba6f51ac5d Fix getting quote’s terms and conditions from form and database 2023-06-08 12:50:16 +02:00
jordi fita mas 9931796744 Add HTTP controller and view to add quotes
It still does not support quotes without contact or payment.
2023-06-07 16:35:31 +02:00
jordi fita mas 083d14e324 Allow to change the current year’s invoice number counter
This is for new users that do not start using the application from the
beginning of the current fiscal year and, therefore, need to create
invoices starting from a specific number.

I had to change the constraint on the currval to allow zero, otherwise
it would not be possible to set 1 as the next number, because users
can also not delete the row.
2023-05-31 20:01:00 +02:00
jordi fita mas 8529da1615 Use HTMx to delete and restore invoice products
It is better that way because it works without JavaScript; if HTMx is
not available, it will just use regulars forms.

The problem is that most of the submit buttons where using formaction
to send the request to a different action, and only one button was the
“real” action.  Since i could not pass the formaction to
invoice-product-form template, i have changed the “default” action to
the one with “ancillary” functions.

I have to use a different action to remove for each product because i
can not pass the index to the backend without JavaScript: it only
depends on the button click, that already has a name for the action.
Thus, in a way, i have “merged” the action and the index in a single
name.
2023-05-29 00:01:11 +02:00
jordi fita mas 07a28639f2 There is no need for array_to_string() in tags 2023-05-27 21:36:10 +02:00
jordi fita mas 27a266097a Do not use pointer to point when retrieving tags to edit
It is the same with go 16, but with Debian’s go 15 it fails and
I believe, but i am not sure, this is the reason.
2023-05-27 20:51:36 +02:00
jordi fita mas 19f81128ec Keep the invoice number when requesting an update while editing 2023-05-26 14:02:39 +02:00
jordi fita mas bec1305e8a Add an empty product to blank invoice form
There is no point in creating a new invoice without products, thus we
were forcing users to always use the “Add product” button for no reason
other than it was easier for me….

I wanted to add the product inside ServeInvoice, when the slug is “new”,
but then it tried to compute the invoice total without price or quantity
and it failed.  Thus, i add that product after it has done the
computation query.
2023-05-26 13:33:49 +02:00
jordi fita mas cbe868b6d6 Remove “shadow” variables inside invoices.ServeInvoice 2023-05-26 13:30:45 +02:00
jordi fita mas 5cae0efe8f Fix validation of product ID for invoice products that have none
For some reason, i assumed that if the invoice product has and ID, that
is it comes from the database, it must also have a product ID, which is
incorrect, because we allow invoice lines with products not added to the
product relation.

I am using zero to mean “no product ID”, so now that validation has to
include the zero as well.
2023-05-22 11:16:21 +02:00
jordi fita mas bbabf5c733 Use a null as product ID when adding new products to invoices
Otherwise, pgx (rightfully) tries to convert a "" into a integer, as
this is the field’s type, cannot, and panics with an error.

Added a IntegerOrNull method to FormField because this is exactly the
same that happens with the invoiceProductId, and made no sense to have
to do the logic twice, or in a function inside form.
2023-05-22 11:06:06 +02:00
jordi fita mas 46b079cb0b Add tooltips to the SVG chart with date and amount 2023-05-21 19:22:46 +02:00
jordi fita mas 02a4fad443 Refactor a bit the code that draws SVG polylines in circles 2023-05-21 18:59:42 +02:00
jordi fita mas eb47988464 Add a background rectangle to the chart and fix NaN when max = 0 2023-05-21 00:14:48 +02:00
jordi fita mas 39b0b801b2 Add income and expenses chart in SVG 2023-05-20 15:53:59 +02:00
jordi fita mas d1b978054b Fix dashboard period ranges and add previous month and quarter options
Oriol told me what he actually wants: a way to see the current month,
quarter, and year for both double-check that the taxes form are filled
in correct and to see whether the business is doing well.  This is
specially important for the quarter period, as he has to fill taxes
each quarter.  Thus, the “last 90 days” thing i did was easier for me,
but completely useless for him.

We also decided to add previous month and previous quarter options
because it would be unfair to expect users check that data exactly the
last day or “lose access” to it.
2023-05-19 14:05:57 +02:00
jordi fita mas 121f03b63c Add expense_tax_amount to properly compute the net income 2023-05-18 12:36:18 +02:00
jordi fita mas 987a99e0df Add a period filter for the dashboard
I do not yet know whether Oriol wants a YTD or MAT period, and i went
for the easiest for me: everything is MAT.
2023-05-17 12:05:30 +02:00
jordi fita mas f68aba1387 Coalesce individual dashboard results to 0 when computing net income 2023-05-16 15:24:14 +02:00
jordi fita mas ef1003a685 Coalesce dashboard results to 0 2023-05-16 15:14:20 +02:00
jordi fita mas ce42880697 Begin the dashboard with expenses, gross income, net income, and taxes
For now i use a too-long SQL query for that, but will probably replace
it with a view.  I have to check that it is correct before i do so,
however.
2023-05-16 14:56:49 +02:00
jordi fita mas 7921b9cf80 Add attach_to_expense SQL function
Just to avoid SQL “logic” in Go source.
2023-05-15 12:38:40 +02:00
jordi fita mas 3161d54aba Add expense’s file input to new and edit forms
I had to change MethodOverrider to check whether the form is encoded as
multipart/form-data or i would not be able to get the method field from
forms with files.

For now i add the file manually, i.e., outside add_expense and
edit_expense PL/pgSQL functions, because it was faster for me, but i
will probably add an attach_to_expense function, or something like that,
to avoid having the whole ON CONFLICT logic inside Golang—this belongs
to the database.
2023-05-14 18:46:16 +02:00
jordi fita mas f639602170 Add contact’s inline form for tags 2023-05-12 11:32:39 +02:00
jordi fita mas 970340277d Add the contact filter form 2023-05-10 18:56:07 +02:00
jordi fita mas 856ddde00e Add the inline form for product tags 2023-05-09 12:18:31 +02:00
jordi fita mas f0f98e200c Add inline tag form for expenses 2023-05-08 12:58:54 +02:00
jordi fita mas 664088c748 Add filter form to expenses 2023-05-07 22:49:52 +02:00
jordi fita mas 49c41681ce Add PUT method to expense’s URL and call edit_expense 2023-05-05 10:59:35 +02:00
jordi fita mas 4a9c3748cd Guard a StatusUnprocessableEntity with check for HTMx request
Otherwise, HTMx would just ignore the HTML returned by the server and
dispatch an error.
2023-05-05 10:57:48 +02:00
jordi fita mas 73497eb051 Add missing return on not found for edit product 2023-05-05 10:54:40 +02:00
jordi fita mas 55d650bd62 Add expenses’ index and add form 2023-05-03 12:46:25 +02:00
jordi fita mas 97ad76d82c Deduplicate the SQL for getting the options for customer’s select field 2023-05-03 12:40:07 +02:00
jordi fita mas 4162da3a06 Force Content-Type to text/html when rendering a template
By chance, i found out that sometimes Go returned a Content-Type header
of text/plain for some responses to HTMx request.  Go’s documentation
for http.ResponseWriter.Write sheds some light to this issue:

> If the Header does not contain a Content-Type line, Write adds a
> Content-Type set to the result of passing the initial 512 bytes of
> written data to DetectContentType.

http.DetectContentType has “sniff signatures” for the most common HTML
elements, such as `<BODY`, `<P`, or even `<!--`, but when the template
only has elements not in that list, the text “text sniff signature”
kicks in because the content does not contain binary data bytes, as
specified in [0], §7.1, step 9.

I can not change mustRenderTemplate’s wr parameter, and its callers’,
to be of type http.ResponseWriter because mustWriteInvoicePdf writes
the template to a pipe object, which is not of this type.  Thus, i have
to resort to type assertion inside the method.

[0]: https://mimesniff.spec.whatwg.org/#identifying-a-resource-with-an-unknown-mime-type
2023-04-29 15:59:26 +02:00
jordi fita mas d941adcdfe Trigger a recompute when price, quantity, discount, or vat changes
I had to add the correct change event to the select in order for this to
work, too; in tags it was already done, i and did something very
similar.
2023-04-28 00:22:28 +02:00
jordi fita mas a06bc3df58 Use slugs too to select invoice products without JavaScript
The product search returns a list of products using its slug as the
“external key”, because i do not want people seeing the id in links,
and the search product list is just a different rendering of the product
index table.

However, now i had two almost identical select queries for product,
one using the product_id and the other its slug, the former intended for
the form to select products using checkboxes—the one non-JavaScript
users see—and the latter for the product search.

Using the slug in both forms i can now simplify the code and have a
single query.
2023-04-26 13:50:02 +02:00
jordi fita mas f2a0cd7d94 Move back most dialogs to regular (but still boosted) pages
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.
2023-04-25 15:28:55 +02:00
jordi fita mas c5fba3246e Fix label for products’ name filter 2023-04-25 15:21:34 +02:00
jordi fita mas e93a798223 Accept “invalid” quantity, price, and discount on invoice update
This is for “free products”, where the user adds an invoice row, does
not select a product from the search, and clicks the update button.
Numerus should select “appropriate” values for those that are left
unspecified.

I also no longer require the product_id to be an integer; if it is
empty, then it is assumed to be a “free product”.
2023-04-24 20:40:10 +02:00
jordi fita mas 7d895fe5f9 Use HTMx to add product rows “inline” in the invoice form
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.
2023-04-24 02:00:38 +02:00
jordi fita mas 2ced61d304 Add the product filter form
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.
2023-04-23 03:20:01 +02:00
jordi fita mas 90982b49ff Move the product_id field from invoice_product to a separate table
We are going to allow invoices with products that are not (yet) inserted
into the products table.

We always allowed to have products in invoices with a totally different
name, description, price, and whatnot, but until now we had the product
id in these invoice lines for statistics purposes.

However, Oriol raised the concern that this requires for the products
to be inserted before we can create an invoice with them, and we do not
plan to have a “create product while invoicing” feature, thus it would
mean that people would need to cancel the new invoice, create the new
product, and then start the invoice again from scratch.

The compromise is to allow products in the invoice that do not have a
product_id, meaning that at the time the invoice was created they were
not (yet) in the products table.  Oriol sees this stop-invoice-create-
product issue more important than the accurate statistics of product
sales, as it will probably be only one or two units off, anyway.

I did not want to allow NULL values to the invoice product’s product_id
field, because NULL means “dunno” instead of “no product”, so i had to
split that field to a separate table that relates an invoice product
with a registered product.
2023-04-19 19:30:12 +02:00
jordi fita mas 835bab357e Only return HTTP 422 while validating the invoice’s form if not HTMx 2023-04-19 19:18:29 +02:00
jordi fita mas 754da87e45 Add validation for minimum length of contact’s name 2023-04-18 21:01:29 +02:00
jordi fita mas 149557e42e “Integrate” the tags’ condition into the input field
We have reconsidered the toggle thing and instead moved the selection
into a little menu on top of the input, like the input’s label does à
la Material Design.

I just moved the checkboxes into a new details, that works as a menu,
but i had to add the type="search" to the existing input in the tags
field, or the CSS would style the checkboxes as well.

I do not do anything when the checkbox selection changes because that
already triggers a POST to the server that returns the new HTML with
the checkbox changed, and the JavaScript only has to retrieve that new
structure, exactly as it does in the initial rendering.

Since we want to add a little description to the options, i no longer
can use the same SelectOption in ToggleField, even though i could have
reused the Group element, but that felt wrong.
2023-04-16 19:01:11 +02:00
jordi fita mas 46fe90c867 Remove the fuzzy tag of a Catalan translation 2023-04-15 20:45:19 +02:00
jordi fita mas 5e01965d7e Replace use of <select> for tags “and” and “or” with checkboxes
I realized that using a select for just two, short, options is overkill:
the select and its options use a lot more real state than the two
radios, which can have tooltips (not yet, though).

Since i am going to replace this field with a custom element that has
a toggle-like aspect, i already added the is="numerus-toggle" attribute
and use it for stying the non-JavaScript field.
2023-04-15 04:05:59 +02:00
jordi fita mas 8f7933ffe2 Allow to select AND or OR for tags filter
This is because Oriol thinks that there may be cases where you want to
search invoices and such that have any of the selected labels, not all
of them, so we agreed on adding an option to choose.

The idea is that it will be a toggle, but this requires JavaScript and
this commit adds it as a dropdown as a first non-JavaScript step.
2023-04-14 02:40:48 +02:00
jordi fita mas d20573aa99 Allow editing invoice tags inline from the index table
I use the same pattern as HTMx’s “Click to Edit” example[0], except that
my edit form is triggered by submit and by focus out of the tags input.

I could not, however, use the standard focus out event because it would
also trigger when removing a tag with the mouse, as for a moment the
remove button has the focus and the search input dispatches a bubbling
focusout.  I had to resort to a custom event for that, but i am not
happy with it.

The autofocus attribute seems to do nothing in this case, so i need to
manually change the focus to the new input with JavaScript.  However,
this means that i can not use the same input ID for all the forms
because getElementById would always return the first in document order,
changing the focus to that same element and automatically submit the
form due to focus out.  That’s why in this form i append the invoice’s
slug to the input’s ID.

Finally, this is the first time i am using an HTMx-only solution and i
needed a way to return back just the HTML for the <td>, without <title>,
breadcrumbs, or <dialog>.  In principle, the template would be the
“layout”, but then i would need to modify everything to check whether
the template file is empty, or something to that effect, so instead i
created a “standalone” template for these cases.

[0]: https://htmx.org/examples/click-to-edit/
2023-04-11 10:46:27 +02:00
jordi fita mas bc48dd4089 Replace tag relations with array attributes
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
2023-04-07 21:31:35 +02:00
jordi fita mas 4b4d7ad87d Refactor duplicated logic in invoice for HTMx trigger and location 2023-04-06 12:20:40 +02:00
jordi fita mas 2df6947577 Create constants for the HTMX request and response headers used
It works better with an IDE, and less chance of mistyping something
without notice.
2023-04-06 12:07:20 +02:00
jordi fita mas 233e7723c3 Use HX-Location instead of HX-Refresh when editing invoices
This makes reload only the <main> portion of the page, instead of the
whole thing, which to me looks faster; haven’t really measured it.

Like with duplicate, i had to add the location query argument when
inside the view page in order to return back to the same page, not the
index.
2023-04-05 10:29:03 +02:00
jordi fita mas dbfa58699c Show the duplicate invoice form in a dialog
Had to add a new hidden field to the form to know whether, when the
request is HTMx-triggered, to refresh the page, as i do when duplicating
from the index, or redirect the client to the new invoice’s view page,
but only if i was duplicating from that same page, not the index.

Since i now have to target main when redirecting to the view page, so
i had to add a location structure with the required json fields and all
that, when “refreshing” i actually tell HTMx to open the index page
again, which seems faster, now that i am used to boosted links.
2023-04-04 14:39:55 +02:00
jordi fita mas b6668e72ef Trigger filter form on change and search, as well as submit as before
Changed the invoice number field’s type to search to add the delete icon
on Chromium.  Firefox does not add that icon, but i do not care; it is
still better that type="text".

Had to emit the change event to the numerus-tag field, otherwise the
form would not detect the change.

I also can not use keyup as a trigger because the changed modifier can
not be used in the <form>, as nothing ever changes, i do not know how to
trigger the form from children (i.e., data-hx-trigger on the <input>
does nothing), and i can not trigger for just any keyup, or i would
make the request even if they only moved the cursor with the arrow keys,
which is very confusing as Firefox resets the position (this may be due
the fact that i reload the whole <main>, but still).
2023-04-03 12:45:15 +02:00
jordi fita mas ca0298213e Filter out nulls in tax array when viewing invoice
This is for the unusual case of products without taxes, that PostgreSQL
by default would generate an array with a single null value in it, but
then pgx would not be able to set that null to the string variable.
2023-04-01 16:07:26 +02:00
jordi fita mas c453715ee1 Remove the number field from new invoice form
Initially, this field was meant to be left almost always blank, except
for when we deleted invoiced and had to “replace” its number with a new
invoice; using the automatic numbering in this cas would not “fill in”
the missing number in the sequence.

However, we decide to not allow removing invoicer not edit their
numbers, therefore, if everything goes as planned, there should not be
any gap in the sequence, and that field is rendered useless.

Oriol suggested making it a read-only field, both for new and edit
forms, but i do not think it makes sense to have a field if you can not
edit it at all, specially in the new invoice dialog, where it would
always be blank.  In the edit form we already show the number in the
title and breadcrumbs, thus no need for the read-only field as
reference.

I still keep a Number member to the form struct, but is now a string
(kind of “a read-only field”, in a way) and just to be written in the
title or breadcrumbs.  I did not like the idea of adding a new SQL
query just for that value.
2023-04-01 15:57:56 +02:00
jordi fita mas 5717a5b9ed Put new invoice and edit invoice forms into a dialog
In this case i have to use the same id for the dialog content in all
pages because, for now, there are a couple of forms that need to replace
it on submit—the new/edit form and the product selection form.

Unfortunately, HTMx does not have support for `formaction` attribute at
this point, so i had to use the workaround described in [0].

[0] https://github.com/bigskysoftware/htmx/issues/623
2023-03-31 13:01:26 +02:00
jordi fita mas b7881c505f Add filters form for invoices
Instead of using links in the invoice tags, that we will replace with a
“click-to-edit field”, with Oriol agreed to add a form with filters that
includes not only the tags but also dates, customer, status, and the
invoice number.

This means i now need dynamic SQL, and i do not think this belongs to
the database (i.e., no PL/pgSQL function for that).  I have looked at
query builder libraries for Golang, and did not find anything that
suited me: either they wanted to manage not only the SQL query but also
all structs, or they managed to confuse Goland’s SQL analyzer.

For now, at least, i am using a very simple approach with arrays, that
still confuses Goland’s analyzer, but just in a very specific part,
which i find tolerable—not that their analyzer is that great to begin
with, but that’s a story for another day.
2023-03-29 16:16:31 +02:00
jordi fita mas a5dc434aa2 Boost the links in the invoice table 2023-03-28 09:57:48 +02:00
jordi fita mas 2417b4ebd2 Remove the link to edit contact from the invoice table
We agreed with Oriol that this link would only serve to confuse people.
I initially added the link because i thought it was a shame to have to
navigate to the contact section to look or change the info of a customer
that you have an invoice for in front of you.  However, it makes little
sense to be able to edit the contact from both sections, and we do not
have a “view page” for contacts to link to, thus the removal.
2023-03-28 09:50:19 +02:00
jordi fita mas 47c23fc4cc Boost the products’ section links and forms
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.
2023-03-27 09:44:04 +02:00
jordi fita mas a1f70ff654 Add tags for products too
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`.
2023-03-26 13:51:57 +02:00
jordi fita mas 4131602fa3 Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).

I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.

Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions.  They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 01:32:53 +01:00
jordi fita mas 41ce5af2ed Boost the main navigation links with HTMx
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”.
2023-03-23 10:55:02 +01:00
jordi fita mas 6e081a1846 Put the edit contact form into a dialog with HTMx
Had to change the data context for that template to include the Slug,
so that the <form> element can set the correct `action` instead of
using the current URI, as it is no longer “correct” (form-wise) when
using HTMx.
2023-03-23 10:46:14 +01:00
jordi fita mas b07fe6cfa2 Show the add contact form in a modal dialog 2023-03-22 14:59:54 +01:00
jordi fita mas b1e3afc48b Show the tax details form in a dialog using HTMx 2023-03-21 11:58:54 +01:00
jordi fita mas 9e757cb9f4 Show the profile form in a dialog using HTMx
Had to split the actual page content and the breadcrumbs because they
do not belong in a dialog.  However, i had to change all templates to
do that.
2023-03-20 13:09:52 +01:00
jordi fita mas 356d0a0892 Handle case of no tag given for invoice
In that case, strings.Split() return an array with a single empty string
element, that does not pass the domain check for tag_name in the
database.

And an invoice with no tags would get an array of a single NULL in
array_agg, so i had to convert it to an empty string in order for it
to work as expected.
2023-03-19 23:10:01 +01:00
jordi fita mas 8efae0485e Add the edit form for invoices
I had to change the way /invoices/new and /invoices/batch are handled,
because httprouter was not happy with the new POST /invoices/:slug/edit
route, claiming that /invoices/:slug conflicts with the previously
existing routes.

I also could not make it work with the PATCH method, even though i
correctly added the patchMethod override function, therefore editing
invoices is also weird because i have to take into account the “quick”
invoice status change.

I use the same form for both new and edit invoices, because the only
changes are that we can not edit the invoice date and number, by
Oriol’s design, but must be able to change the status; very similar
forms.
2023-03-13 15:00:35 +01:00
jordi fita mas 1ab48d2947 Add the String() method to InputField 2023-03-13 14:55:10 +01:00
jordi fita mas c685fc496b Correctly format the scanned value of date and time InputFields 2023-03-13 14:54:28 +01:00
jordi fita mas 2bc05e948c Add invoice tags
I followed the same restrictions as Gitea’s topics, arbitrarily, because
if it is enough for repositories it should be for invoices too,
apparently.
2023-03-10 14:02:55 +01:00
jordi fita mas 5dedaefc22 Add button to download many invoices as PDF in a ZIP archive 2023-03-09 12:11:53 +01:00
jordi fita mas 0c8edb9cae Add option to duplicate an invoice
With Oriol we agreed that a duplicate is just the new invoice form
prefilled with the data from another invoices, but without the number or
the date.
2023-03-08 11:26:02 +01:00
jordi fita mas d725dcf529 Add missing call to PaymentMethod.FillValue 2023-03-08 11:19:59 +01:00
jordi fita mas 039bf3abbd Add the “menu” to change invoice statuses 2023-03-07 11:52:09 +01:00
jordi fita mas f77f933e4a Add the payment method to invoices 2023-03-05 18:50:57 +01:00
jordi fita mas 93e92cf62d Move call to mustGetTaxOptions of AddProducts
I was trying to query the database while the connection was still busy
quering the products.
2023-03-05 18:43:22 +01:00
jordi fita mas 31ef3ea47a Add company’s default payment method
I had to use a deferrable foreign key because the payment methods have
a reference to the company, and the company now a circular reference to
payment method.
2023-03-04 22:15:52 +01:00
jordi fita mas 9894925742 Add the payment method relation and corresponding form 2023-03-03 16:49:06 +01:00
jordi fita mas b84f1774f9 Replace static legal disclaimer with a database field 2023-03-02 10:24:44 +01:00
jordi fita mas d6034ad732 Add discount and tax classes columns to invoice
This was actually the (first) reason we added the tax classes: to show
them in columns on the invoice—without the class we would need a column
for each tax rate, even though they are the same tax.

The invoice design has the product total with taxes at the last column,
above the tax base, that i am not so sure about, but it seems that it
has not brought any problem whatsoever so far, so it remains as is.

Had to reduce the invoice’s font size to give more space to the table
or the columns would be right next to each other.  Oriol also told me
to add more vertical spacing to the table’s footer.
2023-03-01 14:08:12 +01:00