mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
feat(intake): structured per-service intake questions
Services declare up to 8 intake fields (text/yesno, required) stored as JSONB; shared/intake.go is the single normalization point — slug keys, label/dup/unknown-key rules, yes/no canonicalization. Booking create validates answers against the service declaration (missing required -> 400), unknown keys are dropped before persist. Answers surface on UpcomingBooking and CustomerBookingView; the services page gains a compact editor and the public form renders declared questions under contact details. Verified live end-to-end. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
ad60167a31
commit
33b6e2a2a6
20 files changed
+598
-41
No files matched your search
@@ -221,6 +221,7 @@ func (s *Service) Catalog(ctx context.Context, tenantSlug string) (domain.Public
|
||||
Name: service.Name,
|
||||
DurationMinutes: service.DurationMinutes,
|
||||
PriceCents: service.PriceCents,
|
||||
IntakeFields: shared.ParseIntakeFields(service.IntakeFields),
|
||||
})
|
||||
}
|
||||
for _, staff := range staffRecords {
|
||||
@@ -283,6 +284,7 @@ func (s *Service) Create(ctx context.Context, request domain.CreateBookingReques
|
||||
|
||||
status := "confirmed"
|
||||
activityLabel := ""
|
||||
intakeJSON := "{}"
|
||||
switch request.BookingMode {
|
||||
case "appointment":
|
||||
if request.ServiceID == nil || request.ClassSessionID != nil {
|
||||
@@ -295,6 +297,10 @@ func (s *Service) Create(ctx context.Context, request domain.CreateBookingReques
|
||||
if !ok {
|
||||
return domain.CreateBookingResponse{}, fmt.Errorf("%w: serviceId is not available for tenant", ErrInvalidBooking)
|
||||
}
|
||||
intakeJSON, err = shared.NormalizeIntakeAnswers(shared.ParseIntakeFields(service.IntakeFields), request.Intake)
|
||||
if err != nil {
|
||||
return domain.CreateBookingResponse{}, fmt.Errorf("%w: %s", ErrInvalidBooking, err.Error())
|
||||
}
|
||||
activityLabel = service.Name
|
||||
expectedDuration := time.Duration(service.DurationMinutes) * time.Minute
|
||||
if !startsAt.Add(expectedDuration).Equal(endsAt) {
|
||||
@@ -350,6 +356,7 @@ func (s *Service) Create(ctx context.Context, request domain.CreateBookingReques
|
||||
Reference: db.Reference("BK", time.Now()),
|
||||
ManageToken: generateManageToken(),
|
||||
Notes: notes,
|
||||
Intake: intakeJSON,
|
||||
})
|
||||
if err != nil {
|
||||
if db.IsExclusionViolation(err) {
|
||||
@@ -565,6 +572,7 @@ func (s *Service) DashboardSummary(ctx context.Context, principal domain.Princip
|
||||
CustomerName: booking.CustomerName,
|
||||
CustomerEmail: booking.CustomerEmail,
|
||||
Notes: booking.Notes,
|
||||
Intake: shared.ParseIntakeAnswers(booking.Intake),
|
||||
StartsAt: booking.StartsAt,
|
||||
EndsAt: booking.EndsAt,
|
||||
Status: booking.Status,
|
||||
|
||||
Reference in new issue
Block a user