Initial commit for homaroma

This commit is contained in:
Your Name committed 2026-06-03 08:39:12 +07:00
commit f2179cfe57
269 files changed
+22635

No files matched your search

@@ -0,0 +1 @@
ผลิตภัณฑ์ Aroma Oil กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น
@@ -0,0 +1 @@
ผลิตภัณฑ์ Car Diffuser กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น
@@ -0,0 +1 @@
ผลิตภัณฑ์ Oil Ozone กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น
@@ -0,0 +1 @@
ผลิตภัณฑ์ Reed Diffuser กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น
@@ -0,0 +1 @@
ผลิตภัณฑ์ Room Spray กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น
@@ -0,0 +1,4 @@
รายละเอียดฉบับเต็มของ Aroma Oil กลิ่น {{scent}}
- ส่วนประกอบนำเข้าคุณภาพสูง
- กระจายความหอมได้ยาวนานในห้อง
- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว
@@ -0,0 +1,4 @@
รายละเอียดฉบับเต็มของ Car Diffuser กลิ่น {{scent}}
- ส่วนประกอบนำเข้าคุณภาพสูง
- กระจายความหอมได้ยาวนานในห้อง
- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว
@@ -0,0 +1,4 @@
รายละเอียดฉบับเต็มของ Oil Ozone กลิ่น {{scent}}
- ส่วนประกอบนำเข้าคุณภาพสูง
- กระจายความหอมได้ยาวนานในห้อง
- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว
@@ -0,0 +1,4 @@
รายละเอียดฉบับเต็มของ Reed Diffuser กลิ่น {{scent}}
- ส่วนประกอบนำเข้าคุณภาพสูง
- กระจายความหอมได้ยาวนานในห้อง
- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว
@@ -0,0 +1,4 @@
รายละเอียดฉบับเต็มของ Room Spray กลิ่น {{scent}}
- ส่วนประกอบนำเข้าคุณภาพสูง
- กระจายความหอมได้ยาวนานในห้อง
- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว
@@ -0,0 +1,94 @@
-- CreateTable
CREATE TABLE "Scent" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Scent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Product" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"price" DOUBLE PRECISION DEFAULT 0,
"categoryId" INTEGER NOT NULL,
"scentId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" SERIAL NOT NULL,
"url" TEXT NOT NULL,
"order" INTEGER NOT NULL DEFAULT 0,
"productId" INTEGER NOT NULL,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" SERIAL NOT NULL,
"size" TEXT NOT NULL,
"price" DOUBLE PRECISION DEFAULT 0,
"productId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
"role" TEXT NOT NULL DEFAULT 'ADMIN',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Scent_name_key" ON "Scent"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Product_scentId_categoryId_key" ON "Product"("scentId", "categoryId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_productId_size_key" ON "ProductVariant"("productId", "size");
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_scentId_fkey" FOREIGN KEY ("scentId") REFERENCES "Scent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Product" ADD COLUMN "details" TEXT;
@@ -0,0 +1,57 @@
-- CreateTable
CREATE TABLE "Hero" (
"id" SERIAL NOT NULL,
"imageUrl" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Hero_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StoreSettings" (
"id" SERIAL NOT NULL,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 35,
"bankName" TEXT NOT NULL,
"bankAccountNumber" TEXT NOT NULL,
"bankAccountName" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StoreSettings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" SERIAL NOT NULL,
"customerName" TEXT NOT NULL,
"customerPhone" TEXT NOT NULL,
"customerEmail" TEXT NOT NULL,
"shippingAddress" TEXT NOT NULL,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 35,
"totalAmount" DOUBLE PRECISION NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PENDING_PAYMENT',
"paymentSlipUrl" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" SERIAL NOT NULL,
"orderId" INTEGER NOT NULL,
"productId" INTEGER NOT NULL,
"variantId" INTEGER NOT NULL,
"productName" TEXT NOT NULL,
"variantSize" TEXT NOT NULL,
"productImage" TEXT,
"quantity" INTEGER NOT NULL,
"priceAtPurchase" DOUBLE PRECISION NOT NULL,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,241 @@
/*
Warnings:
- Added the required column `description` to the `Hero` table without a default value. This is not possible if the table is not empty.
- Added the required column `tagline` to the `Hero` table without a default value. This is not possible if the table is not empty.
- Added the required column `title` to the `Hero` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Hero" ADD COLUMN "buttonLink" TEXT NOT NULL DEFAULT '/shop',
ADD COLUMN "buttonText" TEXT NOT NULL DEFAULT 'Explore Collection',
ADD COLUMN "description" TEXT NOT NULL,
ADD COLUMN "tagline" TEXT NOT NULL,
ADD COLUMN "title" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "OrderItem" ALTER COLUMN "variantId" DROP NOT NULL,
ALTER COLUMN "variantSize" DROP NOT NULL;
-- AlterTable
ALTER TABLE "StoreSettings" ALTER COLUMN "id" SET DEFAULT 1,
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "bankName" SET DEFAULT 'KASIKORNBANK (KBANK)',
ALTER COLUMN "bankAccountNumber" SET DEFAULT '012-3-45678-9',
ALTER COLUMN "bankAccountName" SET DEFAULT 'iRelax Aroma Co., Ltd.';
DROP SEQUENCE "StoreSettings_id_seq";
-- CreateTable
CREATE TABLE "Article" (
"id" SERIAL NOT NULL,
"slug" TEXT NOT NULL,
"title" TEXT NOT NULL,
"coverImage" TEXT,
"content" TEXT NOT NULL,
"isPublished" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TiktokIncome" (
"id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"orderCreatedTime" TEXT,
"orderSettledTime" TEXT,
"currency" TEXT NOT NULL,
"totalSettlementAmount" DOUBLE PRECISION NOT NULL,
"totalRevenue" DOUBLE PRECISION NOT NULL,
"totalFees" DOUBLE PRECISION NOT NULL,
"rawData" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TiktokIncome_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TiktokProduct" (
"id" TEXT NOT NULL,
"productName" TEXT NOT NULL,
"status" TEXT NOT NULL,
"gmv" DOUBLE PRECISION NOT NULL,
"itemsSold" INTEGER NOT NULL,
"orders" INTEGER NOT NULL,
"rawData" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TiktokProduct_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TiktokProductSku" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"skuName" TEXT NOT NULL,
"status" TEXT NOT NULL,
"gmv" DOUBLE PRECISION NOT NULL,
"skuOrders" INTEGER NOT NULL,
"itemsSold" INTEGER NOT NULL,
"rawData" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TiktokProductSku_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RawMaterial" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"unit" TEXT NOT NULL,
"pricePerUnit" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "RawMaterial_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BomItem" (
"id" SERIAL NOT NULL,
"productVariantId" INTEGER NOT NULL,
"rawMaterialId" INTEGER NOT NULL,
"quantity" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BomItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ShopeeOrder" (
"orderId" TEXT NOT NULL,
"shop" TEXT NOT NULL DEFAULT 'hom-aroma',
"status" TEXT NOT NULL,
"buyerUsername" TEXT,
"orderCreatedAt" TEXT,
"paymentTime" TEXT,
"paymentMethod" TEXT,
"trackingNo" TEXT,
"totalAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"buyerPaidAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 0,
"commission" DOUBLE PRECISION NOT NULL DEFAULT 0,
"completedAt" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ShopeeOrder_pkey" PRIMARY KEY ("orderId")
);
-- CreateTable
CREATE TABLE "ShopeeOrderItem" (
"id" SERIAL NOT NULL,
"orderId" TEXT NOT NULL,
"parentSku" TEXT,
"productName" TEXT NOT NULL,
"skuRef" TEXT,
"variantName" TEXT,
"originalPrice" DOUBLE PRECISION NOT NULL DEFAULT 0,
"dealPrice" DOUBLE PRECISION NOT NULL DEFAULT 0,
"quantity" INTEGER NOT NULL DEFAULT 1,
"totalPayment" DOUBLE PRECISION NOT NULL DEFAULT 0,
"rawData" JSONB NOT NULL,
CONSTRAINT "ShopeeOrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TiktokOrder" (
"id" SERIAL NOT NULL,
"orderId" TEXT NOT NULL,
"shop" TEXT NOT NULL DEFAULT 'hom-aroma',
"status" TEXT NOT NULL,
"subStatus" TEXT,
"orderType" TEXT,
"cancelBy" TEXT,
"cancelReason" TEXT,
"fulfillmentType" TEXT,
"warehouseName" TEXT,
"trackingId" TEXT,
"deliveryOption" TEXT,
"shippingProvider" TEXT,
"buyerUsername" TEXT,
"paymentMethod" TEXT,
"orderAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"refundAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 0,
"originalShipping" DOUBLE PRECISION NOT NULL DEFAULT 0,
"taxes" DOUBLE PRECISION NOT NULL DEFAULT 0,
"province" TEXT,
"district" TEXT,
"country" TEXT,
"createdTime" TEXT,
"paidTime" TEXT,
"rtsTime" TEXT,
"shippedTime" TEXT,
"deliveredTime" TEXT,
"cancelledTime" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TiktokOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TiktokOrderItem" (
"id" SERIAL NOT NULL,
"orderId" TEXT NOT NULL,
"skuId" TEXT,
"sellerSku" TEXT,
"productName" TEXT NOT NULL,
"variation" TEXT,
"quantity" INTEGER NOT NULL DEFAULT 1,
"skuQtyReturn" INTEGER NOT NULL DEFAULT 0,
"unitOriginalPrice" DOUBLE PRECISION NOT NULL DEFAULT 0,
"subtotalBeforeDiscount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"platformDiscount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"sellerDiscount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"subtotalAfterDiscount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"productCategory" TEXT,
"weight" DOUBLE PRECISION NOT NULL DEFAULT 0,
CONSTRAINT "TiktokOrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Article_slug_key" ON "Article"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "RawMaterial_name_key" ON "RawMaterial"("name");
-- CreateIndex
CREATE UNIQUE INDEX "BomItem_productVariantId_rawMaterialId_key" ON "BomItem"("productVariantId", "rawMaterialId");
-- CreateIndex
CREATE UNIQUE INDEX "TiktokOrder_orderId_key" ON "TiktokOrder"("orderId");
-- CreateIndex
CREATE INDEX "TiktokOrder_shop_idx" ON "TiktokOrder"("shop");
-- CreateIndex
CREATE INDEX "TiktokOrder_status_idx" ON "TiktokOrder"("status");
-- CreateIndex
CREATE INDEX "TiktokOrder_createdTime_idx" ON "TiktokOrder"("createdTime");
-- AddForeignKey
ALTER TABLE "BomItem" ADD CONSTRAINT "BomItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BomItem" ADD CONSTRAINT "BomItem_rawMaterialId_fkey" FOREIGN KEY ("rawMaterialId") REFERENCES "RawMaterial"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ShopeeOrderItem" ADD CONSTRAINT "ShopeeOrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "ShopeeOrder"("orderId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TiktokOrderItem" ADD CONSTRAINT "TiktokOrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "TiktokOrder"("orderId") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,60 @@
-- CreateTable
CREATE TABLE "LazadaOrder" (
"id" SERIAL NOT NULL,
"orderNumber" TEXT NOT NULL,
"shop" TEXT NOT NULL DEFAULT 'hom-aroma',
"createTime" TEXT,
"updateTime" TEXT,
"deliveredDate" TEXT,
"customerName" TEXT,
"shippingCity" TEXT,
"shippingPostCode" TEXT,
"shippingRegion" TEXT,
"payMethod" TEXT,
"status" TEXT,
"trackingCode" TEXT,
"shippingProvider" TEXT,
"orderAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LazadaOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LazadaOrderItem" (
"id" SERIAL NOT NULL,
"orderNumber" TEXT NOT NULL,
"orderItemId" TEXT NOT NULL,
"sellerSku" TEXT,
"lazadaSku" TEXT,
"itemName" TEXT NOT NULL,
"variation" TEXT,
"unitPrice" DOUBLE PRECISION NOT NULL DEFAULT 0,
"paidPrice" DOUBLE PRECISION NOT NULL DEFAULT 0,
"sellerDiscountTotal" DOUBLE PRECISION NOT NULL DEFAULT 0,
"platformDiscountTotal" DOUBLE PRECISION NOT NULL DEFAULT 0,
"shippingFee" DOUBLE PRECISION NOT NULL DEFAULT 0,
"status" TEXT,
CONSTRAINT "LazadaOrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "LazadaOrder_orderNumber_key" ON "LazadaOrder"("orderNumber");
-- CreateIndex
CREATE INDEX "LazadaOrder_shop_idx" ON "LazadaOrder"("shop");
-- CreateIndex
CREATE INDEX "LazadaOrder_status_idx" ON "LazadaOrder"("status");
-- CreateIndex
CREATE INDEX "LazadaOrder_createTime_idx" ON "LazadaOrder"("createTime");
-- CreateIndex
CREATE UNIQUE INDEX "LazadaOrderItem_orderItemId_key" ON "LazadaOrderItem"("orderItemId");
-- AddForeignKey
ALTER TABLE "LazadaOrderItem" ADD CONSTRAINT "LazadaOrderItem_orderNumber_fkey" FOREIGN KEY ("orderNumber") REFERENCES "LazadaOrder"("orderNumber") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Category" ADD COLUMN "imageUrl" TEXT;
@@ -0,0 +1,22 @@
-- AlterTable
ALTER TABLE "Order" ADD COLUMN "customerId" INTEGER;
-- CreateTable
CREATE TABLE "Customer" (
"id" SERIAL NOT NULL,
"lineUserId" TEXT NOT NULL,
"displayName" TEXT,
"pictureUrl" TEXT,
"email" TEXT,
"phone" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Customer_lineUserId_key" ON "Customer"("lineUserId");
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,24 @@
-- AlterTable
ALTER TABLE "Order" ADD COLUMN "couponCode" TEXT,
ADD COLUMN "discountAmount" DOUBLE PRECISION NOT NULL DEFAULT 0;
-- CreateTable
CREATE TABLE "Coupon" (
"id" SERIAL NOT NULL,
"code" TEXT NOT NULL,
"type" TEXT NOT NULL,
"value" DOUBLE PRECISION NOT NULL,
"minPurchase" DOUBLE PRECISION NOT NULL DEFAULT 0,
"maxDiscount" DOUBLE PRECISION,
"usageLimit" INTEGER,
"usedCount" INTEGER NOT NULL DEFAULT 0,
"expiresAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Coupon_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Coupon_code_key" ON "Coupon"("code");
@@ -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"
+374
View File
@@ -0,0 +1,374 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model Scent {
id Int @id @default(autoincrement())
name String @unique
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Category {
id Int @id @default(autoincrement())
name String @unique
imageUrl String?
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Product {
id Int @id @default(autoincrement())
name String
description String?
details String?
price Float? @default(0)
categoryId Int
category Category @relation(fields: [categoryId], references: [id])
scentId Int
scent Scent @relation(fields: [scentId], references: [id])
variants ProductVariant[]
images ProductImage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([scentId, categoryId])
}
model ProductImage {
id Int @id @default(autoincrement())
url String
order Int @default(0)
productId Int
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
}
model ProductVariant {
id Int @id @default(autoincrement())
size String
price Float? @default(0)
productId Int
product Product @relation(fields: [productId], references: [id])
bomItems BomItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([productId, size])
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
role String @default("ADMIN")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Customer {
id Int @id @default(autoincrement())
lineUserId String @unique
displayName String?
pictureUrl String?
email String?
phone String?
orders Order[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Hero {
id Int @id @default(autoincrement())
tagline String
title String
description String
imageUrl String
buttonText String @default("Explore Collection")
buttonLink String @default("/shop")
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Coupon {
id Int @id @default(autoincrement())
code String @unique
type String // "PERCENTAGE", "FIXED_AMOUNT", "FREE_SHIPPING"
value Float
minPurchase Float @default(0)
maxDiscount Float?
usageLimit Int?
usedCount Int @default(0)
expiresAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model StoreSettings {
id Int @id @default(1)
shippingFee Float @default(35)
bankName String @default("KASIKORNBANK (KBANK)")
bankAccountName String @default("iRelax Aroma Co., Ltd.")
bankAccountNumber String @default("012-3-45678-9")
updatedAt DateTime @updatedAt
}
model Order {
id Int @id @default(autoincrement())
customerName String
customerEmail String
customerPhone String
shippingAddress String
totalAmount Float
shippingFee Float @default(35)
couponCode String?
discountAmount Float @default(0)
status String @default("PENDING_PAYMENT")
paymentSlipUrl String?
customerId Int?
customer Customer? @relation(fields: [customerId], references: [id])
items OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OrderItem {
id Int @id @default(autoincrement())
orderId Int
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
productId Int
variantId Int?
quantity Int
priceAtPurchase Float
productName String
variantSize String?
productImage String?
}
model Article {
id Int @id @default(autoincrement())
slug String @unique
title String
coverImage String?
content String
isPublished Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TiktokIncome {
id String @id // Order/adjustment ID
type String
orderCreatedTime String?
orderSettledTime String?
currency String
totalSettlementAmount Float
totalRevenue Float
totalFees Float
rawData Json // Stores all other 50+ columns
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TiktokProduct {
id String @id // Product ID
productName String
status String
gmv Float
itemsSold Int
orders Int
rawData Json // Stores remaining tab/live/video metrics
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TiktokProductSku {
id String @id // SKU ID
productId String
skuName String
status String
gmv Float
skuOrders Int
itemsSold Int
rawData Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model RawMaterial {
id Int @id @default(autoincrement())
name String @unique
unit String // e.g., 'kg', 'liter', 'piece'
pricePerUnit Float
bomItems BomItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model BomItem {
id Int @id @default(autoincrement())
productVariantId Int
productVariant ProductVariant @relation(fields: [productVariantId], references: [id], onDelete: Cascade)
rawMaterialId Int
rawMaterial RawMaterial @relation(fields: [rawMaterialId], references: [id], onDelete: Cascade)
quantity Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([productVariantId, rawMaterialId])
}
model ShopeeOrder {
orderId String @id
shop String @default("hom-aroma")
status String
buyerUsername String?
orderCreatedAt String?
paymentTime String?
paymentMethod String?
trackingNo String?
totalAmount Float @default(0)
buyerPaidAmount Float @default(0)
shippingFee Float @default(0)
commission Float @default(0)
completedAt String?
items ShopeeOrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ShopeeOrderItem {
id Int @id @default(autoincrement())
orderId String
order ShopeeOrder @relation(fields: [orderId], references: [orderId], onDelete: Cascade)
parentSku String?
productName String
skuRef String?
variantName String?
originalPrice Float @default(0)
dealPrice Float @default(0)
quantity Int @default(1)
totalPayment Float @default(0)
rawData Json
}
// ── TikTok Orders ──────────────────────────────────────────────────────────────
model TiktokOrder {
id Int @id @default(autoincrement())
orderId String @unique
shop String @default("hom-aroma")
status String // Order Status (Thai)
subStatus String? // Order Substatus
orderType String? // Normal / Pre-order
cancelBy String?
cancelReason String?
fulfillmentType String?
warehouseName String?
trackingId String?
deliveryOption String?
shippingProvider String?
buyerUsername String?
paymentMethod String?
// Financials (order-level)
orderAmount Float @default(0) // Total paid by buyer
refundAmount Float @default(0)
shippingFee Float @default(0) // Shipping Fee After Discount
originalShipping Float @default(0)
taxes Float @default(0)
// Geography
province String?
district String?
country String?
// Timestamps
createdTime String?
paidTime String?
rtsTime String?
shippedTime String?
deliveredTime String?
cancelledTime String?
items TiktokOrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([shop])
@@index([status])
@@index([createdTime])
}
model TiktokOrderItem {
id Int @id @default(autoincrement())
orderId String
order TiktokOrder @relation(fields: [orderId], references: [orderId], onDelete: Cascade)
skuId String?
sellerSku String?
productName String
variation String?
quantity Int @default(1)
skuQtyReturn Int @default(0)
unitOriginalPrice Float @default(0)
subtotalBeforeDiscount Float @default(0)
platformDiscount Float @default(0)
sellerDiscount Float @default(0)
subtotalAfterDiscount Float @default(0) // Revenue ระดับ SKU
productCategory String?
weight Float @default(0)
}
// ── Lazada Orders ──────────────────────────────────────────────────────────────
model LazadaOrder {
id Int @id @default(autoincrement())
orderNumber String @unique
shop String @default("hom-aroma")
createTime String?
updateTime String?
deliveredDate String?
customerName String?
shippingCity String?
shippingPostCode String?
shippingRegion String?
payMethod String?
status String? // Based on items or first item
trackingCode String?
shippingProvider String?
// Aggregate from items
orderAmount Float @default(0) // sum(paidPrice)
shippingFee Float @default(0) // sum(shippingFee)
items LazadaOrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([shop])
@@index([status])
@@index([createTime])
}
model LazadaOrderItem {
id Int @id @default(autoincrement())
orderNumber String
order LazadaOrder @relation(fields: [orderNumber], references: [orderNumber], onDelete: Cascade)
orderItemId String @unique
sellerSku String?
lazadaSku String?
itemName String
variation String?
unitPrice Float @default(0)
paidPrice Float @default(0)
sellerDiscountTotal Float @default(0)
platformDiscountTotal Float @default(0)
shippingFee Float @default(0)
status String?
}
+225
View File
@@ -0,0 +1,225 @@
// seed.cjs - Prisma 7 Seed Script
// Note: Bun loads .env automatically - no need for dotenv
const fs = require('fs');
const path = require('path');
const { PrismaClient } = require('@prisma/client');
const { PrismaPg } = require('@prisma/adapter-pg');
const bcrypt = require('bcryptjs');
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
const scentNames = [
'ตะไคร้', 'ลาเวนเดอร์', 'วานิลลา', 'ซากุระ', 'มะลิ', 'ยูคาลิปตัส', 'เฟรชลาเวนเดอร์',
'เปเปอร์มิ้น', 'โอเชี่ยน', 'เซเว่นซี', 'ดอกแก้ว', 'ดอกโมก', 'ลีลาวดี', 'ส้ม', 'มะนาว',
'แคนตาลูป', 'แอปเปิ้ลแดง', 'แอปเปิ้ลเขียว', 'ดอกลิลลี่', 'โรสเมรี่', 'อัญชัน',
'กุหลาบแดง', 'หมากฝรั่ง', 'ผลไม้รวม', 'มะกรูด', 'แซนด้าวูส', 'ดอกปีบ', 'ดอกบัว',
'โอโซน', 'ไทยสปา', 'กล้วย', 'กาแฟ', 'ดอกชมนาด', 'กลิ่นน้ำนม', 'ดอกการะเวก',
'ดอกจำปา', 'ดอกพิกุล', 'ดอกพุด', 'ดอกราตรี', 'ดอกลำดวน', 'ทับทิม', 'มะละกอสุก',
'ดอกราชาวดี', 'กลิ่นลูกพีช', 'กลิ่นองุ่น'
];
const categoryConfigs = [
{
name: 'Oil Ozone',
sizes: ['30ml', '60ml', '85ml', '120ml'],
priceMap: { '30ml': 69, '60ml': 100, '85ml': 120, '120ml': 150 }
},
{
name: 'Aroma Oil',
sizes: ['30ml', '60ml', '85ml', '120ml'],
priceMap: { '30ml': 90, '60ml': 150, '85ml': 165, '120ml': 225 } // 120ml ปรับเป็น 225 (฿1.875/ml) ให้ถูกกว่า 85ml ต่อ ml
},
{
name: 'Reed Diffuser',
sizes: ['60ml', '85ml', '120ml'],
priceMap: { '60ml': 120, '85ml': 160, '120ml': 190 }
},
{
name: 'Room Spray',
sizes: ['85ml'],
priceMap: { '85ml': 150 }
},
{
name: 'Car Diffuser',
sizes: ['25ml'],
priceMap: { '25ml': 60 }
}
];
async function main() {
process.stdout.write('🚀 Starting Advanced Seeding (Products & Variants)...\n');
// Load or create category description files
const descriptionsDir = path.join(__dirname, 'descriptions');
if (!fs.existsSync(descriptionsDir)) {
fs.mkdirSync(descriptionsDir);
}
const categoryDescriptions = {};
for (const config of categoryConfigs) {
const safeName = config.name.replace(/\s+/g, '_');
const descPath = path.join(descriptionsDir, `${safeName}.txt`);
// Auto-create with default template if not exists
if (!fs.existsSync(descPath)) {
const defaultDesc = `ผลิตภัณฑ์ ${config.name} กลิ่น {{scent}} มอบประสบการณ์ความหอมที่ผ่อนคลายและสดชื่น ช่วยให้บรรยากาศในห้องของคุณน่าอยู่มากยิ่งขึ้น`;
fs.writeFileSync(descPath, defaultDesc, 'utf8');
}
categoryDescriptions[config.name] = fs.readFileSync(descPath, 'utf8');
// Auto-create category details
const detailsDir = path.join(__dirname, 'details');
if (!fs.existsSync(detailsDir)) {
fs.mkdirSync(detailsDir);
}
const detailPath = path.join(detailsDir, `${safeName}.txt`);
if (!fs.existsSync(detailPath)) {
const defaultDetail = `รายละเอียดฉบับเต็มของ ${config.name} กลิ่น {{scent}}\n- ส่วนประกอบนำเข้าคุณภาพสูง\n- กระจายความหอมได้ยาวนานในห้อง\n- ปลอดภัยต่อสุขภาพและเหมาะสำหรับทุกคนในครอบครัว`;
fs.writeFileSync(detailPath, defaultDetail, 'utf8');
}
categoryConfigs[config.name] = categoryConfigs[config.name] || {};
categoryConfigs[config.name].detailsTemplate = fs.readFileSync(detailPath, 'utf8');
}
// 0. Seed Admin User
const adminPassword = await bcrypt.hash('irelax123', 10);
await prisma.user.upsert({
where: { username: 'admin' },
update: { password: adminPassword },
create: {
username: 'admin',
password: adminPassword,
role: 'ADMIN'
}
});
process.stdout.write('✅ Seeded Admin User (admin / irelax123).\n');
// 1. Seed Scents & Categories
const scents = [];
for (const name of scentNames) {
const s = await prisma.scent.upsert({
where: { name },
update: {},
create: { name }
});
scents.push(s);
}
process.stdout.write(`✅ Seeded ${scents.length} Scents.\n`);
const categories = [];
for (const config of categoryConfigs) {
const c = await prisma.category.upsert({
where: { name: config.name },
update: {},
create: { name: config.name }
});
categories.push({ ...c, allowedSizes: config.sizes, priceMap: config.priceMap, detailsTemplate: categoryConfigs[config.name].detailsTemplate });
}
process.stdout.write(`✅ Seeded ${categories.length} Categories.\n`);
// 2. Cross Scents x Categories to create Products
process.stdout.write('📦 Generating Products and Variants...\n');
let productCount = 0;
let variantCount = 0;
for (const scent of scents) {
for (const category of categories) {
const productName = `${scent.name} ${category.name}`;
// Get category description template and replace {{scent}}
let productDescription = categoryDescriptions[category.name] || `Product ${scent.name} in category ${category.name}`;
productDescription = productDescription.replace(/{{scent}}/g, scent.name);
let productDetails = category.detailsTemplate || '';
productDetails = productDetails.replace(/{{scent}}/g, scent.name);
// Upsert Product
const product = await prisma.product.upsert({
where: {
scentId_categoryId: {
scentId: scent.id,
categoryId: category.id
}
},
update: {
name: productName,
description: productDescription,
details: productDetails
},
create: {
name: productName,
scentId: scent.id,
categoryId: category.id,
description: productDescription,
details: productDetails
}
});
productCount++;
// 3. Create Default Product Image
// Using existing category-based image paths as initial seed data
const defaultImagePath = category.name === 'Oil Ozone' || category.name === 'Aroma Oil' || category.name === 'Car Diffuser'
? '/images/oil_ozone.png'
: '/images/reed_diffuser.png';
await prisma.productImage.upsert({
where: { id: (product.id * 10) + 1 }, // Pseudo-stable ID for seed
update: { url: defaultImagePath },
create: {
id: (product.id * 10) + 1,
productId: product.id,
url: defaultImagePath,
order: 0
}
}).catch(() => {
// Fallback for cases where ID might conflict or upsert on non-unique field
return prisma.productImage.create({
data: {
productId: product.id,
url: defaultImagePath,
order: 0
}
});
});
// 4. Create Variants (Sizes) with prices from priceMap
for (const size of category.allowedSizes) {
const price = category.priceMap?.[size] ?? 0;
await prisma.productVariant.upsert({
where: {
productId_size: {
productId: product.id,
size: size
}
},
update: { price }, // อัปเดตราคาเมื่อ re-seed
create: {
productId: product.id,
size: size,
price: price
}
});
variantCount++;
}
}
process.stdout.write(`.`); // Progress indicator
}
process.stdout.write(`\n✨ Seeding Complete!\n`);
process.stdout.write(`📊 Summary:\n`);
process.stdout.write(` - Scents: ${scents.length}\n`);
process.stdout.write(` - Categories: ${categories.length}\n`);
process.stdout.write(` - Products: ${productCount}\n`);
process.stdout.write(` - Variants: ${variantCount}\n`);
}
main()
.catch((e) => {
process.stderr.write(`❌ Error: ${e.message}\n`);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});