Saltar al contenido principal

Booking Platforms Module

Overview

The booking-platforms module manages third-party booking platform integrations for a business. Each platform represents an external channel (e.g., Viator, Airbnb Experience, GetYourGuide, Direct Booking) through which customers can book services. Platforms track commission percentages and can be linked to a customer record for accounting purposes.

All data is scoped to a businessId — no cross-business data access is possible.


Domain Concepts

ConceptDescription
BookingPlatformA named external booking channel with a commission rate and active status
businessIdMulti-tenancy boundary. Every query filters by this field
commissionPercentageThe percentage commission charged by the platform
customerIdOptional reference to a customer record (for AR/invoicing integration)
createdBy / updatedByAudit trail — employee/user UUID who performed the operation

Architecture

The module follows strict Hexagonal Architecture:

domain/
booking-platforms-repository.domain.ts ← IBookingPlatformsRepository port (interface) + BOOKING_PLATFORMS_REPOSITORY token

application/
booking-platforms.service.ts ← Orchestrates use cases (depends on domain port via @Inject)

infrastructure/
booking-platforms.repository.ts ← Kysely adapter (implements the port)

interfaces/
booking-platforms.controller.ts ← HTTP REST adapter
dtos/
create-booking-platform.dto.ts
update-booking-platform.dto.ts
query/
paginate-booking-platforms.query.ts

Dependency flow

interfaces → application → domain ← infrastructure

Infrastructure depends on domain (implements its port); the application layer depends only on the domain interface. The repository is wired via a Symbol-based injection token (BOOKING_PLATFORMS_REPOSITORY) defined in the domain layer.

Known design debt: booking-platforms-repository.domain.ts imports sortableBookingPlatformKeys from the interfaces layer. This is a consistent project-wide pattern (present in all modules) and is accepted as a pragmatic trade-off until a cross-module refactor is done.


Use Cases

Use CaseMethodHTTP
Create booking platformcreateBookingPlatformPOST /booking-platforms
List booking platforms (paginated)getAllBookingPlatformsGET /booking-platforms
Get booking platform by IDgetBookingPlatformByIdGET /booking-platforms/:id
Update booking platformupdateBookingPlatformPATCH /booking-platforms/:id
Delete booking platformdeleteBookingPlatformDELETE /booking-platforms/:id

Authorization

  • Authentication: Bearer token (Firebase) — validated by global AuthGuard
  • Authorization: RolesGuard + CASL permissions
    • Class-level: @PermissionResource(PolicyResource.BookingPlatform)
    • Method-level: @PermissionAction(PolicyAction.Create | Read | Update | Delete)
  • Swagger: @ApiBearerAuth() marks all endpoints as requiring authentication

API Endpoints

POST /booking-platforms — Create Booking Platform

Auth: Bearer token required Permission: BookingPlatform:Create

Request body:

{
"name": "Viator",
"commissionPercentage": 5,
"businessId": "<business-uuid>",
"isActive": true,
"createdBy": "<user-uuid>",
"customerId": "<customer-uuid>"
}

customerId is optional — used to link the platform to a customer record for accounting.

Response: 201 Created — full booking platform object.


GET /booking-platforms — List Booking Platforms

Permission: BookingPlatform:Read

Query params:

ParamRequiredDescription
businessIdyesUUID — scopes the query
pagenoPage number (default: 1)
sizenoPage size (default: 20, 0 = all)
searchnoFull-text search across platform name
orderBynoColumn to sort by
ordernoasc or desc

Sortable columns: name

Response: 200 OKIOffsetPagination<BookingPlatform>

{
"data": [...],
"total": 4,
"page": 1,
"size": 20,
"totalPages": 1
}

The list response includes customerName (from the joined customer table) when a customerId is set.


GET /booking-platforms/:id — Get Booking Platform by ID

Permission: BookingPlatform:Read

Query params: businessId (required)

Response: 200 OK — booking platform object, or 404 Not Found.


PATCH /booking-platforms/:id — Update Booking Platform

Permission: BookingPlatform:Update

Request body:

{
"businessId": "<business-uuid>",
"updatedBy": "<user-uuid>",
"name": "Viator Updated",
"commissionPercentage": 8
}

All fields except businessId and updatedBy are optional.

Response: 200 OK — updated booking platform object.


DELETE /booking-platforms/:id — Delete Booking Platform

Permission: BookingPlatform:Delete

Query params: businessId (required)

Response: 200 OK (no body).


Design Decisions

Hard delete

DELETE performs a hard delete (deleteFrom). There is no soft-delete or isActive flag toggle on delete. The isActive field is managed manually through create/update operations.

businessId is not updatable

The PATCH endpoint destructures businessId out of the entity before passing it to the repository. This prevents accidentally moving a platform from one business to another through a PATCH call.

findAll uses a transaction

The count + data query pair runs inside a Kysely transaction for snapshot isolation. This is a consistent project pattern.

findAll includes a customer join

The list query left-joins the customer table to include customerName in the response. This avoids N+1 queries when displaying platforms with their associated customer names.

Symbol-based repository injection

The repository is bound via BOOKING_PLATFORMS_REPOSITORY Symbol token in the module, and injected into the service via @Inject(BOOKING_PLATFORMS_REPOSITORY). This ensures the application layer depends only on the domain port (IBookingPlatformsRepository), not the concrete Kysely implementation.


Bruno API Collection

Located at: api-client/flowpos/collections/booking-platforms/

FileMethodRoute
list booking platforms.ymlGET/booking-platforms
create booking platform.ymlPOST/booking-platforms
get booking platform by id.ymlGET/booking-platforms/:id
update booking platform.ymlPATCH/booking-platforms/:id
delete booking platform.ymlDELETE/booking-platforms/:id

Environment variables used: BASE_URL, ID_TOKEN, businessId, userId, customerId, bookingPlatformId.

All requests include after-response test scripts for status code validation.