Commit Graph

62 Commits

Author SHA1 Message Date
jordi fita mas 3aa53cf1a9 “Mockup” for the new booking form
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.
2024-04-23 21:07:41 +02:00
jordi fita mas 7eb718dfd9 Allow many campsites for each reservation
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?
2024-04-21 21:28:41 +02:00
jordi fita mas cdd91c815e Show booking on booking grid
I need the campsite_id in booking to know what row to show the booking
at. Besides the need of knowing which actual campsite has been booked,
of course.

This field is nullable because we can not now it until an employee has
confirmed the booking; until that point we only know the campsite type
customer requested.  I do not care much if the campsite_id is from a
different campsite_type, because maybe the customer requested the change
by phone or what have you, therefore the database can not be that
strict.  It must have a value if the booking is confirmed.

It helps me if the arrival_date and departure_date is a single
daterange, because then i can use `&&` and other range operators to work
with these dates.  For instance, i have to intersect it with the range
displayed on the screen in order to know which day i have to put it.
But then i have to know whether the booking begins and ends in the
display range, because i only have to show arrival and departure (i.e.,
the box half-way within the first or last boxes) on these days only.
2024-04-19 21:09:28 +02:00
jordi fita mas e726bde025 Replace admin’s campsite map with a booking grid
Customer told us that they are used to a view of the booking status of
each campsite in the form of a grid: each campsite is a row, and each
day a column; bookings are show as boxes from the first day to the last
day on the corresponding campsite’s row.

I do not yet show the booking boxes, but at least now i have the grid
and date selector form in place.

In the form i would need a couple of input[type=month], but this is not
yet supported in Firefox and Safari. According to MDN, one common way
to bypass that problem is to have two fields, one for the month and the
other for the year; i just did that, but had to create a new input type
in the `form` package just for this.
2024-04-19 11:29:52 +02:00
jordi fita mas 97831668e5 Add the number of maximum nights that tourist tax applies
This is required by law.

I do not know why i have this value and the tax amount in the database,
but the payment percent and the number of days are hardcoded. I guess i
am such an inconsistent mess.
2024-02-27 20:06:28 +01:00
jordi fita mas ea997a4154 Allow campsite type option to be just per unit, not per unit per night
Customer told us that there are some options, such as towels, that have
a fixed price for the whole stay, not a per night price.  Thus, had to
add a boolean to know whether to use sum or max when computing the
cart’s total for each option.
2024-02-11 21:45:00 +01:00
jordi fita mas 92dba96b29 Add campsite_type_pet_cost relation to hold price of dogs in campsites
It is a separate relation, instead of having a field in campsite_type,
because not all campsite types allow dogs.  I could have added a new
field to campsite_type, but then its values it would be meaningless for
campsites that do not allow dogs, and a nullable field is not a valid
solution because NULL means “unknown”, but we **do** know the price —
none.

A separate relation encodes the same information without ambiguities nor
null values, and, in fact, removed the dogs_allowed field from
campsite_type to prevent erroneous status, such as a campsite type that
allows dogs without having a cost — even if the cost is zero, it has to
be added to the new relation.
2024-02-10 06:18:30 +01:00
jordi fita mas e5023a2a41 Handle the booking cart entirely with HTMx
Besides the dynamic final cart, that was already handled by HTMx, i had
to check the maximum number of guests, whether the accommodation allows
“overflow”, whether dogs are allowed, and that the booking dates were
within the campground’s opening and closing dates.

I could do all of this with AlpineJS, but then i would have to add the
same validation to the backend, prior to accept the payment.  Would not
make more sense to have them in a single place, namely the backend? With
HTMx i can do that.

However, i now have to create the form “piecemeal”, because i may not
have the whole information when the visitor arrives to the booking page,
and i still had the same problem as in commit d2858302efa—parsing the
whole form as is would leave guests and options field empty, rather than
at their minimum values.

One of the fieldsets in that booking form are the arrival and departure
dates, that are the sames we use in the campsite type’s page to
“preselect” these values.  Since now are in a separate struct, i can
reuse the same type and validation logic for both pages, making my
JavaScript code useless, but requiring HTMx.  I think this is a good
tradeoff, in fact.
2024-02-10 03:49:44 +01:00
jordi fita mas 2c36e45663 Compute and show the “cart” for the booking form
I have to ask number and age ranges of hosts of guests for all campsite
types, not only those that have price options for adults, children, etc.
because i must compute the tourist tax for adults.  These numbers will
be used to generate de rows for guests when actually creating the
booking, which is not done already.

To satisfy the campsite types that do have a price per guest, not only
per night, i had to add the prices for each range in the
campsite_type_cost relation.  If a campsite type does not have price
per person, then that should be zero; the website then does not display
the price.

