Initial commit for homaroma
This commit is contained in:
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;
|
||||
+22
@@ -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"
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
Reference in new issue
Block a user