camper/pkg/booking/cart.go

255 lines
6.0 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 {
NumAdults int
NumTeenagers int
NumChildren int
NumDogs 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) {
if f.Dates == nil {
return nil, nil
}
if f.Guests == nil {
return nil, nil
}
arrivalDate, err := time.Parse(database.ISODateFormat, f.Dates.ArrivalDate.Val)
if err != nil {
return nil, nil
}
departureDate, err := time.Parse(database.ISODateFormat, f.Dates.DepartureDate.Val)
if err != nil {
return nil, nil
}
draft := &paymentDraft{}
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
}
}
var zonePreferences string
if f.Options != nil && f.Options.ZonePreferences != nil {
zonePreferences = f.Options.ZonePreferences.Val
}
var acsiCard bool
if f.Guests.ACSICard != nil {
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),
arrivalDate,
departureDate,
campsiteType,
draft.NumAdults,
draft.NumTeenagers,
draft.NumChildren,
draft.NumDogs,
zonePreferences,
acsiCard,
database.OptionUnitsArray(optionUnits),
)
var paymentID int
if err = row.Scan(
&f.PaymentSlug.Val,
&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
`, 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
}