The minimal price for any campsite type is one adult for one night,
thus to compute the price i need at least the campsite type, the dates,
and the number of adults, that has a minimum of one.  I changed the
order of the form to ask for these values first, so i can compute the
initial price as soon as possible.  To help further, i show the
<fieldset>s progressively when visitors select options.
2024-02-04 06:37:25 +01:00
jordi fita mas 4adad7fa7d Replace min_nights from campsite_type_costs to range in campsite_type
Customer told us that the minimum number of nights is per campsite type,
not per season.  And he wants this, along with the maximum number of
nights, in order to limit the range of departure dates that guests can
choose when booking.
2024-01-31 23:06:45 +01:00
jordi fita mas 23be6ff26c Add ask_zone_preferences and overflow_allowed to campsite_type
The “overflow” is for when people want to book plots for more guests
than is permitted, which the system would need to add a new plot to the
“shopping cart”, as it were; not implemented yet.

The ask zone preferences is to whether show the corresponding input on
the booking form, that it was done implicitly when the campsite type had
options, because up until now it was only for plots, but it is no longer
the case, thus i need to know when to show it; now it is explicit.
2024-01-29 03:38:11 +01:00
jordi fita mas 629ef1a262 Add function to delete campsite type features 2024-01-26 22:54:19 +01:00
jordi fita mas c284230436 Add public pages for each individual accommodation
A small page with a brief description, carousel, and feature list of
each individual accommodation.

Most of the relations and functions for carousel and features are like
the ones for campsite types, but i had to use the accommodation’s label
to find them, because they do not have slugs; i did not even though
these would be public, and they already have a label, although not
unique for all companies, like UUID slugs are.
2024-01-26 22:27:54 +01:00
jordi fita mas 186a5fdb38 Refactor the processing of the campsite form in a common function 2024-01-25 20:57:07 +01:00
jordi fita mas ad161f57b2 Add database functions for AddCampsite and EditCampsite 2024-01-25 20:48:39 +01:00
jordi fita mas 17aaf045bb Replace raw call to remove_campsite_type_carousel_slide with Go func 2024-01-22 20:56:48 +01:00
jordi fita mas bc790762d6 Add remove_campsite_type_option function 2024-01-22 20:54:03 +01:00
jordi fita mas 5cc5fca6b5 Remove unnecessary receiver from campsite.AdminHandler.deleteSlide 2024-01-22 20:53:01 +01:00
jordi fita mas 1f2ab494dd Add check_in and check_out fields to campsite_type
Apparently, each campsite type could have different check-in and
check-out times, thus i need them in the database.

I thought about using an integer or a datetime field, but customer seems
to want a text field to maybe add “before” and “after” there as well.
Translatable text it is.
2024-01-22 20:19:19 +01:00
jordi fita mas 8205bec34d Remove now-unused locales field from admin handlers 2024-01-16 01:26:35 +01:00
jordi fita mas 0ed6c1b121 Added note about dogs to the campsite type page
The pets icon is just the same as the notpet but without the diagonal
bar.

The price is hardcoded in the query for now, as there is no field
in the campsite type.
2024-01-15 02:07:32 +01:00
jordi fita mas 0beb0bb315 Add VAT and tourist tax information
Since i need the number of decimals to format the price in the template,
i added them to the company, and used them in the call to price_parse().
2024-01-15 01:45:58 +01:00
jordi fita mas dabd197f02 Add additional information field to campsite types
This is actually just to have a fourth column in order to “balance”
lists for types that have a long descriptions.
2024-01-15 00:28:34 +01:00
jordi fita mas f28664acce Replace use of L10NInput with I18nInput for campsite 2024-01-10 21:52:49 +01:00
jordi fita mas 4d0123def7 Bring back the whole list of options in type page, but in accordion
This is how the customer wants it.
2023-12-22 03:27:49 +01:00
jordi fita mas c9a6df658f Add position to campsite type features 2023-12-21 17:33:01 +01:00
jordi fita mas 678b5cc523 Add user-defined order to campsite types, options, seasons and carousels
I use Sortable, exactly like HTMx’s sorting example does[0].  Had to
export the slug or ID of some entries to be able to add it in the hidden
input.

For forms that use ID instead of slug, had to use an input name other
than “id” because otherwise the swap would fail due to bug #1496[1].  It
is apparently fixed in a recent version of HTMx, but i did not want to
update for fear of behaviour changes.

[0]: https://htmx.org/examples/sortable/
[1]: https://github.com/bigskysoftware/htmx/issues/1496
2023-12-20 19:52:14 +01:00
jordi fita mas 048b7cbf90 Add the slug to the form in the campsite type public page
The idea is that the booking form will be prefilled with the values
passed from that other mini-form, and the campsite type is implicit
due to the page where the form is located at, but i need to give it to
the booking page.

