feat: auto-run SQL migrations and one-time seed on boot

This commit is contained in:
mahmood
2026-09-10 17:15:17 +03:30
parent a4c5c9259f
commit f19f49daeb
12 changed files with 2569 additions and 0 deletions
+431
View File
@@ -0,0 +1,431 @@
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'SYSTEM_APPROVER', 'SALES_EXPERT', 'WORKSHOP_SUPERVISOR', 'ACCOUNTANT');
-- CreateEnum
CREATE TYPE "VerificationStep" AS ENUM ('UNVERIFIED', 'STEP_ONE_SUBMITTED', 'STEP_TWO_SUBMITTED', 'VERIFIED');
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('SUSPENDED', 'PLACED', 'ADMIN_APPROVED', 'SOURCING', 'PREPARING', 'DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PreOrderStatus" AS ENUM ('PLACED', 'ADMIN_APPROVED', 'WORKSHOP_APPROVED', 'READY_TO_SHIP', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "LedgerCurrency" AS ENUM ('GOLD_GRAM', 'RIAL');
-- CreateEnum
CREATE TYPE "LedgerDirection" AS ENUM ('DEBIT', 'CREDIT');
-- CreateEnum
CREATE TYPE "LedgerSourceType" AS ENUM ('ORDER_STOCK', 'ORDER_PRE_ORDER', 'REPAYMENT_MELTED_GOLD', 'REPAYMENT_TRANSFER', 'REPAYMENT_CHEQUE', 'REPAYMENT_CASH', 'WALLET_BONUS', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "City" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"province" TEXT,
CONSTRAINT "City_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdminUser" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"branchId" TEXT,
"wagePercent" DECIMAL(6,3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"nationalCode" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"cityId" TEXT,
"referredByAgentId" TEXT,
"goldCreditLimitGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"goldDebtGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"fiatDebtRial" DECIMAL(20,2) NOT NULL DEFAULT 0,
"verificationStep" "VerificationStep" NOT NULL DEFAULT 'UNVERIFIED',
"nationalIdImageUrl" TEXT,
"businessLicenseUrl" TEXT,
"shopImageUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CustomerAddress" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"addressLine" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Branch" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"cityId" TEXT,
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Workshop" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Workshop_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"parentId" TEXT,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Brand" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Collection" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CollectionProduct" (
"collectionId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
CONSTRAINT "CollectionProduct_pkey" PRIMARY KEY ("collectionId","productId")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"brandId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"url" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"sku" TEXT,
"rashWeightGrams" DECIMAL(14,4) NOT NULL,
"defaultWagePercent" DECIMAL(6,3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"branchId" TEXT,
"status" "OrderStatus" NOT NULL DEFAULT 'PLACED',
"totalGoldWeightGrams" DECIMAL(14,4) NOT NULL,
"wageWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
"isSuspendedOverCreditLimit" BOOLEAN NOT NULL DEFAULT false,
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"productVariantId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"rashWeightGramsSnapshot" DECIMAL(14,4) NOT NULL,
"wagePercentSnapshot" DECIMAL(6,3) NOT NULL,
"discountPercentSnapshot" DECIMAL(6,3) NOT NULL DEFAULT 0,
"computedWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderStatusEvent" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"fromStatus" "OrderStatus",
"toStatus" "OrderStatus" NOT NULL,
"note" TEXT,
"actorAdminUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "OrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrder" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"workshopId" TEXT,
"status" "PreOrderStatus" NOT NULL DEFAULT 'PLACED',
"designImageUrl" TEXT,
"colorName" TEXT,
"sizeSpec" TEXT,
"stoneColor" TEXT,
"minaColor" TEXT,
"estimatedWeightGrams" DECIMAL(14,4),
"estimatedPriceRial" DECIMAL(20,2),
"description" TEXT,
"convertedOrderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrderStatusEvent" (
"id" TEXT NOT NULL,
"preOrderId" TEXT NOT NULL,
"changes" JSONB NOT NULL,
"actorAdminUserId" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PreOrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"currency" "LedgerCurrency" NOT NULL,
"direction" "LedgerDirection" NOT NULL,
"sourceType" "LedgerSourceType" NOT NULL,
"amount" DECIMAL(20,4) NOT NULL,
"karatPerMille" INTEGER,
"pricePerGramRialAtEntry" DECIMAL(20,2),
"orderId" TEXT,
"confirmed" BOOLEAN NOT NULL DEFAULT false,
"confirmedAt" TIMESTAMP(3),
"postedByAdminUserId" TEXT,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GoldPriceQuote" (
"id" TEXT NOT NULL,
"karatPerMille" INTEGER NOT NULL,
"pricePerGramRial" DECIMAL(20,2) NOT NULL,
"source" TEXT NOT NULL,
"quotedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GoldPriceQuote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletBonus" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"goldGrams" DECIMAL(14,4) NOT NULL,
"grantedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "WalletBonus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AdminUser_mobile_key" ON "AdminUser"("mobile");
-- CreateIndex
CREATE INDEX "AdminUser_role_idx" ON "AdminUser"("role");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_mobile_key" ON "Customer"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_nationalCode_key" ON "Customer"("nationalCode");
-- CreateIndex
CREATE INDEX "Customer_referredByAgentId_idx" ON "Customer"("referredByAgentId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_sku_key" ON "ProductVariant"("sku");
-- CreateIndex
CREATE UNIQUE INDEX "Order_orderNumber_key" ON "Order"("orderNumber");
-- CreateIndex
CREATE INDEX "Order_customerId_idx" ON "Order"("customerId");
-- CreateIndex
CREATE INDEX "Order_status_idx" ON "Order"("status");
-- CreateIndex
CREATE INDEX "OrderStatusEvent_orderId_idx" ON "OrderStatusEvent"("orderId");
-- CreateIndex
CREATE UNIQUE INDEX "PreOrder_convertedOrderId_key" ON "PreOrder"("convertedOrderId");
-- CreateIndex
CREATE INDEX "PreOrder_customerId_idx" ON "PreOrder"("customerId");
-- CreateIndex
CREATE INDEX "PreOrderStatusEvent_preOrderId_idx" ON "PreOrderStatusEvent"("preOrderId");
-- CreateIndex
CREATE INDEX "LedgerEntry_customerId_currency_idx" ON "LedgerEntry"("customerId", "currency");
-- CreateIndex
CREATE INDEX "LedgerEntry_orderId_idx" ON "LedgerEntry"("orderId");
-- CreateIndex
CREATE INDEX "GoldPriceQuote_karatPerMille_quotedAt_idx" ON "GoldPriceQuote"("karatPerMille", "quotedAt");
-- CreateIndex
CREATE INDEX "WalletBonus_customerId_isActive_idx" ON "WalletBonus"("customerId", "isActive");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_referredByAgentId_fkey" FOREIGN KEY ("referredByAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "Workshop"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_convertedOrderId_fkey" FOREIGN KEY ("convertedOrderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_preOrderId_fkey" FOREIGN KEY ("preOrderId") REFERENCES "PreOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey" FOREIGN KEY ("postedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletBonus" ADD CONSTRAINT "WalletBonus_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,430 @@
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'SYSTEM_APPROVER', 'SALES_EXPERT', 'WORKSHOP_SUPERVISOR', 'ACCOUNTANT');
-- CreateEnum
CREATE TYPE "VerificationStep" AS ENUM ('UNVERIFIED', 'STEP_ONE_SUBMITTED', 'STEP_TWO_SUBMITTED', 'VERIFIED');
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('SUSPENDED', 'PLACED', 'ADMIN_APPROVED', 'SOURCING', 'PREPARING', 'DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PreOrderStatus" AS ENUM ('PLACED', 'ADMIN_APPROVED', 'WORKSHOP_APPROVED', 'READY_TO_SHIP', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "LedgerCurrency" AS ENUM ('GOLD_GRAM', 'RIAL');
-- CreateEnum
CREATE TYPE "LedgerDirection" AS ENUM ('DEBIT', 'CREDIT');
-- CreateEnum
CREATE TYPE "LedgerSourceType" AS ENUM ('ORDER_STOCK', 'ORDER_PRE_ORDER', 'REPAYMENT_MELTED_GOLD', 'REPAYMENT_TRANSFER', 'REPAYMENT_CHEQUE', 'REPAYMENT_CASH', 'WALLET_BONUS', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "City" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"province" TEXT,
CONSTRAINT "City_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdminUser" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"branchId" TEXT,
"wagePercent" DECIMAL(6,3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"nationalCode" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"cityId" TEXT,
"referredByAgentId" TEXT,
"goldCreditLimitGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"goldDebtGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"fiatDebtRial" DECIMAL(20,2) NOT NULL DEFAULT 0,
"verificationStep" "VerificationStep" NOT NULL DEFAULT 'UNVERIFIED',
"nationalIdImageUrl" TEXT,
"businessLicenseUrl" TEXT,
"shopImageUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CustomerAddress" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"addressLine" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Branch" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"cityId" TEXT,
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Workshop" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Workshop_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"parentId" TEXT,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Brand" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Collection" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CollectionProduct" (
"collectionId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
CONSTRAINT "CollectionProduct_pkey" PRIMARY KEY ("collectionId","productId")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"brandId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"url" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"sku" TEXT,
"rashWeightGrams" DECIMAL(14,4) NOT NULL,
"defaultWagePercent" DECIMAL(6,3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"branchId" TEXT,
"status" "OrderStatus" NOT NULL DEFAULT 'PLACED',
"totalGoldWeightGrams" DECIMAL(14,4) NOT NULL,
"wageWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
"isSuspendedOverCreditLimit" BOOLEAN NOT NULL DEFAULT false,
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"productVariantId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"rashWeightGramsSnapshot" DECIMAL(14,4) NOT NULL,
"wagePercentSnapshot" DECIMAL(6,3) NOT NULL,
"discountPercentSnapshot" DECIMAL(6,3) NOT NULL DEFAULT 0,
"computedWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderStatusEvent" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"fromStatus" "OrderStatus",
"toStatus" "OrderStatus" NOT NULL,
"note" TEXT,
"actorAdminUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "OrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrder" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"workshopId" TEXT,
"status" "PreOrderStatus" NOT NULL DEFAULT 'PLACED',
"designImageUrl" TEXT,
"colorName" TEXT,
"sizeSpec" TEXT,
"stoneColor" TEXT,
"minaColor" TEXT,
"estimatedWeightGrams" DECIMAL(14,4),
"estimatedPriceRial" DECIMAL(20,2),
"description" TEXT,
"convertedOrderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrderStatusEvent" (
"id" TEXT NOT NULL,
"preOrderId" TEXT NOT NULL,
"changes" JSONB NOT NULL,
"actorAdminUserId" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PreOrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"currency" "LedgerCurrency" NOT NULL,
"direction" "LedgerDirection" NOT NULL,
"sourceType" "LedgerSourceType" NOT NULL,
"amount" DECIMAL(20,4) NOT NULL,
"karatPerMille" INTEGER,
"pricePerGramRialAtEntry" DECIMAL(20,2),
"orderId" TEXT,
"confirmed" BOOLEAN NOT NULL DEFAULT false,
"confirmedAt" TIMESTAMP(3),
"postedByAdminUserId" TEXT,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GoldPriceQuote" (
"id" TEXT NOT NULL,
"karatPerMille" INTEGER NOT NULL,
"pricePerGramRial" DECIMAL(20,2) NOT NULL,
"source" TEXT NOT NULL,
"quotedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GoldPriceQuote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletBonus" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"goldGrams" DECIMAL(14,4) NOT NULL,
"grantedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "WalletBonus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AdminUser_mobile_key" ON "AdminUser"("mobile");
-- CreateIndex
CREATE INDEX "AdminUser_role_idx" ON "AdminUser"("role");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_mobile_key" ON "Customer"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_nationalCode_key" ON "Customer"("nationalCode");
-- CreateIndex
CREATE INDEX "Customer_referredByAgentId_idx" ON "Customer"("referredByAgentId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_sku_key" ON "ProductVariant"("sku");
-- CreateIndex
CREATE UNIQUE INDEX "Order_orderNumber_key" ON "Order"("orderNumber");
-- CreateIndex
CREATE INDEX "Order_customerId_idx" ON "Order"("customerId");
-- CreateIndex
CREATE INDEX "Order_status_idx" ON "Order"("status");
-- CreateIndex
CREATE INDEX "OrderStatusEvent_orderId_idx" ON "OrderStatusEvent"("orderId");
-- CreateIndex
CREATE UNIQUE INDEX "PreOrder_convertedOrderId_key" ON "PreOrder"("convertedOrderId");
-- CreateIndex
CREATE INDEX "PreOrder_customerId_idx" ON "PreOrder"("customerId");
-- CreateIndex
CREATE INDEX "PreOrderStatusEvent_preOrderId_idx" ON "PreOrderStatusEvent"("preOrderId");
-- CreateIndex
CREATE INDEX "LedgerEntry_customerId_currency_idx" ON "LedgerEntry"("customerId", "currency");
-- CreateIndex
CREATE INDEX "LedgerEntry_orderId_idx" ON "LedgerEntry"("orderId");
-- CreateIndex
CREATE INDEX "GoldPriceQuote_karatPerMille_quotedAt_idx" ON "GoldPriceQuote"("karatPerMille", "quotedAt");
-- CreateIndex
CREATE INDEX "WalletBonus_customerId_isActive_idx" ON "WalletBonus"("customerId", "isActive");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_referredByAgentId_fkey" FOREIGN KEY ("referredByAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "Workshop"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_convertedOrderId_fkey" FOREIGN KEY ("convertedOrderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_preOrderId_fkey" FOREIGN KEY ("preOrderId") REFERENCES "PreOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey" FOREIGN KEY ("postedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletBonus" ADD CONSTRAINT "WalletBonus_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+186
View File
@@ -77552,6 +77552,190 @@ var require_app_module = __commonJS({
}
});
// dist/prisma/migrate.js
var require_migrate = __commonJS({
"dist/prisma/migrate.js"(exports2) {
"use strict";
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.splitSqlStatements = splitSqlStatements;
exports2.runMigrations = runMigrations;
exports2.runOnce = runOnce;
var fs_1 = require("fs");
var path_1 = require("path");
var TRACKING_TABLE = "_goldbank_migrations";
function resolveMigrationsDir() {
const candidates = [
process.env.MIGRATIONS_DIR,
(0, path_1.join)(process.cwd(), "migrations"),
(0, path_1.join)(__dirname, "..", "..", "migrations"),
(0, path_1.join)(__dirname, "..", "..", "prisma", "migrations"),
(0, path_1.join)(process.cwd(), "prisma", "migrations")
].filter((p) => Boolean(p));
const found = candidates.find((dir) => (0, fs_1.existsSync)(dir));
if (!found) {
throw new Error(`\u067E\u0648\u0634\u0647 migrations \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F. \u0645\u0633\u06CC\u0631\u0647\u0627\u06CC \u0628\u0631\u0631\u0633\u06CC\u200C\u0634\u062F\u0647: ${candidates.join(", ")}`);
}
return found;
}
function splitSqlStatements(sql) {
const statements = [];
let current = "";
let i = 0;
while (i < sql.length) {
const char = sql[i];
const rest = sql.slice(i);
if (rest.startsWith("--")) {
const lineEnd = sql.indexOf("\n", i);
i = lineEnd === -1 ? sql.length : lineEnd + 1;
continue;
}
if (rest.startsWith("/*")) {
const commentEnd = sql.indexOf("*/", i + 2);
i = commentEnd === -1 ? sql.length : commentEnd + 2;
continue;
}
if (char === "'" || char === '"') {
const quote = char;
current += char;
i += 1;
while (i < sql.length) {
current += sql[i];
if (sql[i] === quote) {
if (sql[i + 1] === quote) {
current += sql[i + 1];
i += 2;
continue;
}
i += 1;
break;
}
i += 1;
}
continue;
}
const dollarTag = /^\$[A-Za-z_]*\$/.exec(rest);
if (dollarTag) {
const tag = dollarTag[0];
const closing = sql.indexOf(tag, i + tag.length);
const end = closing === -1 ? sql.length : closing + tag.length;
current += sql.slice(i, end);
i = end;
continue;
}
if (char === ";") {
if (current.trim())
statements.push(current.trim());
current = "";
i += 1;
continue;
}
current += char;
i += 1;
}
if (current.trim())
statements.push(current.trim());
return statements;
}
async function runMigrations(prisma) {
const dir = resolveMigrationsDir();
const files = (0, fs_1.readdirSync)(dir).filter((name) => name.endsWith(".sql")).sort();
const trackingExisted = await prisma.$queryRawUnsafe(`SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name = $1
) AS "exists"`, TRACKING_TABLE);
const freshDatabase = !trackingExisted[0]?.exists;
await prisma.$executeRawUnsafe(`CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE}" (
name TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`);
const alreadyApplied = await prisma.$queryRawUnsafe(`SELECT name FROM "${TRACKING_TABLE}"`);
const appliedNames = new Set(alreadyApplied.map((row) => row.name));
const applied = [];
for (const file2 of files) {
if (appliedNames.has(file2))
continue;
const statements = splitSqlStatements((0, fs_1.readFileSync)((0, path_1.join)(dir, file2), "utf8"));
await prisma.$transaction(
async (tx) => {
for (const statement of statements) {
await tx.$executeRawUnsafe(statement);
}
await tx.$executeRawUnsafe(`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`, file2);
},
// The baseline migration is hundreds of DDL statements; the 5s default is
// not enough, especially against a remote database.
{ maxWait: 6e4, timeout: 6e5 }
);
applied.push(file2);
}
return { freshDatabase, applied };
}
async function runOnce(prisma, name, task) {
const rows = await prisma.$queryRawUnsafe(`SELECT name FROM "${TRACKING_TABLE}" WHERE name = $1`, name);
if (rows.length > 0)
return false;
await task();
await prisma.$executeRawUnsafe(`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`, name);
return true;
}
}
});
// dist/prisma/bootstrap.js
var require_bootstrap = __commonJS({
"dist/prisma/bootstrap.js"(exports2) {
"use strict";
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.prepareDatabase = prepareDatabase;
var child_process_1 = require("child_process");
var fs_1 = require("fs");
var path_1 = require("path");
var util_1 = require("util");
var common_12 = require_common();
var client_1 = require("@prisma/client");
var migrate_1 = require_migrate();
var execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
function resolveSeedScript() {
const candidates = [
process.env.SEED_SCRIPT,
(0, path_1.join)(process.cwd(), "seed.js"),
(0, path_1.join)(__dirname, "..", "..", "seed.js")
].filter((p) => Boolean(p));
return candidates.find((file2) => (0, fs_1.existsSync)(file2));
}
async function prepareDatabase() {
if (process.env.RUN_MIGRATIONS === "false")
return;
const logger = new common_12.Logger("DatabaseBootstrap");
const prisma = new client_1.PrismaClient();
try {
const { applied } = await (0, migrate_1.runMigrations)(prisma);
if (applied.length > 0) {
logger.log(`Applied migrations: ${applied.join(", ")}`);
} else {
logger.log("No pending migrations");
}
if (process.env.RUN_SEED === "false")
return;
const seedScript = resolveSeedScript();
if (!seedScript) {
logger.warn("No seed script found \u2014 skipping seed");
return;
}
const seeded = await (0, migrate_1.runOnce)(prisma, "seed:initial", async () => {
const { stdout } = await execFileAsync(process.execPath, [seedScript]);
if (stdout.trim())
logger.log(stdout.trim());
});
if (seeded)
logger.log("Seed data inserted");
} finally {
await prisma.$disconnect();
}
}
}
});
// dist/prisma/prisma-exception.filter.js
var require_prisma_exception_filter = __commonJS({
"dist/prisma/prisma-exception.filter.js"(exports2) {
@@ -77626,8 +77810,10 @@ require_Reflect();
var core_1 = require_core3();
var common_1 = require_common();
var app_module_1 = require_app_module();
var bootstrap_1 = require_bootstrap();
var prisma_exception_filter_1 = require_prisma_exception_filter();
async function bootstrap() {
await (0, bootstrap_1.prepareDatabase)();
const app = await core_1.NestFactory.create(app_module_1.AppModule);
app.setGlobalPrefix("api");
app.useGlobalPipes(new common_1.ValidationPipe({ whitelist: true, transform: true }));
+431
View File
@@ -0,0 +1,431 @@
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'SYSTEM_APPROVER', 'SALES_EXPERT', 'WORKSHOP_SUPERVISOR', 'ACCOUNTANT');
-- CreateEnum
CREATE TYPE "VerificationStep" AS ENUM ('UNVERIFIED', 'STEP_ONE_SUBMITTED', 'STEP_TWO_SUBMITTED', 'VERIFIED');
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('SUSPENDED', 'PLACED', 'ADMIN_APPROVED', 'SOURCING', 'PREPARING', 'DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PreOrderStatus" AS ENUM ('PLACED', 'ADMIN_APPROVED', 'WORKSHOP_APPROVED', 'READY_TO_SHIP', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "LedgerCurrency" AS ENUM ('GOLD_GRAM', 'RIAL');
-- CreateEnum
CREATE TYPE "LedgerDirection" AS ENUM ('DEBIT', 'CREDIT');
-- CreateEnum
CREATE TYPE "LedgerSourceType" AS ENUM ('ORDER_STOCK', 'ORDER_PRE_ORDER', 'REPAYMENT_MELTED_GOLD', 'REPAYMENT_TRANSFER', 'REPAYMENT_CHEQUE', 'REPAYMENT_CASH', 'WALLET_BONUS', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "City" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"province" TEXT,
CONSTRAINT "City_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdminUser" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"branchId" TEXT,
"wagePercent" DECIMAL(6,3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"nationalCode" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"cityId" TEXT,
"referredByAgentId" TEXT,
"goldCreditLimitGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"goldDebtGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"fiatDebtRial" DECIMAL(20,2) NOT NULL DEFAULT 0,
"verificationStep" "VerificationStep" NOT NULL DEFAULT 'UNVERIFIED',
"nationalIdImageUrl" TEXT,
"businessLicenseUrl" TEXT,
"shopImageUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CustomerAddress" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"addressLine" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Branch" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"cityId" TEXT,
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Workshop" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Workshop_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"parentId" TEXT,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Brand" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Collection" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CollectionProduct" (
"collectionId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
CONSTRAINT "CollectionProduct_pkey" PRIMARY KEY ("collectionId","productId")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"brandId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"url" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"sku" TEXT,
"rashWeightGrams" DECIMAL(14,4) NOT NULL,
"defaultWagePercent" DECIMAL(6,3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"branchId" TEXT,
"status" "OrderStatus" NOT NULL DEFAULT 'PLACED',
"totalGoldWeightGrams" DECIMAL(14,4) NOT NULL,
"wageWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
"isSuspendedOverCreditLimit" BOOLEAN NOT NULL DEFAULT false,
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"productVariantId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"rashWeightGramsSnapshot" DECIMAL(14,4) NOT NULL,
"wagePercentSnapshot" DECIMAL(6,3) NOT NULL,
"discountPercentSnapshot" DECIMAL(6,3) NOT NULL DEFAULT 0,
"computedWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderStatusEvent" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"fromStatus" "OrderStatus",
"toStatus" "OrderStatus" NOT NULL,
"note" TEXT,
"actorAdminUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "OrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrder" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"workshopId" TEXT,
"status" "PreOrderStatus" NOT NULL DEFAULT 'PLACED',
"designImageUrl" TEXT,
"colorName" TEXT,
"sizeSpec" TEXT,
"stoneColor" TEXT,
"minaColor" TEXT,
"estimatedWeightGrams" DECIMAL(14,4),
"estimatedPriceRial" DECIMAL(20,2),
"description" TEXT,
"convertedOrderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrderStatusEvent" (
"id" TEXT NOT NULL,
"preOrderId" TEXT NOT NULL,
"changes" JSONB NOT NULL,
"actorAdminUserId" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PreOrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"currency" "LedgerCurrency" NOT NULL,
"direction" "LedgerDirection" NOT NULL,
"sourceType" "LedgerSourceType" NOT NULL,
"amount" DECIMAL(20,4) NOT NULL,
"karatPerMille" INTEGER,
"pricePerGramRialAtEntry" DECIMAL(20,2),
"orderId" TEXT,
"confirmed" BOOLEAN NOT NULL DEFAULT false,
"confirmedAt" TIMESTAMP(3),
"postedByAdminUserId" TEXT,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GoldPriceQuote" (
"id" TEXT NOT NULL,
"karatPerMille" INTEGER NOT NULL,
"pricePerGramRial" DECIMAL(20,2) NOT NULL,
"source" TEXT NOT NULL,
"quotedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GoldPriceQuote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletBonus" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"goldGrams" DECIMAL(14,4) NOT NULL,
"grantedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "WalletBonus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AdminUser_mobile_key" ON "AdminUser"("mobile");
-- CreateIndex
CREATE INDEX "AdminUser_role_idx" ON "AdminUser"("role");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_mobile_key" ON "Customer"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_nationalCode_key" ON "Customer"("nationalCode");
-- CreateIndex
CREATE INDEX "Customer_referredByAgentId_idx" ON "Customer"("referredByAgentId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_sku_key" ON "ProductVariant"("sku");
-- CreateIndex
CREATE UNIQUE INDEX "Order_orderNumber_key" ON "Order"("orderNumber");
-- CreateIndex
CREATE INDEX "Order_customerId_idx" ON "Order"("customerId");
-- CreateIndex
CREATE INDEX "Order_status_idx" ON "Order"("status");
-- CreateIndex
CREATE INDEX "OrderStatusEvent_orderId_idx" ON "OrderStatusEvent"("orderId");
-- CreateIndex
CREATE UNIQUE INDEX "PreOrder_convertedOrderId_key" ON "PreOrder"("convertedOrderId");
-- CreateIndex
CREATE INDEX "PreOrder_customerId_idx" ON "PreOrder"("customerId");
-- CreateIndex
CREATE INDEX "PreOrderStatusEvent_preOrderId_idx" ON "PreOrderStatusEvent"("preOrderId");
-- CreateIndex
CREATE INDEX "LedgerEntry_customerId_currency_idx" ON "LedgerEntry"("customerId", "currency");
-- CreateIndex
CREATE INDEX "LedgerEntry_orderId_idx" ON "LedgerEntry"("orderId");
-- CreateIndex
CREATE INDEX "GoldPriceQuote_karatPerMille_quotedAt_idx" ON "GoldPriceQuote"("karatPerMille", "quotedAt");
-- CreateIndex
CREATE INDEX "WalletBonus_customerId_isActive_idx" ON "WalletBonus"("customerId", "isActive");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_referredByAgentId_fkey" FOREIGN KEY ("referredByAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "Workshop"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_convertedOrderId_fkey" FOREIGN KEY ("convertedOrderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_preOrderId_fkey" FOREIGN KEY ("preOrderId") REFERENCES "PreOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey" FOREIGN KEY ("postedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletBonus" ADD CONSTRAINT "WalletBonus_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,430 @@
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'SYSTEM_APPROVER', 'SALES_EXPERT', 'WORKSHOP_SUPERVISOR', 'ACCOUNTANT');
-- CreateEnum
CREATE TYPE "VerificationStep" AS ENUM ('UNVERIFIED', 'STEP_ONE_SUBMITTED', 'STEP_TWO_SUBMITTED', 'VERIFIED');
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('SUSPENDED', 'PLACED', 'ADMIN_APPROVED', 'SOURCING', 'PREPARING', 'DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PreOrderStatus" AS ENUM ('PLACED', 'ADMIN_APPROVED', 'WORKSHOP_APPROVED', 'READY_TO_SHIP', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "LedgerCurrency" AS ENUM ('GOLD_GRAM', 'RIAL');
-- CreateEnum
CREATE TYPE "LedgerDirection" AS ENUM ('DEBIT', 'CREDIT');
-- CreateEnum
CREATE TYPE "LedgerSourceType" AS ENUM ('ORDER_STOCK', 'ORDER_PRE_ORDER', 'REPAYMENT_MELTED_GOLD', 'REPAYMENT_TRANSFER', 'REPAYMENT_CHEQUE', 'REPAYMENT_CASH', 'WALLET_BONUS', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "City" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"province" TEXT,
CONSTRAINT "City_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdminUser" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"branchId" TEXT,
"wagePercent" DECIMAL(6,3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"nationalCode" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"cityId" TEXT,
"referredByAgentId" TEXT,
"goldCreditLimitGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"goldDebtGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"fiatDebtRial" DECIMAL(20,2) NOT NULL DEFAULT 0,
"verificationStep" "VerificationStep" NOT NULL DEFAULT 'UNVERIFIED',
"nationalIdImageUrl" TEXT,
"businessLicenseUrl" TEXT,
"shopImageUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CustomerAddress" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"addressLine" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Branch" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"cityId" TEXT,
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Workshop" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Workshop_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"parentId" TEXT,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Brand" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Collection" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CollectionProduct" (
"collectionId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
CONSTRAINT "CollectionProduct_pkey" PRIMARY KEY ("collectionId","productId")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"brandId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"url" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"sku" TEXT,
"rashWeightGrams" DECIMAL(14,4) NOT NULL,
"defaultWagePercent" DECIMAL(6,3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"branchId" TEXT,
"status" "OrderStatus" NOT NULL DEFAULT 'PLACED',
"totalGoldWeightGrams" DECIMAL(14,4) NOT NULL,
"wageWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
"isSuspendedOverCreditLimit" BOOLEAN NOT NULL DEFAULT false,
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"productVariantId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"rashWeightGramsSnapshot" DECIMAL(14,4) NOT NULL,
"wagePercentSnapshot" DECIMAL(6,3) NOT NULL,
"discountPercentSnapshot" DECIMAL(6,3) NOT NULL DEFAULT 0,
"computedWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderStatusEvent" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"fromStatus" "OrderStatus",
"toStatus" "OrderStatus" NOT NULL,
"note" TEXT,
"actorAdminUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "OrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrder" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"workshopId" TEXT,
"status" "PreOrderStatus" NOT NULL DEFAULT 'PLACED',
"designImageUrl" TEXT,
"colorName" TEXT,
"sizeSpec" TEXT,
"stoneColor" TEXT,
"minaColor" TEXT,
"estimatedWeightGrams" DECIMAL(14,4),
"estimatedPriceRial" DECIMAL(20,2),
"description" TEXT,
"convertedOrderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrderStatusEvent" (
"id" TEXT NOT NULL,
"preOrderId" TEXT NOT NULL,
"changes" JSONB NOT NULL,
"actorAdminUserId" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PreOrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"currency" "LedgerCurrency" NOT NULL,
"direction" "LedgerDirection" NOT NULL,
"sourceType" "LedgerSourceType" NOT NULL,
"amount" DECIMAL(20,4) NOT NULL,
"karatPerMille" INTEGER,
"pricePerGramRialAtEntry" DECIMAL(20,2),
"orderId" TEXT,
"confirmed" BOOLEAN NOT NULL DEFAULT false,
"confirmedAt" TIMESTAMP(3),
"postedByAdminUserId" TEXT,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GoldPriceQuote" (
"id" TEXT NOT NULL,
"karatPerMille" INTEGER NOT NULL,
"pricePerGramRial" DECIMAL(20,2) NOT NULL,
"source" TEXT NOT NULL,
"quotedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GoldPriceQuote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletBonus" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"goldGrams" DECIMAL(14,4) NOT NULL,
"grantedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "WalletBonus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AdminUser_mobile_key" ON "AdminUser"("mobile");
-- CreateIndex
CREATE INDEX "AdminUser_role_idx" ON "AdminUser"("role");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_mobile_key" ON "Customer"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_nationalCode_key" ON "Customer"("nationalCode");
-- CreateIndex
CREATE INDEX "Customer_referredByAgentId_idx" ON "Customer"("referredByAgentId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_sku_key" ON "ProductVariant"("sku");
-- CreateIndex
CREATE UNIQUE INDEX "Order_orderNumber_key" ON "Order"("orderNumber");
-- CreateIndex
CREATE INDEX "Order_customerId_idx" ON "Order"("customerId");
-- CreateIndex
CREATE INDEX "Order_status_idx" ON "Order"("status");
-- CreateIndex
CREATE INDEX "OrderStatusEvent_orderId_idx" ON "OrderStatusEvent"("orderId");
-- CreateIndex
CREATE UNIQUE INDEX "PreOrder_convertedOrderId_key" ON "PreOrder"("convertedOrderId");
-- CreateIndex
CREATE INDEX "PreOrder_customerId_idx" ON "PreOrder"("customerId");
-- CreateIndex
CREATE INDEX "PreOrderStatusEvent_preOrderId_idx" ON "PreOrderStatusEvent"("preOrderId");
-- CreateIndex
CREATE INDEX "LedgerEntry_customerId_currency_idx" ON "LedgerEntry"("customerId", "currency");
-- CreateIndex
CREATE INDEX "LedgerEntry_orderId_idx" ON "LedgerEntry"("orderId");
-- CreateIndex
CREATE INDEX "GoldPriceQuote_karatPerMille_quotedAt_idx" ON "GoldPriceQuote"("karatPerMille", "quotedAt");
-- CreateIndex
CREATE INDEX "WalletBonus_customerId_isActive_idx" ON "WalletBonus"("customerId", "isActive");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_referredByAgentId_fkey" FOREIGN KEY ("referredByAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "Workshop"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_convertedOrderId_fkey" FOREIGN KEY ("convertedOrderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_preOrderId_fkey" FOREIGN KEY ("preOrderId") REFERENCES "PreOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey" FOREIGN KEY ("postedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletBonus" ADD CONSTRAINT "WalletBonus_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+402
View File
@@ -0,0 +1,402 @@
"use strict";
// prisma/seed.ts
var import_client = require("@prisma/client");
var prisma = new import_client.PrismaClient();
var PLACEHOLDER_PASSWORD_HASH = "placeholder-hash-not-for-production";
var PLACEHOLDER_PRODUCT_IMAGE = "/img/no-items.png";
async function main() {
const [tehran, mashhad, isfahan, tabriz] = await Promise.all([
prisma.city.upsert({ where: { id: "city-tehran" }, update: {}, create: { id: "city-tehran", name: "\u062A\u0647\u0631\u0627\u0646", province: "\u062A\u0647\u0631\u0627\u0646" } }),
prisma.city.upsert({ where: { id: "city-mashhad" }, update: {}, create: { id: "city-mashhad", name: "\u0645\u0634\u0647\u062F", province: "\u062E\u0631\u0627\u0633\u0627\u0646 \u0631\u0636\u0648\u06CC" } }),
prisma.city.upsert({ where: { id: "city-isfahan" }, update: {}, create: { id: "city-isfahan", name: "\u0627\u0635\u0641\u0647\u0627\u0646", province: "\u0627\u0635\u0641\u0647\u0627\u0646" } }),
prisma.city.upsert({ where: { id: "city-tabriz" }, update: {}, create: { id: "city-tabriz", name: "\u062A\u0628\u0631\u06CC\u0632", province: "\u0622\u0630\u0631\u0628\u0627\u06CC\u062C\u0627\u0646 \u0634\u0631\u0642\u06CC" } })
]);
const branchTehran = await prisma.branch.upsert({
where: { id: "branch-tehran-bazaar" },
update: {},
create: { id: "branch-tehran-bazaar", name: "\u0634\u0639\u0628\u0647 \u0628\u0627\u0632\u0627\u0631 \u0628\u0632\u0631\u06AF \u062A\u0647\u0631\u0627\u0646", cityId: tehran.id }
});
const branchMashhad = await prisma.branch.upsert({
where: { id: "branch-mashhad-main" },
update: {},
create: { id: "branch-mashhad-main", name: "\u0634\u0639\u0628\u0647 \u0645\u0631\u06A9\u0632\u06CC \u0645\u0634\u0647\u062F", cityId: mashhad.id }
});
await prisma.workshop.upsert({
where: { id: "workshop-central" },
update: {},
create: { id: "workshop-central", name: "\u06A9\u0627\u0631\u06AF\u0627\u0647 \u0645\u0631\u06A9\u0632\u06CC \u0633\u0627\u062E\u062A \u0637\u0644\u0627" }
});
await prisma.adminUser.upsert({
where: { mobile: "09120000001" },
update: {},
create: {
mobile: "09120000001",
fullName: "\u0645\u062F\u06CC\u0631 \u0627\u0631\u0634\u062F \u0633\u06CC\u0633\u062A\u0645",
passwordHash: PLACEHOLDER_PASSWORD_HASH,
role: import_client.AdminRole.SUPER_ADMIN,
branchId: branchTehran.id
}
});
const agentReza = await prisma.adminUser.upsert({
where: { mobile: "09121234567" },
update: {},
create: {
mobile: "09121234567",
fullName: "\u0631\u0636\u0627 \u0627\u062D\u0645\u062F\u06CC",
passwordHash: PLACEHOLDER_PASSWORD_HASH,
role: import_client.AdminRole.SALES_EXPERT,
branchId: branchTehran.id,
wagePercent: 1.5
}
});
const agentSara = await prisma.adminUser.upsert({
where: { mobile: "09129876543" },
update: {},
create: {
mobile: "09129876543",
fullName: "\u0633\u0627\u0631\u0627 \u06A9\u0631\u06CC\u0645\u06CC",
passwordHash: PLACEHOLDER_PASSWORD_HASH,
role: import_client.AdminRole.SALES_EXPERT,
branchId: branchMashhad.id,
wagePercent: 1.2
}
});
await prisma.adminUser.upsert({
where: { mobile: "09123334444" },
update: {},
create: {
mobile: "09123334444",
fullName: "\u0647\u0648\u0645\u0646 \u06CC\u0632\u062F\u0627\u0646\u06CC",
passwordHash: PLACEHOLDER_PASSWORD_HASH,
role: import_client.AdminRole.WORKSHOP_SUPERVISOR
}
});
await prisma.adminUser.upsert({
where: { mobile: "09125556666" },
update: {},
create: {
mobile: "09125556666",
fullName: "\u0641\u0631\u0632\u0627\u0646\u0647 \u0645\u062D\u0645\u0648\u062F\u06CC",
passwordHash: PLACEHOLDER_PASSWORD_HASH,
role: import_client.AdminRole.ACCOUNTANT,
branchId: branchTehran.id
}
});
const customerData = [
{
id: "customer-1",
fullName: "\u0639\u0644\u06CC \u0645\u062D\u0645\u062F\u06CC",
mobile: "09151112233",
nationalCode: "0012345678",
cityId: tehran.id,
referredByAgentId: agentReza.id,
goldCreditLimitGrams: "200",
verificationStep: import_client.VerificationStep.VERIFIED
},
{
id: "customer-2",
fullName: "\u0645\u0631\u06CC\u0645 \u062D\u0633\u06CC\u0646\u06CC",
mobile: "09151112234",
nationalCode: "0012345679",
cityId: tehran.id,
referredByAgentId: agentReza.id,
goldCreditLimitGrams: "100",
verificationStep: import_client.VerificationStep.VERIFIED
},
{
id: "customer-3",
fullName: "\u062C\u0648\u0627\u062F \u0646\u0648\u0631\u0648\u0632\u06CC",
mobile: "09151112235",
nationalCode: "0012345680",
cityId: mashhad.id,
referredByAgentId: agentSara.id,
goldCreditLimitGrams: "50",
verificationStep: import_client.VerificationStep.STEP_TWO_SUBMITTED
},
{
id: "customer-4",
fullName: "\u0646\u0631\u06AF\u0633 \u0627\u0628\u0631\u0627\u0647\u06CC\u0645\u06CC",
mobile: "09151112236",
nationalCode: "0012345681",
cityId: isfahan.id,
referredByAgentId: null,
goldCreditLimitGrams: "30",
verificationStep: import_client.VerificationStep.UNVERIFIED
},
{
id: "customer-5",
fullName: "\u06A9\u0627\u0645\u0631\u0627\u0646 \u0635\u0627\u062F\u0642\u06CC",
mobile: "09151112237",
nationalCode: "0012345682",
cityId: tehran.id,
referredByAgentId: agentReza.id,
goldCreditLimitGrams: "500",
verificationStep: import_client.VerificationStep.VERIFIED
},
{
id: "customer-6",
fullName: "\u0632\u0647\u0631\u0627 \u0642\u0627\u0633\u0645\u06CC",
mobile: "09151112238",
nationalCode: "0012345683",
cityId: tabriz.id,
referredByAgentId: agentSara.id,
goldCreditLimitGrams: "80",
verificationStep: import_client.VerificationStep.STEP_ONE_SUBMITTED
}
];
const customers = [];
for (const c of customerData) {
customers.push(
await prisma.customer.upsert({
where: { id: c.id },
update: {},
create: {
id: c.id,
fullName: c.fullName,
mobile: c.mobile,
nationalCode: c.nationalCode,
passwordHash: PLACEHOLDER_PASSWORD_HASH,
cityId: c.cityId,
referredByAgentId: c.referredByAgentId,
goldCreditLimitGrams: c.goldCreditLimitGrams,
verificationStep: c.verificationStep
}
})
);
}
const [customer1, customer2, , , customer5] = customers;
const [ringsCategory, braceletsCategory, necklacesCategory, earringsCategory, coinsCategory] = await Promise.all([
prisma.category.upsert({ where: { id: "cat-rings" }, update: {}, create: { id: "cat-rings", title: "\u0627\u0646\u06AF\u0634\u062A\u0631" } }),
prisma.category.upsert({ where: { id: "cat-bracelets" }, update: {}, create: { id: "cat-bracelets", title: "\u062F\u0633\u062A\u0628\u0646\u062F" } }),
prisma.category.upsert({ where: { id: "cat-necklaces" }, update: {}, create: { id: "cat-necklaces", title: "\u06AF\u0631\u062F\u0646\u0628\u0646\u062F" } }),
prisma.category.upsert({ where: { id: "cat-earrings" }, update: {}, create: { id: "cat-earrings", title: "\u06AF\u0648\u0634\u0648\u0627\u0631\u0647" } }),
prisma.category.upsert({ where: { id: "cat-coins" }, update: {}, create: { id: "cat-coins", title: "\u0633\u06A9\u0647 \u0648 \u0634\u0645\u0634" } })
]);
const brandHenza = await prisma.brand.upsert({
where: { id: "brand-henza" },
update: {},
create: { id: "brand-henza", title: "\u0647\u0646\u0632\u0627 \u0637\u0644\u0627" }
});
const brandArtisan = await prisma.brand.upsert({
where: { id: "brand-artisan" },
update: {},
create: { id: "brand-artisan", title: "\u0645\u062C\u0645\u0648\u0639\u0647 \u0635\u0646\u0639\u062A\u06AF\u0631" }
});
const productDefs = [
{
id: "product-classic-ring",
title: "\u0627\u0646\u06AF\u0634\u062A\u0631 \u062A\u06A9 \u0646\u06AF\u06CC\u0646 \u06A9\u0644\u0627\u0633\u06CC\u06A9",
description: "\u0627\u0646\u06AF\u0634\u062A\u0631 \u0637\u0644\u0627\u06CC \u06F1\u06F8 \u0639\u06CC\u0627\u0631 \u0628\u0627 \u0637\u0631\u0627\u062D\u06CC \u0633\u0627\u062F\u0647 \u0648 \u0645\u06CC\u0646\u06CC\u0645\u0627\u0644.",
categoryId: ringsCategory.id,
brandId: brandHenza.id,
variants: [
{ id: "variant-ring-size-6", title: "\u0633\u0627\u06CC\u0632 \u06F6", sku: "RING-CLASSIC-6", rashWeightGrams: "2.850", defaultWagePercent: "9.0" },
{ id: "variant-ring-size-7", title: "\u0633\u0627\u06CC\u0632 \u06F7", sku: "RING-CLASSIC-7", rashWeightGrams: "3.100", defaultWagePercent: "9.0" }
]
},
{
id: "product-woven-bracelet",
title: "\u062F\u0633\u062A\u0628\u0646\u062F \u0628\u0627\u0641\u062A \u0632\u0646\u062C\u06CC\u0631\u06CC",
description: "\u062F\u0633\u062A\u0628\u0646\u062F \u0628\u0627\u0641\u062A\u200C\u062F\u0627\u0631 \u062F\u0633\u062A\u200C\u0633\u0627\u0632 \u0627\u0632 \u0637\u0644\u0627\u06CC \u06F1\u06F8 \u0639\u06CC\u0627\u0631.",
categoryId: braceletsCategory.id,
brandId: brandArtisan.id,
variants: [
{ id: "variant-bracelet-standard", title: "\u0627\u0633\u062A\u0627\u0646\u062F\u0627\u0631\u062F \u06F1\u06F8 \u0633\u0627\u0646\u062A\u06CC", sku: "BRC-WOVEN-18", rashWeightGrams: "14.200", defaultWagePercent: "11.5" }
]
},
{
id: "product-pendant-necklace",
title: "\u06AF\u0631\u062F\u0646\u0628\u0646\u062F \u0622\u0648\u06CC\u0632 \u0627\u0634\u06A9\u06CC",
description: "\u06AF\u0631\u062F\u0646\u0628\u0646\u062F \u0638\u0631\u06CC\u0641 \u0628\u0627 \u0622\u0648\u06CC\u0632 \u0627\u0634\u06A9\u06CC \u0648 \u0632\u0646\u062C\u06CC\u0631 \u0646\u0627\u0632\u06A9.",
categoryId: necklacesCategory.id,
brandId: brandHenza.id,
variants: [
{ id: "variant-necklace-40cm", title: "\u0632\u0646\u062C\u06CC\u0631 \u06F4\u06F0 \u0633\u0627\u0646\u062A\u06CC", sku: "NCK-TEARDROP-40", rashWeightGrams: "5.600", defaultWagePercent: "10.0" },
{ id: "variant-necklace-45cm", title: "\u0632\u0646\u062C\u06CC\u0631 \u06F4\u06F5 \u0633\u0627\u0646\u062A\u06CC", sku: "NCK-TEARDROP-45", rashWeightGrams: "6.100", defaultWagePercent: "10.0" }
]
},
{
id: "product-hoop-earrings",
title: "\u06AF\u0648\u0634\u0648\u0627\u0631\u0647 \u062D\u0644\u0642\u0647\u200C\u0627\u06CC \u06A9\u0644\u0627\u0633\u06CC\u06A9",
description: "\u06AF\u0648\u0634\u0648\u0627\u0631\u0647 \u062D\u0644\u0642\u0647\u200C\u0627\u06CC \u0637\u0644\u0627\u06CC \u06F1\u06F8 \u0639\u06CC\u0627\u0631 \u0645\u0646\u0627\u0633\u0628 \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u0631\u0648\u0632\u0645\u0631\u0647.",
categoryId: earringsCategory.id,
brandId: brandArtisan.id,
variants: [
{ id: "variant-hoop-small", title: "\u06A9\u0648\u0686\u06A9", sku: "ERG-HOOP-S", rashWeightGrams: "1.900", defaultWagePercent: "8.5" },
{ id: "variant-hoop-large", title: "\u0628\u0632\u0631\u06AF", sku: "ERG-HOOP-L", rashWeightGrams: "3.400", defaultWagePercent: "8.5" }
]
},
{
id: "product-full-coin",
title: "\u0633\u06A9\u0647 \u062A\u0645\u0627\u0645 \u0628\u0647\u0627\u0631 \u0622\u0632\u0627\u062F\u06CC",
description: "\u0633\u06A9\u0647 \u0637\u0644\u0627\u06CC \u062A\u0645\u0627\u0645 \u0628\u0647\u0627\u0631 \u0622\u0632\u0627\u062F\u06CC\u060C \u0639\u06CC\u0627\u0631 \u0627\u0633\u062A\u0627\u0646\u062F\u0627\u0631\u062F.",
categoryId: coinsCategory.id,
brandId: brandHenza.id,
variants: [
{ id: "variant-coin-full", title: "\u062A\u0645\u0627\u0645 \u0633\u06A9\u0647", sku: "COIN-FULL", rashWeightGrams: "8.133", defaultWagePercent: "2.0" },
{ id: "variant-coin-half", title: "\u0646\u06CC\u0645 \u0633\u06A9\u0647", sku: "COIN-HALF", rashWeightGrams: "4.066", defaultWagePercent: "2.5" }
]
}
];
const createdVariantIds = {};
let imageCount = 0;
for (const p of productDefs) {
const product = await prisma.product.upsert({
where: { id: p.id },
update: {},
create: {
id: p.id,
title: p.title,
description: p.description,
categoryId: p.categoryId,
brandId: p.brandId
}
});
for (const v of p.variants) {
await prisma.productVariant.upsert({
where: { id: v.id },
update: {},
create: {
id: v.id,
productId: product.id,
title: v.title,
sku: v.sku,
rashWeightGrams: v.rashWeightGrams,
defaultWagePercent: v.defaultWagePercent
}
});
createdVariantIds[v.id] = v.id;
}
await prisma.productImage.upsert({
where: { id: `${p.id}-img-1` },
update: { url: PLACEHOLDER_PRODUCT_IMAGE },
create: {
id: `${p.id}-img-1`,
productId: product.id,
url: PLACEHOLDER_PRODUCT_IMAGE,
sortOrder: 0
}
});
imageCount += 1;
}
const featured = await prisma.collection.upsert({
where: { id: "collection-featured" },
update: {},
create: { id: "collection-featured", title: "\u067E\u0631\u0641\u0631\u0648\u0634\u200C\u0647\u0627\u06CC \u0627\u06CC\u0646 \u0647\u0641\u062A\u0647", description: "\u0645\u0646\u062A\u062E\u0628\u06CC \u0627\u0632 \u0645\u062D\u0628\u0648\u0628\u200C\u062A\u0631\u06CC\u0646 \u0645\u062D\u0635\u0648\u0644\u0627\u062A \u0641\u0631\u0648\u0634\u06AF\u0627\u0647." }
});
await prisma.collectionProduct.upsert({
where: { collectionId_productId: { collectionId: featured.id, productId: "product-classic-ring" } },
update: {},
create: { collectionId: featured.id, productId: "product-classic-ring" }
});
await prisma.collectionProduct.upsert({
where: { collectionId_productId: { collectionId: featured.id, productId: "product-pendant-necklace" } },
update: {},
create: { collectionId: featured.id, productId: "product-pendant-necklace" }
});
await prisma.collectionProduct.upsert({
where: { collectionId_productId: { collectionId: featured.id, productId: "product-full-coin" } },
update: {},
create: { collectionId: featured.id, productId: "product-full-coin" }
});
await prisma.goldPriceQuote.create({
data: { karatPerMille: 750, pricePerGramRial: "61500000", source: "seed-illustrative" }
});
const existingDemoOrder = await prisma.order.findUnique({ where: { orderNumber: "ORD-SEED-0001" } });
if (!existingDemoOrder) {
const rashWeight = "3.100";
const wagePercent = 9;
const discountPercent = 0;
const computedWeight = (parseFloat(rashWeight) * (1 + (wagePercent - discountPercent) / 100)).toFixed(4);
const quantity = 1;
const flatRialChargePerUnit = "500000";
const order = await prisma.order.create({
data: {
orderNumber: "ORD-SEED-0001",
customerId: customer1.id,
branchId: branchTehran.id,
status: "DELIVERED_TO_CUSTOMER",
totalGoldWeightGrams: computedWeight,
wageWeightGrams: (parseFloat(computedWeight) - parseFloat(rashWeight)).toFixed(4),
flatRialCharge: flatRialChargePerUnit,
deliveredAt: /* @__PURE__ */ new Date(),
items: {
create: [
{
productVariantId: "variant-ring-size-7",
quantity,
rashWeightGramsSnapshot: rashWeight,
wagePercentSnapshot: wagePercent.toString(),
discountPercentSnapshot: discountPercent.toString(),
computedWeightGrams: computedWeight,
flatRialCharge: flatRialChargePerUnit
}
]
},
statusEvents: {
create: [{ toStatus: "PLACED" }, { toStatus: "ADMIN_APPROVED" }, { toStatus: "DELIVERED_TO_CUSTOMER" }]
}
}
});
await prisma.ledgerEntry.create({
data: {
customerId: customer1.id,
currency: "GOLD_GRAM",
direction: "DEBIT",
sourceType: "ORDER_STOCK",
amount: computedWeight,
orderId: order.id,
confirmed: true,
confirmedAt: /* @__PURE__ */ new Date(),
note: "\u0628\u062F\u0647\u06CC \u0648\u0632\u0646\u06CC \u0637\u0644\u0627 \u0647\u0646\u06AF\u0627\u0645 \u062A\u062D\u0648\u06CC\u0644 (\u062F\u0627\u062F\u0647 \u0622\u0632\u0645\u0627\u06CC\u0634\u06CC)"
}
});
await prisma.ledgerEntry.create({
data: {
customerId: customer1.id,
currency: "RIAL",
direction: "DEBIT",
sourceType: "ORDER_STOCK",
amount: flatRialChargePerUnit,
orderId: order.id,
confirmed: true,
confirmedAt: /* @__PURE__ */ new Date(),
note: "\u0647\u0632\u06CC\u0646\u0647 \u062B\u0627\u0628\u062A \u0631\u06CC\u0627\u0644\u06CC \u0647\u0646\u06AF\u0627\u0645 \u062A\u062D\u0648\u06CC\u0644 (\u062F\u0627\u062F\u0647 \u0622\u0632\u0645\u0627\u06CC\u0634\u06CC)"
}
});
await prisma.customer.update({
where: { id: customer1.id },
data: { goldDebtGrams: { increment: computedWeight }, fiatDebtRial: { increment: flatRialChargePerUnit } }
});
await prisma.ledgerEntry.create({
data: {
customerId: customer1.id,
currency: "GOLD_GRAM",
direction: "CREDIT",
sourceType: "REPAYMENT_MELTED_GOLD",
amount: "1.5000",
karatPerMille: 750,
confirmed: false,
note: "\u062A\u0633\u0648\u06CC\u0647 \u0628\u0627 \u0637\u0644\u0627\u06CC \u0622\u0628\u200C\u0634\u062F\u0647\u060C \u062F\u0631 \u0627\u0646\u062A\u0638\u0627\u0631 \u062A\u0627\u06CC\u06CC\u062F (\u062F\u0627\u062F\u0647 \u0622\u0632\u0645\u0627\u06CC\u0634\u06CC)"
}
});
}
await prisma.customer.update({ where: { id: customer2.id }, data: { goldDebtGrams: "8.2500", fiatDebtRial: "2000000" } });
await prisma.customer.update({ where: { id: customer5.id }, data: { goldDebtGrams: "0", fiatDebtRial: "0" } });
console.log("Seed complete:", {
cities: 4,
branches: 2,
adminUsers: 5,
customers: customers.length,
products: productDefs.length,
variants: Object.keys(createdVariantIds).length,
productImages: imageCount
});
}
main().catch((e) => {
console.error(e);
process.exit(1);
}).finally(async () => {
await prisma.$disconnect();
});
+13
View File
@@ -83,8 +83,21 @@ async function main() {
await esbuild.build(config);
copyPrismaIntoRelease();
// SQL migrations are read at runtime by src/prisma/migrate.ts
cpSync(path.join(root, 'prisma', 'migrations'), path.join(releaseDir, 'migrations'), {
recursive: true,
});
// Seeding runs as a separate process on a fresh database (src/prisma/bootstrap.ts)
await esbuild.build({
...config,
entryPoints: [path.join(root, 'prisma', 'seed.ts')],
outfile: path.join(releaseDir, 'seed.js'),
});
console.log('نوشت:', config.outfile);
console.log('Prisma client در release/vendor کپی شد.');
console.log('migrations و seed.js در release کپی شدند.');
}
main().catch((err) => {
+3
View File
@@ -2,9 +2,12 @@ import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
import { prepareDatabase } from './prisma/bootstrap';
import { PrismaExceptionFilter } from './prisma/prisma-exception.filter';
async function bootstrap() {
await prepareDatabase();
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
+59
View File
@@ -0,0 +1,59 @@
import { execFile } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
import { promisify } from 'util';
import { Logger } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { runMigrations, runOnce } from './migrate';
const execFileAsync = promisify(execFile);
function resolveSeedScript(): string | undefined {
const candidates = [
process.env.SEED_SCRIPT,
join(process.cwd(), 'seed.js'),
join(__dirname, '..', '..', 'seed.js'),
].filter((p): p is string => Boolean(p));
return candidates.find((file) => existsSync(file));
}
/**
* Runs pending SQL migrations before Nest boots, then seeds once.
*
* Seeding is gated on a marker row written only after the seed succeeds, so an
* interrupted seed is retried on the next boot (the seed itself upserts).
* Set RUN_MIGRATIONS=false or RUN_SEED=false to skip.
*/
export async function prepareDatabase(): Promise<void> {
if (process.env.RUN_MIGRATIONS === 'false') return;
const logger = new Logger('DatabaseBootstrap');
const prisma = new PrismaClient();
try {
const { applied } = await runMigrations(prisma);
if (applied.length > 0) {
logger.log(`Applied migrations: ${applied.join(', ')}`);
} else {
logger.log('No pending migrations');
}
if (process.env.RUN_SEED === 'false') return;
const seedScript = resolveSeedScript();
if (!seedScript) {
logger.warn('No seed script found — skipping seed');
return;
}
const seeded = await runOnce(prisma, 'seed:initial', async () => {
const { stdout } = await execFileAsync(process.execPath, [seedScript]);
if (stdout.trim()) logger.log(stdout.trim());
});
if (seeded) logger.log('Seed data inserted');
} finally {
await prisma.$disconnect();
}
}
+178
View File
@@ -0,0 +1,178 @@
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join } from 'path';
import { PrismaClient } from '@prisma/client';
const TRACKING_TABLE = '_goldbank_migrations';
/**
* Applies the .sql files in prisma/migrations (copied to ./migrations in the
* Docker release) and records them so each one runs once.
*
* The deploy image is copy-only and has no Prisma CLI, so `migrate deploy` is
* not an option — this runs the same SQL through the client instead.
*/
export type MigrationResult = {
/** True when the tracking table was created by this run (empty database). */
freshDatabase: boolean;
applied: string[];
};
function resolveMigrationsDir(): string {
const candidates = [
process.env.MIGRATIONS_DIR,
join(process.cwd(), 'migrations'),
join(__dirname, '..', '..', 'migrations'),
join(__dirname, '..', '..', 'prisma', 'migrations'),
join(process.cwd(), 'prisma', 'migrations'),
].filter((p): p is string => Boolean(p));
const found = candidates.find((dir) => existsSync(dir));
if (!found) {
throw new Error(
`پوشه migrations پیدا نشد. مسیرهای بررسی‌شده: ${candidates.join(', ')}`,
);
}
return found;
}
/**
* Postgres rejects multiple statements in one parameterised query, so each file
* is split first. Quoting rules matter: identifiers/strings may contain ';'.
*/
export function splitSqlStatements(sql: string): string[] {
const statements: string[] = [];
let current = '';
let i = 0;
while (i < sql.length) {
const char = sql[i];
const rest = sql.slice(i);
if (rest.startsWith('--')) {
const lineEnd = sql.indexOf('\n', i);
i = lineEnd === -1 ? sql.length : lineEnd + 1;
continue;
}
if (rest.startsWith('/*')) {
const commentEnd = sql.indexOf('*/', i + 2);
i = commentEnd === -1 ? sql.length : commentEnd + 2;
continue;
}
if (char === "'" || char === '"') {
const quote = char;
current += char;
i += 1;
while (i < sql.length) {
current += sql[i];
if (sql[i] === quote) {
// Doubled quote is an escaped quote, not the end of the literal.
if (sql[i + 1] === quote) {
current += sql[i + 1];
i += 2;
continue;
}
i += 1;
break;
}
i += 1;
}
continue;
}
const dollarTag = /^\$[A-Za-z_]*\$/.exec(rest);
if (dollarTag) {
const tag = dollarTag[0];
const closing = sql.indexOf(tag, i + tag.length);
const end = closing === -1 ? sql.length : closing + tag.length;
current += sql.slice(i, end);
i = end;
continue;
}
if (char === ';') {
if (current.trim()) statements.push(current.trim());
current = '';
i += 1;
continue;
}
current += char;
i += 1;
}
if (current.trim()) statements.push(current.trim());
return statements;
}
export async function runMigrations(prisma: PrismaClient): Promise<MigrationResult> {
const dir = resolveMigrationsDir();
const files = readdirSync(dir)
.filter((name) => name.endsWith('.sql'))
.sort();
const trackingExisted = await prisma.$queryRawUnsafe<{ exists: boolean }[]>(
`SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name = $1
) AS "exists"`,
TRACKING_TABLE,
);
const freshDatabase = !trackingExisted[0]?.exists;
await prisma.$executeRawUnsafe(
`CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE}" (
name TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`,
);
const alreadyApplied = await prisma.$queryRawUnsafe<{ name: string }[]>(
`SELECT name FROM "${TRACKING_TABLE}"`,
);
const appliedNames = new Set(alreadyApplied.map((row) => row.name));
const applied: string[] = [];
for (const file of files) {
if (appliedNames.has(file)) continue;
const statements = splitSqlStatements(readFileSync(join(dir, file), 'utf8'));
await prisma.$transaction(
async (tx) => {
for (const statement of statements) {
await tx.$executeRawUnsafe(statement);
}
await tx.$executeRawUnsafe(
`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`,
file,
);
},
// The baseline migration is hundreds of DDL statements; the 5s default is
// not enough, especially against a remote database.
{ maxWait: 60_000, timeout: 600_000 },
);
applied.push(file);
}
return { freshDatabase, applied };
}
/** Records a one-off task (e.g. seeding) in the same table so it never repeats. */
export async function runOnce(
prisma: PrismaClient,
name: string,
task: () => Promise<void>,
): Promise<boolean> {
const rows = await prisma.$queryRawUnsafe<{ name: string }[]>(
`SELECT name FROM "${TRACKING_TABLE}" WHERE name = $1`,
name,
);
if (rows.length > 0) return false;
await task();
await prisma.$executeRawUnsafe(
`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`,
name,
);
return true;
}