feat(db): add Prisma schema and global PrismaModule
Models mirror Hiboutik entities (categories tree, brands, suppliers, products and their versions, customers) plus addresses and orders for the upcoming sync. Every synced model is matched with Hiboutik through a unique hiboutikId. The generated client is ignored by git and env files other than .env.example are ignored.
This commit is contained in:
4
prisma/customObjects/prisma.custom.object.ts
Normal file
4
prisma/customObjects/prisma.custom.object.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Prisma } from '../generated/client';
|
||||
|
||||
/** Product loaded with its versions (`include: { versions: true }`). */
|
||||
export type ProductWithVersions = Prisma.ProductGetPayload<{ include: { versions: true } }>;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
9
prisma/prisma.module.ts
Normal file
9
prisma/prisma.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
20
prisma/prisma.service.ts
Normal file
20
prisma/prisma.service.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from './generated/client';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
|
||||
/** Prisma client shared by the whole app (global PrismaModule), connected to `DATABASE_URL` through the pg driver adapter. */
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
constructor() {
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||
super({ adapter });
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
216
prisma/schema.prisma
Normal file
216
prisma/schema.prisma
Normal file
@@ -0,0 +1,216 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Get a free hosted Postgres database in seconds: `npx create-db`
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "./generated/"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
/// Product category, mirrored from Hiboutik. Categories form a tree (parent / children).
|
||||
model Category {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Enabled in the Hiboutik POS (category_enabled)
|
||||
enabled Boolean @default(true)
|
||||
hiboutikId Int @unique
|
||||
name String
|
||||
/// Display order among its siblings (category_position)
|
||||
position Int @default(0)
|
||||
/// Displayed on the website (category_enabled_www)
|
||||
webEnabled Boolean @default(true)
|
||||
|
||||
/// Null for a root category
|
||||
parent Category? @relation("CategoryTree", fields: [parentId], references: [id], onDelete: SetNull)
|
||||
parentId String?
|
||||
children Category[] @relation("CategoryTree")
|
||||
|
||||
products Product[]
|
||||
|
||||
@@map("categories")
|
||||
}
|
||||
|
||||
/// Product brand, mirrored from Hiboutik.
|
||||
model Brand {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
enabled Boolean @default(true)
|
||||
hiboutikId Int @unique
|
||||
name String
|
||||
|
||||
products Product[]
|
||||
|
||||
@@map("brands")
|
||||
}
|
||||
|
||||
/// Product supplier, mirrored from Hiboutik.
|
||||
model Supplier {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
enabled Boolean @default(true)
|
||||
hiboutikId Int @unique
|
||||
name String
|
||||
|
||||
products Product[]
|
||||
|
||||
@@map("suppliers")
|
||||
}
|
||||
|
||||
/// Product mirrored from Hiboutik. What is actually sold is one of its versions (sizes).
|
||||
model Product {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Archived in Hiboutik (product_arch)
|
||||
archived Boolean @default(false)
|
||||
barcode String?
|
||||
description String
|
||||
/// Null when the product has no discount
|
||||
discountPrice Decimal? @db.Decimal(10, 2)
|
||||
/// Displayed on the website (product_display_www)
|
||||
enabled Boolean @default(true)
|
||||
/// Sum of the available stock of all its versions
|
||||
globalStock Int @default(0)
|
||||
hiboutikId Int @unique
|
||||
/// Last update in Hiboutik
|
||||
hiboutikUpdatedAt DateTime?
|
||||
name String
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
|
||||
brand Brand? @relation(fields: [brandId], references: [id], onDelete: SetNull)
|
||||
brandId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
||||
categoryId String?
|
||||
supplier Supplier? @relation(fields: [supplierId], references: [id], onDelete: SetNull)
|
||||
supplierId String?
|
||||
|
||||
versions ProductVersion[]
|
||||
|
||||
@@map("products")
|
||||
}
|
||||
|
||||
/// Sellable version of a product: one per Hiboutik size, or a single one (hiboutikSizeId 0) for a product without sizes.
|
||||
model ProductVersion {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Not unique: Hiboutik is the source of truth and may hold the same barcode on several products
|
||||
barcode String?
|
||||
/// Size-specific discount, null when none
|
||||
discountPrice Decimal? @db.Decimal(10, 2)
|
||||
/// Hiboutik size id, 0 for a product without sizes
|
||||
hiboutikSizeId Int
|
||||
/// Size-specific price, null to use the product price (copy of the product price for a product without sizes)
|
||||
price Decimal? @db.Decimal(10, 2)
|
||||
stock Int @default(0)
|
||||
/// Size name, or the product name for a product without sizes
|
||||
versionName String
|
||||
|
||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade, onUpdate: Restrict)
|
||||
productId String
|
||||
|
||||
orderLines OrderLine[]
|
||||
|
||||
@@unique([productId, hiboutikSizeId])
|
||||
@@index([barcode])
|
||||
@@map("productVersions")
|
||||
}
|
||||
|
||||
/// Customer mirrored from Hiboutik.
|
||||
model Customer {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
dateOfBirth DateTime?
|
||||
/// Kept when removed in Hiboutik (an empty Hiboutik email does not erase it)
|
||||
email String?
|
||||
firstName String
|
||||
hiboutikId Int @unique
|
||||
/// Last update in Hiboutik
|
||||
hiboutikUpdatedAt DateTime?
|
||||
lastName String
|
||||
phone String?
|
||||
|
||||
addresses CustomerAddress[]
|
||||
orders Order[]
|
||||
|
||||
@@index([email])
|
||||
@@map("customers")
|
||||
}
|
||||
|
||||
/// Address of a customer, mirrored from Hiboutik (sync not implemented yet).
|
||||
model CustomerAddress {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
address String
|
||||
city String
|
||||
country String
|
||||
hiboutikId Int @unique
|
||||
isDefault Boolean @default(false)
|
||||
zipCode String
|
||||
|
||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||
customerId String
|
||||
|
||||
@@map("customer_addresses")
|
||||
}
|
||||
|
||||
/// Where an order was placed.
|
||||
enum OrderSource {
|
||||
STORE // hiboutik
|
||||
WEB // site
|
||||
}
|
||||
|
||||
/// Order placed in store (Hiboutik sale) or on the website.
|
||||
model Order {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
/// Null for a website order not (yet) sent to Hiboutik
|
||||
hiboutikSaleId Int? @unique
|
||||
orderedAt DateTime
|
||||
source OrderSource
|
||||
status String
|
||||
total Decimal @db.Decimal(10, 2)
|
||||
|
||||
/// Null for an anonymous sale or a deleted customer
|
||||
customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
|
||||
customerId String?
|
||||
|
||||
lines OrderLine[]
|
||||
|
||||
@@map("orders")
|
||||
}
|
||||
|
||||
/// Line of an order.
|
||||
model OrderLine {
|
||||
id String @id @default(uuid())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Snapshot of the product at order time: the catalog can change or be deleted afterwards
|
||||
barcode String?
|
||||
hiboutikProductId Int
|
||||
hiboutikSizeId Int
|
||||
productName String
|
||||
quantity Int
|
||||
unitPrice Decimal @db.Decimal(10, 2)
|
||||
versionName String
|
||||
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
orderId String
|
||||
// Link to the current catalog, set to null when the version is deleted
|
||||
productVersion ProductVersion? @relation(fields: [productVersionId], references: [id], onDelete: SetNull)
|
||||
productVersionId String?
|
||||
|
||||
@@map("order_lines")
|
||||
}
|
||||
Reference in New Issue
Block a user