The booking page does not yet use that information.
2023-10-19 21:38:44 +02:00
jordi fita mas 96fb253354 Show the campsite type’s calendar in an “infinite scroll” carousel
Oriol does not want to waste so much vertical space for the calendar,
and wants it to show in a carousel, initially with only 6 months, and
loading the next three each time the user scrolls past the last.

I now use HTMx in the public page too for this auto-loading behavior,
based on their “infinite scroll” example[0].

Had to put the /calendar URI inside campsites because in the
calendar.gohtml i do not know the current type’s UUID, and can not use
a relative URL to “add subdirectories”, because the type does not end
with a slash.

Had to change season.CollectCalendar to expect the first month and a
number of months to show, to be able to load only 6 or 3 months after
the current, for the initial carousel content, or after the last month
of the carousel.

[0]: https://htmx.org/examples/infinite-scroll/
2023-10-18 21:06:41 +02:00
jordi fita mas 6e7df4ca79 Do not accept “subdirectories” for public campsite types URL
For now, it ends with the UUID or 404.
2023-10-18 20:58:52 +02:00
jordi fita mas 852acaccc3 Add the calendar to the public campsite type page
I had to export the Calendar type from Season to use it from
campsite/types, and also renamed them because season.SeasonCalendar is
a bit redundant compared to just season.Calendar.

I still have not added the HTMx code to switch year because i am not
sure whether Oriol will want to show a whole year or just half a year.

The calculation for the text color taking into account the contrast with
the background is from [0].

[0]: https://www.smashingmagazine.com/2020/07/css-techniques-legibility/#foreground-contrast
2023-10-14 23:14:23 +02:00
jordi fita mas d1b43e5062 Show “Starting from” price with campsite types that have options
In the old website, the prices where show with all the options, but in
the new design only a single price is show, that in the case of
campsites with options is the price per night of the “base” plus the
minimum options selected.
2023-10-13 20:44:24 +02:00
jordi fita mas 2e10966ad7 Add the list of features for campsite type 2023-10-13 20:30:31 +02:00
jordi fita mas d784291a04 Create Go functions for PostgreSQL functions for campsite type option
I am going to base these to create the function for campsite type
feature, and since i intend to
2023-10-13 19:05:17 +02:00
jordi fita mas a174837aea Handle errors for Commit and Rollback in campsite/types/option 2023-10-13 18:57:46 +02:00
jordi fita mas 7fc87b5cb5 Add the info and facilities fields to campsite type
I wish CKEditor would have support for <dl>, but it is unlikely that
they will add any time soon[0], specially when in 2018 they already
“forgotten”[1] that this was requested back in 2007[2].

Therefore, in the demo i did it more or less like in the original
design, with “extra” <h4>, although i added them under a <h3>, to avoid
“gaps” in the outline.

[0]: https://github.com/ckeditor/ckeditor5/issues/775
[1]: https://github.com/ckeditor/ckeditor5/issues/775#issuecomment-358591747
[2]: https://dev.ckeditor.com/ticket/1333
2023-10-13 18:09:28 +02:00
jordi fita mas 59fe8dd131 Add Go functions for campsite_type PostgreSQL functions
I want these because when there are changes in the signature i then have
to find where it is used, and it is easier to do when the compiler tells
you.

For relations it is less necessary because GoLand knows how to validate
SQL strings for them, but it seems to not work with functions,
apparently due to the lack of the “FROM” keyword.

Besides, it tx.FunctionName(ctx, params...) is shorter than
tx.Exec("select functions_name($1, $2…)", params...).
2023-10-13 12:53:30 +02:00
jordi fita mas 0ebbf9613d Check errors for Commit and Rollback in package campsite/types 2023-10-13 12:42:05 +02:00
jordi fita mas ebf47b5d75 Add the “spiel” for campsite types
This is the text that introduces the carousel; it is not a spiel, but
this is what i call it.

It turns out that this text needs to have paragraphs and headings, much
like home’s slider, rather than the one in services page, thus no need
to change its font size or to align all items in the carousel in the
middle.
2023-10-12 18:47:08 +02:00
jordi fita mas 471ed9e870 Add the carousel for campsite types
I can not reuse the carousel package because these carousels need the
campsite site’s slug as a first parameters: i can not have a relation
per campsite type, as i do in home and services pages, because the
campsite types are added by administration types; even if i had a
single relation for slides of home and services pages, these would go
in a different relation due to the foreign key to campsite type.

What i could reuse, however, is the Slide and SlideEntry types from
that package, although i had to export carousel.Translation to be usable
from the types package.  I should change that to use locale.Translation,
but this was the easier option, or i would need to change the queries
and templates for carousel package too.

