Allow updating bookings
I need to retrieve the values from the database and put them in the form, like all other forms, but in this case the processing is done as if it were a new form, because everything comes from the query string and there is no need to do any extra work then. Had to move the <footer> from the fields.gohtml to form.gohtml because then it could not know that it was editing an existing booking. Had to move the <fieldset> out too, in order to give it an ID and make it htmx’s target, or it would replace the form, causing even more problems —the button would disappear then—. The target **must** be in <form> because it is needed for tis children’s hx-get and for its own hx-put.
This commit is contained in:
parent
30e87c309e
commit
c9e8165f83
|
@ -0,0 +1,92 @@
|
||||||
|
-- Deploy camper:edit_booking_from_payment to pg
|
||||||
|
-- requires: roles
|
||||||
|
-- requires: schema_camper
|
||||||
|
-- requires: booking
|
||||||
|
-- requires: booking__payment_fields
|
||||||
|
-- requires: booking__stay
|
||||||
|
-- requires: booking_option
|
||||||
|
-- requires: payment
|
||||||
|
-- requires: payment__acsi_card
|
||||||
|
-- requires: payment_option
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to camper, public;
|
||||||
|
|
||||||
|
create or replace function edit_booking_from_payment(booking_slug uuid, payment_slug uuid) returns integer as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
bid integer;
|
||||||
|
begin
|
||||||
|
with p as (
|
||||||
|
select company_id
|
||||||
|
, campsite_type_id
|
||||||
|
, daterange(arrival_date, departure_date) as stay
|
||||||
|
, subtotal_nights
|
||||||
|
, number_adults
|
||||||
|
, subtotal_adults
|
||||||
|
, number_teenagers
|
||||||
|
, subtotal_teenagers
|
||||||
|
, number_children
|
||||||
|
, subtotal_children
|
||||||
|
, number_dogs
|
||||||
|
, subtotal_dogs
|
||||||
|
, subtotal_tourist_tax
|
||||||
|
, total
|
||||||
|
, currency_code
|
||||||
|
, zone_preferences
|
||||||
|
, acsi_card
|
||||||
|
from payment
|
||||||
|
where payment.slug = payment_slug
|
||||||
|
)
|
||||||
|
update booking
|
||||||
|
set company_id = p.company_id
|
||||||
|
, campsite_type_id = p.campsite_type_id
|
||||||
|
, stay = p.stay
|
||||||
|
, subtotal_nights = p.subtotal_nights
|
||||||
|
, number_adults = p.number_adults
|
||||||
|
, subtotal_adults = p.subtotal_adults
|
||||||
|
, number_teenagers = p.number_teenagers
|
||||||
|
, subtotal_teenagers = p.subtotal_teenagers
|
||||||
|
, number_children = p.number_children
|
||||||
|
, subtotal_children = p.subtotal_children
|
||||||
|
, number_dogs = p.number_dogs
|
||||||
|
, subtotal_dogs = p.subtotal_dogs
|
||||||
|
, subtotal_tourist_tax = p.subtotal_tourist_tax
|
||||||
|
, total = p.total
|
||||||
|
, currency_code = p.currency_code
|
||||||
|
, zone_preferences = p.zone_preferences
|
||||||
|
, acsi_card = p.acsi_card
|
||||||
|
from p
|
||||||
|
where slug = booking_slug
|
||||||
|
returning booking_id into bid;
|
||||||
|
|
||||||
|
delete from booking_option
|
||||||
|
where booking_id = bid;
|
||||||
|
|
||||||
|
insert into booking_option
|
||||||
|
( booking_id
|
||||||
|
, campsite_type_option_id
|
||||||
|
, units
|
||||||
|
, subtotal
|
||||||
|
)
|
||||||
|
select bid
|
||||||
|
, campsite_type_option_id
|
||||||
|
, units
|
||||||
|
, subtotal
|
||||||
|
from payment_option
|
||||||
|
join payment using (payment_id)
|
||||||
|
where payment.slug = payment_slug
|
||||||
|
;
|
||||||
|
|
||||||
|
return bid;
|
||||||
|
end;
|
||||||
|
$$
|
||||||
|
language plpgsql
|
||||||
|
;
|
||||||
|
|
||||||
|
revoke execute on function edit_booking_from_payment(uuid, uuid) from public;
|
||||||
|
grant execute on function edit_booking_from_payment(uuid, uuid) to employee;
|
||||||
|
grant execute on function edit_booking_from_payment(uuid, uuid) to admin;
|
||||||
|
|
||||||
|
commit;
|
|
@ -21,6 +21,7 @@ import (
|
||||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminHandler struct {
|
type AdminHandler struct {
|
||||||
|
@ -48,13 +49,69 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
|
||||||
case "new":
|
case "new":
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
f, err := newAdminBookingForm(r, conn, company, user.Locale)
|
serveAdminBookingForm(w, r, user, company, conn, 0, "/admin/bookings/new")
|
||||||
if err != nil {
|
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
|
||||||
|
}
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
f.MustRender(w, r, user, company)
|
f.MustRender(w, r, user, company)
|
||||||
|
case http.MethodPut:
|
||||||
|
updateBooking(w, r, user, company, conn, slug)
|
||||||
default:
|
default:
|
||||||
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -150,12 +207,19 @@ func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user
|
||||||
type adminBookingForm struct {
|
type adminBookingForm struct {
|
||||||
*bookingForm
|
*bookingForm
|
||||||
ID int
|
ID int
|
||||||
|
URL string
|
||||||
Campsites []*CampsiteEntry
|
Campsites []*CampsiteEntry
|
||||||
selected []int
|
selected []int
|
||||||
Months []*Month
|
Months []*Month
|
||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newEmptyAdminBookingForm(ctx context.Context, conn *database.Conn, company *auth.Company, l *locale.Locale) *adminBookingForm {
|
||||||
|
return &adminBookingForm{
|
||||||
|
bookingForm: newEmptyBookingForm(ctx, conn, company, l),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newAdminBookingForm(r *http.Request, conn *database.Conn, company *auth.Company, l *locale.Locale) (*adminBookingForm, error) {
|
func newAdminBookingForm(r *http.Request, conn *database.Conn, company *auth.Company, l *locale.Locale) (*adminBookingForm, error) {
|
||||||
inner, err := newBookingForm(r, company, conn, l)
|
inner, err := newBookingForm(r, company, conn, l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -171,31 +235,40 @@ func newAdminBookingForm(r *http.Request, conn *database.Conn, company *auth.Com
|
||||||
}
|
}
|
||||||
// Dates and Campsite are valid
|
// Dates and Campsite are valid
|
||||||
if inner.Guests != nil {
|
if inner.Guests != nil {
|
||||||
arrivalDate, _ := time.Parse(database.ISODateFormat, inner.Dates.ArrivalDate.Val)
|
|
||||||
from := arrivalDate.AddDate(0, 0, -1)
|
|
||||||
departureDate, _ := time.Parse(database.ISODateFormat, inner.Dates.DepartureDate.Val)
|
|
||||||
to := departureDate.AddDate(0, 0, 2)
|
|
||||||
f.Months = CollectMonths(from, to)
|
|
||||||
f.Campsites, err = CollectCampsiteEntries(r.Context(), company, conn, from, to, inner.CampsiteType.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
selected := r.Form["campsite"]
|
selected := r.Form["campsite"]
|
||||||
for _, s := range selected {
|
if err = f.FetchCampsites(r.Context(), conn, company, selected); err != nil {
|
||||||
ID, _ := strconv.Atoi(s)
|
return nil, err
|
||||||
for _, c := range f.Campsites {
|
|
||||||
if c.ID == ID {
|
|
||||||
f.selected = append(f.selected, c.ID)
|
|
||||||
c.Selected = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func findSubtotal(ID int, cart *bookingCart) string {
|
func findSubtotal(ID int, cart *bookingCart) string {
|
||||||
none := "0.0"
|
none := "0.0"
|
||||||
if cart == nil || cart.Draft == nil {
|
if cart == nil || cart.Draft == nil {
|
||||||
|
@ -218,6 +291,65 @@ func (f *adminBookingForm) MustRender(w http.ResponseWriter, r *http.Request, us
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
func addBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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) {
|
||||||
f, err := newAdminBookingForm(r, conn, company, user.Locale)
|
f, err := newAdminBookingForm(r, conn, company, user.Locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -226,6 +358,7 @@ func addBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
f.ID = bookingID
|
||||||
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
|
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
|
@ -237,42 +370,14 @@ func addBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := conn.MustBegin(r.Context())
|
tx := conn.MustBegin(r.Context())
|
||||||
if err := performAddBooking(r.Context(), tx, f); err == nil {
|
defer tx.Rollback(r.Context())
|
||||||
if err := tx.Commit(r.Context()); err != nil {
|
if err := act(r.Context(), tx, f); err != nil {
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := tx.Rollback(r.Context()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
tx.MustCommit(r.Context())
|
||||||
httplib.Redirect(w, r, "/admin/bookings", http.StatusSeeOther)
|
httplib.Redirect(w, r, "/admin/bookings", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func performAddBooking(ctx context.Context, tx *database.Tx, f *adminBookingForm) error {
|
|
||||||
var bookingID int
|
|
||||||
var err error
|
|
||||||
bookingID, err = tx.AddBookingFromPayment(ctx, f.PaymentSlug.Val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.EditBooking(
|
|
||||||
ctx,
|
|
||||||
bookingID,
|
|
||||||
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 (f *adminBookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
|
func (f *adminBookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
|
||||||
v := form.NewValidator(l)
|
v := form.NewValidator(l)
|
||||||
|
|
||||||
|
@ -327,7 +432,7 @@ func (f *adminBookingForm) Valid(ctx context.Context, conn *database.Conn, l *lo
|
||||||
f.Error = errors.New(l.Gettext("You must select at least one accommodation."))
|
f.Error = errors.New(l.Gettext("You must select at least one accommodation."))
|
||||||
v.AllOK = false
|
v.AllOK = false
|
||||||
} else if f.Dates.ArrivalDate.Error == nil && f.Dates.DepartureDate.Error == nil {
|
} else if f.Dates.ArrivalDate.Error == nil && f.Dates.DepartureDate.Error == nil {
|
||||||
if available, err := datesAvailable(ctx, conn, f.Dates, f.selected); err != nil {
|
if available, err := datesAvailable(ctx, conn, f.ID, f.Dates, f.selected); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if !available {
|
} else if !available {
|
||||||
f.Error = errors.New(l.Gettext("The selected accommodations have no available openings in the requested dates."))
|
f.Error = errors.New(l.Gettext("The selected accommodations have no available openings in the requested dates."))
|
||||||
|
@ -338,6 +443,162 @@ func (f *adminBookingForm) Valid(ctx context.Context, conn *database.Conn, l *lo
|
||||||
return v.AllOK, nil
|
return v.AllOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func datesAvailable(ctx context.Context, conn *database.Conn, dates *DateFields, selectedCampsites []int) (bool, error) {
|
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 campsite_id = any ($3) and stay && daterange($1::date, $2::date))", dates.ArrivalDate, dates.DepartureDate, selectedCampsites)
|
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
|
||||||
|
, 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,
|
||||||
|
&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
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,20 +149,24 @@ type bookingCustomerFields struct {
|
||||||
Agreement *form.Checkbox
|
Agreement *form.Checkbox
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBookingForm(r *http.Request, company *auth.Company, conn *database.Conn, l *locale.Locale) (*bookingForm, error) {
|
func newEmptyBookingForm(ctx context.Context, conn *database.Conn, company *auth.Company, l *locale.Locale) *bookingForm {
|
||||||
if err := r.ParseForm(); err != nil {
|
return &bookingForm{
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f := &bookingForm{
|
|
||||||
CampsiteType: &form.Select{
|
CampsiteType: &form.Select{
|
||||||
Name: "campsite_type",
|
Name: "campsite_type",
|
||||||
Options: form.MustGetOptions(r.Context(), conn, "select type.slug, coalesce(i18n.name, type.name) as l10n_name from campsite_type as type left join campsite_type_i18n as i18n on type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1 where company_id = $2 and active order by position, l10n_name", l.Language, company.ID),
|
Options: form.MustGetOptions(ctx, conn, "select type.slug, coalesce(i18n.name, type.name) as l10n_name from campsite_type as type left join campsite_type_i18n as i18n on type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1 where company_id = $2 and active order by position, l10n_name", l.Language, company.ID),
|
||||||
},
|
},
|
||||||
PaymentSlug: &form.Input{
|
PaymentSlug: &form.Input{
|
||||||
Name: "payment_slug",
|
Name: "payment_slug",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBookingForm(r *http.Request, company *auth.Company, conn *database.Conn, l *locale.Locale) (*bookingForm, error) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := newEmptyBookingForm(r.Context(), conn, company, l)
|
||||||
f.CampsiteType.FillValue(r)
|
f.CampsiteType.FillValue(r)
|
||||||
f.PaymentSlug.FillValue(r)
|
f.PaymentSlug.FillValue(r)
|
||||||
campsiteType := f.CampsiteType.String()
|
campsiteType := f.CampsiteType.String()
|
||||||
|
@ -270,7 +274,10 @@ func NewDateFields(ctx context.Context, conn *database.Conn, campsiteType string
|
||||||
func (f *DateFields) FillValues(r *http.Request, l *locale.Locale) {
|
func (f *DateFields) FillValues(r *http.Request, l *locale.Locale) {
|
||||||
f.ArrivalDate.FillValue(r)
|
f.ArrivalDate.FillValue(r)
|
||||||
f.DepartureDate.FillValue(r)
|
f.DepartureDate.FillValue(r)
|
||||||
|
f.AdjustValues(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DateFields) AdjustValues(l *locale.Locale) {
|
||||||
if f.ArrivalDate.Val != "" {
|
if f.ArrivalDate.Val != "" {
|
||||||
arrivalDate, err := time.Parse(database.ISODateFormat, f.ArrivalDate.Val)
|
arrivalDate, err := time.Parse(database.ISODateFormat, f.ArrivalDate.Val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -362,6 +369,10 @@ func (f *bookingGuestFields) FillValues(r *http.Request, l *locale.Locale) {
|
||||||
if f.ACSICard != nil {
|
if f.ACSICard != nil {
|
||||||
f.ACSICard.FillValue(r)
|
f.ACSICard.FillValue(r)
|
||||||
}
|
}
|
||||||
|
f.AdjustValues(numGuests, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *bookingGuestFields) AdjustValues(numGuests int, l *locale.Locale) {
|
||||||
if numGuests > f.MaxGuests {
|
if numGuests > f.MaxGuests {
|
||||||
if f.OverflowAllowed {
|
if f.OverflowAllowed {
|
||||||
f.Overflow = true
|
f.Overflow = true
|
||||||
|
@ -472,6 +483,42 @@ func (f *bookingOptionFields) FillValues(r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *bookingOptionFields) FillFromDatabase(ctx context.Context, conn *database.Conn, bookingID int) error {
|
||||||
|
rows, err := conn.Query(ctx, `
|
||||||
|
select campsite_type_option.campsite_type_option_id
|
||||||
|
, coalesce(units, lower(range))::text
|
||||||
|
, to_price(coalesce(subtotal, 0), decimal_digits)
|
||||||
|
from booking
|
||||||
|
join campsite_type_option using (campsite_type_id)
|
||||||
|
left join booking_option
|
||||||
|
on booking.booking_id = booking_option.booking_id
|
||||||
|
and booking_option.campsite_type_option_id = campsite_type_option.campsite_type_option_id
|
||||||
|
join currency using (currency_code)
|
||||||
|
where booking.booking_id = $1
|
||||||
|
`, bookingID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var units string
|
||||||
|
var subtotal string
|
||||||
|
if err = rows.Scan(&id, &units, &subtotal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, option := range f.Options {
|
||||||
|
if option.ID == id {
|
||||||
|
option.Input.Val = units
|
||||||
|
option.Subtotal = subtotal
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *bookingOptionFields) Valid(v *form.Validator, l *locale.Locale) {
|
func (f *bookingOptionFields) Valid(v *form.Validator, l *locale.Locale) {
|
||||||
for _, option := range f.Options {
|
for _, option := range f.Options {
|
||||||
if v.CheckRequired(option.Input, fmt.Sprintf(l.Gettext("%s can not be empty"), option.Label)) {
|
if v.CheckRequired(option.Input, fmt.Sprintf(l.Gettext("%s can not be empty"), option.Label)) {
|
||||||
|
|
|
@ -357,6 +357,10 @@ func (tx *Tx) AddBookingFromPayment(ctx context.Context, paymentSlug string) (in
|
||||||
return tx.GetInt(ctx, "select add_booking_from_payment($1)", paymentSlug)
|
return tx.GetInt(ctx, "select add_booking_from_payment($1)", paymentSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) EditBookingFromPayment(ctx context.Context, bookingSlug string, paymentSlug string) (int, error) {
|
||||||
|
return tx.GetInt(ctx, "select edit_booking_from_payment($1, $2)", bookingSlug, paymentSlug)
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *Tx) EditBooking(ctx context.Context, bookingID int, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, bookingStatus string, campsiteIDs []int) error {
|
func (tx *Tx) EditBooking(ctx context.Context, bookingID int, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, bookingStatus string, campsiteIDs []int) error {
|
||||||
_, err := tx.Exec(ctx, "select edit_booking($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", bookingID, customerName, zeronull.Text(customerAddress), zeronull.Text(customerPostCode), zeronull.Text(customerCity), zeronull.Text(customerCountryCode), zeronull.Text(customerEmail), zeronull.Text(customerPhone), customerLangTag, bookingStatus, campsiteIDs)
|
_, err := tx.Exec(ctx, "select edit_booking($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", bookingID, customerName, zeronull.Text(customerAddress), zeronull.Text(customerPostCode), zeronull.Text(customerCity), zeronull.Text(customerCountryCode), zeronull.Text(customerEmail), zeronull.Text(customerPhone), customerLangTag, bookingStatus, campsiteIDs)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert camper:edit_booking_from_payment from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop function if exists camper.edit_booking_from_payment(uuid, uuid);
|
||||||
|
|
||||||
|
commit;
|
|
@ -289,3 +289,4 @@ booking__payment_fields [booking positive_integer nonnegative_integer] 2024-04-2
|
||||||
booking_option [roles schema_camper booking campsite_type_option positive_integer nonnegative_integer] 2024-04-24T11:14:03Z jordi fita mas <jordi@tandem.blog> # Add booking campsite option relation
|
booking_option [roles schema_camper booking campsite_type_option positive_integer nonnegative_integer] 2024-04-24T11:14:03Z jordi fita mas <jordi@tandem.blog> # Add booking campsite option relation
|
||||||
add_booking_from_payment [roles schema_camper booking booking__payment_fields booking__stay booking_option payment payment__acsi_card payment_customer payment_option] 2024-04-24T11:23:22Z jordi fita mas <jordi@tandem.blog> # Add function to create a pre-booking from a payment
|
add_booking_from_payment [roles schema_camper booking booking__payment_fields booking__stay booking_option payment payment__acsi_card payment_customer payment_option] 2024-04-24T11:23:22Z jordi fita mas <jordi@tandem.blog> # Add function to create a pre-booking from a payment
|
||||||
edit_booking [roles schema_camper booking booking__payment_fields booking__stay booking_campsite] 2024-04-24T16:27:10Z jordi fita mas <jordi@tandem.blog> # Add function to update a booking
|
edit_booking [roles schema_camper booking booking__payment_fields booking__stay booking_campsite] 2024-04-24T16:27:10Z jordi fita mas <jordi@tandem.blog> # Add function to update a booking
|
||||||
|
edit_booking_from_payment [roles schema_camper booking booking__payment_fields booking__stay booking_option payment payment__acsi_card payment_option] 2024-04-25T17:18:41Z jordi fita mas <jordi@tandem.blog> # Add function to edit a booking from a payment
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
-- Test edit_booking_from_payment
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(12);
|
||||||
|
|
||||||
|
set search_path to camper, public;
|
||||||
|
|
||||||
|
select has_function('camper', 'edit_booking_from_payment', array['uuid', 'uuid']);
|
||||||
|
select function_lang_is('camper', 'edit_booking_from_payment', array['uuid', 'uuid'], 'plpgsql');
|
||||||
|
select function_returns('camper', 'edit_booking_from_payment', array['uuid', 'uuid'], 'integer');
|
||||||
|
select isnt_definer('camper', 'edit_booking_from_payment', array['uuid', 'uuid']);
|
||||||
|
select volatility_is('camper', 'edit_booking_from_payment', array['uuid', 'uuid'], 'volatile');
|
||||||
|
select function_privs_are('camper', 'edit_booking_from_payment', array ['uuid', 'uuid'], 'guest', array[]::text[]);
|
||||||
|
select function_privs_are('camper', 'edit_booking_from_payment', array ['uuid', 'uuid'], 'employee', array['EXECUTE']);
|
||||||
|
select function_privs_are('camper', 'edit_booking_from_payment', array ['uuid', 'uuid'], 'admin', array['EXECUTE']);
|
||||||
|
select function_privs_are('camper', 'edit_booking_from_payment', array ['uuid', 'uuid'], 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
|
|
||||||
|
set client_min_messages to warning;
|
||||||
|
truncate booking_option cascade;
|
||||||
|
truncate booking cascade;
|
||||||
|
truncate payment_option cascade;
|
||||||
|
truncate payment cascade;
|
||||||
|
truncate campsite_type_option cascade;
|
||||||
|
truncate campsite_type cascade;
|
||||||
|
truncate media cascade;
|
||||||
|
truncate media_content cascade;
|
||||||
|
truncate company cascade;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, tourist_tax_max_days, country_code, currency_code, default_lang_tag)
|
||||||
|
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 7, 'ES', 'EUR', 'ca')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into media_content (media_type, bytes)
|
||||||
|
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into media (media_id, company_id, original_filename, content_hash)
|
||||||
|
values (6, 2, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into campsite_type (campsite_type_id, company_id, name, media_id, max_campers, bookable_nights)
|
||||||
|
values (12, 2, 'Wooden lodge', 6, 7, '[1, 7]')
|
||||||
|
, (14, 2, 'Bungalow', 6, 7, '[1, 7]')
|
||||||
|
, (16, 2, 'Plot', 6, 7, '[1, 7]')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into campsite_type_option (campsite_type_option_id, campsite_type_id, name, range, per_night)
|
||||||
|
values (18, 12, 'Big tent', '[0, 4)', true)
|
||||||
|
, (20, 12, 'Small tent', '[0, 4)', true)
|
||||||
|
, (22, 14, 'Electricity', '[0, 5)', false)
|
||||||
|
, (24, 14, 'Car', '[0, 4)', true)
|
||||||
|
, (26, 16, 'Autocaravan', '[0, 4)', true)
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment (payment_id, slug, company_id, campsite_type_id, arrival_date, departure_date, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, currency_code, zone_preferences, acsi_card, payment_status)
|
||||||
|
values (28, '4ef35e2f-ef98-42d6-a724-913bd761ca8c', 2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'EUR', 'pref I before E', true, 'draft')
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into payment_option (payment_id, campsite_type_option_id, units, subtotal)
|
||||||
|
values (28, 18, 1, 1500)
|
||||||
|
;
|
||||||
|
|
||||||
|
insert into booking (booking_id, slug, company_id, campsite_type_id, stay, holder_name, address, postal_code, city, country_code, email, phone, lang_tag, zone_preferences, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, acsi_card, currency_code, booking_status)
|
||||||
|
values (30, 'e3c478f1-8895-4cc6-b644-b19b5a553bb9', 2, 14, daterange('2024-08-29', '2024-09-03'), 'First', 'Fake St., 123', '17800', 'Girona', 'ES', 'customer@example.com', '+34 977 97 79 77', 'ca', '', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, false, 'EUR', 'confirmed');
|
||||||
|
|
||||||
|
insert into booking_option (booking_id, campsite_type_option_id, units, subtotal)
|
||||||
|
values (30, 22, 2, 111)
|
||||||
|
, (30, 24, 3, 2222)
|
||||||
|
;
|
||||||
|
|
||||||
|
select lives_ok(
|
||||||
|
$$ select edit_booking_from_payment('e3c478f1-8895-4cc6-b644-b19b5a553bb9', '4ef35e2f-ef98-42d6-a724-913bd761ca8c') $$,
|
||||||
|
'Should be able to update a booking from a payment'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq(
|
||||||
|
$$ select booking_id, company_id, campsite_type_id, stay, holder_name, address, postal_code, city, country_code::text, email::text, phone::text, lang_tag, zone_preferences, subtotal_nights::integer, number_adults::integer, subtotal_adults::integer, number_teenagers::integer, subtotal_teenagers::integer, number_children::integer, subtotal_children::integer, number_dogs::integer, subtotal_dogs::integer, subtotal_tourist_tax::integer, total::integer, acsi_card, currency_code::text, booking_status from booking $$,
|
||||||
|
$$ values (30, 2, 12, daterange('2024-08-28', '2024-09-04'), 'First', 'Fake St., 123', '17800', 'Girona', 'ES', 'customer@example.com', '+34 977 97 79 77', 'ca', 'pref I before E', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, true, 'EUR', 'confirmed')
|
||||||
|
$$,
|
||||||
|
'Should have updated the booking'
|
||||||
|
);
|
||||||
|
|
||||||
|
select bag_eq (
|
||||||
|
$$ select booking_id, campsite_type_option_id, units, subtotal from booking_option $$,
|
||||||
|
$$ values (30, 18, 1, 1500)
|
||||||
|
$$ ,
|
||||||
|
'Should have updated the booking options too'
|
||||||
|
);
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Verify camper:edit_booking_from_payment on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select has_function_privilege('camper.edit_booking_from_payment(uuid, uuid)', 'execute');
|
||||||
|
|
||||||
|
rollback;
|
|
@ -882,7 +882,7 @@ label[x-show] > span, label[x-show] > br {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#booking-form #campsites-booking {
|
#booking-form #campsites-booking, #booking-form .error {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,277 +4,271 @@
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.adminBookingForm*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.adminBookingForm*/ -}}
|
||||||
{{ CSRFInput }}
|
<input type="hidden" name="{{ .PaymentSlug.Name }}" value="{{ .PaymentSlug.Val }}">
|
||||||
<fieldset>
|
{{ if .Error -}}
|
||||||
<input type="hidden" name="{{ .PaymentSlug.Name }}" value="{{ .PaymentSlug.Val }}">
|
<p class="error">{{ .Error }}</p>
|
||||||
{{ with .CampsiteType -}}
|
{{- end }}
|
||||||
<label>
|
{{ with .CampsiteType -}}
|
||||||
{{( pgettext "Accommodation" "title" )}}<br>
|
<label>
|
||||||
<select name="{{ .Name }}"
|
{{( pgettext "Accommodation" "title" )}}<br>
|
||||||
required
|
<select name="{{ .Name }}"
|
||||||
data-hx-get="/admin/bookings/new" data-hx-trigger="change"
|
required
|
||||||
{{ template "error-attrs" . }}
|
data-hx-get="{{ $.URL }}" data-hx-trigger="change"
|
||||||
>
|
{{ template "error-attrs" . }}
|
||||||
<option value="">{{( gettext "Choose an accommodation" )}}</option>
|
|
||||||
{{ template "list-options" . }}
|
|
||||||
</select><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Dates -}}
|
|
||||||
<fieldset class="booking-period"
|
|
||||||
data-hx-get="/admin/bookings/new"
|
|
||||||
data-hx-trigger="change delay:500ms"
|
|
||||||
>
|
>
|
||||||
{{ with .ArrivalDate -}}
|
<option value="">{{( gettext "Choose an accommodation" )}}</option>
|
||||||
<label>
|
{{ template "list-options" . }}
|
||||||
{{( pgettext "Arrival date" "input" )}}<br>
|
</select><br>
|
||||||
<input type="date" required
|
{{ template "error-message" . }}
|
||||||
min="{{ formatDateAttr .MinDate }}"
|
</label>
|
||||||
max="{{ formatDateAttr .MaxDate }}"
|
{{- end }}
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
{{ with .Dates -}}
|
||||||
><br>
|
<fieldset class="booking-period"
|
||||||
{{ template "error-message" . }}
|
data-hx-get="{{ $.URL }}"
|
||||||
</label>
|
data-hx-trigger="change delay:500ms"
|
||||||
{{- end }}
|
>
|
||||||
{{ with .DepartureDate -}}
|
{{ with .ArrivalDate -}}
|
||||||
<label>
|
<label>
|
||||||
{{( pgettext "Departure date" "input" )}}<br>
|
{{( pgettext "Arrival date" "input" )}}<br>
|
||||||
<input type="date" required
|
<input type="date" required
|
||||||
min="{{ formatDateAttr .MinDate }}"
|
min="{{ formatDateAttr .MinDate }}"
|
||||||
max="{{ formatDateAttr .MaxDate }}"
|
max="{{ formatDateAttr .MaxDate }}"
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
</fieldset>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Options -}}
|
|
||||||
{{ with .ZonePreferences -}}
|
|
||||||
<label>{{( pgettext "Area preferences (optional)" "input" )}}<br>
|
|
||||||
<input type="text"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
><br>
|
><br>
|
||||||
<a href="/{{ currentLocale }}/campground?zones" target="_blank">{{( gettext "Campground map" )}}</a><br>
|
|
||||||
{{ template "error-message" . }}
|
{{ template "error-message" . }}
|
||||||
</label>
|
</label>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{ with .DepartureDate -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Departure date" "input" )}}<br>
|
||||||
|
<input type="date" required
|
||||||
|
min="{{ formatDateAttr .MinDate }}"
|
||||||
|
max="{{ formatDateAttr .MaxDate }}"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
</fieldset>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Options -}}
|
||||||
|
{{ with .ZonePreferences -}}
|
||||||
|
<label>{{( pgettext "Area preferences (optional)" "input" )}}<br>
|
||||||
|
<input type="text"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
<a href="/{{ currentLocale }}/campground?zones" target="_blank">{{( gettext "Campground map" )}}</a><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ with $guests := .Guests -}}
|
{{- end }}
|
||||||
{{ $draft := $.Cart.Draft }}
|
{{ with $guests := .Guests -}}
|
||||||
<fieldset class="booking-items"
|
{{ $draft := $.Cart.Draft }}
|
||||||
data-hx-get="/admin/bookings/new" data-hx-trigger="change"
|
<fieldset class="booking-items"
|
||||||
>
|
data-hx-get="{{ $.URL }}" data-hx-trigger="change"
|
||||||
<table>
|
>
|
||||||
<thead>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{( pgettext "Units" "header" )}}</th>
|
||||||
|
<th scope="col">{{( pgettext "Decription" "header" )}}</th>
|
||||||
|
<th scope="col" class="numeric">{{( pgettext "Total" "header" )}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $draft.NumNights }}</td>
|
||||||
|
<td>{{( pgettext "Night" "cart" )}}</td>
|
||||||
|
<td class="numeric">{{ formatPrice $draft.Nights }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ with .NumberAdults -}}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{( pgettext "Units" "header" )}}</th>
|
<td><input id="adults" type="number" required
|
||||||
<th scope="col">{{( pgettext "Decription" "header" )}}</th>
|
name="{{ .Name }}" value="{{ .Val }}"
|
||||||
<th scope="col" class="numeric">{{( pgettext "Total" "header" )}}</th>
|
min="1"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests }}"{{ end }}
|
||||||
|
{{ template "error-attrs" . }}
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label for="adults">{{( pgettext "Adults aged 17 or older" "input" )}}</label><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</td>
|
||||||
|
<td class="numeric">{{ formatPrice $draft.Adults }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
{{- end }}
|
||||||
<tbody>
|
{{ with .NumberTeenagers -}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $draft.NumNights }}</td>
|
<td>
|
||||||
<td>{{( pgettext "Night" "cart" )}}</td>
|
<input id="teenagers" type="number" required
|
||||||
<td class="numeric">{{ formatPrice $draft.Nights }}</td>
|
name="{{ .Name }}" value="{{ .Val }}"
|
||||||
|
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
|
||||||
|
{{ template "error-attrs" . }}
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label for="teenagers">{{( pgettext "Teenagers from 11 to 16 years old" "input" )}}</label><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</td>
|
||||||
|
<td class="numeric">{{ formatPrice $draft.Teenagers }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ with .NumberAdults -}}
|
{{- end }}
|
||||||
<tr>
|
{{ with .NumberChildren -}}
|
||||||
<td><input id="adults" type="number" required
|
<tr>
|
||||||
name="{{ .Name }}" value="{{ .Val }}"
|
<td>
|
||||||
min="1"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests }}"{{ end }}
|
<input id="children" type="number" required
|
||||||
{{ template "error-attrs" . }}
|
name="{{ .Name }}" value="{{ .Val }}"
|
||||||
>
|
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
|
||||||
</td>
|
{{ template "error-attrs" . }}
|
||||||
<td>
|
>
|
||||||
<label for="adults">{{( pgettext "Adults aged 17 or older" "input" )}}</label><br>
|
</td>
|
||||||
{{ template "error-message" . }}
|
<td>
|
||||||
</td>
|
<label for="children">{{( pgettext "Children from 2 to 10 years old" "input" )}}</label><br>
|
||||||
<td class="numeric">{{ formatPrice $draft.Adults }}</td>
|
{{ template "error-message" . }}
|
||||||
</tr>
|
</td>
|
||||||
{{- end }}
|
<td class="numeric">{{ formatPrice $draft.Children }}</td>
|
||||||
{{ with .NumberTeenagers -}}
|
</tr>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .NumberDogs -}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input id="dogs" type="number" required
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" min="0"
|
||||||
|
{{ template "error-attrs" . }}
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label for="dogs">{{( pgettext "Dogs" "input" )}}</label><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</td>
|
||||||
|
<td class="numeric">{{ formatPrice $draft.Dogs }}</td>
|
||||||
|
</tr>
|
||||||
|
{{- end }}
|
||||||
|
{{ with $.Options -}}
|
||||||
|
{{ range .Options -}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<input id="teenagers" type="number" required
|
<input id="{{ .Input.Name }}" type="number" required
|
||||||
name="{{ .Name }}" value="{{ .Val }}"
|
name="{{ .Input.Name }}" value="{{ .Input.Val }}"
|
||||||
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
|
min="{{ .Min }}" max="{{ .Max }}"
|
||||||
{{ template "error-attrs" . }}
|
{{ template "error-attrs" .Input }}
|
||||||
>
|
>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="teenagers">{{( pgettext "Teenagers from 11 to 16 years old" "input" )}}</label><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</td>
|
|
||||||
<td class="numeric">{{ formatPrice $draft.Teenagers }}</td>
|
|
||||||
</tr>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .NumberChildren -}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input id="children" type="number" required
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}"
|
|
||||||
min="0"{{if not $guests.OverflowAllowed }} max="{{ $guests.MaxGuests | dec }}"{{ end }}
|
|
||||||
{{ template "error-attrs" . }}
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="children">{{( pgettext "Children from 2 to 10 years old" "input" )}}</label><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</td>
|
|
||||||
<td class="numeric">{{ formatPrice $draft.Children }}</td>
|
|
||||||
</tr>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .NumberDogs -}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input id="dogs" type="number" required
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" min="0"
|
|
||||||
{{ template "error-attrs" . }}
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="dogs">{{( pgettext "Dogs" "input" )}}</label><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</td>
|
|
||||||
<td class="numeric">{{ formatPrice $draft.Dogs }}</td>
|
|
||||||
</tr>
|
|
||||||
{{- end }}
|
|
||||||
{{ with $.Options -}}
|
|
||||||
{{ range .Options -}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input id="{{ .Input.Name }}" type="number" required
|
|
||||||
name="{{ .Input.Name }}" value="{{ .Input.Val }}"
|
|
||||||
min="{{ .Min }}" max="{{ .Max }}"
|
|
||||||
{{ template "error-attrs" .Input }}
|
|
||||||
>
|
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<label for="{{ .Input.Name }}">{{ .Label }}</label><br>
|
<label for="{{ .Input.Name }}">{{ .Label }}</label><br>
|
||||||
{{ template "error-message" .Input }}
|
{{ template "error-message" .Input }}
|
||||||
</td>
|
</td>
|
||||||
<td class="numeric">{{ formatPrice .Subtotal }}</td>
|
<td class="numeric">{{ formatPrice .Subtotal }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
<tr>
|
|
||||||
<td>{{ $draft.NumAdults }}</td>
|
|
||||||
<td>{{( pgettext "Tourist tax" "cart" )}}</td>
|
|
||||||
<td class="numeric">{{ formatPrice $draft.TouristTax }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<th scope="row" colspan="2" class="numeric">{{( pgettext "Total" "header" )}}</th>
|
|
||||||
<td class="numeric">{{ formatPrice $draft.Total }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
{{ if not .NumberDogs -}}
|
|
||||||
<small>{{( gettext "Note: This accommodation does <strong>not</strong> allow dogs.") | raw }}</small>
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ if .Error -}}
|
<tr>
|
||||||
<p class="error">{{ .Error }}</p>
|
<td>{{ $draft.NumAdults }}</td>
|
||||||
{{- end }}
|
<td>{{( pgettext "Tourist tax" "cart" )}}</td>
|
||||||
</fieldset>
|
<td class="numeric">{{ formatPrice $draft.TouristTax }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" colspan="2" class="numeric">{{( pgettext "Total" "header" )}}</th>
|
||||||
|
<td class="numeric">{{ formatPrice $draft.Total }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
{{ if not .NumberDogs -}}
|
||||||
|
<small>{{( gettext "Note: This accommodation does <strong>not</strong> allow dogs.") | raw }}</small>
|
||||||
|
{{- end }}
|
||||||
|
{{ if .Error -}}
|
||||||
|
<p class="error">{{ .Error }}</p>
|
||||||
|
{{- end }}
|
||||||
|
</fieldset>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Customer -}}
|
||||||
|
<fieldset class="customer-details">
|
||||||
|
<legend>{{( pgettext "Customer Details" "title" )}}</legend>
|
||||||
|
{{ with .FullName -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Full name" "input" )}}<br>
|
||||||
|
<input type="text" required minlength="2"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Country -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Country (optional)" "input" )}}<br>
|
||||||
|
<select name="{{ .Name }}"
|
||||||
|
{{ template "error-attrs" . }}
|
||||||
|
>
|
||||||
|
<option>{{( gettext "Choose a country" )}}</option>
|
||||||
|
{{ template "list-options" . }}
|
||||||
|
</select><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Address -}}
|
||||||
|
<label class="colspan">
|
||||||
|
{{( pgettext "Address (optional)" "input" )}}<br>
|
||||||
|
<input type="text"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .PostalCode -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Postcode (optional)" "input" )}}<br>
|
||||||
|
<input type="text"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .City -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Town or village (optional)" "input" )}}<br>
|
||||||
|
<input type="text"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Email -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Email (optional)" "input" )}}<br>
|
||||||
|
<input type="email"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with .Phone -}}
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Phone (optional)" "input" )}}<br>
|
||||||
|
<input type="tel"
|
||||||
|
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
||||||
|
><br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
{{ with $.Guests.ACSICard -}}
|
||||||
|
<label class="colspan" data-hx-get="{{ $.URL }}" data-hx-trigger="change">
|
||||||
|
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
|
||||||
|
{{ template "error-attrs" . }}
|
||||||
|
> {{( pgettext "ACSI card? (optional)" "input" )}}<br>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</label>
|
||||||
|
{{- end }}
|
||||||
|
</fieldset>
|
||||||
|
{{- end }}
|
||||||
|
{{ if .Campsites -}}
|
||||||
|
<h3>{{( pgettext "Campsites" "title" )}}</h3>
|
||||||
|
{{ template "grid.gohtml" . }}
|
||||||
|
{{ if .Error -}}
|
||||||
|
<p class="error">{{ .Error }}</p>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ with .Customer -}}
|
{{- end }}
|
||||||
<fieldset class="customer-details">
|
|
||||||
<legend>{{( pgettext "Customer Details" "title" )}}</legend>
|
|
||||||
{{ with .FullName -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Full name" "input" )}}<br>
|
|
||||||
<input type="text" required minlength="2"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Country -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Country (optional)" "input" )}}<br>
|
|
||||||
<select name="{{ .Name }}"
|
|
||||||
{{ template "error-attrs" . }}
|
|
||||||
>
|
|
||||||
<option>{{( gettext "Choose a country" )}}</option>
|
|
||||||
{{ template "list-options" . }}
|
|
||||||
</select><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Address -}}
|
|
||||||
<label class="colspan">
|
|
||||||
{{( pgettext "Address (optional)" "input" )}}<br>
|
|
||||||
<input type="text"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .PostalCode -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Postcode (optional)" "input" )}}<br>
|
|
||||||
<input type="text"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .City -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Town or village (optional)" "input" )}}<br>
|
|
||||||
<input type="text"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Email -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Email (optional)" "input" )}}<br>
|
|
||||||
<input type="email"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with .Phone -}}
|
|
||||||
<label>
|
|
||||||
{{( pgettext "Phone (optional)" "input" )}}<br>
|
|
||||||
<input type="tel"
|
|
||||||
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
|
|
||||||
><br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
{{ with $.Guests.ACSICard -}}
|
|
||||||
<label class="colspan" data-hx-get="/admin/bookings/new" data-hx-trigger="change">
|
|
||||||
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
|
|
||||||
{{ template "error-attrs" . }}
|
|
||||||
> {{( pgettext "ACSI card? (optional)" "input" )}}<br>
|
|
||||||
{{ template "error-message" . }}
|
|
||||||
</label>
|
|
||||||
{{- end }}
|
|
||||||
</fieldset>
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Campsites -}}
|
|
||||||
<h3>{{( pgettext "Campsites" "title" )}}</h3>
|
|
||||||
{{ template "grid.gohtml" . }}
|
|
||||||
{{- end }}
|
|
||||||
</fieldset>
|
|
||||||
<footer>
|
|
||||||
<button type="submit">
|
|
||||||
{{- if .ID -}}
|
|
||||||
{{( pgettext "Update" "action" )}}
|
|
||||||
{{- else -}}
|
|
||||||
{{( pgettext "Add" "action" )}}
|
|
||||||
{{- end -}}
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{{ define "campsite-heading" -}}
|
{{ define "campsite-heading" -}}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.CampsiteEntry*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.CampsiteEntry*/ -}}
|
||||||
|
@ -282,6 +276,6 @@
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="campsite"
|
name="campsite"
|
||||||
value="{{ .ID }}"
|
value="{{ .ID }}"
|
||||||
{{- if .Selected }} checked="checked"{{ end -}}
|
{{- if .Selected }} checked="checked"{{ end -}}
|
||||||
> {{ .Label }}</label>
|
> {{ .Label }}</label>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{ define "head" -}}
|
||||||
|
<script src="/static/idiomorph-ext@0.3.0.min.js"></script>
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{ define "breadcrumb" -}}
|
{{ define "breadcrumb" -}}
|
||||||
<li><a href="./">{{( pgettext "Bookings" "title" )}}</a></li>
|
<li><a href="./">{{( pgettext "Bookings" "title" )}}</a></li>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -18,19 +22,28 @@
|
||||||
{{ define "content" -}}
|
{{ define "content" -}}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.adminBookingForm*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.adminBookingForm*/ -}}
|
||||||
<h2>{{ template "title" .}}</h2>
|
<h2>{{ template "title" .}}</h2>
|
||||||
{{ if .Error -}}
|
|
||||||
<p class="error">{{ .Error }}</p>
|
|
||||||
{{- end }}
|
|
||||||
<form id="booking-form"
|
<form id="booking-form"
|
||||||
data-hx-ext="morph"
|
data-hx-ext="morph"
|
||||||
data-hx-swap="morph:innerHTML"
|
data-hx-swap="morph:innerHTML"
|
||||||
data-hx-include="this"
|
data-hx-include="this"
|
||||||
data-hx-target="this"
|
data-hx-target="#booking-form-fields"
|
||||||
data-hx-replace-url="true"
|
data-hx-replace-url="true"
|
||||||
{{- if .ID }} data-hx-put="/admin/bookings/{{ .CurrentLabel }}"
|
{{- if .URL }} data-hx-put="{{ .URL }}"
|
||||||
{{- else }} action="/admin/bookings" method="post"
|
{{- else }} action="/admin/bookings" method="post"
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
>
|
>
|
||||||
{{ template "fields.gohtml" . }}
|
{{ CSRFInput }}
|
||||||
|
<fieldset id="booking-form-fields">
|
||||||
|
{{ template "fields.gohtml" . }}
|
||||||
|
</fieldset>
|
||||||
|
<footer>
|
||||||
|
<button type="submit">
|
||||||
|
{{- if .ID -}}
|
||||||
|
{{( pgettext "Update" "action" )}}
|
||||||
|
{{- else -}}
|
||||||
|
{{( pgettext "Add" "action" )}}
|
||||||
|
{{- end -}}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
Loading…
Reference in New Issue