From cdd91c815edcc5cae08d886acf84ba3d756d22f2 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Fri, 19 Apr 2024 21:09:28 +0200 Subject: [PATCH] Show booking on booking grid I need the campsite_id in booking to know what row to show the booking at. Besides the need of knowing which actual campsite has been booked, of course. This field is nullable because we can not now it until an employee has confirmed the booking; until that point we only know the campsite type customer requested. I do not care much if the campsite_id is from a different campsite_type, because maybe the customer requested the change by phone or what have you, therefore the database can not be that strict. It must have a value if the booking is confirmed. It helps me if the arrival_date and departure_date is a single daterange, because then i can use `&&` and other range operators to work with these dates. For instance, i have to intersect it with the range displayed on the screen in order to know which day i have to put it. But then i have to know whether the booking begins and ends in the display range, because i only have to show arrival and departure (i.e., the box half-way within the first or last boxes) on these days only. --- demo/demo.sql | 36 ++++++----- deploy/booking__campsite_id.sql | 13 ++++ deploy/booking__stay.sql | 24 +++++++ pkg/campsite/admin.go | 79 +++++++++++++++++++---- po/ca.po | 38 +++++------ po/es.po | 38 +++++------ po/fr.po | 38 +++++------ revert/booking__campsite_id.sql | 9 +++ revert/booking__stay.sql | 23 +++++++ sqitch.plan | 2 + test/booking.sql | 72 +++++++++++++-------- verify/booking__campsite_id.sql | 10 +++ verify/booking__stay.sql | 10 +++ web/static/camper.css | 24 ++++++- web/templates/admin/campsite/index.gohtml | 15 ++++- 15 files changed, 314 insertions(+), 117 deletions(-) create mode 100644 deploy/booking__campsite_id.sql create mode 100644 deploy/booking__stay.sql create mode 100644 revert/booking__campsite_id.sql create mode 100644 revert/booking__stay.sql create mode 100644 verify/booking__campsite_id.sql create mode 100644 verify/booking__stay.sql diff --git a/demo/demo.sql b/demo/demo.sql index 9e700aa..f65aa5b 100644 --- a/demo/demo.sql +++ b/demo/demo.sql @@ -1421,12 +1421,12 @@ values (72, daterange(make_date(extract(year from current_date)::int, 2, 4), m , (72, daterange(make_date(extract(year from current_date)::int, 9, 1), make_date(extract(year from current_date)::int, 10, 13))) ; -insert into acsi_option (campsite_type_id, campsite_type_option_id, units) -values (72, 102, 1) - , (72, 103, 1) - , (72, 104, 1) - , (72, 107, 1) - , (72, 109, 1) +insert into acsi_option (campsite_type_id, campsite_type_option_id, units, option_group) +values (72, 102, 1, 1) + , (72, 103, 1, 2) + , (72, 104, 1, 3) + , (72, 107, 1, 4) + , (72, 109, 1, 5) ; alter table surroundings_highlight alter column surroundings_highlight_id restart with 112; @@ -1488,16 +1488,20 @@ select translate_surroundings_ad(52, 'fr', 'Venez faire du canyoning à Sadernes alter table booking alter column booking_id restart with 122; -insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card, booking_status) -values (52, 72, 'Juli Verd', current_date + interval '23 days', current_date + interval '25 days', 0, false, 'created') - , (52, 72, 'Pere Gil', current_date + interval '24 days', current_date + interval '25 days', 1, true, 'cancelled') - , (52, 73, 'Calèndula Groga', current_date + interval '24 days', current_date + interval '25 days', 0, false, 'confirmed') - , (52, 73, 'Rosa Blava', current_date + interval '15 days', current_date + interval '22 days', 0, false, 'checked-in') - , (52, 74, 'Margarita Blanca', current_date + interval '7 days', current_date + interval '8 days', 0, false, 'invoiced') - , (52, 74, 'Camèlia Vermella', current_date + interval '7 days', current_date + interval '8 days', 0, false, 'created') - , (52, 74, 'Valeriana Rosa', current_date + interval '3 days', current_date + interval '8 days', 0, true, 'cancelled') - , (52, 75, 'Jacint Violeta', current_date + interval '30 days', current_date + interval '33 days', 0, false, 'checked-in') - , (52, 76, 'Hortènsia Grisa', current_date + interval '29 days', current_date + interval '34 days', 0, false, 'invoiced') +insert into booking (company_id, campsite_type_id, campsite_id, holder_name, stay, number_dogs, acsi_card, booking_status) +values (52, 72, null, 'Juli Verd', daterange((current_date + interval '23 days')::date, (current_date + interval '25 days')::date), 0, false, 'created') + , (52, 72, null, 'Camèlia Vermella', daterange((current_date + interval '7 days')::date, (current_date + interval '8 days')::date), 0, false, 'created') + , (52, 72, 90, 'Margarita Blanca', daterange((current_date + interval '7 days')::date, (current_date + interval '8 days')::date), 0, false, 'invoiced') + , (52, 72, 90, 'Rosa Blava', daterange((current_date + interval '8 days')::date, (current_date + interval '11 days')::date), 0, false, 'checked-in') + , (52, 72, 90, 'Calèndula Groga', daterange((current_date + interval '14 days')::date, (current_date + interval '21 days')::date), 0, false, 'confirmed') + , (52, 72, 91, 'Jacint Violeta', daterange((current_date + interval '9 days')::date, (current_date + interval '13 days')::date), 0, false, 'checked-in') + , (52, 72, 92, 'Hortènsia Grisa', daterange((current_date + interval '4 days')::date, (current_date + interval '8 days')::date), 0, false, 'invoiced') + , (52, 72, 93, 'Pere Gil', daterange((current_date + interval '9 days')::date, (current_date + interval '19 days')::date), 1, true, 'confirmed') + , (52, 72, 94, 'Juli Verd', daterange((current_date + interval '11 days')::date, (current_date + interval '13 days')::date), 0, false, 'confirmed') + , (52, 72, 94, 'Camèlia Vermella', daterange((current_date + interval '13 days')::date, (current_date + interval '15 days')::date), 0, false, 'confirmed') + , (52, 72, 94, 'Valeriana Rosa', daterange((current_date + interval '15 days')::date, (current_date + interval '17 days')::date), 0, false, 'confirmed') + , (52, 72, null, 'Pere Gil', daterange((current_date + interval '24 days')::date, (current_date + interval '25 days')::date), 1, true, 'cancelled') + , (52, 72, 83, 'Valeriana Rosa', daterange((current_date + interval '3 days')::date, (current_date + interval '8 days')::date), 0, true, 'cancelled') ; diff --git a/deploy/booking__campsite_id.sql b/deploy/booking__campsite_id.sql new file mode 100644 index 0000000..e16d9d7 --- /dev/null +++ b/deploy/booking__campsite_id.sql @@ -0,0 +1,13 @@ +-- Deploy camper:booking__campsite_id to pg +-- requires: booking + +begin; + +set search_path to camper, public; + +alter table booking + add column campsite_id integer references campsite +, add constraint booking_needs_campsite check ( booking_status in ('created', 'cancelled') or campsite_id is not null ) +; + +commit; diff --git a/deploy/booking__stay.sql b/deploy/booking__stay.sql new file mode 100644 index 0000000..fe5a1b8 --- /dev/null +++ b/deploy/booking__stay.sql @@ -0,0 +1,24 @@ +-- Deploy camper:booking__stay to pg +-- requires: booking + +begin; + +set search_path to camper, public; + +alter table booking +add column stay daterange constraint stay_not_empty check (not isempty(stay)) +; + +update booking +set stay = daterange(arrival_date, departure_date) +; + +alter table booking + drop column if exists arrival_date +, drop column if exists departure_date +, alter column stay set not null +; + +create index stay_idx on booking using gist (stay); + +commit; diff --git a/pkg/campsite/admin.go b/pkg/campsite/admin.go index a57e0ec..20fd947 100644 --- a/pkg/campsite/admin.go +++ b/pkg/campsite/admin.go @@ -7,7 +7,6 @@ package campsite import ( "context" - "fmt" "net/http" "time" @@ -91,20 +90,20 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat } func serveCampsiteIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { - campsites, err := collectCampsiteEntries(r.Context(), company, conn) - if err != nil { - panic(err) - } - page := newCampsiteIndex(campsites) + page := newCampsiteIndex() if err := page.Parse(r); err != nil { panic(err) } - fmt.Println(page.From, page.To) + var err error + page.Campsites, err = collectCampsiteEntries(r.Context(), company, conn, page.From.Date(), page.To.Date()) + if err != nil { + panic(err) + } page.Months = collectMonths(page.From.Date(), page.To.Date()) page.MustRender(w, r, user, company) } -func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*campsiteEntry, error) { +func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time) ([]*campsiteEntry, error) { rows, err := conn.Query(ctx, ` select campsite.label , campsite_type.name @@ -118,6 +117,7 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da } defer rows.Close() + byLabel := make(map[string]*campsiteEntry) var campsites []*campsiteEntry for rows.Next() { entry := &campsiteEntry{} @@ -125,15 +125,69 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da return nil, err } campsites = append(campsites, entry) + byLabel[entry.Label] = entry + } + + if err := collectBookingEntries(ctx, company, conn, from, to, byLabel); err != nil { + return nil, err } return campsites, nil } +func collectBookingEntries(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time, campsites map[string]*campsiteEntry) error { + lastDay := to.AddDate(0, 1, 0) + rows, err := conn.Query(ctx, ` + select campsite.label + , lower(stay * daterange($2::date, $3::date)) + , holder_name + , booking_status + , upper(stay * daterange($2::date, $3::date)) - lower(stay * daterange($2::date, $3::date)) + , stay &> daterange($2::date, $3::date) + , stay &< daterange($2::date, $3::date) + from booking + join campsite using (campsite_id) + where booking.company_id = $1 + and stay && daterange($2::date, $3::date) + and booking_status <> 'cancelled' + order by label`, company.ID, from, lastDay) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + entry := &bookingEntry{} + var label string + var date time.Time + if err = rows.Scan(&label, &date, &entry.Holder, &entry.Status, &entry.Nights, &entry.Begin, &entry.End); err != nil { + return err + } + campsite := campsites[label] + if campsite != nil { + if campsite.Bookings == nil { + campsite.Bookings = make(map[time.Time]*bookingEntry) + } + campsite.Bookings[date] = entry + } + } + + return nil +} + type campsiteEntry struct { - Label string - Type string - Active bool + Label string + Type string + Active bool + Bookings map[time.Time]*bookingEntry +} + +type bookingEntry struct { + Holder string + Status string + Nights int + Begin bool + End bool } type campsiteIndex struct { @@ -143,12 +197,11 @@ type campsiteIndex struct { Months []*Month } -func newCampsiteIndex(campsites []*campsiteEntry) *campsiteIndex { +func newCampsiteIndex() *campsiteIndex { now := time.Now() from := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) to := from.AddDate(0, 3, 0) return &campsiteIndex{ - Campsites: campsites, From: &form.Month{ Name: "from", Year: from.Year(), diff --git a/po/ca.po b/po/ca.po index 8ef8bff..008d2ae 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-04-19 11:18+0200\n" +"POT-Creation-Date: 2024-04-19 20:53+0200\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -910,7 +910,7 @@ msgstr "Menú" #: web/templates/admin/campsite/carousel/index.gohtml:10 #: web/templates/admin/campsite/form.gohtml:15 #: web/templates/admin/campsite/index.gohtml:6 -#: web/templates/admin/campsite/index.gohtml:15 +#: web/templates/admin/campsite/index.gohtml:18 #: web/templates/admin/campsite/type/feature/form.gohtml:16 #: web/templates/admin/campsite/type/feature/index.gohtml:10 #: web/templates/admin/campsite/type/carousel/form.gohtml:16 @@ -1462,31 +1462,31 @@ msgctxt "input" msgid "Info (Second Column)" msgstr "Informació (segona columna)" -#: web/templates/admin/campsite/index.gohtml:14 +#: web/templates/admin/campsite/index.gohtml:15 msgctxt "action" msgid "Add Campsite" msgstr "Afegeix allotjament" -#: web/templates/admin/campsite/index.gohtml:21 -msgid "From Date" -msgstr "De la data" - -#: web/templates/admin/campsite/index.gohtml:28 -msgid "To Date" -msgstr "A la data" - -#: web/templates/admin/campsite/index.gohtml:35 -msgctxt "action" -msgid "Show" -msgstr "Mostra" - -#: web/templates/admin/campsite/index.gohtml:50 +#: web/templates/admin/campsite/index.gohtml:32 #: web/templates/admin/amenity/index.gohtml:20 msgctxt "header" msgid "Label" msgstr "Etiqueta" #: web/templates/admin/campsite/index.gohtml:78 +msgid "From Date" +msgstr "De la data" + +#: web/templates/admin/campsite/index.gohtml:85 +msgid "To Date" +msgstr "A la data" + +#: web/templates/admin/campsite/index.gohtml:92 +msgctxt "action" +msgid "Show" +msgstr "Mostra" + +#: web/templates/admin/campsite/index.gohtml:96 msgid "No campsites added yet." msgstr "No s’ha afegit cap allotjament encara." @@ -2637,12 +2637,12 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Mainada (entre 2 i 10 anys)" -#: pkg/campsite/admin.go:366 pkg/booking/public.go:172 +#: pkg/campsite/admin.go:419 pkg/booking/public.go:172 #: pkg/booking/public.go:227 msgid "Selected campsite type is not valid." msgstr "El tipus d’allotjament escollit no és vàlid." -#: pkg/campsite/admin.go:367 pkg/amenity/admin.go:282 +#: pkg/campsite/admin.go:420 pkg/amenity/admin.go:282 msgid "Label can not be empty." msgstr "No podeu deixar l’etiqueta en blanc." diff --git a/po/es.po b/po/es.po index 752d949..5f619ea 100644 --- a/po/es.po +++ b/po/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-04-19 11:18+0200\n" +"POT-Creation-Date: 2024-04-19 20:53+0200\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -910,7 +910,7 @@ msgstr "Menú" #: web/templates/admin/campsite/carousel/index.gohtml:10 #: web/templates/admin/campsite/form.gohtml:15 #: web/templates/admin/campsite/index.gohtml:6 -#: web/templates/admin/campsite/index.gohtml:15 +#: web/templates/admin/campsite/index.gohtml:18 #: web/templates/admin/campsite/type/feature/form.gohtml:16 #: web/templates/admin/campsite/type/feature/index.gohtml:10 #: web/templates/admin/campsite/type/carousel/form.gohtml:16 @@ -1462,31 +1462,31 @@ msgctxt "input" msgid "Info (Second Column)" msgstr "Información (segunda columna)" -#: web/templates/admin/campsite/index.gohtml:14 +#: web/templates/admin/campsite/index.gohtml:15 msgctxt "action" msgid "Add Campsite" msgstr "Añadir alojamiento" -#: web/templates/admin/campsite/index.gohtml:21 -msgid "From Date" -msgstr "De la fecha" - -#: web/templates/admin/campsite/index.gohtml:28 -msgid "To Date" -msgstr "A la fecha" - -#: web/templates/admin/campsite/index.gohtml:35 -msgctxt "action" -msgid "Show" -msgstr "Mostrar" - -#: web/templates/admin/campsite/index.gohtml:50 +#: web/templates/admin/campsite/index.gohtml:32 #: web/templates/admin/amenity/index.gohtml:20 msgctxt "header" msgid "Label" msgstr "Etiqueta" #: web/templates/admin/campsite/index.gohtml:78 +msgid "From Date" +msgstr "De la fecha" + +#: web/templates/admin/campsite/index.gohtml:85 +msgid "To Date" +msgstr "A la fecha" + +#: web/templates/admin/campsite/index.gohtml:92 +msgctxt "action" +msgid "Show" +msgstr "Mostrar" + +#: web/templates/admin/campsite/index.gohtml:96 msgid "No campsites added yet." msgstr "No se ha añadido ningún alojamiento todavía." @@ -2637,12 +2637,12 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Niños (de 2 a 10 años)" -#: pkg/campsite/admin.go:366 pkg/booking/public.go:172 +#: pkg/campsite/admin.go:419 pkg/booking/public.go:172 #: pkg/booking/public.go:227 msgid "Selected campsite type is not valid." msgstr "El tipo de alojamiento escogido no es válido." -#: pkg/campsite/admin.go:367 pkg/amenity/admin.go:282 +#: pkg/campsite/admin.go:420 pkg/amenity/admin.go:282 msgid "Label can not be empty." msgstr "No podéis dejar la etiqueta en blanco." diff --git a/po/fr.po b/po/fr.po index 2745400..60f9d74 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-04-19 11:18+0200\n" +"POT-Creation-Date: 2024-04-19 20:53+0200\n" "PO-Revision-Date: 2024-02-06 10:05+0100\n" "Last-Translator: Oriol Carbonell \n" "Language-Team: French \n" @@ -910,7 +910,7 @@ msgstr "Menu" #: web/templates/admin/campsite/carousel/index.gohtml:10 #: web/templates/admin/campsite/form.gohtml:15 #: web/templates/admin/campsite/index.gohtml:6 -#: web/templates/admin/campsite/index.gohtml:15 +#: web/templates/admin/campsite/index.gohtml:18 #: web/templates/admin/campsite/type/feature/form.gohtml:16 #: web/templates/admin/campsite/type/feature/index.gohtml:10 #: web/templates/admin/campsite/type/carousel/form.gohtml:16 @@ -1462,31 +1462,31 @@ msgctxt "input" msgid "Info (Second Column)" msgstr "Info (deuxième colonne)" -#: web/templates/admin/campsite/index.gohtml:14 +#: web/templates/admin/campsite/index.gohtml:15 msgctxt "action" msgid "Add Campsite" msgstr "Ajouter un camping" -#: web/templates/admin/campsite/index.gohtml:21 -msgid "From Date" -msgstr "Partir de la date" - -#: web/templates/admin/campsite/index.gohtml:28 -msgid "To Date" -msgstr "À la date" - -#: web/templates/admin/campsite/index.gohtml:35 -msgctxt "action" -msgid "Show" -msgstr "Montrer" - -#: web/templates/admin/campsite/index.gohtml:50 +#: web/templates/admin/campsite/index.gohtml:32 #: web/templates/admin/amenity/index.gohtml:20 msgctxt "header" msgid "Label" msgstr "Label" #: web/templates/admin/campsite/index.gohtml:78 +msgid "From Date" +msgstr "Partir de la date" + +#: web/templates/admin/campsite/index.gohtml:85 +msgid "To Date" +msgstr "À la date" + +#: web/templates/admin/campsite/index.gohtml:92 +msgctxt "action" +msgid "Show" +msgstr "Montrer" + +#: web/templates/admin/campsite/index.gohtml:96 msgid "No campsites added yet." msgstr "Aucun camping n’a encore été ajouté." @@ -2637,12 +2637,12 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Enfants (de 2 à 10 anys)" -#: pkg/campsite/admin.go:366 pkg/booking/public.go:172 +#: pkg/campsite/admin.go:419 pkg/booking/public.go:172 #: pkg/booking/public.go:227 msgid "Selected campsite type is not valid." msgstr "Le type d’emplacement sélectionné n’est pas valide." -#: pkg/campsite/admin.go:367 pkg/amenity/admin.go:282 +#: pkg/campsite/admin.go:420 pkg/amenity/admin.go:282 msgid "Label can not be empty." msgstr "L'étiquette ne peut pas être vide." diff --git a/revert/booking__campsite_id.sql b/revert/booking__campsite_id.sql new file mode 100644 index 0000000..643c945 --- /dev/null +++ b/revert/booking__campsite_id.sql @@ -0,0 +1,9 @@ +-- Revert camper:booking__campsite_id from pg + +begin; + +alter table camper.booking +drop column if exists campsite_id +; + +commit; diff --git a/revert/booking__stay.sql b/revert/booking__stay.sql new file mode 100644 index 0000000..22f5b77 --- /dev/null +++ b/revert/booking__stay.sql @@ -0,0 +1,23 @@ +-- Revert camper:booking__stay from pg + +begin; + +set search_path to camper, public; + +alter table booking + add column arrival_date date +, add column departure_date date +; + +update booking +set arrival_date = lower(stay) + , departure_date = upper(stay) +; + +alter table booking + drop column if exists stay +, alter column arrival_date set not null +, alter column departure_date set not null +; + +commit; diff --git a/sqitch.plan b/sqitch.plan index f98c802..ecf415c 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -281,3 +281,5 @@ draft_payment [draft_payment@v6] 2024-03-20T17:11:41Z jordi fita mas # Add option_group column to acsi_option draft_payment [draft_payment@v7 acsi_option__option_group] 2024-04-03T08:15:40Z jordi fita mas # Take option_group in account when discounting ACSI options in draft_payment +booking__stay [booking] 2024-04-19T16:02:11Z jordi fita mas # Replace booking arrival and departure dates with a daterange +booking__campsite_id [booking] 2024-04-19T17:58:25Z jordi fita mas # Add campsite_id to booking diff --git a/test/booking.sql b/test/booking.sql index bbe8b19..031357e 100644 --- a/test/booking.sql +++ b/test/booking.sql @@ -5,7 +5,7 @@ reset client_min_messages; begin; -select plan(79); +select plan(84); set search_path to camper, public; @@ -43,20 +43,22 @@ select col_type_is('booking', 'campsite_type_id', 'integer'); select col_not_null('booking', 'campsite_type_id'); select col_hasnt_default('booking', 'campsite_type_id'); +select has_column('booking', 'campsite_id'); +select col_is_fk('booking', 'campsite_id'); +select fk_ok('booking', 'campsite_id', 'campsite', 'campsite_id'); +select col_type_is('booking', 'campsite_id', 'integer'); +select col_is_null('booking', 'campsite_id'); +select col_hasnt_default('booking', 'campsite_id'); + select has_column('booking', 'holder_name'); select col_type_is('booking', 'holder_name', 'text'); select col_not_null('booking', 'holder_name'); select col_hasnt_default('booking', 'holder_name'); -select has_column('booking', 'arrival_date'); -select col_type_is('booking', 'arrival_date', 'date'); -select col_not_null('booking', 'arrival_date'); -select col_hasnt_default('booking', 'arrival_date'); - -select has_column('booking', 'departure_date'); -select col_type_is('booking', 'departure_date', 'date'); -select col_not_null('booking', 'departure_date'); -select col_hasnt_default('booking', 'departure_date'); +select has_column('booking', 'stay'); +select col_type_is('booking', 'stay', 'daterange'); +select col_not_null('booking', 'stay'); +select col_hasnt_default('booking', 'stay'); select has_column('booking', 'number_dogs'); select col_type_is('booking', 'number_dogs', 'integer'); @@ -130,9 +132,9 @@ values (10, 2, 'Wooden lodge', 6, 7, '[1, 7]') , (12, 4, 'Bungalow', 8, 6, '[2, 6]') ; -insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) -values (2, 10, 'Holder 2', '2024-01-18', '2024-01-19', 0, false) - , (4, 12, 'Holder 4', '2024-01-18', '2024-01-19', 0, false) +insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) +values (2, 10, 'Holder 2', daterange('2024-01-18', '2024-01-19'), 0, false) + , (4, 12, 'Holder 4', daterange('2024-01-18', '2024-01-19'), 0, false) ; prepare booking_data as @@ -162,7 +164,7 @@ select bag_eq( ); select lives_ok( - $$ insert into booking(company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (2, 10, 'New Holder', '2024-01-18', '2024-01-19', 0, false) $$, + $$ insert into booking(company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) values (2, 10, 'New Holder', daterange('2024-01-18', '2024-01-19'), 0, false) $$, 'Users from company 2 should be able to insert a new booking type to their company.' ); @@ -188,7 +190,7 @@ select bag_eq( ); select throws_ok( - $$ insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (4, 12, 'Another holder', '2024-01-18', '2024-01-19', 0, false) $$, + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) values (4, 12, 'Another holder', daterange('2024-01-18', '2024-01-19'), 0, false) $$, '42501', 'new row violates row-level security policy for table "booking"', 'Users from company 2 should NOT be able to insert new bookings to company 4.' ); @@ -245,29 +247,47 @@ select bag_eq( ); select throws_ok( - $$ insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (2, 10, ' ', '2024-01-18', '2024-01-19', 0, false) $$, + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) values (2, 10, ' ', daterange('2024-01-18', '2024-01-19'), 0, false) $$, '23514', 'new row for relation "booking" violates check constraint "holder_name_not_empty"', 'Should not be able to add bookings with a blank holder name.' ); select throws_ok( - $$ insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (2, 10, 'Holder', '2024-01-18', '2024-01-17', 0, false) $$, - '23514', 'new row for relation "booking" violates check constraint "departure_after_arrival"', - 'Should not be able to add bookings with a departure date before the arrival.' + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-18'), 0, false) $$, + '23514', 'new row for relation "booking" violates check constraint "stay_not_empty"', + 'Should not be able to add bookings with an empty stay.' ); select throws_ok( - $$ insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (2, 10, 'Holder', '2024-01-18', '2024-01-18', 0, false) $$, - '23514', 'new row for relation "booking" violates check constraint "departure_after_arrival"', - 'Should not be able to add bookings with a departure date equal to the arrival.' -); - -select throws_ok( - $$ insert into booking (company_id, campsite_type_id, holder_name, arrival_date, departure_date, number_dogs, acsi_card) values (2, 10, 'Holder', '2024-01-18', '2024-01-19', -1, false) $$, + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-19'), -1, false) $$, '23514', 'new row for relation "booking" violates check constraint "number_dogs_nonnegative"', 'Should not be able to add bookings owing dogs to holder.' ); +select throws_ok( + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card, booking_status) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-19'), 0, false, 'confirmed') $$, + '23514', 'new row for relation "booking" violates check constraint "booking_needs_campsite"', + 'Should not be able to confirm bookings without a campsite.' +); + +select throws_ok( + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card, booking_status) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-19'), 0, false, 'checked-in') $$, + '23514', 'new row for relation "booking" violates check constraint "booking_needs_campsite"', + 'Should not be able to checke bookings in without a campsite.' +); + +select throws_ok( + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card, booking_status) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-19'), 0, false, 'invoiced') $$, + '23514', 'new row for relation "booking" violates check constraint "booking_needs_campsite"', + 'Should not be able to invoice bookings without a campsite.' +); + +select lives_ok( + $$ insert into booking (company_id, campsite_type_id, holder_name, stay, number_dogs, acsi_card, booking_status) values (2, 10, 'Holder', daterange('2024-01-18', '2024-01-19'), 0, false, 'cancelled') $$, + 'Should be able to cancel bookings even without a campsite.' +); + + select * from finish(); diff --git a/verify/booking__campsite_id.sql b/verify/booking__campsite_id.sql new file mode 100644 index 0000000..0e10cea --- /dev/null +++ b/verify/booking__campsite_id.sql @@ -0,0 +1,10 @@ +-- Verify camper:booking__campsite_id on pg + +begin; + +select campsite_id +from camper.booking +where false +; + +rollback; diff --git a/verify/booking__stay.sql b/verify/booking__stay.sql new file mode 100644 index 0000000..9c1af78 --- /dev/null +++ b/verify/booking__stay.sql @@ -0,0 +1,10 @@ +-- Verify camper:booking__stay on pg + +begin; + +select stay +from camper.booking +where false +; + +rollback; diff --git a/web/static/camper.css b/web/static/camper.css index 5ac7e7a..a081770 100644 --- a/web/static/camper.css +++ b/web/static/camper.css @@ -808,16 +808,36 @@ label[x-show] > span, label[x-show] > br { text-align: center; } -#campsites-booking thead { +#campsites-booking thead, +#campsites-booking tbody th { position: sticky; + z-index: 10; +} + +#campsites-booking thead { top: 0; } #campsites-booking tbody th { - position: sticky; left: 0; } +#campsites-booking tbody td { + position: relative; +} + +#campsites-booking tbody div { + border: 1px solid; + position: absolute; + z-index: 5; + top: 0; + left: calc(2.25ch * var(--booking-begin, 0) / 2); + bottom: 0; + white-space: nowrap; + overflow: hidden; + width: calc(2.25ch * (var(--booking-nights, 1) + (var(--booking-end, 0) - var(--booking-begin, 0)) / 2) - 1px); +} + #booking-filter, #booking-filter fieldset { display: flex; gap: 1ch; diff --git a/web/templates/admin/campsite/index.gohtml b/web/templates/admin/campsite/index.gohtml index a7a67be..1b49baf 100644 --- a/web/templates/admin/campsite/index.gohtml +++ b/web/templates/admin/campsite/index.gohtml @@ -43,7 +43,7 @@ - {{ range .Campsites -}} + {{ range $campsite := .Campsites -}} {{- if isAdmin -}} @@ -53,8 +53,17 @@ {{- end -}} {{ range $.Months }} - {{ range .Days }} - + {{ range $day := .Days }} + {{ with index $campsite.Bookings $day -}} + +
{{ .Holder }}
+ + {{- else -}} + + {{- end }} {{- end }} {{- end }}