2023-02-11 21:16:48 +00:00
|
|
|
{{ define "title" -}}
|
|
|
|
{{( pgettext "Invoices" "title" )}}
|
|
|
|
{{- end }}
|
|
|
|
|
2023-03-20 12:09:52 +00:00
|
|
|
{{ define "breadcrumbs" -}}
|
|
|
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}}
|
2023-07-07 09:16:43 +00:00
|
|
|
<nav data-hx-target="main">
|
|
|
|
<p data-hx-boost="true">
|
2023-02-11 21:16:48 +00:00
|
|
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
|
|
|
<a>{{( pgettext "Invoices" "title" )}}</a>
|
|
|
|
</p>
|
2023-03-09 11:11:53 +00:00
|
|
|
|
|
|
|
<form id="batch-form" action="{{ companyURI "/invoices/batch" }}" method="post">
|
|
|
|
{{ csrfToken }}
|
Add option to export the list of quotes, invoices, and expenses to ODS
This was requested by a potential user, as they want to be able to do
whatever they want to do to these lists with a spreadsheet.
In fact, they requested to be able to export to CSV, but, as always,
using CSV is a minefield because of Microsoft: since their Excel product
is fucking unable to write and read CSV from different locales, even if
using the same exact Excel product, i can not also create a CSV file
that is guaranteed to work on all locales. If i used the non-standard
sep=; thing to tell Excel that it is a fucking stupid application, then
proper applications would show that line as a row, which is the correct
albeit undesirable behaviour.
The solution is to use a spreadsheet file format that does not have this
issue. As far as I know, by default Excel is able to read XLSX and ODS
files, but i refuse to use the artificially complex, not the actually
used in Excel, and lobbied standard that Microsoft somehow convinced ISO
to publish, as i am using a different format because of the mess they
made, and i do not want to bend over in front of them, so ODS it is.
ODS is neither an elegant or good format by any means, but at least i
can write them using simple strings, because there is no ODS library
in Debian and i am not going to write yet another DEB package for an
overengineered package to write a simple table—all i want is to say
“here are these n columns, and these m columns; have a good day!”.
Part of #51.
2023-07-18 11:29:36 +00:00
|
|
|
{{ with .Filters }}
|
|
|
|
{{ template "hidden-select-field" .Customer }}
|
|
|
|
{{ template "hidden-select-field" .InvoiceStatus }}
|
|
|
|
{{ template "hidden-field" .FromDate }}
|
|
|
|
{{ template "hidden-field" .ToDate }}
|
|
|
|
{{ template "hidden-field" .InvoiceNumber }}
|
|
|
|
{{ template "hidden-field" .Tags }}
|
|
|
|
{{ template "hidden-field" .TagsCondition }}
|
|
|
|
{{ end }}
|
2023-03-09 11:11:53 +00:00
|
|
|
<p>
|
2023-05-24 10:13:09 +00:00
|
|
|
{{ template "filters-toggle" }}
|
2023-03-09 11:11:53 +00:00
|
|
|
<button type="submit"
|
|
|
|
name="action" value="download"
|
|
|
|
>{{( pgettext "Download invoices" "action" )}}</button>
|
Add option to export the list of quotes, invoices, and expenses to ODS
This was requested by a potential user, as they want to be able to do
whatever they want to do to these lists with a spreadsheet.
In fact, they requested to be able to export to CSV, but, as always,
using CSV is a minefield because of Microsoft: since their Excel product
is fucking unable to write and read CSV from different locales, even if
using the same exact Excel product, i can not also create a CSV file
that is guaranteed to work on all locales. If i used the non-standard
sep=; thing to tell Excel that it is a fucking stupid application, then
proper applications would show that line as a row, which is the correct
albeit undesirable behaviour.
The solution is to use a spreadsheet file format that does not have this
issue. As far as I know, by default Excel is able to read XLSX and ODS
files, but i refuse to use the artificially complex, not the actually
used in Excel, and lobbied standard that Microsoft somehow convinced ISO
to publish, as i am using a different format because of the mess they
made, and i do not want to bend over in front of them, so ODS it is.
ODS is neither an elegant or good format by any means, but at least i
can write them using simple strings, because there is no ODS library
in Debian and i am not going to write yet another DEB package for an
overengineered package to write a simple table—all i want is to say
“here are these n columns, and these m columns; have a good day!”.
Part of #51.
2023-07-18 11:29:36 +00:00
|
|
|
<button type="submit"
|
|
|
|
name="action" value="export"
|
|
|
|
>{{( pgettext "Export list" "action" )}}</button>
|
2023-07-07 09:16:43 +00:00
|
|
|
<a class="primary button" data-hx-boost="true"
|
2023-03-09 11:11:53 +00:00
|
|
|
href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a>
|
|
|
|
</p>
|
|
|
|
</form>
|
2023-02-11 21:16:48 +00:00
|
|
|
</nav>
|
2023-03-20 12:09:52 +00:00
|
|
|
{{- end }}
|
2023-02-11 21:16:48 +00:00
|
|
|
|
2023-03-20 12:09:52 +00:00
|
|
|
{{ define "content" }}
|
2023-02-11 21:16:48 +00:00
|
|
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}}
|
2023-05-24 10:13:09 +00:00
|
|
|
<form class="filters" method="GET" action="{{ companyURI "/invoices"}}"
|
|
|
|
data-hx-target="main" data-hx-boost="true" data-hx-trigger="change,search,submit"
|
|
|
|
aria-labelledby="filters-toggle"
|
|
|
|
>
|
2023-07-16 18:56:11 +00:00
|
|
|
<fieldset>
|
|
|
|
{{ with .Filters }}
|
|
|
|
{{ template "select-field" .Customer }}
|
|
|
|
{{ template "select-field" .InvoiceStatus }}
|
|
|
|
{{ template "input-field" .FromDate }}
|
|
|
|
{{ template "input-field" .ToDate }}
|
|
|
|
{{ template "input-field" .InvoiceNumber }}
|
|
|
|
{{ template "tags-field" .Tags | addTagsAttr (print `data-conditions="` .TagsCondition.Name `-field"`) }}
|
|
|
|
{{ template "toggle-field" .TagsCondition }}
|
|
|
|
{{ end }}
|
|
|
|
</fieldset>
|
2023-05-24 10:13:09 +00:00
|
|
|
<noscript>
|
|
|
|
<button type="submit">{{( pgettext "Filter" "action" )}}</button>
|
|
|
|
</noscript>
|
2023-07-16 18:56:11 +00:00
|
|
|
{{ if .Filters.HasValue }}
|
|
|
|
<a href="{{ companyURI "/invoices" }}" class="button">{{( pgettext "Reset" "action" )}}</a>
|
|
|
|
{{ end }}
|
2023-05-24 10:13:09 +00:00
|
|
|
</form>
|
2023-05-23 21:13:21 +00:00
|
|
|
<table id="invoice-list">
|
2023-02-11 21:16:48 +00:00
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>{{( pgettext "All" "invoice" )}}</th>
|
|
|
|
<th>{{( pgettext "Date" "title" )}}</th>
|
|
|
|
<th>{{( pgettext "Invoice Num." "title" )}}</th>
|
|
|
|
<th>{{( pgettext "Customer" "title" )}}</th>
|
|
|
|
<th>{{( pgettext "Status" "title" )}}</th>
|
2023-03-10 13:02:55 +00:00
|
|
|
<th>{{( pgettext "Tags" "title" )}}</th>
|
2023-02-22 13:39:38 +00:00
|
|
|
<th>{{( pgettext "Amount" "title" )}}</th>
|
2024-08-26 12:47:22 +00:00
|
|
|
<th>{{( pgettext "Collections" "title" )}}</th>
|
2023-03-08 10:54:06 +00:00
|
|
|
<th>{{( pgettext "Actions" "title" )}}</th>
|
2023-02-11 21:16:48 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
2023-02-12 20:06:48 +00:00
|
|
|
{{ with .Invoices }}
|
|
|
|
{{- range $invoice := . }}
|
|
|
|
<tr>
|
2023-03-09 11:11:53 +00:00
|
|
|
{{ $title := .Number | printf (pgettext "Select invoice %v" "action") }}
|
|
|
|
<td><input type="checkbox" form="batch-form"
|
|
|
|
name="invoice" value="{{ .Slug }}"
|
|
|
|
aria-label="{{ $title }}"
|
|
|
|
title="{{ $title }}"/></td>
|
2023-02-12 20:06:48 +00:00
|
|
|
<td>{{ .Date|formatDate }}</td>
|
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 14:16:31 +00:00
|
|
|
<td><a href="{{ companyURI "/invoices/"}}{{ .Slug }}" data-hx-target="main"
|
|
|
|
data-hx-boost="true">{{ .Number }}</a></td>
|
2023-03-28 07:50:19 +00:00
|
|
|
<td>{{ .CustomerName }}</td>
|
Remove status parameter from edit_expense and forms
For the same reasons as with expenses[0], users are no longer expected
to manually set invoice status, and is now linked to their collections.
In this case, however, we had to remove the ‘sent’ and ‘unpaid’ status
options, because these _should_ only be set manually, as there is no
way for the application to know when to set them. Thus, there could
be inconsistencies, like invoices set to ‘unpaid’ when they actually
have collections, or invoices that were ‘sent’, then transitioned to
‘partial’/‘paid’ due to a collection, but then reset to ‘created’ if the
collection was deleted.
[0]: ac0143b2b0b772e155ef8525e147786700403578
2024-08-26 08:42:38 +00:00
|
|
|
<td class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</td>
|
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 08:46:27 +00:00
|
|
|
<td data-hx-get="{{companyURI "/invoices/"}}{{ .Slug }}/tags/edit"
|
|
|
|
data-hx-target="this"
|
|
|
|
data-hx-swap="outerHTML"
|
|
|
|
>
|
2023-03-10 13:02:55 +00:00
|
|
|
{{- range $index, $tag := .Tags }}
|
|
|
|
{{- if gt $index 0 }}, {{ end -}}
|
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 14:16:31 +00:00
|
|
|
{{ . }}
|
2023-03-10 13:02:55 +00:00
|
|
|
{{- end }}
|
|
|
|
</td>
|
2023-02-22 13:39:38 +00:00
|
|
|
<td class="numeric">{{ .Total|formatPrice }}</td>
|
2023-07-06 09:49:36 +00:00
|
|
|
{{- $title = .Number | printf (pgettext "Download invoice %s" "action") -}}
|
2024-08-26 12:47:22 +00:00
|
|
|
<td class="invoice-download">
|
|
|
|
<a href="{{ companyURI "/invoices"}}/{{ .Slug }}/collections"
|
|
|
|
title="{{( pgettext "Collections" "title" )}}"
|
|
|
|
aria-label="{{( pgettext "Collections" "title" )}}"
|
|
|
|
>
|
|
|
|
<i class="ri-bank-card-2-line"></i>
|
|
|
|
</a>
|
|
|
|
</td>
|
2023-03-08 10:54:06 +00:00
|
|
|
<td class="actions">
|
|
|
|
<details class="menu">
|
2023-07-06 09:49:36 +00:00
|
|
|
{{- $title = .Number | printf (gettext "Actions for invoice %s") -}}
|
|
|
|
<summary aria-label="{{ $title }}"><i class="ri-more-line"></i></summary>
|
2023-03-08 10:54:06 +00:00
|
|
|
<ul role="menu" class="action-menu">
|
2023-03-13 14:00:35 +00:00
|
|
|
<li role="presentation">
|
2023-03-31 11:01:26 +00:00
|
|
|
<a role="menuitem" href="{{ companyURI "/invoices"}}/{{ .Slug }}/edit"
|
2023-04-25 13:28:55 +00:00
|
|
|
data-hx-target="main" data-hx-boost="true"
|
2023-03-31 11:01:26 +00:00
|
|
|
>
|
2023-03-13 14:00:35 +00:00
|
|
|
<i class="ri-edit-line"></i>
|
|
|
|
{{( pgettext "Edit" "action" )}}
|
|
|
|
</a>
|
|
|
|
</li>
|
2023-03-08 10:54:06 +00:00
|
|
|
<li role="presentation">
|
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 12:39:55 +00:00
|
|
|
<a role="menuitem" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}"
|
2023-04-25 13:28:55 +00:00
|
|
|
data-hx-target="main" data-hx-boost="true"
|
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 12:39:55 +00:00
|
|
|
>
|
2023-03-08 10:54:06 +00:00
|
|
|
<i class="ri-file-copy-line"></i>
|
|
|
|
{{( pgettext "Duplicate" "action" )}}
|
|
|
|
</a>
|
|
|
|
</li>
|
2024-08-21 09:22:53 +00:00
|
|
|
<li role="presentation">
|
2024-08-26 12:47:22 +00:00
|
|
|
<a role="menuitem"
|
|
|
|
href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf"
|
|
|
|
download="{{ .Number}}-{{ .CustomerName | slugify}}.pdf"
|
|
|
|
><i class="ri-download-line"></i>
|
|
|
|
{{( pgettext "Download invoice" "action" )}}
|
2024-08-21 09:22:53 +00:00
|
|
|
</a>
|
|
|
|
</li>
|
2023-03-08 10:54:06 +00:00
|
|
|
</ul>
|
|
|
|
</details>
|
|
|
|
</td>
|
2023-02-12 20:06:48 +00:00
|
|
|
</tr>
|
|
|
|
{{- end }}
|
|
|
|
{{ else }}
|
|
|
|
<tr>
|
2023-05-14 16:47:16 +00:00
|
|
|
<td colspan="9">{{( gettext "No invoices added yet." )}}</td>
|
2023-02-12 20:06:48 +00:00
|
|
|
</tr>
|
|
|
|
{{ end }}
|
2023-02-11 21:16:48 +00:00
|
|
|
</tbody>
|
2023-06-20 09:33:28 +00:00
|
|
|
{{ if .Invoices }}
|
|
|
|
<tfoot>
|
|
|
|
<tr>
|
|
|
|
<th scope="row" colspan="6">{{( gettext "Total" )}}</th>
|
|
|
|
<td class="numeric">{{ .TotalAmount|formatPrice }}</td>
|
|
|
|
<td colspan="2"></td>
|
|
|
|
</tr>
|
|
|
|
</tfoot>
|
|
|
|
{{ end }}
|
2023-02-11 21:16:48 +00:00
|
|
|
</table>
|
|
|
|
{{- end }}
|