Files
pos/server/prisma/schema.prisma
jason 65eb405cf1 Rename roles, add multi-vendor support, and Events system
Roles: owner→admin, manager→vendor, cashier→user across all routes,
seed, and client UI. Role badge colours updated in UsersPage.

Multi-vendor:
- GET /vendors and GET /users now return all records for admin role;
  vendor/user roles remain scoped to their vendorId
- POST /users: admin can specify vendorId to assign user to any vendor
- vendors/users now include vendor name in responses for admin context

Events (new):
- Prisma schema: Event, EventTax, EventProduct models; Transaction.eventId
- POST/GET/PUT/DELETE /api/v1/events — full CRUD, vendor-scoped
- PUT /events/:id/taxes + DELETE — upsert/remove per-event tax rate overrides
- POST/GET/DELETE /events/:id/products — product allowlist (empty=all)
- GET /events/:id/transactions — paginated list scoped to event
- GET /events/:id/reports/summary — revenue, avg tx, top products for event
- Transactions: eventId accepted in both single POST and batch POST
- Catalog sync: active/upcoming events included in /catalog/sync response

Client:
- Layout nav filtered by role (user role sees Catalog only)
- Dashboard cards filtered by role
- Events page: list, create/edit modal, detail modal with Configuration
  (tax overrides + product allowlist) and Reports tabs

DB: DATABASE_URL updated to file:./prisma/dev.db in .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 07:27:30 -05:00

185 lines
5.1 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Vendor {
id String @id @default(cuid())
name String
businessNum String?
taxSettings String? // JSON string
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
categories Category[]
products Product[]
taxes Tax[]
transactions Transaction[]
events Event[]
}
model Role {
id String @id @default(cuid())
name String @unique // admin | vendor | user
users User[]
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
name String
vendorId String
roleId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
role Role @relation(fields: [roleId], references: [id])
refreshTokens RefreshToken[]
transactions Transaction[]
}
model RefreshToken {
id String @id @default(cuid())
token String @unique
userId String
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Category {
id String @id @default(cuid())
name String
vendorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
products Product[]
}
model Tax {
id String @id @default(cuid())
name String
rate Float
vendorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
products Product[]
eventOverrides EventTax[]
}
model Product {
id String @id @default(cuid())
name String
sku String?
description String?
price Float
vendorId String
categoryId String?
taxId String?
tags String? // comma-separated or JSON string
version Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
category Category? @relation(fields: [categoryId], references: [id])
tax Tax? @relation(fields: [taxId], references: [id])
transactionItems TransactionItem[]
eventProducts EventProduct[]
}
model Transaction {
id String @id @default(cuid())
idempotencyKey String @unique
vendorId String
userId String
eventId String?
status String // pending | completed | failed | refunded
paymentMethod String // cash | card
subtotal Float
taxTotal Float
discountTotal Float
total Float
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
user User @relation(fields: [userId], references: [id])
event Event? @relation(fields: [eventId], references: [id])
items TransactionItem[]
}
model TransactionItem {
id String @id @default(cuid())
transactionId String
productId String
productName String
quantity Int
unitPrice Float
taxRate Float
discount Float
total Float
transaction Transaction @relation(fields: [transactionId], references: [id])
product Product @relation(fields: [productId], references: [id])
}
// ─── Events ───────────────────────────────────────────────────────────────────
model Event {
id String @id @default(cuid())
vendorId String
name String
description String?
startsAt DateTime
endsAt DateTime
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor Vendor @relation(fields: [vendorId], references: [id])
taxOverrides EventTax[]
products EventProduct[]
transactions Transaction[]
}
// Tax rate overrides for a specific event. Shadows the vendor-level Tax for the
// event duration. Empty = use vendor defaults.
model EventTax {
id String @id @default(cuid())
eventId String
taxId String
rate Float // override rate in percent
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
tax Tax @relation(fields: [taxId], references: [id])
@@unique([eventId, taxId])
}
// Allowlist of products available at an event. Empty = all vendor products available.
model EventProduct {
id String @id @default(cuid())
eventId String
productId String
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id])
@@unique([eventId, productId])
}