“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
package booking
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/season"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Month struct {
|
|
|
|
Year int
|
|
|
|
Month time.Month
|
|
|
|
Name string
|
|
|
|
Days []time.Time
|
|
|
|
Spans []*Span
|
|
|
|
}
|
|
|
|
|
|
|
|
type Span struct {
|
|
|
|
Weekend bool
|
|
|
|
Today bool
|
|
|
|
Count int
|
|
|
|
}
|
|
|
|
|
|
|
|
func isWeekend(t time.Time) bool {
|
|
|
|
switch t.Weekday() {
|
|
|
|
case time.Saturday, time.Sunday:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func CollectMonths(from time.Time, to time.Time) []*Month {
|
|
|
|
current := time.Date(from.Year(), from.Month(), from.Day(), 0, 0, 0, 0, time.UTC)
|
|
|
|
now := time.Now()
|
|
|
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
|
|
|
var months []*Month
|
|
|
|
for !current.Equal(to) {
|
|
|
|
span := &Span{
|
|
|
|
Weekend: isWeekend(current),
|
|
|
|
Today: current.Equal(today),
|
|
|
|
}
|
|
|
|
month := &Month{
|
|
|
|
Year: current.Year(),
|
|
|
|
Month: current.Month(),
|
|
|
|
Name: season.LongMonthNames[current.Month()-1],
|
|
|
|
Days: make([]time.Time, 0, 31),
|
|
|
|
Spans: make([]*Span, 0, 10),
|
|
|
|
}
|
|
|
|
month.Spans = append(month.Spans, span)
|
|
|
|
for current.Month() == month.Month && !current.Equal(to) {
|
|
|
|
month.Days = append(month.Days, current)
|
|
|
|
if span.Weekend != isWeekend(current) || span.Today != current.Equal(today) {
|
|
|
|
span = &Span{
|
|
|
|
Weekend: isWeekend(current),
|
|
|
|
Today: current.Equal(today),
|
|
|
|
}
|
|
|
|
month.Spans = append(month.Spans, span)
|
|
|
|
}
|
|
|
|
span.Count = span.Count + 1
|
|
|
|
current = current.AddDate(0, 0, 1)
|
|
|
|
}
|
|
|
|
months = append(months, month)
|
|
|
|
}
|
|
|
|
return months
|
|
|
|
}
|
|
|
|
|
|
|
|
type CampsiteEntry struct {
|
“Finish” the new booking form
Had to bring the same fields that i have for a payment to booking,
except that some of those should be nullable, because it is unreasonable
to ask front desk to gather all customer data when they have a booking
via phone, for instance.
Therefore, i can not take advantage of the validation for customer data
that i use in the public-facing form, but, fortunately, most of the
validations where in separated functions, thus only had to rewrite that
one for this case.
I already have to create a booking from a payment, when receiving a
payment from the public instance, thus i made that function and reused
it here. Then i “overwrite” the newly created pre-booking with the
customer data from the form, and set is as confirmed, as we do not see
any point of allowing pre-bookings from employees.
2024-04-24 18:12:29 +00:00
|
|
|
ID int
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
Label string
|
|
|
|
Type string
|
|
|
|
Active bool
|
“Finish” the new booking form
Had to bring the same fields that i have for a payment to booking,
except that some of those should be nullable, because it is unreasonable
to ask front desk to gather all customer data when they have a booking
via phone, for instance.
Therefore, i can not take advantage of the validation for customer data
that i use in the public-facing form, but, fortunately, most of the
validations where in separated functions, thus only had to rewrite that
one for this case.
I already have to create a booking from a payment, when receiving a
payment from the public instance, thus i made that function and reused
it here. Then i “overwrite” the newly created pre-booking with the
customer data from the form, and set is as confirmed, as we do not see
any point of allowing pre-bookings from employees.
2024-04-24 18:12:29 +00:00
|
|
|
Selected bool
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
Bookings map[time.Time]*CampsiteBooking
|
|
|
|
}
|
|
|
|
|
|
|
|
type CampsiteBooking struct {
|
|
|
|
Holder string
|
|
|
|
Status string
|
|
|
|
Nights int
|
|
|
|
Begin bool
|
|
|
|
End bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func CollectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time, campsiteType string) ([]*CampsiteEntry, error) {
|
|
|
|
rows, err := conn.Query(ctx, `
|
“Finish” the new booking form
Had to bring the same fields that i have for a payment to booking,
except that some of those should be nullable, because it is unreasonable
to ask front desk to gather all customer data when they have a booking
via phone, for instance.
Therefore, i can not take advantage of the validation for customer data
that i use in the public-facing form, but, fortunately, most of the
validations where in separated functions, thus only had to rewrite that
one for this case.
I already have to create a booking from a payment, when receiving a
payment from the public instance, thus i made that function and reused
it here. Then i “overwrite” the newly created pre-booking with the
customer data from the form, and set is as confirmed, as we do not see
any point of allowing pre-bookings from employees.
2024-04-24 18:12:29 +00:00
|
|
|
select campsite_id
|
|
|
|
, campsite.label
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
, campsite_type.name
|
|
|
|
, campsite.active
|
|
|
|
from campsite
|
|
|
|
join campsite_type using (campsite_type_id)
|
|
|
|
where campsite.company_id = $1
|
|
|
|
and ($2::uuid is null or campsite_type.slug = $2::uuid)
|
|
|
|
order by label`, company.ID, database.ZeroNullUUID(campsiteType))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
byLabel := make(map[string]*CampsiteEntry)
|
|
|
|
var campsites []*CampsiteEntry
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &CampsiteEntry{}
|
“Finish” the new booking form
Had to bring the same fields that i have for a payment to booking,
except that some of those should be nullable, because it is unreasonable
to ask front desk to gather all customer data when they have a booking
via phone, for instance.
Therefore, i can not take advantage of the validation for customer data
that i use in the public-facing form, but, fortunately, most of the
validations where in separated functions, thus only had to rewrite that
one for this case.
I already have to create a booking from a payment, when receiving a
payment from the public instance, thus i made that function and reused
it here. Then i “overwrite” the newly created pre-booking with the
customer data from the form, and set is as confirmed, as we do not see
any point of allowing pre-bookings from employees.
2024-04-24 18:12:29 +00:00
|
|
|
if err = rows.Scan(&entry.ID, &entry.Label, &entry.Type, &entry.Active); err != nil {
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
campsites = append(campsites, entry)
|
|
|
|
byLabel[entry.Label] = entry
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := collectCampsiteBookings(ctx, company, conn, from, to, byLabel); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return campsites, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func collectCampsiteBookings(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time, campsites map[string]*CampsiteEntry) error {
|
|
|
|
rows, err := conn.Query(ctx, `
|
|
|
|
select campsite.label
|
|
|
|
, lower(booking_campsite.stay * daterange($2::date, $3::date))
|
|
|
|
, holder_name
|
|
|
|
, booking_status
|
|
|
|
, upper(booking_campsite.stay * daterange($2::date, $3::date)) - lower(booking_campsite.stay * daterange($2::date, $3::date))
|
|
|
|
, booking_campsite.stay &> daterange($2::date, $3::date)
|
|
|
|
, booking_campsite.stay &< daterange($2::date, ($3 - 1)::date)
|
|
|
|
from booking_campsite
|
|
|
|
join booking using (booking_id)
|
|
|
|
join campsite using (campsite_id)
|
|
|
|
where booking.company_id = $1
|
|
|
|
and booking_campsite.stay && daterange($2::date, $3::date)
|
|
|
|
order by label`, company.ID, from, to)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &CampsiteBooking{}
|
|
|
|
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]*CampsiteBooking)
|
|
|
|
}
|
|
|
|
campsite.Bookings[date] = entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|