2023-10-27 14:04:43 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package booking
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-04-28 18:28:45 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/ods"
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
"errors"
|
2024-05-02 22:28:48 +00:00
|
|
|
"fmt"
|
2023-10-27 14:04:43 +00:00
|
|
|
"net/http"
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
"strconv"
|
2024-01-18 20:05:30 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2023-10-27 14:04:43 +00:00
|
|
|
|
“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 19:07:41 +00:00
|
|
|
"golang.org/x/text/language"
|
|
|
|
|
2023-10-27 14:04:43 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
2023-10-27 14:04:43 +00:00
|
|
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
“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 19:07:41 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
2023-10-27 14:04:43 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
2024-04-25 18:27:08 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
2023-10-27 14:04:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type AdminHandler struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewAdminHandler() *AdminHandler {
|
|
|
|
return &AdminHandler{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var head string
|
|
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
|
|
|
|
switch head {
|
|
|
|
case "":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
2024-01-18 20:05:30 +00:00
|
|
|
serveBookingIndex(w, r, user, company, conn)
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
case http.MethodPost:
|
|
|
|
addBooking(w, r, user, company, conn)
|
2024-01-18 20:05:30 +00:00
|
|
|
default:
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
2024-01-18 20:05:30 +00:00
|
|
|
}
|
“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 19:07:41 +00:00
|
|
|
case "new":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
2024-04-25 18:27:08 +00:00
|
|
|
serveAdminBookingForm(w, r, user, company, conn, 0, "/admin/bookings/new")
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if !uuid.Valid(head) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
h.bookingHandler(user, company, conn, head).ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func serveAdminBookingForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, id int, url string) {
|
|
|
|
f, err := newAdminBookingForm(r, conn, company, user.Locale)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
f.ID = id
|
|
|
|
f.URL = url
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *AdminHandler) bookingHandler(user *auth.User, company *auth.Company, conn *database.Conn, slug string) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var head string
|
|
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
|
|
|
|
switch head {
|
|
|
|
case "":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if len(r.Form) > 0 {
|
|
|
|
// Act as if it was a new form, because everything needed to render form fields is
|
|
|
|
// already passed as in request query.
|
|
|
|
id, err := conn.GetInt(r.Context(), "select booking_id from booking where slug = $1", slug)
|
|
|
|
if err != nil {
|
|
|
|
if database.ErrorIsNotFound(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
serveAdminBookingForm(w, r, user, company, conn, id, "/admin/bookings/"+slug)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
f := newEmptyAdminBookingForm(r.Context(), conn, company, user.Locale)
|
|
|
|
if err := f.FillFromDatabase(r.Context(), conn, company, slug, user.Locale); err != nil {
|
|
|
|
if database.ErrorIsNotFound(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
“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 19:07:41 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
f.MustRender(w, r, user, company)
|
2024-04-25 18:27:08 +00:00
|
|
|
case http.MethodPut:
|
|
|
|
updateBooking(w, r, user, company, conn, slug)
|
“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 19:07:41 +00:00
|
|
|
default:
|
2024-04-25 18:27:08 +00:00
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
“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 19:07:41 +00:00
|
|
|
}
|
2024-04-26 15:09:36 +00:00
|
|
|
case "check-in":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
serveCheckInForm(w, r, user, company, conn, slug)
|
|
|
|
case http.MethodPost:
|
|
|
|
checkInBooking(w, r, user, company, conn, slug)
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
|
|
|
}
|
|
|
|
case "guest":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
serveGuestForm(w, r, user, company, conn, slug)
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
|
|
|
}
|
2023-10-27 14:04:43 +00:00
|
|
|
default:
|
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-18 20:05:30 +00:00
|
|
|
func serveBookingIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
2024-05-02 22:28:48 +00:00
|
|
|
filters := newFilterForm(r.Context(), conn, company, user.Locale)
|
|
|
|
if err := filters.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bookings, err := collectBookingEntries(r.Context(), conn, user.Locale.Language, filters)
|
2024-01-18 20:05:30 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-05-02 22:28:48 +00:00
|
|
|
page := &bookingIndex{
|
|
|
|
Bookings: filters.buildCursor(bookings),
|
|
|
|
Filters: filters,
|
|
|
|
}
|
2024-01-18 20:05:30 +00:00
|
|
|
page.MustRender(w, r, user, company)
|
|
|
|
}
|
|
|
|
|
2024-05-02 22:28:48 +00:00
|
|
|
func collectBookingEntries(ctx context.Context, conn *database.Conn, lang language.Tag, filters *filterForm) ([]*bookingEntry, error) {
|
|
|
|
where, args := filters.BuildQuery([]interface{}{lang.String()})
|
|
|
|
rows, err := conn.Query(ctx, fmt.Sprintf(`
|
|
|
|
select booking_id
|
|
|
|
, left(slug::text, 10)
|
“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 19:07:41 +00:00
|
|
|
, '/admin/bookings/' || slug
|
2024-04-19 19:29:36 +00:00
|
|
|
, lower(stay)
|
|
|
|
, upper(stay)
|
2024-01-18 20:05:30 +00:00
|
|
|
, holder_name
|
|
|
|
, booking.booking_status
|
|
|
|
, coalesce(i18n.name, status.name)
|
|
|
|
from booking
|
|
|
|
join booking_status as status using (booking_status)
|
|
|
|
left join booking_status_i18n as i18n on status.booking_status = i18n.booking_status and i18n.lang_tag = $1
|
2024-05-02 22:28:48 +00:00
|
|
|
where (%s)
|
|
|
|
order by lower(stay) desc
|
|
|
|
, booking_id desc
|
|
|
|
LIMIT %d
|
2024-05-03 17:00:02 +00:00
|
|
|
`, where, filters.PerPage()+1), args...)
|
2024-01-18 20:05:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var entries []*bookingEntry
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &bookingEntry{}
|
2024-05-02 22:28:48 +00:00
|
|
|
if err = rows.Scan(&entry.ID, &entry.Reference, &entry.URL, &entry.ArrivalDate, &entry.DepartureDate, &entry.HolderName, &entry.Status, &entry.StatusLabel); err != nil {
|
2024-01-18 20:05:30 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type bookingEntry struct {
|
2024-05-02 22:28:48 +00:00
|
|
|
ID int
|
2024-01-18 20:05:30 +00:00
|
|
|
Reference string
|
|
|
|
URL string
|
|
|
|
ArrivalDate time.Time
|
|
|
|
DepartureDate time.Time
|
|
|
|
HolderName string
|
|
|
|
Status string
|
|
|
|
StatusLabel string
|
|
|
|
}
|
|
|
|
|
2024-05-02 22:28:48 +00:00
|
|
|
type bookingIndex struct {
|
|
|
|
Bookings []*bookingEntry
|
|
|
|
Filters *filterForm
|
|
|
|
}
|
2024-01-18 20:05:30 +00:00
|
|
|
|
|
|
|
func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
|
|
switch r.URL.Query().Get("format") {
|
|
|
|
case "ods":
|
|
|
|
columns := []string{
|
|
|
|
"Reference",
|
|
|
|
"Arrival Date",
|
|
|
|
"Departure Date",
|
|
|
|
"Holder Name",
|
|
|
|
"Status",
|
|
|
|
}
|
2024-05-02 22:28:48 +00:00
|
|
|
table, err := ods.WriteTable(page.Bookings, columns, user.Locale, func(sb *strings.Builder, entry *bookingEntry) error {
|
2024-04-28 18:28:45 +00:00
|
|
|
if err := ods.WriteCellString(sb, entry.Reference); err != nil {
|
2024-01-18 20:05:30 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-04-28 18:28:45 +00:00
|
|
|
ods.WriteCellDate(sb, entry.ArrivalDate)
|
|
|
|
ods.WriteCellDate(sb, entry.DepartureDate)
|
|
|
|
if err := ods.WriteCellString(sb, entry.HolderName); err != nil {
|
2024-01-18 20:05:30 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-04-28 18:28:45 +00:00
|
|
|
if err := ods.WriteCellString(sb, entry.StatusLabel); err != nil {
|
2024-01-18 20:05:30 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-28 18:28:45 +00:00
|
|
|
ods.MustWriteResponse(w, table, user.Locale.Pgettext("bookings.ods", "filename"))
|
2024-01-18 20:05:30 +00:00
|
|
|
default:
|
2024-05-03 17:00:02 +00:00
|
|
|
if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
|
2024-05-02 22:28:48 +00:00
|
|
|
template.MustRenderAdminNoLayout(w, r, user, company, "booking/results.gohtml", page)
|
|
|
|
} else {
|
|
|
|
template.MustRenderAdminFiles(w, r, user, company, page, "booking/index.gohtml", "booking/results.gohtml")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
“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 19:07:41 +00:00
|
|
|
type adminBookingForm struct {
|
|
|
|
*bookingForm
|
|
|
|
ID int
|
2024-04-25 18:27:08 +00:00
|
|
|
URL string
|
2024-05-02 23:01:01 +00:00
|
|
|
Status string
|
“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 19:07:41 +00:00
|
|
|
Campsites []*CampsiteEntry
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
selected []int
|
“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 19:07:41 +00:00
|
|
|
Months []*Month
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
Error error
|
“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 19:07:41 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 18:27:08 +00:00
|
|
|
func newEmptyAdminBookingForm(ctx context.Context, conn *database.Conn, company *auth.Company, l *locale.Locale) *adminBookingForm {
|
|
|
|
return &adminBookingForm{
|
|
|
|
bookingForm: newEmptyBookingForm(ctx, conn, company, l),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
“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 19:07:41 +00:00
|
|
|
func newAdminBookingForm(r *http.Request, conn *database.Conn, company *auth.Company, l *locale.Locale) (*adminBookingForm, error) {
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
inner, err := newBookingForm(r, company, conn, l)
|
“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 19:07:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
if inner.Options != nil {
|
|
|
|
for _, option := range inner.Options.Options {
|
|
|
|
option.Subtotal = findSubtotal(option.ID, inner.Cart)
|
“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 19:07:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
f := &adminBookingForm{
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
bookingForm: inner,
|
“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 19:07:41 +00:00
|
|
|
}
|
|
|
|
// Dates and Campsite are valid
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
if inner.Guests != nil {
|
2024-04-25 18:27:08 +00:00
|
|
|
selected := r.Form["campsite"]
|
|
|
|
if err = f.FetchCampsites(r.Context(), conn, company, selected); err != nil {
|
“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 19:07:41 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
}
|
|
|
|
return f, nil
|
|
|
|
}
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
|
2024-04-25 18:27:08 +00:00
|
|
|
func (f *adminBookingForm) FetchCampsites(ctx context.Context, conn *database.Conn, company *auth.Company, selected []string) error {
|
|
|
|
arrivalDate, _ := time.Parse(database.ISODateFormat, f.Dates.ArrivalDate.Val)
|
|
|
|
from := arrivalDate.AddDate(0, 0, -1)
|
|
|
|
departureDate, _ := time.Parse(database.ISODateFormat, f.Dates.DepartureDate.Val)
|
|
|
|
to := departureDate.AddDate(0, 0, 2)
|
|
|
|
f.Months = CollectMonths(from, to)
|
|
|
|
var err error
|
|
|
|
f.Campsites, err = CollectCampsiteEntries(ctx, company, conn, from, to, f.CampsiteType.String())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range selected {
|
|
|
|
ID, _ := strconv.Atoi(s)
|
|
|
|
for _, c := range f.Campsites {
|
|
|
|
if c.ID == ID {
|
|
|
|
f.selected = append(f.selected, c.ID)
|
|
|
|
c.Selected = true
|
|
|
|
break
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
}
|
|
|
|
}
|
“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 19:07:41 +00:00
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
|
|
|
|
return nil
|
“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 19:07:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func findSubtotal(ID int, cart *bookingCart) string {
|
|
|
|
none := "0.0"
|
|
|
|
if cart == nil || cart.Draft == nil {
|
|
|
|
return none
|
|
|
|
}
|
|
|
|
for _, option := range cart.Draft.Options {
|
|
|
|
if option.ID == ID {
|
|
|
|
return option.Subtotal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return none
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *adminBookingForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
|
|
if httplib.IsHTMxRequest(r) {
|
|
|
|
template.MustRenderAdminNoLayoutFiles(w, r, user, company, f, "booking/fields.gohtml", "booking/grid.gohtml")
|
|
|
|
} else {
|
|
|
|
template.MustRenderAdminFiles(w, r, user, company, f, "booking/form.gohtml", "booking/fields.gohtml", "booking/grid.gohtml")
|
|
|
|
}
|
|
|
|
}
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
|
|
|
|
func addBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
2024-04-25 18:27:08 +00:00
|
|
|
processAdminBookingForm(w, r, user, company, conn, 0, func(ctx context.Context, tx *database.Tx, f *adminBookingForm) error {
|
|
|
|
var err error
|
|
|
|
f.ID, err = tx.AddBookingFromPayment(ctx, f.PaymentSlug.Val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return tx.EditBooking(
|
|
|
|
ctx,
|
|
|
|
f.ID,
|
|
|
|
f.Customer.FullName.Val,
|
|
|
|
f.Customer.Address.Val,
|
|
|
|
f.Customer.PostalCode.Val,
|
|
|
|
f.Customer.City.Val,
|
|
|
|
f.Customer.Country.String(),
|
|
|
|
f.Customer.Email.Val,
|
|
|
|
f.Customer.Phone.Val,
|
|
|
|
language.Make("und"),
|
|
|
|
"confirmed",
|
|
|
|
f.selected,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) {
|
|
|
|
var bookingID int
|
|
|
|
var bookingStatus string
|
|
|
|
var langTag string
|
|
|
|
row := conn.QueryRow(r.Context(), "select booking_id, booking_status, lang_tag from booking where slug = $1", slug)
|
|
|
|
if err := row.Scan(&bookingID, &bookingStatus, &langTag); err != nil {
|
|
|
|
if database.ErrorIsNotFound(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-05-03 15:21:20 +00:00
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(r.Form["cancel"]) > 0 {
|
|
|
|
if err := conn.CancelBooking(r.Context(), bookingID); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if bookingStatus == "created" {
|
|
|
|
httplib.Redirect(w, r, "/admin/prebookings", http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
httplib.Redirect(w, r, "/admin/bookings", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
processAdminBookingForm(w, r, user, company, conn, bookingID, func(ctx context.Context, tx *database.Tx, f *adminBookingForm) error {
|
|
|
|
var err error
|
|
|
|
_, err = tx.EditBookingFromPayment(ctx, slug, f.PaymentSlug.Val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-04-28 19:00:15 +00:00
|
|
|
if bookingStatus == "created" {
|
|
|
|
bookingStatus = "confirmed"
|
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
return tx.EditBooking(
|
|
|
|
ctx,
|
|
|
|
f.ID,
|
|
|
|
f.Customer.FullName.Val,
|
|
|
|
f.Customer.Address.Val,
|
|
|
|
f.Customer.PostalCode.Val,
|
|
|
|
f.Customer.City.Val,
|
|
|
|
f.Customer.Country.String(),
|
|
|
|
f.Customer.Email.Val,
|
|
|
|
f.Customer.Phone.Val,
|
|
|
|
language.Make(langTag),
|
|
|
|
bookingStatus,
|
|
|
|
f.selected,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func processAdminBookingForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, bookingID int, act func(ctx context.Context, tx *database.Tx, f *adminBookingForm) error) {
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
f, err := newAdminBookingForm(r, conn, company, user.Locale)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if err := user.VerifyCSRFToken(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
f.ID = bookingID
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
|
|
|
|
panic(err)
|
|
|
|
} else if !ok {
|
|
|
|
if !httplib.IsHTMxRequest(r) {
|
|
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
|
|
}
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tx := conn.MustBegin(r.Context())
|
2024-04-25 18:27:08 +00:00
|
|
|
defer tx.Rollback(r.Context())
|
|
|
|
if err := act(r.Context(), tx, f); err != nil {
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-25 18:27:08 +00:00
|
|
|
tx.MustCommit(r.Context())
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
httplib.Redirect(w, r, "/admin/bookings", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *adminBookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
|
|
|
|
v := form.NewValidator(l)
|
|
|
|
|
|
|
|
if f.Dates == nil {
|
|
|
|
return false, errors.New("no booking date fields")
|
|
|
|
}
|
|
|
|
if f.Guests == nil {
|
|
|
|
return false, errors.New("no guests fields")
|
|
|
|
}
|
|
|
|
if f.Customer == nil {
|
|
|
|
return false, errors.New("no customer fields")
|
|
|
|
}
|
|
|
|
if f.Cart == nil {
|
|
|
|
return false, errors.New("no booking cart")
|
|
|
|
}
|
|
|
|
|
|
|
|
v.CheckSelectedOptions(f.CampsiteType, l.GettextNoop("Selected campsite type is not valid."))
|
|
|
|
f.Dates.Valid(v, l)
|
|
|
|
f.Guests.Valid(v, l)
|
|
|
|
if f.Options != nil {
|
|
|
|
f.Options.Valid(v, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
var country string
|
|
|
|
if f.Customer.Country.ValidOptionsSelected() {
|
|
|
|
country = f.Customer.Country.Selected[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.CheckRequired(f.Customer.FullName, l.GettextNoop("Full name can not be empty.")) {
|
|
|
|
v.CheckMinLength(f.Customer.FullName, 1, l.GettextNoop("Full name must have at least one letter."))
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Customer.PostalCode.Val != "" {
|
|
|
|
if country == "" {
|
|
|
|
v.Check(f.Customer.PostalCode, false, l.GettextNoop("Country can not be empty to validate the postcode."))
|
|
|
|
} else if _, err := v.CheckValidPostalCode(ctx, conn, f.Customer.PostalCode, country, l.GettextNoop("This postcode is not valid.")); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if f.Customer.Email.Val != "" {
|
|
|
|
v.CheckValidEmail(f.Customer.Email, l.GettextNoop("This email is not valid. It should be like name@domain.com."))
|
|
|
|
}
|
|
|
|
if f.Customer.Phone.Val != "" {
|
|
|
|
if country == "" {
|
|
|
|
v.Check(f.Customer.Phone, false, l.GettextNoop("Country can not be empty to validate the phone."))
|
|
|
|
} else if _, err := v.CheckValidPhone(ctx, conn, f.Customer.Phone, country, l.GettextNoop("This phone number is not valid.")); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.selected) == 0 {
|
|
|
|
f.Error = errors.New(l.Gettext("You must select at least one accommodation."))
|
|
|
|
v.AllOK = false
|
|
|
|
} else if f.Dates.ArrivalDate.Error == nil && f.Dates.DepartureDate.Error == nil {
|
2024-04-25 18:27:08 +00:00
|
|
|
if available, err := datesAvailable(ctx, conn, f.ID, f.Dates, f.selected); err != nil {
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
return false, err
|
|
|
|
} else if !available {
|
|
|
|
f.Error = errors.New(l.Gettext("The selected accommodations have no available openings in the requested dates."))
|
|
|
|
v.AllOK = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.AllOK, nil
|
|
|
|
}
|
|
|
|
|
2024-04-25 18:27:08 +00:00
|
|
|
func datesAvailable(ctx context.Context, conn *database.Conn, bookingID int, dates *DateFields, selectedCampsites []int) (bool, error) {
|
|
|
|
return conn.GetBool(ctx, `
|
|
|
|
select not exists (
|
|
|
|
select 1
|
|
|
|
from camper.booking_campsite
|
|
|
|
where booking_id <> $1
|
|
|
|
and campsite_id = any ($4)
|
|
|
|
and stay && daterange($2::date, $3::date)
|
|
|
|
)
|
|
|
|
`,
|
|
|
|
bookingID,
|
|
|
|
dates.ArrivalDate,
|
|
|
|
dates.DepartureDate,
|
|
|
|
selectedCampsites,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *adminBookingForm) FillFromDatabase(ctx context.Context, conn *database.Conn, company *auth.Company, slug string, l *locale.Locale) error {
|
|
|
|
f.Cart = &bookingCart{Draft: &paymentDraft{}}
|
|
|
|
f.Customer = newBookingCustomerFields(ctx, conn, l)
|
|
|
|
var arrivalDate string
|
|
|
|
var departureDate string
|
|
|
|
var acsiCard bool
|
|
|
|
var zonePreferences string
|
|
|
|
var selected []string
|
|
|
|
row := conn.QueryRow(ctx, `
|
|
|
|
select booking_id
|
|
|
|
, '/admin/bookings/' || booking.slug
|
2024-05-02 23:01:01 +00:00
|
|
|
, booking_status
|
2024-04-25 18:27:08 +00:00
|
|
|
, array[campsite_type.slug::text]
|
|
|
|
, lower(booking.stay)::text
|
|
|
|
, upper(booking.stay)::text
|
|
|
|
, upper(booking.stay) - lower(booking.stay)
|
|
|
|
, to_price(subtotal_nights, decimal_digits)
|
|
|
|
, number_adults
|
|
|
|
, to_price(subtotal_adults, decimal_digits)
|
|
|
|
, number_teenagers
|
|
|
|
, to_price(subtotal_teenagers, decimal_digits)
|
|
|
|
, number_children
|
|
|
|
, to_price(subtotal_children, decimal_digits)
|
|
|
|
, number_dogs
|
|
|
|
, to_price(subtotal_dogs, decimal_digits)
|
|
|
|
, to_price(subtotal_tourist_tax, decimal_digits)
|
|
|
|
, to_price(total, decimal_digits)
|
|
|
|
, acsi_card
|
|
|
|
, holder_name
|
|
|
|
, coalesce(address, '')
|
|
|
|
, coalesce(postal_code, '')
|
|
|
|
, coalesce(city, '')
|
|
|
|
, array[coalesce(country_code::text, '')]
|
|
|
|
, coalesce(email::text, '')
|
|
|
|
, coalesce(phone::text, '')
|
|
|
|
, zone_preferences
|
|
|
|
, array_agg(coalesce(campsite_id::text, ''))
|
|
|
|
from booking
|
|
|
|
join campsite_type using (campsite_type_id)
|
|
|
|
left join campsite_type_pet_cost as pet using (campsite_type_id)
|
|
|
|
left join booking_campsite using (booking_id)
|
|
|
|
join currency using (currency_code)
|
|
|
|
where booking.slug = $1
|
|
|
|
group by booking_id
|
|
|
|
, campsite_type.slug
|
|
|
|
, booking.stay
|
|
|
|
, subtotal_nights
|
|
|
|
, number_adults
|
|
|
|
, subtotal_adults
|
|
|
|
, number_teenagers
|
|
|
|
, subtotal_teenagers
|
|
|
|
, number_children
|
|
|
|
, subtotal_children
|
|
|
|
, number_dogs
|
|
|
|
, subtotal_dogs
|
|
|
|
, subtotal_tourist_tax
|
|
|
|
, total
|
|
|
|
, acsi_card
|
|
|
|
, holder_name
|
|
|
|
, address
|
|
|
|
, postal_code
|
|
|
|
, city
|
|
|
|
, country_code
|
|
|
|
, email
|
|
|
|
, phone
|
|
|
|
, zone_preferences
|
|
|
|
, decimal_digits
|
|
|
|
`, slug)
|
|
|
|
if err := row.Scan(
|
|
|
|
&f.ID,
|
|
|
|
&f.URL,
|
2024-05-02 23:01:01 +00:00
|
|
|
&f.Status,
|
2024-04-25 18:27:08 +00:00
|
|
|
&f.CampsiteType.Selected,
|
|
|
|
&arrivalDate,
|
|
|
|
&departureDate,
|
|
|
|
&f.Cart.Draft.NumNights,
|
|
|
|
&f.Cart.Draft.Nights,
|
|
|
|
&f.Cart.Draft.NumAdults,
|
|
|
|
&f.Cart.Draft.Adults,
|
|
|
|
&f.Cart.Draft.NumTeenagers,
|
|
|
|
&f.Cart.Draft.Teenagers,
|
|
|
|
&f.Cart.Draft.NumChildren,
|
|
|
|
&f.Cart.Draft.Children,
|
|
|
|
&f.Cart.Draft.NumDogs,
|
|
|
|
&f.Cart.Draft.Dogs,
|
|
|
|
&f.Cart.Draft.TouristTax,
|
|
|
|
&f.Cart.Draft.Total,
|
|
|
|
&acsiCard,
|
|
|
|
&f.Customer.FullName.Val,
|
|
|
|
&f.Customer.Address.Val,
|
|
|
|
&f.Customer.PostalCode.Val,
|
|
|
|
&f.Customer.City.Val,
|
|
|
|
&f.Customer.Country.Selected,
|
|
|
|
&f.Customer.Email.Val,
|
|
|
|
&f.Customer.Phone.Val,
|
|
|
|
&zonePreferences,
|
|
|
|
&selected,
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
f.Dates, err = NewDateFields(ctx, conn, f.CampsiteType.String())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Dates.ArrivalDate.Val = arrivalDate
|
|
|
|
f.Dates.DepartureDate.Val = departureDate
|
|
|
|
f.Dates.AdjustValues(l)
|
|
|
|
|
|
|
|
f.Guests, err = newBookingGuestFields(ctx, conn, f.CampsiteType.String(), arrivalDate, departureDate)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Guests.NumberAdults.Val = strconv.Itoa(f.Cart.Draft.NumAdults)
|
|
|
|
f.Guests.NumberTeenagers.Val = strconv.Itoa(f.Cart.Draft.NumTeenagers)
|
|
|
|
f.Guests.NumberChildren.Val = strconv.Itoa(f.Cart.Draft.NumChildren)
|
|
|
|
if f.Guests.NumberDogs != nil {
|
|
|
|
f.Guests.NumberDogs.Val = strconv.Itoa(f.Cart.Draft.NumDogs)
|
|
|
|
}
|
|
|
|
if f.Guests.ACSICard != nil {
|
|
|
|
f.Guests.ACSICard.Checked = acsiCard
|
|
|
|
}
|
|
|
|
f.Guests.AdjustValues(f.Cart.Draft.NumAdults+f.Cart.Draft.NumTeenagers+f.Cart.Draft.NumChildren, l)
|
|
|
|
|
|
|
|
f.Options, err = newBookingOptionFields(ctx, conn, f.CampsiteType.String(), l)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if f.Options != nil {
|
|
|
|
if f.Options.ZonePreferences != nil {
|
|
|
|
f.Options.ZonePreferences.Val = zonePreferences
|
|
|
|
}
|
|
|
|
if err = f.Options.FillFromDatabase(ctx, conn, f.ID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = f.FetchCampsites(ctx, conn, company, selected); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
“Finish” the new booking form
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.
2024-04-24 18:12:29 +00:00
|
|
|
}
|