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 }}