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 }