camper/pkg/booking/cart.go

257 lines
6.1 KiB
Go

package booking
import (
"context"
"strconv"
"time"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type bookingCart struct {
Draft *paymentDraft
Lines []*cartLine
Total string
DownPayment string
DownPaymentPercent int
Enabled bool
}
type cartLine struct {
Concept string
Units int
Subtotal string
}
type paymentDraft struct {
ArrivalDate time.Time
DepartureDate time.Time
NumAdults int
NumTeenagers int
NumChildren int
NumDogs int
ZonePreferences string
ACSICard bool
PaymentID int
NumNights int
Nights string
Adults string
Teenagers string
Children string
Dogs string
TouristTax string
Total string
DownPaymentPercent int
DownPayment string
Options []*paymentOption
}
type paymentOption struct {
ID int
Label string
Units int
Subtotal string
}
func draftPayment(ctx context.Context, conn *database.Conn, f *bookingForm, campsiteType string) (*paymentDraft, error) {
var err error
if f.Dates == nil {
return nil, nil
}
draft := &paymentDraft{}
draft.ArrivalDate, err = time.Parse(database.ISODateFormat, f.Dates.ArrivalDate.Val)
if err != nil {
return nil, nil
}
draft.DepartureDate, err = time.Parse(database.ISODateFormat, f.Dates.DepartureDate.Val)
if err != nil {
return nil, nil
}
if f.Guests == nil {
return nil, nil
}
draft.NumAdults, err = strconv.Atoi(f.Guests.NumberAdults.Val)
if err != nil {
return nil, nil
}
draft.NumTeenagers, err = strconv.Atoi(f.Guests.NumberTeenagers.Val)
if err != nil {
return nil, nil
}
draft.NumChildren, err = strconv.Atoi(f.Guests.NumberChildren.Val)
if err != nil {
return nil, nil
}
if f.Guests.NumberDogs != nil {
draft.NumDogs, err = strconv.Atoi(f.Guests.NumberDogs.Val)
if err != nil {
return nil, nil
}
}
if f.Options != nil && f.Options.ZonePreferences != nil {
draft.ZonePreferences = f.Options.ZonePreferences.Val
}
if f.Guests.ACSICard != nil {
draft.ACSICard = f.Guests.ACSICard.Checked
}
optionMap := make(map[int]*campsiteTypeOption)
var typeOptions []*campsiteTypeOption
if f.Options != nil {
typeOptions = f.Options.Options
}
optionUnits := make([]*database.OptionUnits, 0, len(typeOptions))
for _, option := range typeOptions {
units, _ := strconv.Atoi(option.Input.Val)
if units < 1 {
continue
}
optionMap[option.ID] = option
optionUnits = append(optionUnits, &database.OptionUnits{
OptionID: option.ID,
Units: units,
})
}
row := conn.QueryRow(ctx, `
select payment.slug
, payment_id
, departure_date - arrival_date
, to_price(subtotal_nights, decimal_digits)
, to_price(subtotal_adults, decimal_digits)
, to_price(subtotal_teenagers, decimal_digits)
, to_price(subtotal_children, decimal_digits)
, to_price(subtotal_dogs, decimal_digits)
, to_price(subtotal_tourist_tax, decimal_digits)
, to_price(total, decimal_digits)
, to_price(payment.down_payment, decimal_digits)
, (payment.down_payment_percent * 100)::int
from draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) as payment
join currency using (currency_code)
`,
database.ZeroNullUUID(f.PaymentSlug.Val),
draft.ArrivalDate,
draft.DepartureDate,
campsiteType,
draft.NumAdults,
draft.NumTeenagers,
draft.NumChildren,
draft.NumDogs,
draft.ZonePreferences,
draft.ACSICard,
database.OptionUnitsArray(optionUnits),
)
if err = row.Scan(
&f.PaymentSlug.Val,
&draft.PaymentID,
&draft.NumNights,
&draft.Nights,
&draft.Adults,
&draft.Teenagers,
&draft.Children,
&draft.Dogs,
&draft.TouristTax,
&draft.Total,
&draft.DownPayment,
&draft.DownPaymentPercent,
); err != nil {
if database.ErrorIsNotFound(err) {
return nil, nil
}
return nil, err
}
rows, err := conn.Query(ctx, `
select campsite_type_option_id
, units
, to_price(subtotal, decimal_digits)
from payment_option
join payment using (payment_id)
join currency using (currency_code)
where payment_id = $1
order by campsite_type_option_id
`, draft.PaymentID)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var optionID int
var units int
var subtotal string
err = rows.Scan(&optionID, &units, &subtotal)
if err != nil {
return nil, err
}
option := optionMap[optionID]
if option == nil {
continue
}
draft.Options = append(draft.Options, &paymentOption{
ID: option.ID,
Label: option.Label,
Units: units,
Subtotal: subtotal,
})
}
if rows.Err() != nil {
return nil, rows.Err()
}
return draft, nil
}
func newBookingCart(ctx context.Context, conn *database.Conn, f *bookingForm, campsiteType string) (*bookingCart, error) {
cart := &bookingCart{
Total: "0.0",
}
draft, err := draftPayment(ctx, conn, f, campsiteType)
if err != nil {
return nil, err
}
if draft == nil {
return cart, nil
}
cart.Draft = draft
cart.DownPaymentPercent = draft.DownPaymentPercent
maybeAddLine := func(units int, subtotal string, concept string) {
if units > 0 && subtotal != "" {
cart.Lines = append(cart.Lines, &cartLine{
Concept: concept,
Units: units,
Subtotal: subtotal,
})
}
}
maybeAddLine(draft.NumNights, draft.Nights, locale.PgettextNoop("Night", "cart"))
maybeAddLine(draft.NumAdults, draft.Adults, locale.PgettextNoop("Adult", "cart"))
maybeAddLine(draft.NumTeenagers, draft.Teenagers, locale.PgettextNoop("Teenager", "cart"))
maybeAddLine(draft.NumChildren, draft.Children, locale.PgettextNoop("Child", "cart"))
maybeAddLine(draft.NumDogs, draft.Dogs, locale.PgettextNoop("Dog", "cart"))
for _, option := range draft.Options {
maybeAddLine(option.Units, option.Subtotal, option.Label)
}
maybeAddLine(draft.NumAdults, draft.TouristTax, locale.PgettextNoop("Tourist tax", "cart"))
if draft.Total != "0.0" {
cart.Total = draft.Total
cart.Enabled = f.Guests.Error == nil
if draft.DownPayment != draft.Total {
cart.DownPayment = draft.DownPayment
}
}
return cart, nil
}