Carts & Items Inventory
The inventory system manages golf cart fleet and items/add-ons at each course, with reservation tracking and optional Facilities integration.
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Admin UI │────▶│ CourseInventory │────▶│ Cart/Item │
│ (inventory) │ │ Controller │ │ Repositories │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ Facilities │ │ PostgreSQL │
│ Cart Adapter │ │ (inventory) │
└──────────────────┘ └─────────────────┘
Sources:
libs/tee-time-services/src/lib/facade/course-inventory.controller.tslibs/prisma/teetime-client/src/repositories/course-cart-inventory.repository.tslibs/prisma/teetime-client/src/repositories/course-item-inventory.repository.ts
Cart Inventory
Cart Types
| Type | Description |
|---|---|
ELECTRIC | Electric golf cart |
PULL | Pull/push cart (manual) |
PUSH | Push cart |
Cart Status
| Status | Description |
|---|---|
ACTIVE | Available for reservation |
INACTIVE | Not currently in use |
MAINTENANCE | Under repair/service |
Cart Model
interface CourseCartInventory {
id: string;
courseId: string;
type: 'ELECTRIC' | 'PULL' | 'PUSH';
totalUnits: number;
availableUnits: number;
reservedUnits: number;
pricePerRound: number; // Decimal (stored as cents in API)
status: 'ACTIVE' | 'INACTIVE' | 'MAINTENANCE';
createdAt: Date;
updatedAt: Date;
}
Cart API
// List carts
GET /v1/courses/:courseId/carts
// Query params: type, status
// Create cart
POST /v1/courses/:courseId/carts
{
"type": "electric",
"totalUnits": 20,
"availableUnits": 20,
"pricePerRoundCents": 3500 // $35.00
}
// Update cart
PUT /v1/courses/:courseId/carts/:id
{
"availableUnits": 18,
"status": "active"
}
// Delete cart
DELETE /v1/courses/:courseId/carts/:id
Item Inventory
Item Categories
| Category | Description | Examples |
|---|---|---|
RENTAL | Equipment rentals | Clubs, shoes, rangefinder |
SALE | Items for purchase | Balls, gloves, tees |
SNACK | Food & beverage | Water, snacks |
Item Status
Status is automatically calculated based on stock:
| Status | Condition |
|---|---|
IN_STOCK | stockLevel > lowStockThreshold |
LOW_STOCK | stockLevel <= lowStockThreshold && stockLevel > 0 |
OUT_OF_STOCK | stockLevel === 0 |
Item Model
interface CourseItemInventory {
id: string;
courseId: string;
name: string;
description?: string;
category: 'RENTAL' | 'SALE' | 'SNACK';
stockLevel: number;
lowStockThreshold: number; // Default: 5
price: number; // Decimal
status: 'IN_STOCK' | 'LOW_STOCK' | 'OUT_OF_STOCK'; // Computed
createdAt: Date;
updatedAt: Date;
}
Item API
// List items
GET /v1/courses/:courseId/items
// Query params: category, status, lowStockOnly
// Create item
POST /v1/courses/:courseId/items
{
"name": "Premium Golf Balls (Dozen)",
"category": "sale",
"stockLevel": 50,
"lowStockThreshold": 10,
"priceCents": 4500 // $45.00
}
// Update item
PUT /v1/courses/:courseId/items/:id
{
"stockLevel": 45
}
// Get low stock count
// (returns count in response metadata)
GET /v1/courses/:courseId/items?lowStockOnly=true
Cart Reservations
Track cart assignments to bookings:
Reservation Status
| Status | Description |
|---|---|
PENDING | Reserved, not yet picked up |
CONFIRMED | Cart in use |
RETURNED | Cart returned |
CANCELLED | Reservation cancelled |
Reservation Model
interface CourseCartReservation {
id: string;
cartId: string;
bookingId?: string;
playerId?: string;
teeTimeId?: string;
reservationDate: Date;
status: 'PENDING' | 'CONFIRMED' | 'RETURNED' | 'CANCELLED';
createdAt: Date;
updatedAt: Date;
// Relations
cart?: CourseCartInventory;
player?: Player;
}
Reservation API
// List reservations for date
GET /v1/courses/:courseId/cart-reservations?date=2025-12-15
// Response
[
{
"id": "res-123",
"cartId": "cart-456",
"bookingId": "booking-789",
"playerId": "player-abc",
"reservationDate": "2025-12-15",
"status": "confirmed",
"cart": {
"type": "electric",
"pricePerRoundCents": 3500
},
"player": {
"firstName": "John",
"lastName": "Smith"
}
}
]
Additional Item Reservations
Track add-ons purchased with bookings:
interface CourseAdditionalItemReservation {
id: string;
itemId: string;
bookingId: string;
quantity: number;
totalPrice: number;
status: 'PENDING' | 'CONFIRMED' | 'CANCELLED';
createdAt: Date;
// Relations
item?: CourseItemInventory;
booking?: Booking;
}
Facilities Integration
For courses using centralized Facilities cart management:
Configuration
// Course model
{
"useFacilitiesCarts": true,
"facilitiesClubId": "facilities-club-123",
"facilitiesTenantId": "facilities-tenant-456"
}
Adapter Behavior
When useFacilitiesCarts is enabled:
| Operation | Behavior |
|---|---|
GET /carts | Delegates to Facilities API |
POST /carts | Blocked (read-only) |
PUT /carts | Blocked (read-only) |
DELETE /carts | Blocked (read-only) |
GET /reservations | Merges Facilities + local data |
// In FacilitiesCartAdapter
async listCarts(courseId: string): Promise<CartInventory[]> {
if (course.useFacilitiesCarts) {
return this.facilitiesClient.getCarts(course.facilitiesClubId);
}
return this.cartRepository.findAll({ courseId });
}
Course Status Integration
When course status transitions to CLOSED:
- All cart inventory marked
INACTIVE - Pending reservations marked
RETURNED - No new reservations allowed
When status returns to OPEN:
- Cart inventory restored to
ACTIVE - New reservations enabled
See Course Status for details.
Inventory Summary
Get aggregate inventory stats:
// GET /v1/courses/:courseId/carts/summary
{
"totalCarts": 30,
"availableCarts": 25,
"reservedCarts": 5,
"byType": {
"electric": { "total": 20, "available": 16 },
"pull": { "total": 10, "available": 9 }
}
}
// GET /v1/courses/:courseId/items/summary
{
"totalItems": 15,
"inStock": 12,
"lowStock": 2,
"outOfStock": 1
}
Currency Handling
API uses cents for all prices; database stores decimals:
// API → Database
const priceDecimal = dto.priceCents / 100;
// Database → API
const priceCents = Math.round(entity.price * 100);
Data Model (Prisma)
model CourseCartInventory {
id String @id @default(uuid())
courseId String
type CartType
totalUnits Int
availableUnits Int
reservedUnits Int @default(0)
pricePerRound Decimal @db.Decimal(10, 2)
status CartStatus @default(ACTIVE)
course Course @relation(fields: [courseId])
reservations CourseCartReservation[]
@@index([courseId, type])
}
model CourseItemInventory {
id String @id @default(uuid())
courseId String
name String
description String?
category ItemCategory
stockLevel Int
lowStockThreshold Int @default(5)
price Decimal @db.Decimal(10, 2)
status ItemStatus @default(IN_STOCK)
course Course @relation(fields: [courseId])
reservations CourseAdditionalItemReservation[]
@@index([courseId, category])
}
enum CartType {
ELECTRIC
PULL
PUSH
}
enum CartStatus {
ACTIVE
INACTIVE
MAINTENANCE
}
enum ItemCategory {
RENTAL
SALE
SNACK
}
enum ItemStatus {
IN_STOCK
LOW_STOCK
OUT_OF_STOCK
}
Metrics
| Metric | Description |
|---|---|
cart_inventory_total | Carts by type and status |
cart_utilization_rate | Reserved / available ratio |
item_inventory_low_stock | Items below threshold |
cart_reservation_total | Reservations by status |
Troubleshooting
Cart Availability Mismatch
- Verify reservation status updates
- Check for orphaned reservations
- Reconcile
availableUnitswith actual reservations
Facilities Integration Issues
- Verify
useFacilitiesCartsflag - Check Facilities API connectivity
- Review tenant/club ID configuration
Low Stock Alerts Not Triggering
- Verify
lowStockThresholdis set - Check stock level updates
- Review notification service connectivity