Merge branch 'dev'
# Conflicts: # apps/admin-panel/dist/index.html # apps/admin-panel/nginx.conf # apps/admin-panel/vite.config.ts # apps/api/release/app.js
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// Generates distinct SVG placeholder images per product category, so the 200
|
||||
// mock products render varied artwork instead of one shared no-items.png icon.
|
||||
// Not photography — an honest stand-in, styled to the app's own gold/cream
|
||||
// palette, with a distinct glyph + background tint per category so the grid
|
||||
// reads as varied rather than one repeated tile.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const OUT_DIR = path.join(__dirname, '../../user-app/public/img/products');
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
// category slug -> [glyph path (viewBox 0 0 64 64), accent color]
|
||||
const GLYPHS = {
|
||||
rings: {
|
||||
accent: '#B08D57',
|
||||
path: '<circle cx="32" cy="40" r="14" fill="none" stroke="currentColor" stroke-width="4"/><circle cx="32" cy="20" r="6" fill="currentColor"/>',
|
||||
},
|
||||
bracelets: {
|
||||
accent: '#8D6A46',
|
||||
path: '<ellipse cx="32" cy="32" rx="22" ry="14" fill="none" stroke="currentColor" stroke-width="4"/><ellipse cx="32" cy="32" rx="14" ry="8" fill="none" stroke="currentColor" stroke-width="2" opacity="0.5"/>',
|
||||
},
|
||||
necklaces: {
|
||||
accent: '#C9A24B',
|
||||
path: '<path d="M14 14 C14 34 24 44 32 44 C40 44 50 34 50 14" fill="none" stroke="currentColor" stroke-width="3"/><path d="M32 44 L32 52 L26 58 L38 58 Z" fill="currentColor"/>',
|
||||
},
|
||||
earrings: {
|
||||
accent: '#A67C52',
|
||||
path: '<circle cx="32" cy="16" r="5" fill="currentColor"/><path d="M32 21 L32 34" stroke="currentColor" stroke-width="3"/><circle cx="32" cy="42" r="10" fill="none" stroke="currentColor" stroke-width="3"/>',
|
||||
},
|
||||
coins: {
|
||||
accent: '#D4AF37',
|
||||
path: '<circle cx="32" cy="32" r="22" fill="none" stroke="currentColor" stroke-width="4"/><circle cx="32" cy="32" r="14" fill="none" stroke="currentColor" stroke-width="2" opacity="0.6"/><text x="32" y="38" font-size="14" text-anchor="middle" fill="currentColor" font-family="sans-serif">Au</text>',
|
||||
},
|
||||
};
|
||||
|
||||
// A handful of warm background tints so 200 tiles don't look identical even
|
||||
// within one category.
|
||||
const BG_TINTS = ['#F6F3EF', '#F3EADC', '#FAF5EC', '#F0E6D6', '#F8F1E6', '#EFE4D3'];
|
||||
|
||||
function svgFor(categorySlug, variantIndex) {
|
||||
const g = GLYPHS[categorySlug] || GLYPHS.rings;
|
||||
const bg = BG_TINTS[variantIndex % BG_TINTS.length];
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="480" height="480">
|
||||
<rect width="64" height="64" fill="${bg}"/>
|
||||
<g transform="translate(0,0)" color="${g.accent}" opacity="0.92">
|
||||
${g.path}
|
||||
</g>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
let written = 0;
|
||||
for (const slug of Object.keys(GLYPHS)) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const file = path.join(OUT_DIR, `${slug}-${i}.svg`);
|
||||
fs.writeFileSync(file, svgFor(slug, i));
|
||||
written++;
|
||||
}
|
||||
}
|
||||
console.log(`wrote ${written} placeholder SVGs to ${OUT_DIR}`);
|
||||
@@ -0,0 +1,118 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const seedPath = path.join(__dirname, 'seed.ts');
|
||||
let content = fs.readFileSync(seedPath, 'utf-8');
|
||||
|
||||
const marker = ' imageCount += 1;\n }\n\n // \u2500\u2500 Featured collection';
|
||||
if (!content.includes(marker)) {
|
||||
console.error('MARKER NOT FOUND');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const insertion = ` imageCount += 1;
|
||||
}
|
||||
|
||||
// \u2500\u2500 200 mock products for a realistic-looking catalogue \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
||||
// These are generated, not hand-authored like the 5 above (whose ids are
|
||||
// referenced elsewhere in this seed, e.g. by orders \u2014 left untouched).
|
||||
// Images: real product photography does not exist in this project, so each
|
||||
// mock product cycles through 6 generated SVG placeholders per category
|
||||
// (apps/user-app/public/img/products/<slug>-N.svg), giving the catalogue
|
||||
// grid genuine visual variety instead of one repeated icon.
|
||||
const MOCK_CATEGORIES = [
|
||||
{ slug: 'rings', id: ringsCategory.id, nouns: ['انگشتر نگیندار', 'انگشتر سولیتر', 'انگشتر آویزدار', 'انگشتر پهن', 'انگشتر ظریف', 'انگشتر رینگی'] },
|
||||
{ slug: 'bracelets', id: braceletsCategory.id, nouns: ['دستبند زنجیری', 'دستبند پاندول', 'دستبند بافت', 'دستبند تنیس', 'دستبند پلاکدار', 'دستبند چرمی طلاکوب'] },
|
||||
{ slug: 'necklaces', id: necklacesCategory.id, nouns: ['گردنبند آویز قلب', 'گردنبند ماه و ستاره', 'گردنبند ظریف', 'گردنبند رولو', 'گردنبند صلیب', 'گردنبند اسم'] },
|
||||
{ slug: 'earrings', id: earringsCategory.id, nouns: ['گوشواره حلقهای', 'گوشواره آویز', 'گوشواره میخی', 'گوشواره پروانهای', 'گوشواره مرواریدی', 'گوشواره هندسی'] },
|
||||
{ slug: 'coins', id: coinsCategory.id, nouns: ['ربع سکه بهار آزادی', 'نیم سکه بهار آزادی', 'سکه امامی', 'سکه پارسیان', 'شمش طلای ۱۰ گرمی', 'شمش طلای ۵ گرمی'] },
|
||||
];
|
||||
const MOCK_ADJECTIVES = ['کلاسیک', 'مدرن', 'مینیمال', 'لوکس', 'دستساز', 'ظریف', 'خاص', 'شیک', 'سنتی', 'رزگلد'];
|
||||
const MOCK_BRANDS = [brandMontzar.id, brandArtisan.id];
|
||||
|
||||
const MOCK_PRODUCT_COUNT = 200;
|
||||
let mockImageCount = 0;
|
||||
for (let i = 0; i < MOCK_PRODUCT_COUNT; i++) {
|
||||
const cat = MOCK_CATEGORIES[i % MOCK_CATEGORIES.length];
|
||||
const noun = cat.nouns[i % cat.nouns.length];
|
||||
const adjective = MOCK_ADJECTIVES[i % MOCK_ADJECTIVES.length];
|
||||
const brandId = MOCK_BRANDS[i % MOCK_BRANDS.length];
|
||||
const productId = \`mock-product-\${i + 1}\`;
|
||||
const title = \`\${noun} \${adjective} \${i + 1}\`;
|
||||
// Deterministic but varied numeric fields \u2014 no randomness, so a re-seed
|
||||
// is stable and diffable.
|
||||
const baseWeight = 1.5 + ((i * 37) % 250) / 20; // ~1.5g .. ~14g
|
||||
const wagePercent = (6 + ((i * 13) % 12)).toFixed(1); // 6.0% .. 17.9%
|
||||
const qty = (i * 7) % 15; // 0..14, so some are genuinely out of stock
|
||||
const isPreOrder = i % 9 === 0;
|
||||
|
||||
const product = await prisma.product.upsert({
|
||||
where: { id: productId },
|
||||
update: {},
|
||||
create: {
|
||||
id: productId,
|
||||
title,
|
||||
description: \`\${noun} با طراحی \${adjective}، مناسب استفاده روزمره یا هدیه.\`,
|
||||
categoryId: cat.id,
|
||||
brandId,
|
||||
productCode: \`P-\${String(i + 1).padStart(4, '0')}\`,
|
||||
},
|
||||
});
|
||||
|
||||
const variantId = \`\${productId}-variant-1\`;
|
||||
await prisma.productVariant.upsert({
|
||||
where: { id: variantId },
|
||||
update: {},
|
||||
create: {
|
||||
id: variantId,
|
||||
productId: product.id,
|
||||
title: 'استاندارد',
|
||||
sku: \`MOCK-\${String(i + 1).padStart(4, '0')}\`,
|
||||
rashWeightGrams: baseWeight.toFixed(3),
|
||||
defaultWagePercent: wagePercent,
|
||||
qty,
|
||||
isPreOrder,
|
||||
},
|
||||
});
|
||||
|
||||
// A second variant on ~1 in 4 products, so the "\u062a\u0646\u0648\u0639 \u0648\u0632\u0646\u06cc" chip row has
|
||||
// something real to show on more than the 5 hand-authored products.
|
||||
if (i % 4 === 0) {
|
||||
const variant2Id = \`\${productId}-variant-2\`;
|
||||
await prisma.productVariant.upsert({
|
||||
where: { id: variant2Id },
|
||||
update: {},
|
||||
create: {
|
||||
id: variant2Id,
|
||||
productId: product.id,
|
||||
title: 'سایز بزرگ',
|
||||
sku: \`MOCK-\${String(i + 1).padStart(4, '0')}-L\`,
|
||||
rashWeightGrams: (baseWeight * 1.35).toFixed(3),
|
||||
defaultWagePercent: wagePercent,
|
||||
qty: (qty + 3) % 15,
|
||||
isPreOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const imgIndex = i % 6;
|
||||
const imageUrl = \`/img/products/\${cat.slug}-\${imgIndex}.svg\`;
|
||||
await prisma.productImage.upsert({
|
||||
where: { id: \`\${productId}-img-1\` },
|
||||
update: { url: imageUrl },
|
||||
create: {
|
||||
id: \`\${productId}-img-1\`,
|
||||
productId: product.id,
|
||||
url: imageUrl,
|
||||
sortOrder: 0,
|
||||
},
|
||||
});
|
||||
mockImageCount += 1;
|
||||
}
|
||||
imageCount += mockImageCount;
|
||||
|
||||
// \u2500\u2500 Featured collection`;
|
||||
|
||||
content = content.replace(marker, insertion);
|
||||
fs.writeFileSync(seedPath, content, 'utf-8');
|
||||
console.log('inserted mock product generator into seed.ts');
|
||||
@@ -1,431 +0,0 @@
|
||||
-- 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,241 @@
|
||||
-- Sync schema drift (admin/product/customer fields + new tables).
|
||||
-- NOTE: Do NOT drop "_goldbank_migrations" — that table is owned by the runtime
|
||||
-- migration runner, not by Prisma schema. Prisma migrate diff wrongly included it.
|
||||
-- Statements below are idempotent so a partially-applied previous attempt can finish.
|
||||
|
||||
-- CreateEnum
|
||||
DO $$ BEGIN CREATE TYPE "CustomerKind" AS ENUM ('SALES_AGENT', 'APP_SHOPKEEPER', 'WALK_IN_SHOPKEEPER', 'BANKER', 'WORKSHOP_SUPERVISOR'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "OrderChannel" AS ENUM ('RETAIL', 'WHOLESALE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "CommentStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "PromotionScope" AS ENUM ('PRODUCT', 'CATEGORY', 'CUSTOMER', 'BRAND'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "PromotionUsage" AS ENUM ('ONCE', 'UNTIL_END_DATE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "MessageAudience" AS ENUM ('SINGLE_NUMBER', 'MULTIPLE_NUMBERS', 'ALL_CUSTOMERS', 'WHOLESALE_CUSTOMERS', 'RETAIL_CUSTOMERS', 'SALES_AGENTS'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "AdminUser" ADD COLUMN IF NOT EXISTS "userTypeId" INTEGER;
|
||||
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "isActive" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "description" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "iconUrl" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "imageUrl" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "metaTag" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "type" INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
ALTER TABLE "City" ADD COLUMN IF NOT EXISTS "percent" DECIMAL(6,3);
|
||||
ALTER TABLE "City" ADD COLUMN IF NOT EXISTS "percentAux" DECIMAL(6,3);
|
||||
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "avatarFileId" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "canPlaceOrders" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "isArchived" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "kind" "CustomerKind" NOT NULL DEFAULT 'APP_SHOPKEEPER';
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "memberCode" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "pasazhName" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "storeName" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "zone" TEXT;
|
||||
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "accountName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "bankName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "hallmark" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "labName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "originalValue" DECIMAL(14,4);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "priceRial" DECIMAL(20,2);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "receivedAt" TIMESTAMP(3);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "transferTo" TEXT;
|
||||
|
||||
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "channel" "OrderChannel" NOT NULL DEFAULT 'RETAIL';
|
||||
|
||||
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "productCode" TEXT;
|
||||
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "showType" INTEGER NOT NULL DEFAULT 3;
|
||||
|
||||
ALTER TABLE "ProductVariant" ADD COLUMN IF NOT EXISTS "isPreOrder" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "ProductVariant" ADD COLUMN IF NOT EXISTS "qty" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "supervisorId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "user_type" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "user_type_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "permission" (
|
||||
"rid" TEXT NOT NULL,
|
||||
"userTypeId" INTEGER NOT NULL,
|
||||
CONSTRAINT "permission_pkey" PRIMARY KEY ("rid","userTypeId")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Comment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"productId" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"status" "CommentStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"replyToId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Setting" (
|
||||
"key" TEXT NOT NULL,
|
||||
"value" JSONB NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Setting_pkey" PRIMARY KEY ("key")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Promotion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"scope" "PromotionScope" NOT NULL DEFAULT 'PRODUCT',
|
||||
"usage" "PromotionUsage" NOT NULL DEFAULT 'ONCE',
|
||||
"discountPercent" DECIMAL(6,3) NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"platform" INTEGER NOT NULL DEFAULT 3,
|
||||
"startsAt" TIMESTAMP(3),
|
||||
"endsAt" TIMESTAMP(3),
|
||||
"productId" TEXT,
|
||||
"categoryId" TEXT,
|
||||
"customerId" TEXT,
|
||||
"brandId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Promotion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "PromotionExclusion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL,
|
||||
"itemId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "PromotionExclusion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "BranchAllocation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"branchId" TEXT NOT NULL,
|
||||
"allocatorAdminUserId" TEXT,
|
||||
"note" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "BranchAllocation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "BranchAllocationItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"allocationId" TEXT NOT NULL,
|
||||
"productId" TEXT NOT NULL,
|
||||
"weightGrams" DECIMAL(14,4),
|
||||
CONSTRAINT "BranchAllocationItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "SmsMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"audience" "MessageAudience" NOT NULL,
|
||||
"recipients" TEXT,
|
||||
"body" TEXT NOT NULL,
|
||||
"sentCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"sentByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SmsMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Notification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"audience" "MessageAudience" NOT NULL DEFAULT 'ALL_CUSTOMERS',
|
||||
"sentByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "NotificationRecipient" (
|
||||
"id" TEXT NOT NULL,
|
||||
"notificationId" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"readAt" TIMESTAMP(3),
|
||||
CONSTRAINT "NotificationRecipient_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "NewsletterSubscriber" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"mobile" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "NewsletterSubscriber_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "CustomerActivityLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"detail" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "CustomerActivityLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "UploadedFile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"purpose" TEXT,
|
||||
"uploadedByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "UploadedFile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "permission_userTypeId_idx" ON "permission"("userTypeId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_productId_idx" ON "Comment"("productId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_customerId_idx" ON "Comment"("customerId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_status_idx" ON "Comment"("status");
|
||||
CREATE INDEX IF NOT EXISTS "Promotion_scope_isActive_idx" ON "Promotion"("scope", "isActive");
|
||||
CREATE INDEX IF NOT EXISTS "PromotionExclusion_type_idx" ON "PromotionExclusion"("type");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "PromotionExclusion_type_itemId_key" ON "PromotionExclusion"("type", "itemId");
|
||||
CREATE INDEX IF NOT EXISTS "BranchAllocation_branchId_idx" ON "BranchAllocation"("branchId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "BranchAllocationItem_allocationId_productId_key" ON "BranchAllocationItem"("allocationId", "productId");
|
||||
CREATE INDEX IF NOT EXISTS "SmsMessage_createdAt_idx" ON "SmsMessage"("createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "Notification_createdAt_idx" ON "Notification"("createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "NotificationRecipient_customerId_readAt_idx" ON "NotificationRecipient"("customerId", "readAt");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NotificationRecipient_notificationId_customerId_key" ON "NotificationRecipient"("notificationId", "customerId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NewsletterSubscriber_email_key" ON "NewsletterSubscriber"("email");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NewsletterSubscriber_mobile_key" ON "NewsletterSubscriber"("mobile");
|
||||
CREATE INDEX IF NOT EXISTS "CustomerActivityLog_customerId_createdAt_idx" ON "CustomerActivityLog"("customerId", "createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "UploadedFile_purpose_idx" ON "UploadedFile"("purpose");
|
||||
CREATE INDEX IF NOT EXISTS "AdminUser_userTypeId_idx" ON "AdminUser"("userTypeId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Product_productCode_key" ON "Product"("productCode");
|
||||
|
||||
-- AddForeignKey
|
||||
DO $$ BEGIN ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_userTypeId_fkey" FOREIGN KEY ("userTypeId") REFERENCES "user_type"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Customer" ADD CONSTRAINT "Customer_avatarFileId_fkey" FOREIGN KEY ("avatarFileId") REFERENCES "UploadedFile"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Workshop" ADD CONSTRAINT "Workshop_supervisorId_fkey" FOREIGN KEY ("supervisorId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "permission" ADD CONSTRAINT "permission_userTypeId_fkey" FOREIGN KEY ("userTypeId") REFERENCES "user_type"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_replyToId_fkey" FOREIGN KEY ("replyToId") REFERENCES "Comment"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocation" ADD CONSTRAINT "BranchAllocation_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocation" ADD CONSTRAINT "BranchAllocation_allocatorAdminUserId_fkey" FOREIGN KEY ("allocatorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocationItem" ADD CONSTRAINT "BranchAllocationItem_allocationId_fkey" FOREIGN KEY ("allocationId") REFERENCES "BranchAllocation"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocationItem" ADD CONSTRAINT "BranchAllocationItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "SmsMessage" ADD CONSTRAINT "SmsMessage_sentByAdminUserId_fkey" FOREIGN KEY ("sentByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Notification" ADD CONSTRAINT "Notification_sentByAdminUserId_fkey" FOREIGN KEY ("sentByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "NotificationRecipient" ADD CONSTRAINT "NotificationRecipient_notificationId_fkey" FOREIGN KEY ("notificationId") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "NotificationRecipient" ADD CONSTRAINT "NotificationRecipient_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "CustomerActivityLog" ADD CONSTRAINT "CustomerActivityLog_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "UploadedFile" ADD CONSTRAINT "UploadedFile_uploadedByAdminUserId_fkey" FOREIGN KEY ("uploadedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
@@ -346,10 +346,10 @@ async function main() {
|
||||
prisma.category.upsert({ where: { id: 'cat-coins' }, update: {}, create: { id: 'cat-coins', title: 'سکه و شمش' } }),
|
||||
]);
|
||||
|
||||
const brandHenza = await prisma.brand.upsert({
|
||||
const brandMontzar = await prisma.brand.upsert({
|
||||
where: { id: 'brand-henza' },
|
||||
update: {},
|
||||
create: { id: 'brand-henza', title: 'هنزا طلا' },
|
||||
update: { title: 'منتزر طلا' },
|
||||
create: { id: 'brand-henza', title: 'منتزر طلا' },
|
||||
});
|
||||
const brandArtisan = await prisma.brand.upsert({
|
||||
where: { id: 'brand-artisan' },
|
||||
@@ -363,7 +363,7 @@ async function main() {
|
||||
title: 'انگشتر تک نگین کلاسیک',
|
||||
description: 'انگشتر طلای ۱۸ عیار با طراحی ساده و مینیمال.',
|
||||
categoryId: ringsCategory.id,
|
||||
brandId: brandHenza.id,
|
||||
brandId: brandMontzar.id,
|
||||
variants: [
|
||||
{ id: 'variant-ring-size-6', title: 'سایز ۶', sku: 'RING-CLASSIC-6', rashWeightGrams: '2.850', defaultWagePercent: '9.0' },
|
||||
{ id: 'variant-ring-size-7', title: 'سایز ۷', sku: 'RING-CLASSIC-7', rashWeightGrams: '3.100', defaultWagePercent: '9.0' },
|
||||
@@ -384,7 +384,7 @@ async function main() {
|
||||
title: 'گردنبند آویز اشکی',
|
||||
description: 'گردنبند ظریف با آویز اشکی و زنجیر نازک.',
|
||||
categoryId: necklacesCategory.id,
|
||||
brandId: brandHenza.id,
|
||||
brandId: brandMontzar.id,
|
||||
variants: [
|
||||
{ id: 'variant-necklace-40cm', title: 'زنجیر ۴۰ سانتی', sku: 'NCK-TEARDROP-40', rashWeightGrams: '5.600', defaultWagePercent: '10.0' },
|
||||
{ id: 'variant-necklace-45cm', title: 'زنجیر ۴۵ سانتی', sku: 'NCK-TEARDROP-45', rashWeightGrams: '6.100', defaultWagePercent: '10.0' },
|
||||
@@ -406,7 +406,7 @@ async function main() {
|
||||
title: 'سکه تمام بهار آزادی',
|
||||
description: 'سکه طلای تمام بهار آزادی، عیار استاندارد.',
|
||||
categoryId: coinsCategory.id,
|
||||
brandId: brandHenza.id,
|
||||
brandId: brandMontzar.id,
|
||||
variants: [
|
||||
{ id: 'variant-coin-full', title: 'تمام سکه', sku: 'COIN-FULL', rashWeightGrams: '8.133', defaultWagePercent: '2.0' },
|
||||
{ id: 'variant-coin-half', title: 'نیم سکه', sku: 'COIN-HALF', rashWeightGrams: '4.066', defaultWagePercent: '2.5' },
|
||||
|
||||
+40
-6
@@ -93025,9 +93025,31 @@ var require_migrate = __commonJS({
|
||||
statements.push(current.trim());
|
||||
return statements;
|
||||
}
|
||||
var LEGACY_NAME_ALIASES = {
|
||||
"20260910113720_first": "0001_init.sql"
|
||||
};
|
||||
function discoverMigrationFiles(dir) {
|
||||
const entries = (0, fs_1.readdirSync)(dir);
|
||||
const files = [];
|
||||
for (const entry of entries) {
|
||||
const full = (0, path_1.join)(dir, entry);
|
||||
const stat = (0, fs_1.statSync)(full);
|
||||
if (stat.isFile() && entry.endsWith(".sql")) {
|
||||
files.push({ name: entry, path: full });
|
||||
continue;
|
||||
}
|
||||
if (stat.isDirectory()) {
|
||||
const sqlPath = (0, path_1.join)(full, "migration.sql");
|
||||
if ((0, fs_1.existsSync)(sqlPath)) {
|
||||
files.push({ name: entry, path: sqlPath });
|
||||
}
|
||||
}
|
||||
}
|
||||
return files.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
async function runMigrations(prisma) {
|
||||
const dir = resolveMigrationsDir();
|
||||
const files = (0, fs_1.readdirSync)(dir).filter((name) => name.endsWith(".sql")).sort();
|
||||
const files = discoverMigrationFiles(dir);
|
||||
const trackingExisted = await prisma.$queryRawUnsafe(`SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = current_schema() AND table_name = $1
|
||||
@@ -93041,21 +93063,25 @@ var require_migrate = __commonJS({
|
||||
const appliedNames = new Set(alreadyApplied.map((row) => row.name));
|
||||
const applied = [];
|
||||
for (const file2 of files) {
|
||||
if (appliedNames.has(file2))
|
||||
const legacyName = LEGACY_NAME_ALIASES[file2.name];
|
||||
if (appliedNames.has(file2.name) || legacyName && appliedNames.has(legacyName))
|
||||
continue;
|
||||
const statements = splitSqlStatements((0, fs_1.readFileSync)((0, path_1.join)(dir, file2), "utf8"));
|
||||
const statements = splitSqlStatements((0, fs_1.readFileSync)(file2.path, "utf8")).filter((statement) => {
|
||||
const normalized = statement.replace(/\s+/g, " ").trim().toLowerCase();
|
||||
return !normalized.startsWith(`drop table "${TRACKING_TABLE.toLowerCase()}"`) && !normalized.startsWith(`drop table ${TRACKING_TABLE.toLowerCase()}`);
|
||||
});
|
||||
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);
|
||||
await tx.$executeRawUnsafe(`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`, file2.name);
|
||||
},
|
||||
// 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);
|
||||
applied.push(file2.name);
|
||||
}
|
||||
return { freshDatabase, applied };
|
||||
}
|
||||
@@ -93202,6 +93228,9 @@ var node_path_1 = require("node:path");
|
||||
var app_module_1 = require_app_module();
|
||||
var bootstrap_1 = require_bootstrap();
|
||||
var prisma_exception_filter_1 = require_prisma_exception_filter();
|
||||
var EXTRA_MIME_TYPES = {
|
||||
".avif": "image/avif"
|
||||
};
|
||||
async function bootstrap() {
|
||||
await (0, bootstrap_1.prepareDatabase)();
|
||||
const app = await core_1.NestFactory.create(app_module_1.AppModule);
|
||||
@@ -93212,7 +93241,12 @@ async function bootstrap() {
|
||||
app.useStaticAssets(process.env.UPLOAD_DIR ?? (0, node_path_1.join)(process.cwd(), "storage", "uploads"), {
|
||||
prefix: "/uploads/",
|
||||
index: false,
|
||||
dotfiles: "deny"
|
||||
dotfiles: "deny",
|
||||
setHeaders: (res, path) => {
|
||||
const mime = EXTRA_MIME_TYPES[(0, node_path_1.extname)(path).toLowerCase()];
|
||||
if (mime)
|
||||
res.setHeader("Content-Type", mime);
|
||||
}
|
||||
});
|
||||
await app.listen(process.env.PORT ?? 3e3, "0.0.0.0");
|
||||
}
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
-- 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,241 @@
|
||||
-- Sync schema drift (admin/product/customer fields + new tables).
|
||||
-- NOTE: Do NOT drop "_goldbank_migrations" — that table is owned by the runtime
|
||||
-- migration runner, not by Prisma schema. Prisma migrate diff wrongly included it.
|
||||
-- Statements below are idempotent so a partially-applied previous attempt can finish.
|
||||
|
||||
-- CreateEnum
|
||||
DO $$ BEGIN CREATE TYPE "CustomerKind" AS ENUM ('SALES_AGENT', 'APP_SHOPKEEPER', 'WALK_IN_SHOPKEEPER', 'BANKER', 'WORKSHOP_SUPERVISOR'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "OrderChannel" AS ENUM ('RETAIL', 'WHOLESALE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "CommentStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "PromotionScope" AS ENUM ('PRODUCT', 'CATEGORY', 'CUSTOMER', 'BRAND'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "PromotionUsage" AS ENUM ('ONCE', 'UNTIL_END_DATE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE TYPE "MessageAudience" AS ENUM ('SINGLE_NUMBER', 'MULTIPLE_NUMBERS', 'ALL_CUSTOMERS', 'WHOLESALE_CUSTOMERS', 'RETAIL_CUSTOMERS', 'SALES_AGENTS'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "AdminUser" ADD COLUMN IF NOT EXISTS "userTypeId" INTEGER;
|
||||
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "isActive" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "description" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "iconUrl" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "imageUrl" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "metaTag" TEXT;
|
||||
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "type" INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
ALTER TABLE "City" ADD COLUMN IF NOT EXISTS "percent" DECIMAL(6,3);
|
||||
ALTER TABLE "City" ADD COLUMN IF NOT EXISTS "percentAux" DECIMAL(6,3);
|
||||
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "avatarFileId" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "canPlaceOrders" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "isArchived" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "kind" "CustomerKind" NOT NULL DEFAULT 'APP_SHOPKEEPER';
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "memberCode" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "pasazhName" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "storeName" TEXT;
|
||||
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "zone" TEXT;
|
||||
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "accountName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "bankName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "hallmark" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "labName" TEXT;
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "originalValue" DECIMAL(14,4);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "priceRial" DECIMAL(20,2);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "receivedAt" TIMESTAMP(3);
|
||||
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "transferTo" TEXT;
|
||||
|
||||
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "channel" "OrderChannel" NOT NULL DEFAULT 'RETAIL';
|
||||
|
||||
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "productCode" TEXT;
|
||||
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "showType" INTEGER NOT NULL DEFAULT 3;
|
||||
|
||||
ALTER TABLE "ProductVariant" ADD COLUMN IF NOT EXISTS "isPreOrder" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "ProductVariant" ADD COLUMN IF NOT EXISTS "qty" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "address" TEXT;
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "phone" TEXT;
|
||||
ALTER TABLE "Workshop" ADD COLUMN IF NOT EXISTS "supervisorId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "user_type" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "user_type_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "permission" (
|
||||
"rid" TEXT NOT NULL,
|
||||
"userTypeId" INTEGER NOT NULL,
|
||||
CONSTRAINT "permission_pkey" PRIMARY KEY ("rid","userTypeId")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Comment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"productId" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"status" "CommentStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"replyToId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Setting" (
|
||||
"key" TEXT NOT NULL,
|
||||
"value" JSONB NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Setting_pkey" PRIMARY KEY ("key")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Promotion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"scope" "PromotionScope" NOT NULL DEFAULT 'PRODUCT',
|
||||
"usage" "PromotionUsage" NOT NULL DEFAULT 'ONCE',
|
||||
"discountPercent" DECIMAL(6,3) NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"platform" INTEGER NOT NULL DEFAULT 3,
|
||||
"startsAt" TIMESTAMP(3),
|
||||
"endsAt" TIMESTAMP(3),
|
||||
"productId" TEXT,
|
||||
"categoryId" TEXT,
|
||||
"customerId" TEXT,
|
||||
"brandId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "Promotion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "PromotionExclusion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL,
|
||||
"itemId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "PromotionExclusion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "BranchAllocation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"branchId" TEXT NOT NULL,
|
||||
"allocatorAdminUserId" TEXT,
|
||||
"note" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "BranchAllocation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "BranchAllocationItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"allocationId" TEXT NOT NULL,
|
||||
"productId" TEXT NOT NULL,
|
||||
"weightGrams" DECIMAL(14,4),
|
||||
CONSTRAINT "BranchAllocationItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "SmsMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"audience" "MessageAudience" NOT NULL,
|
||||
"recipients" TEXT,
|
||||
"body" TEXT NOT NULL,
|
||||
"sentCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"sentByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SmsMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Notification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"audience" "MessageAudience" NOT NULL DEFAULT 'ALL_CUSTOMERS',
|
||||
"sentByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "NotificationRecipient" (
|
||||
"id" TEXT NOT NULL,
|
||||
"notificationId" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"readAt" TIMESTAMP(3),
|
||||
CONSTRAINT "NotificationRecipient_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "NewsletterSubscriber" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"mobile" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "NewsletterSubscriber_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "CustomerActivityLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"detail" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "CustomerActivityLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "UploadedFile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"purpose" TEXT,
|
||||
"uploadedByAdminUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "UploadedFile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "permission_userTypeId_idx" ON "permission"("userTypeId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_productId_idx" ON "Comment"("productId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_customerId_idx" ON "Comment"("customerId");
|
||||
CREATE INDEX IF NOT EXISTS "Comment_status_idx" ON "Comment"("status");
|
||||
CREATE INDEX IF NOT EXISTS "Promotion_scope_isActive_idx" ON "Promotion"("scope", "isActive");
|
||||
CREATE INDEX IF NOT EXISTS "PromotionExclusion_type_idx" ON "PromotionExclusion"("type");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "PromotionExclusion_type_itemId_key" ON "PromotionExclusion"("type", "itemId");
|
||||
CREATE INDEX IF NOT EXISTS "BranchAllocation_branchId_idx" ON "BranchAllocation"("branchId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "BranchAllocationItem_allocationId_productId_key" ON "BranchAllocationItem"("allocationId", "productId");
|
||||
CREATE INDEX IF NOT EXISTS "SmsMessage_createdAt_idx" ON "SmsMessage"("createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "Notification_createdAt_idx" ON "Notification"("createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "NotificationRecipient_customerId_readAt_idx" ON "NotificationRecipient"("customerId", "readAt");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NotificationRecipient_notificationId_customerId_key" ON "NotificationRecipient"("notificationId", "customerId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NewsletterSubscriber_email_key" ON "NewsletterSubscriber"("email");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "NewsletterSubscriber_mobile_key" ON "NewsletterSubscriber"("mobile");
|
||||
CREATE INDEX IF NOT EXISTS "CustomerActivityLog_customerId_createdAt_idx" ON "CustomerActivityLog"("customerId", "createdAt");
|
||||
CREATE INDEX IF NOT EXISTS "UploadedFile_purpose_idx" ON "UploadedFile"("purpose");
|
||||
CREATE INDEX IF NOT EXISTS "AdminUser_userTypeId_idx" ON "AdminUser"("userTypeId");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Product_productCode_key" ON "Product"("productCode");
|
||||
|
||||
-- AddForeignKey
|
||||
DO $$ BEGIN ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_userTypeId_fkey" FOREIGN KEY ("userTypeId") REFERENCES "user_type"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Customer" ADD CONSTRAINT "Customer_avatarFileId_fkey" FOREIGN KEY ("avatarFileId") REFERENCES "UploadedFile"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Workshop" ADD CONSTRAINT "Workshop_supervisorId_fkey" FOREIGN KEY ("supervisorId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "permission" ADD CONSTRAINT "permission_userTypeId_fkey" FOREIGN KEY ("userTypeId") REFERENCES "user_type"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Comment" ADD CONSTRAINT "Comment_replyToId_fkey" FOREIGN KEY ("replyToId") REFERENCES "Comment"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Promotion" ADD CONSTRAINT "Promotion_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocation" ADD CONSTRAINT "BranchAllocation_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocation" ADD CONSTRAINT "BranchAllocation_allocatorAdminUserId_fkey" FOREIGN KEY ("allocatorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocationItem" ADD CONSTRAINT "BranchAllocationItem_allocationId_fkey" FOREIGN KEY ("allocationId") REFERENCES "BranchAllocation"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "BranchAllocationItem" ADD CONSTRAINT "BranchAllocationItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "SmsMessage" ADD CONSTRAINT "SmsMessage_sentByAdminUserId_fkey" FOREIGN KEY ("sentByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "Notification" ADD CONSTRAINT "Notification_sentByAdminUserId_fkey" FOREIGN KEY ("sentByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "NotificationRecipient" ADD CONSTRAINT "NotificationRecipient_notificationId_fkey" FOREIGN KEY ("notificationId") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "NotificationRecipient" ADD CONSTRAINT "NotificationRecipient_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "CustomerActivityLog" ADD CONSTRAINT "CustomerActivityLog_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN ALTER TABLE "UploadedFile" ADD CONSTRAINT "UploadedFile_uploadedByAdminUserId_fkey" FOREIGN KEY ("uploadedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
@@ -2062,10 +2062,10 @@ async function main() {
|
||||
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({
|
||||
const brandMontzar = await prisma.brand.upsert({
|
||||
where: { id: "brand-henza" },
|
||||
update: {},
|
||||
create: { id: "brand-henza", title: "\u0647\u0646\u0632\u0627 \u0637\u0644\u0627" }
|
||||
update: { title: "\u0645\u0646\u062A\u0632\u0631 \u0637\u0644\u0627" },
|
||||
create: { id: "brand-henza", title: "\u0645\u0646\u062A\u0632\u0631 \u0637\u0644\u0627" }
|
||||
});
|
||||
const brandArtisan = await prisma.brand.upsert({
|
||||
where: { id: "brand-artisan" },
|
||||
@@ -2078,7 +2078,7 @@ async function main() {
|
||||
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,
|
||||
brandId: brandMontzar.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" }
|
||||
@@ -2099,7 +2099,7 @@ async function main() {
|
||||
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,
|
||||
brandId: brandMontzar.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" }
|
||||
@@ -2121,7 +2121,7 @@ async function main() {
|
||||
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,
|
||||
brandId: brandMontzar.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" }
|
||||
|
||||
+13
-1
@@ -2,11 +2,19 @@ import 'reflect-metadata';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'node:path';
|
||||
import { extname, join } from 'node:path';
|
||||
import type { Response } from 'express';
|
||||
import { AppModule } from './app.module';
|
||||
import { prepareDatabase } from './prisma/bootstrap';
|
||||
import { PrismaExceptionFilter } from './prisma/prisma-exception.filter';
|
||||
|
||||
// serve-static's bundled `mime` version predates .avif, so it falls back to
|
||||
// application/octet-stream — browsers still render the image (they sniff
|
||||
// content), but a strict client or an <img> under some CSPs would not.
|
||||
const EXTRA_MIME_TYPES: Record<string, string> = {
|
||||
'.avif': 'image/avif',
|
||||
};
|
||||
|
||||
async function bootstrap() {
|
||||
await prepareDatabase();
|
||||
|
||||
@@ -21,6 +29,10 @@ async function bootstrap() {
|
||||
prefix: '/uploads/',
|
||||
index: false,
|
||||
dotfiles: 'deny',
|
||||
setHeaders: (res: Response, path: string) => {
|
||||
const mime = EXTRA_MIME_TYPES[extname(path).toLowerCase()];
|
||||
if (mime) res.setHeader('Content-Type', mime);
|
||||
},
|
||||
});
|
||||
// Bind all interfaces explicitly so the container is reachable from nginx.
|
||||
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, readdirSync, readFileSync } from 'fs';
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@@ -105,11 +105,59 @@ export function splitSqlStatements(sql: string): string[] {
|
||||
return statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug fixed here (was silently a no-op): this used to only look for flat
|
||||
* `<name>.sql` files directly under prisma/migrations. Prisma's own CLI
|
||||
* (`prisma migrate dev`) creates a FOLDER per migration instead
|
||||
* (`<timestamp>_<name>/migration.sql`), which `readdirSync(...).filter(name
|
||||
* => name.endsWith('.sql'))` never matches — a directory name doesn't end in
|
||||
* ".sql". Every migration created the normal way was silently skipped; only
|
||||
* a hand-added flat duplicate (prisma/migrations/0001_init.sql, now removed)
|
||||
* ever ran. Both layouts are now discovered, sorted together by name so
|
||||
* later migrations still apply after earlier ones regardless of shape.
|
||||
*/
|
||||
type MigrationFile = { name: string; path: string };
|
||||
|
||||
/**
|
||||
* Databases migrated before the folder-discovery fix recorded the baseline
|
||||
* under the name of the hand-added flat duplicate that used to sit alongside
|
||||
* it (prisma/migrations/0001_init.sql, identical SQL bar a trailing newline).
|
||||
* Without this, upgrading replays the baseline against a database that already
|
||||
* has it and dies on `type "AdminRole" already exists`.
|
||||
*/
|
||||
const LEGACY_NAME_ALIASES: Record<string, string> = {
|
||||
'20260910113720_first': '0001_init.sql',
|
||||
};
|
||||
|
||||
function discoverMigrationFiles(dir: string): MigrationFile[] {
|
||||
const entries = readdirSync(dir);
|
||||
const files: MigrationFile[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const full = join(dir, entry);
|
||||
const stat = statSync(full);
|
||||
|
||||
if (stat.isFile() && entry.endsWith('.sql')) {
|
||||
files.push({ name: entry, path: full });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const sqlPath = join(full, 'migration.sql');
|
||||
if (existsSync(sqlPath)) {
|
||||
// Tracked by the folder name (e.g. "20260910113720_first"), matching
|
||||
// how Prisma's own _prisma_migrations table identifies a migration.
|
||||
files.push({ name: entry, path: sqlPath });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
export async function runMigrations(prisma: PrismaClient): Promise<MigrationResult> {
|
||||
const dir = resolveMigrationsDir();
|
||||
const files = readdirSync(dir)
|
||||
.filter((name) => name.endsWith('.sql'))
|
||||
.sort();
|
||||
const files = discoverMigrationFiles(dir);
|
||||
|
||||
const trackingExisted = await prisma.$queryRawUnsafe<{ exists: boolean }[]>(
|
||||
`SELECT EXISTS (
|
||||
@@ -134,9 +182,19 @@ export async function runMigrations(prisma: PrismaClient): Promise<MigrationResu
|
||||
|
||||
const applied: string[] = [];
|
||||
for (const file of files) {
|
||||
if (appliedNames.has(file)) continue;
|
||||
const legacyName = LEGACY_NAME_ALIASES[file.name];
|
||||
if (appliedNames.has(file.name) || (legacyName && appliedNames.has(legacyName))) continue;
|
||||
|
||||
const statements = splitSqlStatements(readFileSync(join(dir, file), 'utf8'));
|
||||
const statements = splitSqlStatements(readFileSync(file.path, 'utf8')).filter(
|
||||
(statement) => {
|
||||
// Prisma migrate diff once emitted DROP TABLE "_goldbank_migrations"
|
||||
// because that tracking table exists in the live DB but not in schema.
|
||||
// Never allow a migration to destroy our own bookkeeping.
|
||||
const normalized = statement.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
return !normalized.startsWith(`drop table "${TRACKING_TABLE.toLowerCase()}"`)
|
||||
&& !normalized.startsWith(`drop table ${TRACKING_TABLE.toLowerCase()}`);
|
||||
},
|
||||
);
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
for (const statement of statements) {
|
||||
@@ -144,14 +202,14 @@ export async function runMigrations(prisma: PrismaClient): Promise<MigrationResu
|
||||
}
|
||||
await tx.$executeRawUnsafe(
|
||||
`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`,
|
||||
file,
|
||||
file.name,
|
||||
);
|
||||
},
|
||||
// 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);
|
||||
applied.push(file.name);
|
||||
}
|
||||
|
||||
return { freshDatabase, applied };
|
||||
|
||||
Reference in New Issue
Block a user