Icon rebuilt as a full-bleed 1200x1200 square (Shopify rounds corners itself). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
5.7 KiB
Plaintext
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])
|
|
}
|