Besides that, they work exactly like the slides in home and services
pages.
2023-10-12 17:43:23 +02:00
jordi fita mas 9293a341ef Add campsite type options, mainly for plots 2023-10-06 13:26:01 +02:00
jordi fita mas 5f38ab8fd3 Add internationalization and localization to seasons 2023-10-03 21:14:37 +02:00
jordi fita mas ef6a8f5aee Add the campsite type cost per season 2023-10-01 21:14:39 +02:00
jordi fita mas 680d51e704 Add max campers and dogs allowed fields to campsite types 2023-09-29 20:17:39 +02:00
jordi fita mas 6d84b8baad Fix campground and campsite nomenclature for SVG map
I am using the US terms for campground and campsite, that’s why the
relation is called ‘campsite’ instead of ‘pitch’, but i used the wrong
terminology in the SVG map because the customer uses the UK term and
call themselves campsite, so i mixed things.

It is now the campground map and each individual area is a campsite,
as i have been using all along.
2023-09-25 12:34:05 +02:00
jordi fita mas e3503187d3 Add campsite map in SVG
I intend to use the same SVG file for customers and employees, so i had
to change Oriol’s design to add a class to layers that are supposed to
be only for customers, like trees.  These are hidden in the admin area.

I understood that customers and employees have to click on a campsite to
select it, and then they can book or whatever they need to do to them.
Since customers and employees most certainly will need to have different
listeners on campsites, i decided to add the link with JavaScript.  To
do so, i need a custom XML attribute with the campsite’s identifier.

Since i have seen that all campsites have a label, i changed the
“identifier” to the unique combination (company_id, label).  The
company_id is there because different companies could have the same
label; i left the campsite_id primary key for foreign constraints.

In this case, as a test, i add an <a> element to the campsite with a
link to edit it; we’ll discuss with Oriol what exactly it needs to do.

However, the original design had the labels in a different layer, that
interfered with the link, as the numbers must be above the path and
the link must wrap the path in order to “inherit” its shape.  I had no
other recourse than to move the labels in the same layer as the paths’.
2023-09-24 03:17:13 +02:00
jordi fita mas 8c0dbf7806 Fix the label and prompt for the carousel media field 2023-09-22 02:17:56 +02:00
jordi fita mas 97cf117da3 Manage all media uploads in a single place
It made no sense to have a file upload in each form that needs a media,
because to reuse an existing media users would need to upload the exact
same file again; this is very unusual and unfriendly.

A better option is to have a “centralized” media section, where people
can upload files there, and then have a picker to select from there.
Ideally, there would be an upload option in the picker, but i did not
add it yet.

I’ve split the content from the media because i want users to have the
option to update a media, for instance when they need to upload a
reduced or cropped version of the same photo, without an edit they would
need to upload the file as a new media and then update all places where
the old version was used.  And i did not want to trouble people that
uploads the same photo twice: without the separate relation, doing so
would throw a constraint error.

I do not believe there is any security problem to have all companies
link their media to the same file, as they were already readable by
everyone and could upload the data from a different company to their
own; in other words, it is not worse than it was now.
2023-09-21 01:56:44 +02:00
jordi fita mas d08fa31c81 Add translate_campsite_type SQL function
It makes sense that, if i have it for the home page sliders, i should
also have it for the campsite type, for consistence, at least.
2023-09-15 01:23:51 +02:00
jordi fita mas f746c82b46 Make home page’s carousel manageable via the database
I debated with myself whether to create the home_carousel relation or
rather if it would be better to have a single carousel relation for all
pages.  However, i thought that it would be actually harder to maintain
a single relation because i would need an additional column to tell one
carrousel from another, and what would that column be? An enum? A
foreign key to another relation? home_carousel carries no such issues.

I was starting to duplicate logic all over the packages, such as the
way to encode media paths or “localization” (l10n) input fields.
Therefore, i refactorized them.

In the case of media path, i added a function that accepts rows of
media, because always need the same columns from the row, and it was
yet another repetition if i needed to pass them all the time.  Plus,
these kind of functions can be called as `table.function`, that make
them look like columns from the table; if PostgreSQL implemented virtual
generated columns, i would have used that instead.

I am not sure whether that media_path function can be immutable. An
immutable function is “guaranteed to return the same results given the
same arguments forever”, which would be true if the inputs where the
hash and the original_filename columns, instead of the whole rows, but
i left it as static because i did not know whether PostgreSQL interprets
the “same row but with different values” as a different input.  That is,
whether PostgreSQL’s concept of row is the actual tuple or the space
that has a rowid, irrespective of contents; in the latter case, the
function can not be immutable.  Just to be in the safe side, i left it
stable.

The home page was starting to grow a bit too much inside the app
package, new that it has its own admin handler, and moved it all to a
separate package.
2023-09-15 01:05:38 +02:00