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 }