bundle-offers-app/prisma/schema.prisma
metatroncubeswdev ba7a7c89a2 Rename app to Offerlyst: Bundles & BOGO; add App Store icon and brand assets
Icon rebuilt as a full-bleed 1200x1200 square (Shopify rounds corners itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:03:32 -04:00

181 lines
5.7 KiB
Plaintext

// Offerlyst app database (brief §14). Postgres.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean @default(false)
locale String?
collaborator Boolean? @default(false)
emailVerified Boolean? @default(false)
refreshToken String?
refreshTokenExpires DateTime?
}
model Shop {
id String @id @default(cuid())
domain String @unique
planCode String @default("free") // free | starter | growth | plus
currency String @default("USD")
timezone String @default("UTC") // IANA
moneyFormat String @default("${{amount}}")
discountNodeId String?
appInstallationId String?
/// Settings JSON: exclusion tag, combinesWith, bxgy allocation, cart behaviour, design tokens
settings Json @default("{}")
/// Next offer start/end boundary: the scheduler republishes at this time
nextSyncAt DateTime?
lastPublishedAt DateTime?
configBytes Int @default(0)
storefrontBytes Int @default(0)
publishError String?
/// Free plan: bundle orders in the current month
monthKey String?
monthOrders Int @default(0)
reviewPromptShown Boolean @default(false)
installedAt DateTime @default(now())
uninstalledAt DateTime?
offers Offer[]
usages OrderUsage[]
audit AuditLog[]
}
model Offer {
id String @id @default(cuid())
shopId String
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
/// short id used in the function config and storefront data
code String
handle String
oldHandles String[] @default([])
name String
badge String?
description String?
/// { "fr": { "name": "...", "badge": "...", "description": "..." } }
i18n Json @default("{}")
sequence Int @default(10)
active Boolean @default(true)
published Boolean @default(false)
/// ISO country codes (market targeting); empty = everywhere
countries String[] @default([])
type String @default("fixed") // fixed | mix | bxgy
discountType String @default("percent") // percent | amount | fixed_price
discountValue Decimal @default(0) @db.Decimal(12, 2)
mixQty Int @default(3)
buyQty Int @default(2)
getQty Int @default(1)
getPercent Decimal @default(100) @db.Decimal(5, 2)
startsAt DateTime?
endsAt DateTime?
maxPerOrder Int @default(0)
showHome Boolean @default(true)
showProduct Boolean @default(true)
showCartHints Boolean @default(true)
imageUrl String?
/// Unpublished automatically because the plan limit was exceeded
limitedByPlan Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lines OfferLine[]
scopes OfferScope[]
usages OrderUsage[]
@@unique([shopId, code])
@@unique([shopId, handle])
@@index([shopId, sequence])
}
model OfferLine {
id String @id @default(cuid())
offerId String
offer Offer @relation(fields: [offerId], references: [id], onDelete: Cascade)
sequence Int @default(10)
productId String
/// null = any variant of the product
variantId String?
quantity Int @default(1)
/// display cache (from the resource picker / products webhooks)
title String?
handle String?
imageUrl String?
}
model OfferScope {
id String @id @default(cuid())
offerId String
offer Offer @relation(fields: [offerId], references: [id], onDelete: Cascade)
role String // mix | buy | get
kind String // product | variant | collection
refId String // numeric id
/// product id for variants
parentId String?
title String?
handle String?
imageUrl String?
@@index([offerId, role])
}
model OrderUsage {
id String @id @default(cuid())
shopId String
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
orderId String
orderName String?
offerId String?
offer Offer? @relation(fields: [offerId], references: [id], onDelete: SetNull)
offerName String
discount Decimal @db.Decimal(12, 2)
bundleRevenue Decimal @db.Decimal(12, 2)
orderTotal Decimal @db.Decimal(12, 2)
currency String
createdAt DateTime @default(now())
@@unique([shopId, orderId, offerName])
@@index([shopId, createdAt])
}
/// Orders without a bundle, for the "AOV with vs without bundles" metric
model OrderStat {
id String @id @default(cuid())
shopId String
orderId String
total Decimal @db.Decimal(12, 2)
hasBundle Boolean
currency String
createdAt DateTime @default(now())
@@unique([shopId, orderId])
@@index([shopId, createdAt])
}
model AuditLog {
id String @id @default(cuid())
shopId String
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
offerId String?
staffUser String?
field String
oldValue String?
newValue String?
at DateTime @default(now())
@@index([shopId, offerId, at])
}