For host, database name, and options i can leave them blank to use the
default value, but for the port i can’t with a SpinBox, since it _must_
have a value.
Qt’s driver uses -1 to mean “default port”, but i can not use the same
approach with the user interface, because there is no port -1, and makes
no sense to allow that value, specially since i then i have to allow 0
as well, which is a reserved port.
I also can not leave the SpinBox with PostgreSQL’s default port, 5432,
because that is slightly different than telling libpq to use the
“default”: if someone uses a ‘service=’ option, it would use the port in
the SpinBox rather than the one in ‘pg_service.conf’, if any.
My solution is to use a checkbox to tell the application “i wan to use a
port number”, and (will) only pass the port number if it is checked. On
the other hand, now i do not have a keyboard shortcut to focus on the
port number field.
I want to allow people to set all options of QSqlDatabase, that way i do
not force the ‘service=camper’ thing, and everyone can use whatever
parameters they see fit.
I plan to store these other options as Settings, because usualy this
should only be done once at setup, and then we only need to input the
username and login to enter. For that same reason, i do not want to
show them all the time; only if the user wants to change anything.
As far as i know, neither QtWidgets nor Qt Quick Controls have anything
that works as Gtk’s Expander, so i had to create that component. HTML
calls it <details>, Apple “disclosure control”[0], and both Microsoft
and Gtk “expander”[1, 2]. I’ve choosen Gtk name, but macOS looks.
[0]: https://developer.apple.com/design/human-interface-guidelines/
disclosure-controls
[1]: https://learn.microsoft.com/en-us/windows/apps/design/controls/
expander
[2]: https://docs.gtk.org/gtk4/class.Expander.html
This is to accept Alt+(whatever has & in front in the label) for labels,
buttons, actions, and whatever requires a nmenonic.
I created an attached property because it is kind of similar to QML’s
Keys property.
Since i only plan to use a single connection, i can use QtSql’s default
connection name.
According to the documentation, i have to make sure that no query or
database object is open when i remove the database. I now use C++
scopes for that, but i need to have a QString declared outside of it to
get the default connection’s name.
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.
Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established. In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.
With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher. I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.
I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.
I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
We forgot that different accommodation types are not always operating on
the whole season calendar, thus we need a specific date for each type.
Someday i will add the field in the administration panel, but for now i
will have to add them by hand, as people are starting to book plots on
dates that are not operating.
I’ve removed the total amount because it is very difficult to get it
with pagination, and customer never saw it (it was from Numerus), thus
they won’t miss it—i hope.
To hold the common logic of detecting pagination, forming the key, and
splitting its values later on.
I can take advantage that a form with action="get" already adds its
fields to the query string to have a common template for pagination. The
only problem is that i have different column spans for different tables,
therefore had to add a colspan to the struct.
It is just the index of bookings in the created state, but we thought it
would make easier to understand the difference between a booking from
a customer not yet confirmed, from a booking confirmed or created by the
staff.
The idea is that we will marshal the payment, send it to the campsite’s
instance by email, and then unmarshal it as a booking, that way we can
have a one way replication from the internal to the public instance with
a way back to send the payments.
For testing purposes, i just create the booking in the same instance.
Had to change the booking relation’s permissions to allow insert from
a guest, much like for payments, because the notification from Redsys
comes as a guest connection. I need this even with all the
marshal/unmarshal shenanigans because not everyone will have an internal
instance, thus need to allow bookings from guest connections.
Most will be families living at the same address. And, if they are not,
it is far easier to replace the incorrect address with the actual,
rather than write the same address to all family members under the same
household.
Copied as much as i could from Numerus, and made as few modifications as
i could to adapt to this code base; it is, quite frankly, a piece of
shit.
We need to be able to create invoices from scratch “just in case”,
apparently, but it is not yet possible to create an invoice from a
booking.
I need to retrieve the values from the database and put them in the
form, like all other forms, but in this case the processing is done as
if it were a new form, because everything comes from the query string
and there is no need to do any extra work then.
Had to move the <footer> from the fields.gohtml to form.gohtml because
then it could not know that it was editing an existing booking. Had to
move the <fieldset> out too, in order to give it an ID and make it
htmx’s target, or it would replace the form, causing even more problems
—the button would disappear then—. The target **must** be in <form>
because it is needed for tis children’s hx-get and for its own hx-put.
At first i thought i would need to keep the second query of draftPayment
in newBookingCart, its caller, and i added these fields to pass back
the parsed or retrieved values, but when i could move that query within
draftPayment i forgot to remove them.
I wanted to use a regular <a>, but apparently rendering that many
anchors is too resource-intensive for Firefox, and it is noticeably
slower. It was even worse, in fact, because i had to have different
content for the main grid and the grid show in the new booking form,
as i did not want to have these links there, and had call a template for
each cell: 3 months × ~30 days × ~100 campsites = 9000 calls!
Using JavaScript for that is shameful, but it does not add much to the
existing markup, and no need for template fuckery.
I am using double-click to follow these links, instead of single click,
because it would be too easy to misclik on the grid, but that forced me
to add `user-select: none` to prevent the selection of text when double-
clicking.
Had to bring the same fields that i have for a payment to booking,
except that some of those should be nullable, because it is unreasonable
to ask front desk to gather all customer data when they have a booking
via phone, for instance.
Therefore, i can not take advantage of the validation for customer data
that i use in the public-facing form, but, fortunately, most of the
validations where in separated functions, thus only had to rewrite that
one for this case.
I already have to create a booking from a payment, when receiving a
payment from the public instance, thus i made that function and reused
it here. Then i “overwrite” the newly created pre-booking with the
customer data from the form, and set is as confirmed, as we do not see
any point of allowing pre-bookings from employees.
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
This is actually only used for plots, but, of course, it means that
every booking now can potentially have many booked campsites, and have
to create a relation for it.
I now have a conundrum regarding stay dates: i need them to be in the
same table as the campsite_id, because constraints only work on a single
relation and without the dates i can not make sure that i am not
overbooking a given campsite; but, on the other hand, all campsites
under the same booking must be for the same dates.
Where does stay belong, then? In booking or booking_campsite? If in
booking then i can not have a constraint that most assuredly will bite
me in the back, but if in booking_campsite then each campsite could
potentially have different dates.
As far as i can see, i can not use a exclude constraint with <> for
dates in booking_campsite to ensure that all rows with the same
booking_id have the same stay (i.e., exclude those that have a different
stay for the same booking_id).
For now, the say is in **both** relations: in booking, because i need it
when it is a prebooking, at least, and in booking_campsite for the
aforementioned constraint requirements.
Will this come back and bite me? Yes, it will. But what can i do?