Initial commit: Hedena Gold monorepo.

First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
This commit is contained in:
s.esmaeili
2026-10-15 17:19:10 +03:30
commit ea86636027
1121 changed files with 401065 additions and 0 deletions
+164433
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,396 @@
-- Additive migration: 0001_init created the original gold-bank tables.
-- The admin-panel port later added columns/enums/tables to schema.prisma
-- without a SQL migration, which produced P2022 on production
-- ("The column Product.productCode does not exist").
--
-- Statements are idempotent so a database that was already brought up to date
-- with `prisma db push` still records this file as applied.
-- ── Enums ────────────────────────────────────────────────────────────────
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 $$;
-- ── Columns on existing tables ───────────────────────────────────────────
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 "AdminUser" ADD COLUMN IF NOT EXISTS "userTypeId" INTEGER;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "kind" "CustomerKind" NOT NULL DEFAULT 'APP_SHOPKEEPER';
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "phone" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "address" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "storeName" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "pasazhName" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "zone" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "memberCode" 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 "Branch" ADD COLUMN IF NOT EXISTS "address" TEXT;
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "phone" TEXT;
ALTER TABLE "Branch" ADD COLUMN IF NOT EXISTS "isActive" BOOLEAN NOT NULL DEFAULT true;
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;
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "type" INTEGER NOT NULL DEFAULT 1;
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "imageUrl" TEXT;
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "iconUrl" TEXT;
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "metaTag" TEXT;
ALTER TABLE "Category" ADD COLUMN IF NOT EXISTS "description" TEXT;
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 "qty" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "ProductVariant" ADD COLUMN IF NOT EXISTS "isPreOrder" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "channel" "OrderChannel" NOT NULL DEFAULT 'RETAIL';
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "labName" TEXT;
ALTER TABLE "LedgerEntry" ADD COLUMN IF NOT EXISTS "hallmark" 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 "transferTo" 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);
-- ── New tables ───────────────────────────────────────────────────────────
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")
);
-- ── Indexes ──────────────────────────────────────────────────────────────
CREATE INDEX IF NOT EXISTS "AdminUser_userTypeId_idx" ON "AdminUser"("userTypeId");
CREATE UNIQUE INDEX IF NOT EXISTS "Product_productCode_key" ON "Product"("productCode");
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");
-- ── Foreign keys added after 0001_init ───────────────────────────────────
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 $$;
@@ -0,0 +1,430 @@
-- CreateEnum
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'SYSTEM_APPROVER', 'SALES_EXPERT', 'WORKSHOP_SUPERVISOR', 'ACCOUNTANT');
-- CreateEnum
CREATE TYPE "VerificationStep" AS ENUM ('UNVERIFIED', 'STEP_ONE_SUBMITTED', 'STEP_TWO_SUBMITTED', 'VERIFIED');
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('SUSPENDED', 'PLACED', 'ADMIN_APPROVED', 'SOURCING', 'PREPARING', 'DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PreOrderStatus" AS ENUM ('PLACED', 'ADMIN_APPROVED', 'WORKSHOP_APPROVED', 'READY_TO_SHIP', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "LedgerCurrency" AS ENUM ('GOLD_GRAM', 'RIAL');
-- CreateEnum
CREATE TYPE "LedgerDirection" AS ENUM ('DEBIT', 'CREDIT');
-- CreateEnum
CREATE TYPE "LedgerSourceType" AS ENUM ('ORDER_STOCK', 'ORDER_PRE_ORDER', 'REPAYMENT_MELTED_GOLD', 'REPAYMENT_TRANSFER', 'REPAYMENT_CHEQUE', 'REPAYMENT_CASH', 'WALLET_BONUS', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "City" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"province" TEXT,
CONSTRAINT "City_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdminUser" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"branchId" TEXT,
"wagePercent" DECIMAL(6,3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AdminUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"nationalCode" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"cityId" TEXT,
"referredByAgentId" TEXT,
"goldCreditLimitGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"goldDebtGrams" DECIMAL(14,4) NOT NULL DEFAULT 0,
"fiatDebtRial" DECIMAL(20,2) NOT NULL DEFAULT 0,
"verificationStep" "VerificationStep" NOT NULL DEFAULT 'UNVERIFIED',
"nationalIdImageUrl" TEXT,
"businessLicenseUrl" TEXT,
"shopImageUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CustomerAddress" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"addressLine" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Branch" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"cityId" TEXT,
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Workshop" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Workshop_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"parentId" TEXT,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Brand" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Collection" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Collection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CollectionProduct" (
"collectionId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
CONSTRAINT "CollectionProduct_pkey" PRIMARY KEY ("collectionId","productId")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"brandId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductImage" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"url" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ProductImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductVariant" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"sku" TEXT,
"rashWeightGrams" DECIMAL(14,4) NOT NULL,
"defaultWagePercent" DECIMAL(6,3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"branchId" TEXT,
"status" "OrderStatus" NOT NULL DEFAULT 'PLACED',
"totalGoldWeightGrams" DECIMAL(14,4) NOT NULL,
"wageWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
"isSuspendedOverCreditLimit" BOOLEAN NOT NULL DEFAULT false,
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"productVariantId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"rashWeightGramsSnapshot" DECIMAL(14,4) NOT NULL,
"wagePercentSnapshot" DECIMAL(6,3) NOT NULL,
"discountPercentSnapshot" DECIMAL(6,3) NOT NULL DEFAULT 0,
"computedWeightGrams" DECIMAL(14,4) NOT NULL,
"flatRialCharge" DECIMAL(20,2) NOT NULL DEFAULT 0,
CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrderStatusEvent" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"fromStatus" "OrderStatus",
"toStatus" "OrderStatus" NOT NULL,
"note" TEXT,
"actorAdminUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "OrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrder" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"cityId" TEXT,
"workshopId" TEXT,
"status" "PreOrderStatus" NOT NULL DEFAULT 'PLACED',
"designImageUrl" TEXT,
"colorName" TEXT,
"sizeSpec" TEXT,
"stoneColor" TEXT,
"minaColor" TEXT,
"estimatedWeightGrams" DECIMAL(14,4),
"estimatedPriceRial" DECIMAL(20,2),
"description" TEXT,
"convertedOrderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreOrderStatusEvent" (
"id" TEXT NOT NULL,
"preOrderId" TEXT NOT NULL,
"changes" JSONB NOT NULL,
"actorAdminUserId" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PreOrderStatusEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"currency" "LedgerCurrency" NOT NULL,
"direction" "LedgerDirection" NOT NULL,
"sourceType" "LedgerSourceType" NOT NULL,
"amount" DECIMAL(20,4) NOT NULL,
"karatPerMille" INTEGER,
"pricePerGramRialAtEntry" DECIMAL(20,2),
"orderId" TEXT,
"confirmed" BOOLEAN NOT NULL DEFAULT false,
"confirmedAt" TIMESTAMP(3),
"postedByAdminUserId" TEXT,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GoldPriceQuote" (
"id" TEXT NOT NULL,
"karatPerMille" INTEGER NOT NULL,
"pricePerGramRial" DECIMAL(20,2) NOT NULL,
"source" TEXT NOT NULL,
"quotedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GoldPriceQuote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletBonus" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"goldGrams" DECIMAL(14,4) NOT NULL,
"grantedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "WalletBonus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AdminUser_mobile_key" ON "AdminUser"("mobile");
-- CreateIndex
CREATE INDEX "AdminUser_role_idx" ON "AdminUser"("role");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_mobile_key" ON "Customer"("mobile");
-- CreateIndex
CREATE UNIQUE INDEX "Customer_nationalCode_key" ON "Customer"("nationalCode");
-- CreateIndex
CREATE INDEX "Customer_referredByAgentId_idx" ON "Customer"("referredByAgentId");
-- CreateIndex
CREATE UNIQUE INDEX "ProductVariant_sku_key" ON "ProductVariant"("sku");
-- CreateIndex
CREATE UNIQUE INDEX "Order_orderNumber_key" ON "Order"("orderNumber");
-- CreateIndex
CREATE INDEX "Order_customerId_idx" ON "Order"("customerId");
-- CreateIndex
CREATE INDEX "Order_status_idx" ON "Order"("status");
-- CreateIndex
CREATE INDEX "OrderStatusEvent_orderId_idx" ON "OrderStatusEvent"("orderId");
-- CreateIndex
CREATE UNIQUE INDEX "PreOrder_convertedOrderId_key" ON "PreOrder"("convertedOrderId");
-- CreateIndex
CREATE INDEX "PreOrder_customerId_idx" ON "PreOrder"("customerId");
-- CreateIndex
CREATE INDEX "PreOrderStatusEvent_preOrderId_idx" ON "PreOrderStatusEvent"("preOrderId");
-- CreateIndex
CREATE INDEX "LedgerEntry_customerId_currency_idx" ON "LedgerEntry"("customerId", "currency");
-- CreateIndex
CREATE INDEX "LedgerEntry_orderId_idx" ON "LedgerEntry"("orderId");
-- CreateIndex
CREATE INDEX "GoldPriceQuote_karatPerMille_quotedAt_idx" ON "GoldPriceQuote"("karatPerMille", "quotedAt");
-- CreateIndex
CREATE INDEX "WalletBonus_customerId_isActive_idx" ON "WalletBonus"("customerId", "isActive");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_referredByAgentId_fkey" FOREIGN KEY ("referredByAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CollectionProduct" ADD CONSTRAINT "CollectionProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductImage" ADD CONSTRAINT "ProductImage_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productVariantId_fkey" FOREIGN KEY ("productVariantId") REFERENCES "ProductVariant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "Workshop"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrder" ADD CONSTRAINT "PreOrder_convertedOrderId_fkey" FOREIGN KEY ("convertedOrderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_preOrderId_fkey" FOREIGN KEY ("preOrderId") REFERENCES "PreOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey" FOREIGN KEY ("actorAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey" FOREIGN KEY ("postedByAdminUserId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletBonus" ADD CONSTRAINT "WalletBonus_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,241 @@
-- Sync schema drift (admin/product/customer fields + new tables).
-- NOTE: Do NOT drop "_hedenagold_migrations" (or legacy "_goldbank_migrations") — 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 $$;
@@ -0,0 +1,8 @@
-- Placeholder: the original migration.sql for this migration was lost from
-- disk (directory existed but was empty) while the database's
-- _prisma_migrations ledger already recorded it as applied and finished.
-- Recreated as an empty no-op file, then registered via
-- `prisma migrate resolve --applied`, so Prisma tooling stops blocking on
-- P3015 without re-running or guessing at SQL that already ran against this
-- database. The actual schema effect (CustomerKind.RETAIL) is present in
-- schema.prisma and already exists in the live database.
@@ -0,0 +1,12 @@
-- Placeholder: this migration is recorded as applied in the database's
-- _prisma_migrations ledger, but no migration.sql for it ever existed on
-- disk in this checkout (added Customer.firstName/lastName/productPercent
-- directly against the live DB, schema.prisma was never regenerated from it
-- as a tracked migration). Recreated as an empty no-op file purely so Prisma
-- CLI tooling (migrate status/dev) stops blocking on a missing-migration
-- error. Does not re-run or alter anything — the columns already exist live.
--
-- NOT reconciled with schema.prisma: Customer.firstName, Customer.lastName,
-- Customer.productPercent exist in the database but have no field in
-- prisma/schema.prisma's Customer model. This is a pre-existing drift,
-- separate from this change, and should be looked at on its own.
@@ -0,0 +1,12 @@
-- Placeholder: this migration is recorded as applied in the database's
-- _prisma_migrations ledger, but no migration.sql for it ever existed on
-- disk in this checkout (added Customer.parentAgentId + Customer.birthDate
-- directly against the live DB; schema.prisma was never regenerated from it
-- as a tracked migration). Recreated as an empty no-op file purely so Prisma
-- CLI tooling (migrate status/dev) stops blocking on a missing-migration
-- error. Does not re-run or alter anything — the columns already exist live.
--
-- NOT reconciled with schema.prisma: Customer.parentAgentId,
-- Customer.birthDate exist in the database but have no field in
-- prisma/schema.prisma's Customer model. This is a pre-existing drift,
-- separate from this change, and should be looked at on its own.
@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "ProductImageRole" AS ENUM ('FEATURED', 'FEATURED_MOBILE', 'GALLERY', 'GALLERY_MOBILE');
-- AlterTable
ALTER TABLE "ProductImage" ADD COLUMN "role" "ProductImageRole" NOT NULL DEFAULT 'GALLERY';
@@ -0,0 +1,11 @@
-- AlterTable
ALTER TABLE "ProductVariant" ADD COLUMN "imageId" TEXT;
-- CreateIndex
CREATE INDEX "ProductVariant_imageId_idx" ON "ProductVariant"("imageId");
-- AddForeignKey
-- ON DELETE SET NULL: removing a gallery image must never cascade into the
-- variant, because OrderItem rows reference variants and losing one would
-- break order history. The variant simply loses its picture.
ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "ProductImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,36 @@
-- Sales agents ("کارشناس فروش") are modelled as AdminUser rows
-- (role=SALES_EXPERT), not Customer rows — see admin-users.service.ts and
-- admin-customers.service.ts's listReagents(). ReagentCustomer.vue used to
-- post agent data at the Customer endpoints instead, so agents ended up as
-- Customer rows (kind SALES_AGENT), which then leaked into every
-- Customer-backed list, including "لیست مشتریان تک" (listSingle had no kind
-- filter at all). Fixed at the application layer alongside this migration;
-- this migration only adds what that fix needs:
--
-- 1. CustomerKind.RETAIL — legacy kept retail ("تک") customers in an
-- entirely separate table (SingleCustomer/`login`) that could never
-- contain a wholesale/agent/shopkeeper/banker row. The redesigned
-- schema folded everything into one Customer model, so listSingle()
-- needs an explicit kind to filter on to reproduce that separation.
-- 2. AdminUser gets the handful of Customer-only columns
-- (ReagentCustomer.vue) that a sales agent's form actually collects:
-- avatar, address, store/pasazh name, member code. Only ever set when
-- role=SALES_EXPERT; every other AdminUser leaves them null.
-- AlterEnum
ALTER TYPE "CustomerKind" ADD VALUE IF NOT EXISTS 'RETAIL';
-- AlterTable
ALTER TABLE "AdminUser"
ADD COLUMN "avatarFileId" TEXT,
ADD COLUMN "address" TEXT,
ADD COLUMN "storeName" TEXT,
ADD COLUMN "pasazhName" TEXT,
ADD COLUMN "memberCode" TEXT;
-- CreateIndex
CREATE INDEX "AdminUser_avatarFileId_idx" ON "AdminUser"("avatarFileId");
-- AddForeignKey
ALTER TABLE "AdminUser" ADD CONSTRAINT "AdminUser_avatarFileId_fkey"
FOREIGN KEY ("avatarFileId") REFERENCES "UploadedFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,40 @@
-- Customer OTP login challenges.
--
-- Additive only: creates one new table and its indexes. No existing table,
-- column or row is touched.
--
-- Ports henza_server's SMS login (ApiMobileController.php:39-78). The reference
-- kept the code in plain text on the customer row with no expiry and no attempt
-- limit; this stores a bcrypt hash and adds both, per docs/parity/DECISIONS.md
-- D6 / T1.
CREATE TABLE IF NOT EXISTS "CustomerOtp" (
"id" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"codeHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"attempts" INTEGER NOT NULL DEFAULT 0,
"consumedAt" TIMESTAMP(3),
"requestIp" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CustomerOtp_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "CustomerOtp_mobile_createdAt_idx"
ON "CustomerOtp"("mobile", "createdAt");
CREATE INDEX IF NOT EXISTS "CustomerOtp_customerId_idx"
ON "CustomerOtp"("customerId");
CREATE INDEX IF NOT EXISTS "CustomerOtp_expiresAt_idx"
ON "CustomerOtp"("expiresAt");
DO $$
BEGIN
ALTER TABLE "CustomerOtp"
ADD CONSTRAINT "CustomerOtp_customerId_fkey"
FOREIGN KEY ("customerId") REFERENCES "Customer"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
@@ -0,0 +1,97 @@
-- D0 — split Customer.fullName into firstName / lastName (ADDITIVE).
--
-- Reference: henza_server has two separate columns from the start
-- database/migrations/2020_10_01_120430_customers_table.php:18-19
-- $table->string('fname')->nullable();
-- $table->string('lname')->nullable();
-- The target collapsed them into one `fullName`, so the admin edit form shows
-- the whole name in the "first name" box and saves an empty last name
-- (admin-customers.service.ts:231-233 returns `fname: c.fullName, lname: ''`).
--
-- NOTHING IS DROPPED. `fullName` stays and is kept in sync as
-- `firstName || ' ' || lastName`, so every existing reader keeps working
-- (D0 requirement 4).
--
-- D13 / storefront impact: `Customer` is a SHARED table — both the B2B app and
-- the retail storefront read it. This migration only ADDS two nullable columns
-- and leaves `fullName` populated and authoritative, so no storefront query,
-- view or report changes behaviour. No storefront column is touched.
-- 1. The two new columns. Nullable: a customer may legitimately have no
-- surname, and the reference's own columns are nullable.
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "firstName" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "lastName" TEXT;
-- 2. Backfill: first whitespace-separated token -> firstName, the REST ->
-- lastName (D0 requirement 2). Multi-space and leading/trailing whitespace
-- are normalised first so " علی محمدی " splits correctly.
--
-- Rows with a single token get firstName = that token and lastName = NULL.
-- Rows with more than two tokens put every remaining token in lastName;
-- they are ALSO listed in the review report (see the companion script) so a
-- human can correct them — the split cannot be guessed safely
-- (D0 requirement 3).
UPDATE "Customer"
SET
"firstName" = split_part(btrim(regexp_replace("fullName", '\s+', ' ', 'g')), ' ', 1),
"lastName" = NULLIF(
substr(
btrim(regexp_replace("fullName", '\s+', ' ', 'g')),
length(split_part(btrim(regexp_replace("fullName", '\s+', ' ', 'g')), ' ', 1)) + 2
),
''
)
WHERE "firstName" IS NULL
AND "fullName" IS NOT NULL
AND btrim("fullName") <> '';
-- 3. Keep `fullName` in sync from now on.
--
-- Writers may update either the parts or the whole; this trigger makes the
-- parts authoritative whenever they are the thing that changed, and
-- otherwise leaves an explicit `fullName` write alone. That way old code
-- that still writes only `fullName` is not broken (D0 requirement 4).
-- Precedence when BOTH the parts and fullName change in the SAME statement:
-- THE PARTS WIN. fullName is rebuilt from them and the submitted fullName is
-- discarded. Rationale: after D0 the parts are the field the forms edit, so
-- they carry the operator's intent; fullName is the derived, legacy-facing
-- value. Choosing the opposite would silently undo an edit made in the two
-- boxes the operator actually typed into.
-- The first branch below tests the parts, so it wins by construction.
CREATE OR REPLACE FUNCTION customer_sync_full_name() RETURNS trigger AS $$
BEGIN
-- The name parts changed (or are being set on insert): rebuild fullName.
-- This is checked FIRST, so when both change in one statement the parts win.
IF (TG_OP = 'INSERT' AND (NEW."firstName" IS NOT NULL OR NEW."lastName" IS NOT NULL))
OR (TG_OP = 'UPDATE'
AND (NEW."firstName" IS DISTINCT FROM OLD."firstName"
OR NEW."lastName" IS DISTINCT FROM OLD."lastName")) THEN
NEW."fullName" := btrim(
coalesce(NEW."firstName", '') || ' ' || coalesce(NEW."lastName", '')
);
-- Only fullName changed: derive the parts from it, so the two never drift.
ELSIF TG_OP = 'UPDATE' AND NEW."fullName" IS DISTINCT FROM OLD."fullName"
AND NEW."fullName" IS NOT NULL AND btrim(NEW."fullName") <> '' THEN
NEW."firstName" := split_part(btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')), ' ', 1);
NEW."lastName" := NULLIF(
substr(
btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')),
length(split_part(btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')), ' ', 1)) + 2
),
''
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS customer_sync_full_name_trg ON "Customer";
CREATE TRIGGER customer_sync_full_name_trg
BEFORE INSERT OR UPDATE ON "Customer"
FOR EACH ROW EXECUTE FUNCTION customer_sync_full_name();
-- 4. Name search hits both parts in the admin grid.
CREATE INDEX IF NOT EXISTS "Customer_firstName_idx" ON "Customer" ("firstName");
CREATE INDEX IF NOT EXISTS "Customer_lastName_idx" ON "Customer" ("lastName");
@@ -0,0 +1,49 @@
-- D9 — add Customer.makingChargeOffsetPercent (ADDITIVE, behaviour-neutral).
--
-- Reference: `customers.percent`, a real per-customer column.
-- database/migrations/2020_10_19_153228_customer_add.php:32
-- $table->double('percent')->default(0);
--
-- What it does in the reference:
-- * it is ADDED to the variant's making charge when the app builds an order
-- line: henza_pwa/src/Mixins/product/index.ts:69-75
-- getWage(v) = myStore.state.percent + v.percentage_pay
-- and `state.percent` is this column, delivered by POST /me-data
-- (henza_pwa/src/store/myDataStore.ts:29,59,122-127).
-- * it shifts the catalogue making-charge filter bounds:
-- ApiMobileController.php:391-396 and :76-77.
-- * it is NOT part of the price formula itself — the extra percent there
-- comes from ConfigStatic['product_price_extra_percent']
-- (ProductVariant.php:50,183).
--
-- Why this name and not `productPercent`: the reference has three unrelated
-- things called `percent` (see docs/parity/MIGRATION-STATE.md). This is the
-- per-customer making-charge offset. It is mapped back to the legacy key
-- `percent` in toRow(), so the admin panel's field name does not change.
-- No duplicate field is created.
--
-- The target currently reads `referredByAgent.wagePercent` instead, which is
-- the wrong field entirely (CUST-02) — that is corrected in the service layer,
-- not here.
--
-- D13 / storefront impact: NONE. `customers.percent` is a B2B wholesale field;
-- it is consumed only by the B2B app's cart and catalogue filter. The
-- storefront's own percent is ConfigStatic['percent'] (the wallet cashback
-- rate), a different key in a different table, which this migration does not
-- touch. `Customer` is a shared table, but adding a column with a default that
-- reproduces today's behaviour changes no storefront read.
-- DEFAULT 0 is deliberate and behaviour-neutral:
-- * it matches the reference's own column default (customer_add.php:32);
-- * with 0 the computed wage is `0 + variant.percentage_pay`, i.e. exactly
-- the making charge alone — which is what the target does today, since it
-- has no such column. So no existing customer's price moves.
-- NOTE: the reference's createCustomer() writes 3, not 0, for NEW customers
-- (AdminController.php:338) and makes the field required on create (:354).
-- That is an application-layer default for new rows, NOT a backfill value,
-- and it is handled in the service, not in this migration.
ALTER TABLE "Customer"
ADD COLUMN IF NOT EXISTS "makingChargeOffsetPercent" DECIMAL(6,3) NOT NULL DEFAULT 0;
COMMENT ON COLUMN "Customer"."makingChargeOffsetPercent" IS
'Legacy customers.percent — per-customer making-charge offset, ADDED to ProductVariant.defaultWagePercent when pricing an order line, and used to shift the catalogue making-charge filter bounds. Not the wallet cashback rate (that is the storefront ConfigStatic[percent]) and not the wholesale per-line charge (that is OrderItem.wagePercentSnapshot).';
@@ -0,0 +1,180 @@
-- D17 — SalesExpertProfile, and firstName/lastName on AdminUser.
--
-- Two additive changes in one migration:
--
-- 1. SalesExpertProfile — the sales-expert fields that legacy kept on the
-- `customers` row (type=1) and that were lost when a sales expert became
-- an AdminUser. A separate table, not columns on AdminUser, because these
-- are meaningful only for role = SALES_EXPERT and would be permanently
-- null for every other admin.
--
-- 2. AdminUser.firstName / lastName — closes D0 requirement 7, applying the
-- same split the Customer table already got. For ALL admins, not only
-- sales experts.
--
-- NOTHING IS DROPPED. `fullName` stays and is kept in sync by a trigger.
--
-- IDEMPOTENT (D14/D15): this must run cleanly under BOTH paths — the Prisma CLI
-- in development, and the app's own runner (src/prisma/migrate.ts) at container
-- boot, which may replay it. Hence IF NOT EXISTS everywhere, CREATE OR REPLACE
-- for the function, DROP ... IF EXISTS before CREATE TRIGGER, and a guarded
-- backfill that only touches rows not already filled.
--
-- D13 / storefront impact: NONE. AdminUser is an admin-side table the retail
-- storefront never reads, and SalesExpertProfile is new. `customers` is not
-- touched.
-- ───────────────────────────────────────────────────────────────────────────
-- 1. SalesExpertProfile
-- ───────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS "SalesExpertProfile" (
"id" TEXT NOT NULL,
"adminUserId" TEXT NOT NULL,
-- legacy customers.city_id — a filterable column on the sales-expert grid
-- (ReagentListModel.js:14) AND the source of the commission autofill
-- (ReagentCustomer.vue:215-228).
"cityId" TEXT,
-- legacy customers.gram_level — the «سطح اعتبار» column, filterable
-- (ReagentListModel.js:17).
--
-- DECIMAL(14,4) deliberately matches Customer.goldCreditLimitGrams exactly
-- (schema.prisma:221, verified numeric_precision=14 numeric_scale=4), so the
-- same credit limit has identical precision and rendering on both tables.
"gramLevel" DECIMAL(14,4) NOT NULL DEFAULT 0,
-- legacy customers.phone / ncode — collected by the reference form
-- (ReagentCustomer.vue:95,31) and stored, though nothing reads them back.
-- Added on the owner's instruction so the form matches the reference.
--
-- Types and constraints copied from the reference, which is deliberately lax:
-- ncode : string(20), NULLABLE, **NOT UNIQUE**
-- (2020_10_01_120430_customers_table.php:22)
-- phone : string(15), NULLABLE, not unique
-- (2020_10_25_155247_customers_cols.php:17)
-- Neither has a server-side validation rule: createCustomer validates only
-- `mobile` (required|unique) and `percent` (required) — AdminController.php:354.
-- Client-side both are `required: false` (ReagentCustomer.vue:31,95).
-- No uniqueness is invented here; that would reject data the reference accepts.
"phone" VARCHAR(15),
"nationalCode" VARCHAR(20),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SalesExpertProfile_pkey" PRIMARY KEY ("id")
);
-- One profile per admin user.
CREATE UNIQUE INDEX IF NOT EXISTS "SalesExpertProfile_adminUserId_key"
ON "SalesExpertProfile" ("adminUserId");
-- The grid filters on city.
CREATE INDEX IF NOT EXISTS "SalesExpertProfile_cityId_idx"
ON "SalesExpertProfile" ("cityId");
DO $$
BEGIN
ALTER TABLE "SalesExpertProfile"
ADD CONSTRAINT "SalesExpertProfile_adminUserId_fkey"
FOREIGN KEY ("adminUserId") REFERENCES "AdminUser"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$
BEGIN
ALTER TABLE "SalesExpertProfile"
ADD CONSTRAINT "SalesExpertProfile_cityId_fkey"
FOREIGN KEY ("cityId") REFERENCES "City"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
-- Give every EXISTING sales expert a profile row, so the grid's city and
-- credit-limit columns have something to left-join to from day one.
--
-- Idempotent by construction: `WHERE NOT EXISTS` means a replay inserts
-- nothing, and it never touches a profile an operator has already filled in.
--
-- The id is derived from the AdminUser id rather than random, so a replay on a
-- database where the row was deleted recreates the same key instead of
-- accumulating duplicates. Values are the column defaults: gramLevel 0 and
-- nulls elsewhere — deliberately neutral, since the reference has no data to
-- migrate here and inventing a credit limit would change behaviour.
INSERT INTO "SalesExpertProfile" ("id", "adminUserId", "gramLevel", "createdAt", "updatedAt")
SELECT 'sep_' || a."id", a."id", 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
FROM "AdminUser" a
WHERE a."role" = 'SALES_EXPERT'
AND NOT EXISTS (
SELECT 1 FROM "SalesExpertProfile" s WHERE s."adminUserId" = a."id"
);
-- ───────────────────────────────────────────────────────────────────────────
-- 2. AdminUser.firstName / lastName (D0 requirement 7)
-- ───────────────────────────────────────────────────────────────────────────
ALTER TABLE "AdminUser" ADD COLUMN IF NOT EXISTS "firstName" TEXT;
ALTER TABLE "AdminUser" ADD COLUMN IF NOT EXISTS "lastName" TEXT;
-- Backfill: first whitespace-separated token -> firstName, the REST ->
-- lastName. Identical rule to the Customer migration, so both tables split the
-- same way. Rows with more than two tokens are a guess and are listed by
-- scripts/report-name-split.cjs for a human to correct.
--
-- `WHERE "firstName" IS NULL` makes the replay a no-op and, just as important,
-- prevents a re-run from overwriting a name a human has since corrected.
UPDATE "AdminUser"
SET
"firstName" = split_part(btrim(regexp_replace("fullName", '\s+', ' ', 'g')), ' ', 1),
"lastName" = NULLIF(
substr(
btrim(regexp_replace("fullName", '\s+', ' ', 'g')),
length(split_part(btrim(regexp_replace("fullName", '\s+', ' ', 'g')), ' ', 1)) + 2
),
''
)
WHERE "firstName" IS NULL
AND "fullName" IS NOT NULL
AND btrim("fullName") <> '';
-- Same two-way sync as Customer.
--
-- Precedence when BOTH the parts and fullName change in ONE statement:
-- THE PARTS WIN — fullName is rebuilt from them and the submitted fullName is
-- discarded. The parts are what the forms edit, so they carry the operator's
-- intent; fullName is the derived, legacy-facing value. The first branch tests
-- the parts, so it wins by construction.
CREATE OR REPLACE FUNCTION admin_user_sync_full_name() RETURNS trigger AS $$
BEGIN
IF (TG_OP = 'INSERT' AND (NEW."firstName" IS NOT NULL OR NEW."lastName" IS NOT NULL))
OR (TG_OP = 'UPDATE'
AND (NEW."firstName" IS DISTINCT FROM OLD."firstName"
OR NEW."lastName" IS DISTINCT FROM OLD."lastName")) THEN
NEW."fullName" := btrim(
coalesce(NEW."firstName", '') || ' ' || coalesce(NEW."lastName", '')
);
ELSIF TG_OP = 'UPDATE' AND NEW."fullName" IS DISTINCT FROM OLD."fullName"
AND NEW."fullName" IS NOT NULL AND btrim(NEW."fullName") <> '' THEN
NEW."firstName" := split_part(btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')), ' ', 1);
NEW."lastName" := NULLIF(
substr(
btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')),
length(split_part(btrim(regexp_replace(NEW."fullName", '\s+', ' ', 'g')), ' ', 1)) + 2
),
''
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS admin_user_sync_full_name_trg ON "AdminUser";
CREATE TRIGGER admin_user_sync_full_name_trg
BEFORE INSERT OR UPDATE ON "AdminUser"
FOR EACH ROW EXECUTE FUNCTION admin_user_sync_full_name();
-- Name search and sort hit both parts.
CREATE INDEX IF NOT EXISTS "AdminUser_firstName_idx" ON "AdminUser" ("firstName");
CREATE INDEX IF NOT EXISTS "AdminUser_lastName_idx" ON "AdminUser" ("lastName");
@@ -0,0 +1,52 @@
-- Sales-expert app login: the expert's own trading account.
--
-- WHY THIS COLUMN EXISTS
-- ----------------------
-- In the reference a sales agent WAS a Customer row (customers.type = 1), so
-- when the agent placed an order from the app, setOrder did
-- $customer = Customer::findOrFail(auth()->id());
-- $gramLevel = $customer->gram_level;
-- (ApiMobileController.php:534-535) — the order was simply that agent's own
-- order, against that agent's own credit limit and ledger.
--
-- This system models a sales expert as an AdminUser (role SALES_EXPERT, D17),
-- which has no ledger, no gold debt and no credit limit. An AdminUser therefore
-- cannot be the subject of an Order: Order.customerId is a required FK to
-- Customer, and every ledger entry hangs off a Customer.
--
-- Rather than reverse D17 (the owner ruled the expert stays an AdminUser), each
-- expert gets ONE linked Customer row that represents only their own trading
-- position. Orders the expert places for themselves land on that row, exactly
-- as the reference's single row did. The expert's REFERRED customers remain
-- separate rows pointing at the AdminUser via Customer.referredByAgentId —
-- that relationship is unchanged.
--
-- PURELY ADDITIVE
-- ---------------
-- One nullable column plus a unique index. No existing row is modified, no
-- data is backfilled and nothing is deleted. A profile with a NULL
-- tradingCustomerId simply has no trading account yet; the API creates it
-- lazily on first login, so existing experts keep working untouched.
ALTER TABLE "SalesExpertProfile"
ADD COLUMN IF NOT EXISTS "tradingCustomerId" TEXT;
COMMENT ON COLUMN "SalesExpertProfile"."tradingCustomerId" IS
'The Customer row that carries this expert''s OWN orders, debt and credit limit. Their referred customers are separate rows linked by Customer.referredByAgentId. Null until the expert first signs into the app, when it is created from the profile (gramLevel -> goldCreditLimitGrams, wagePercent -> makingChargeOffsetPercent).';
-- One trading account per expert, and no two experts may share one.
CREATE UNIQUE INDEX IF NOT EXISTS "SalesExpertProfile_tradingCustomerId_key"
ON "SalesExpertProfile" ("tradingCustomerId");
-- SET NULL, not CASCADE: if the trading Customer is ever removed, the expert
-- and their profile must survive — deleting a customer must never delete an
-- admin account.
DO $$
BEGIN
ALTER TABLE "SalesExpertProfile"
ADD CONSTRAINT "SalesExpertProfile_tradingCustomerId_fkey"
FOREIGN KEY ("tradingCustomerId") REFERENCES "Customer"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
@@ -0,0 +1,38 @@
-- Revocable admin sessions.
--
-- Until now an admin-panel token was purely self-contained: once signed it was
-- valid for its full 12h and nothing could cut it short — not deactivating the
-- account, not a central "sign out everywhere". Connecting the central IAM made
-- that gap concrete, because an IAM session revocation had no effect on a token
-- this API had already issued.
--
-- Each issued token now carries a `jti` recorded here, and JwtStrategy rejects a
-- token whose row is revoked. Rows are only written (and only enforced) while
-- session tracking is enabled, so a deployment that leaves it off keeps exactly
-- the previous behaviour and the previous query count.
CREATE TABLE "AdminSession" (
"id" TEXT NOT NULL,
"jti" TEXT NOT NULL,
"adminUserId" TEXT NOT NULL,
"iamSessionId" TEXT,
"iamSub" TEXT,
"ipAddress" TEXT,
"userAgent" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3),
"lastSeenAt" TIMESTAMP(3),
"revokedAt" TIMESTAMP(3),
"revokedReason" TEXT,
CONSTRAINT "AdminSession_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "AdminSession_jti_key" ON "AdminSession"("jti");
CREATE INDEX "AdminSession_adminUserId_idx" ON "AdminSession"("adminUserId");
CREATE INDEX "AdminSession_iamSessionId_idx" ON "AdminSession"("iamSessionId");
CREATE INDEX "AdminSession_iamSub_idx" ON "AdminSession"("iamSub");
CREATE INDEX "AdminSession_expiresAt_idx" ON "AdminSession"("expiresAt");
ALTER TABLE "AdminSession"
ADD CONSTRAINT "AdminSession_adminUserId_fkey"
FOREIGN KEY ("adminUserId") REFERENCES "AdminUser"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,33 @@
-- Revocable app sessions (customer app and sales-expert app).
--
-- The admin panel got this in 20260925120000_admin_session; the app token family
-- had the same gap: a 30-day self-contained token that nothing could end early.
-- That matters more here, because the app's login is OTP-only — there is no
-- password an operator could change to cut access.
--
-- No foreign key on purpose: an app token's subject is a Customer for a customer
-- and an AdminUser for a sales expert (D21), which one FK cannot express.
CREATE TABLE "AppSession" (
"id" TEXT NOT NULL,
"jti" TEXT NOT NULL,
"subjectId" TEXT NOT NULL,
"subjectType" TEXT NOT NULL,
"mobile" TEXT,
"iamSessionId" TEXT,
"iamSub" TEXT,
"ipAddress" TEXT,
"userAgent" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3),
"lastSeenAt" TIMESTAMP(3),
"revokedAt" TIMESTAMP(3),
"revokedReason" TEXT,
CONSTRAINT "AppSession_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "AppSession_jti_key" ON "AppSession"("jti");
CREATE INDEX "AppSession_subjectId_idx" ON "AppSession"("subjectId");
CREATE INDEX "AppSession_iamSessionId_idx" ON "AppSession"("iamSessionId");
CREATE INDEX "AppSession_iamSub_idx" ON "AppSession"("iamSub");
CREATE INDEX "AppSession_expiresAt_idx" ON "AppSession"("expiresAt");
@@ -0,0 +1,110 @@
-- Accounting integration (D27): Tahesab / Zarrin, chosen in the admin panel.
-- Plan: docs/ACCOUNTING-INTEGRATION-PLAN.md.
--
-- Adds the link table (app row <-> accounting record), the outbox the push
-- jobs run from, the issue list pulls report into, and the cached balance and
-- stock columns read back from the accounting software. Nothing existing
-- changes meaning: goldDebtGrams/fiatDebtRial stay the internal ledger caches.
-- CreateEnum
CREATE TYPE "AccountingEntityType" AS ENUM ('CUSTOMER', 'PRODUCT');
-- CreateEnum
CREATE TYPE "AccountingOutboxKind" AS ENUM ('CUSTOMER_PUSH', 'PRODUCT_PUSH', 'ORDER_DOCUMENT', 'ORDER_DOCUMENT_DELETE');
-- CreateEnum
CREATE TYPE "AccountingOutboxStatus" AS ENUM ('PENDING', 'SENDING', 'SENT', 'FAILED', 'NEEDS_REVIEW', 'RESOLVED');
-- AlterTable
ALTER TABLE "Customer" ADD COLUMN "accountingBalanceAt" TIMESTAMP(3),
ADD COLUMN "accountingGoldBalance" DECIMAL(14,4),
ADD COLUMN "accountingRialBalance" DECIMAL(20,2);
-- AlterTable
ALTER TABLE "Product" ADD COLUMN "accountingStockAt" TIMESTAMP(3),
ADD COLUMN "accountingStockCount" DECIMAL(14,4),
ADD COLUMN "accountingStockWeight" DECIMAL(14,4);
-- CreateTable
CREATE TABLE "AccountingLink" (
"id" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"entityType" "AccountingEntityType" NOT NULL,
"entityId" TEXT NOT NULL,
"externalCode" TEXT NOT NULL,
"externalName" TEXT,
"origin" TEXT NOT NULL,
"snapshot" JSONB,
"syncedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AccountingLink_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AccountingOutbox" (
"id" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"kind" "AccountingOutboxKind" NOT NULL,
"entityId" TEXT NOT NULL,
"status" "AccountingOutboxStatus" NOT NULL DEFAULT 'PENDING',
"attempts" INTEGER NOT NULL DEFAULT 0,
"nextAttemptAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastError" TEXT,
"externalRef" TEXT,
"progress" JSONB,
"lastRequest" JSONB,
"lastResponse" JSONB,
"sentAt" TIMESTAMP(3),
"resolvedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AccountingOutbox_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AccountingIssue" (
"id" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"externalCode" TEXT NOT NULL,
"entityId" TEXT,
"message" TEXT NOT NULL,
"data" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"resolvedAt" TIMESTAMP(3),
CONSTRAINT "AccountingIssue_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "AccountingLink_entityType_entityId_idx" ON "AccountingLink"("entityType", "entityId");
-- CreateIndex
CREATE UNIQUE INDEX "AccountingLink_provider_entityType_entityId_key" ON "AccountingLink"("provider", "entityType", "entityId");
-- CreateIndex
CREATE UNIQUE INDEX "AccountingLink_provider_entityType_externalCode_key" ON "AccountingLink"("provider", "entityType", "externalCode");
-- CreateIndex
CREATE INDEX "AccountingOutbox_status_nextAttemptAt_idx" ON "AccountingOutbox"("status", "nextAttemptAt");
-- CreateIndex
CREATE INDEX "AccountingOutbox_kind_entityId_idx" ON "AccountingOutbox"("kind", "entityId");
-- CreateIndex
CREATE INDEX "AccountingIssue_resolvedAt_idx" ON "AccountingIssue"("resolvedAt");
-- CreateIndex
CREATE UNIQUE INDEX "AccountingIssue_provider_kind_externalCode_key" ON "AccountingIssue"("provider", "kind", "externalCode");
-- The new admin screen's permission. The seeded super-admin user type (85,
-- prisma/seed.ts) gets every rid in ALL_RIDS on a fresh seed; an existing
-- database has to be granted the new one here.
INSERT INTO "permission" ("rid", "userTypeId")
SELECT 'config.accounting', 85
WHERE EXISTS (SELECT 1 FROM "user_type" WHERE "id" = 85)
ON CONFLICT DO NOTHING;
@@ -0,0 +1,210 @@
-- ============================================================================
-- Preparation for the legacy data migration — PURELY ADDITIVE.
--
-- Owner decisions of 2026-10-06 and 2026-10-07, recorded in
-- docs/DATA-MIGRATION-MAP.md §5.2, §5.5, §10, §10.4, §10.5.5.
--
-- Nothing here drops, renames or retypes an existing column. Every statement
-- either removes a constraint or adds a new column/table, so code that works
-- today keeps working. Verified NOT touched:
-- * Customer.accounting{Gold,Rial}Balance / accountingBalanceAt (D27)
-- * Product.accountingStock{Weight,Count} / accountingStockAt (D27)
-- * ProductVariant.imageId (PROTECTED.md §B)
-- * AccountingLink / AccountingOutbox / AccountingIssue
-- * SmsMessage and the SMS module
-- These are target-only capabilities under PROTECTED.md §G ("a keep-out
-- list"), and the owner confirmed on 2026-10-06 that their data is real.
--
-- D15: idempotent (IF NOT EXISTS / duplicate_object guards) because this runs
-- under BOTH ledgers — the Prisma CLI in development and the app's own runner
-- (src/prisma/migrate.ts) in the deploy container, which wraps each file in a
-- transaction. The runner's splitSqlStatements() handles $$-quoted blocks
-- (migrate.ts:83-91), so the DO blocks below are split correctly.
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 1. Customer.nationalCode becomes OPTIONAL
--
-- Source reality (docs/migration-precheck.sql, run 2026-10-06 on the live
-- `oapi` MySQL schema): empty_ncode = 1595 of n_customers = 1595. NOT ONE of
-- the 1,616 legacy customers has a national code. dup_ncode_groups = 0.
--
-- Owner ruling 2026-10-07: "ساختن کد ملی فیک تخلف دیتایی است و مانع از
-- احراز هویت‌های قانونی بعدی می‌شود" — fabricating one is a data violation, so
-- the column must be nullable. Without this the customer migration cannot
-- insert a single row.
--
-- This also RETIRES three existing workarounds that only exist because the
-- column is required. They write the MOBILE NUMBER into the national-code
-- field, i.e. they store wrong data today:
-- * admin-customers.service.ts:625 nationalCode: mobile
-- comment: "schema requires it and it is unique; singleCustomer.vue
-- collects no ncode"
-- * admin-customers.service.ts:685 nationalCode: nationalCode || mobile
-- * customer-auth.service.ts:258 ... || admin.mobile
-- Removing those fallbacks is a SERVICE-LAYER change, deliberately NOT done
-- here: this migration only makes it possible. Until they are removed the
-- behaviour is exactly as it is today.
--
-- The UNIQUE index "Customer_nationalCode_key" (first/migration.sql:304) is
-- left in place. In PostgreSQL NULLs are not equal to each other, so any
-- number of rows may hold NULL under a unique index — which is precisely why
-- the migration tool must write NULL and never the empty string: '' would
-- collide on the second row (see §10.4).
--
-- DROP NOT NULL takes only a brief ACCESS EXCLUSIVE lock on the catalog; it
-- does not rewrite the table and does not scan the data.
ALTER TABLE "Customer" ALTER COLUMN "nationalCode" DROP NOT NULL;
COMMENT ON COLUMN "Customer"."nationalCode" IS
'Legacy customers.ncode. OPTIONAL: no legacy customer has one (1595/1595 empty as of 2026-10-06), and fabricating a value is a data violation (owner, 2026-10-07). Still UNIQUE — write NULL, never '''', because NULLs do not collide under a unique index but empty strings do.';
-- ----------------------------------------------------------------------------
-- 2. Customer.birthDate — new, nullable, unread until backfilled
--
-- Legacy `customers.birth_date timestamp NULL` (oapi dump line 484), populated
-- on 917 of 1,595 live rows (~57%). Owner ruling 2026-10-07: migrate it —
-- "نرخ پرشدگی بالای ۵۵٪ است و داده برای کمپین‌های وفاداری و پیامکی کاربرد
-- مستقیم دارد".
--
-- Two sibling columns were deliberately NOT ported, same ruling:
-- * marriage_date (5 rows) — "۵ ردیف توجیه معماری ندارد"
-- * token_mobile (99 rows) — "توکن‌ها اغلب منقضی شده‌اند · زباله دیتایی"
--
-- ⚠ THE COLUMN IS CREATED EMPTY ON PURPOSE. Do not backfill it until the
-- calendar question is settled (§5.9): legacy stores some dates as JALALI
-- STRINGS elsewhere (cashes.recive_date is varchar(255), 88 of 89 values
-- starting "14…"), so birth_date may hold Jalali years mis-entered into a
-- timestamp. Copying those unchanged would silently produce 917 WRONG
-- birthdays, which for an SMS campaign is worse than having none.
-- docs/migration-precheck-3.sql answers this.
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "birthDate" TIMESTAMP(3);
COMMENT ON COLUMN "Customer"."birthDate" IS
'Legacy customers.birth_date (917 of 1595 rows populated). Created empty: verify Jalali-vs-Gregorian with docs/migration-precheck-3.sql BEFORE backfilling — legacy stores dates as Jalali strings in other tables.';
-- ----------------------------------------------------------------------------
-- 3. Rejection trail for document verification
--
-- Legacy has THREE states per verification step, not two
-- (MajorCustomerController.php:106-135): 1 = awaiting review, 2 = approved,
-- 3 = REJECTED. The target's VerificationStep enum has no rejected state, so
-- 14 already-rejected customers had nowhere to go.
--
-- Owner ruling 2026-10-07: mapping them to "awaiting" is "کاملاً مردود" —
-- it would put closed cases back in the review queue and create duplicate
-- work. REJECTED is added to the enum (statement 4 below) and these two
-- columns carry the reason.
--
-- Legacy records NO reason: all 49 `customers` columns were checked and none
-- is a reason/note/description field, and on rejection the server sends only
-- a FIXED SMS ("اطلاعات شما تایید نشده است. لطفا با پیشتیبانی تماس حاصل
-- فرمایید") with no cause stored anywhere. So for those 14 rows the reason is
-- unrecoverable; the owner asked the tool to write an explicit sentinel rather
-- than NULL, so support knows WHY it is blank:
-- رد شده در سیستم قدیم — فاقد شرح علت ثبت‌شده
-- That text is written by the migration tool, not by this migration, and only
-- WHERE verificationRejectionReason IS NULL so a re-run can never overwrite a
-- real reason entered in the new system (§5.5).
--
-- verificationRejectedAt stays NULL for the 14 legacy rows: legacy did not
-- record a rejection timestamp either.
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "verificationRejectionReason" TEXT;
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "verificationRejectedAt" TIMESTAMP(3);
COMMENT ON COLUMN "Customer"."verificationRejectionReason" IS
'Why document verification was rejected. Legacy stored no reason (only a fixed SMS), so rows migrated from henza carry the sentinel «رد شده در سیستم قدیم — فاقد شرح علت ثبت‌شده». Write only WHERE this IS NULL so a re-run never overwrites a real reason.';
COMMENT ON COLUMN "Customer"."verificationRejectedAt" IS
'When verification was rejected. NULL for rows migrated from henza — legacy did not record a rejection timestamp.';
-- ----------------------------------------------------------------------------
-- 4. VerificationStep gains REJECTED
--
-- ADD VALUE is additive: existing rows and existing queries are unaffected.
-- IF NOT EXISTS makes a re-run a no-op.
--
-- ⚠ CONSUMER AUDIT STILL OWED. Every read of VerificationStep must be checked
-- for a Persian label / switch that does not yet know this value, or the admin
-- panel will render a blank cell. Flagged in §10.5.5 as a separate task; it is
-- a display gap, not a data risk, and nothing writes REJECTED until the
-- migration tool runs.
--
-- Postgres note: before v12 ADD VALUE could not run inside a transaction, and
-- the app's own runner wraps each file in one (migrate.ts:198). This project
-- targets a modern Postgres where that restriction is lifted. If the deploy
-- container ever errors with "ALTER TYPE ... ADD VALUE cannot be executed
-- inside a transaction block", this statement must move to its own file.
DO $$
BEGIN
ALTER TYPE "VerificationStep" ADD VALUE IF NOT EXISTS 'REJECTED';
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
-- ----------------------------------------------------------------------------
-- 5. CustomerFavorite — the legacy `fav` table finally gets a home
--
-- Legacy `fav` holds 251 rows (customer_id, product_id) and the target had NO
-- model for it, so every customer's saved products would have been lost.
-- Owner ruling 2026-10-06: they matter. docs/PWA-PORT-PLAN.md reached the same
-- conclusion independently.
--
-- Legacy `fav` has only two columns and NO primary key, so duplicate pairs are
-- possible in principle. The composite primary key here closes that, and the
-- precheck confirmed fav_duplicate_pairs = 0, so no de-duplication is needed —
-- but the tool should still SELECT DISTINCT, since the source cannot enforce it.
--
-- 14 of the 251 rows point at a customer that no longer exists
-- (fav.customer_id orphans). Those must be SKIPPED by the tool; the foreign
-- keys below would reject them anyway.
--
-- ON DELETE CASCADE on both sides: a favourite is a pure join row with no
-- meaning once either end is gone. This differs from ProductVariant.imageId,
-- which uses SetNull precisely so a deleted image never cascades into order
-- history — the distinction is deliberate.
--
-- Legacy also keeps a denormalised `product.count_fav` counter. It is NOT
-- recreated as a column; derive it from this table (§5.2 group B) rather than
-- showing a stale or zero value.
CREATE TABLE IF NOT EXISTS "CustomerFavorite" (
"customerId" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CustomerFavorite_pkey" PRIMARY KEY ("customerId", "productId")
);
-- The PK already indexes (customerId, productId) left-to-right, so "my
-- favourites" needs no extra index. This one serves the reverse lookup —
-- "how many customers favourited this product" — which is how count_fav is
-- derived.
CREATE INDEX IF NOT EXISTS "CustomerFavorite_productId_idx"
ON "CustomerFavorite"("productId");
DO $$
BEGIN
ALTER TABLE "CustomerFavorite"
ADD CONSTRAINT "CustomerFavorite_customerId_fkey"
FOREIGN KEY ("customerId") REFERENCES "Customer"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$
BEGIN
ALTER TABLE "CustomerFavorite"
ADD CONSTRAINT "CustomerFavorite_productId_fkey"
FOREIGN KEY ("productId") REFERENCES "Product"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
COMMENT ON TABLE "CustomerFavorite" IS
'Legacy `fav` (251 rows). Composite PK replaces the source table''s missing primary key. Derive product.count_fav from here rather than storing a counter.';
@@ -0,0 +1,22 @@
-- ============================================================================
-- Customer.cityNameRaw — PURELY ADDITIVE.
--
-- Owner ruling 2026-10-07 (docs/DATA-MIGRATION-MAP.md §5.9): keep the city a
-- customer typed at sign-up verbatim, and set cityId only on an exact match.
--
-- Legacy `customers.new_city` is free text (all 172 live values are names, 0
-- are ids). The legacy city table has only 15 rows, so 43 of those names match
-- no known city at all, and only ~72 match one of the 15 after ي/ك
-- normalisation. Dropping the text would lose where those customers are.
--
-- Legacy sign-up also forced city_id = 5 (تهران) on every new customer
-- (Major/CustomerController.php:21); 91 of the 92 customers with both values
-- carry that forced Tehran. For them the typed name is the truth.
--
-- D15: idempotent, runs under both migration ledgers.
-- ============================================================================
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "cityNameRaw" TEXT;
COMMENT ON COLUMN "Customer"."cityNameRaw" IS
'Legacy customers.new_city — city name as typed at sign-up, kept verbatim. cityId is set too only on an exact (ي/ك-normalised) match; legacy forced city_id=5 (Tehran) on sign-up, so prefer this text over that cityId.';
@@ -0,0 +1,31 @@
-- ============================================================================
-- Customer identity cleanup — data only, no schema change. Idempotent.
--
-- Owner ruling 2026-10-07: «هر دیتایی باید سر جای خودش بشینه».
--
-- 1. nationalCode held the MOBILE (or "<mobile>-<provider>-<code>") on every
-- customer created without a national code, because the column used to be
-- NOT NULL. It is optional since 20261007120000, and the code paths that
-- wrote those stand-ins are fixed in the same change as this file. The
-- stand-ins are cleared to NULL; a real national code is never touched.
-- Accounting is unaffected: it only ever received validNationalCode(),
-- which already mapped a mobile to null.
--
-- 2. passwordHash '' becomes the explicit "no password login" marker
-- (auth/password-hash.ts). Both are refused before bcrypt by
-- compareLocalHash, so nobody's ability to sign in changes.
-- ============================================================================
UPDATE "Customer"
SET "nationalCode" = NULL
WHERE "nationalCode" = ''
OR "nationalCode" = "mobile"
OR "nationalCode" LIKE "mobile" || '-%';
UPDATE "Customer" SET "passwordHash" = '!OTP_ONLY_NO_PASSWORD_LOGIN' WHERE "passwordHash" = '';
UPDATE "AdminUser" SET "passwordHash" = '!NO_PASSWORD_SET_YET' WHERE "passwordHash" = '';
-- The earlier comment named the wrong source table: `fav` belongs to the retail
-- shop; wholesale favourites come from `major_favorites` (DATA-MIGRATION-MAP §10.7).
COMMENT ON TABLE "CustomerFavorite" IS
'Legacy `major_favorites` (wholesale; deduplicated on import). Composite PK replaces the source table''s missing primary key. Derive a favourite count from here rather than storing a counter.';
@@ -0,0 +1,47 @@
-- ============================================================================
-- Order.salesAgentId / Order.referrerCustomerId — additive, idempotent.
--
-- Owner ruling 2026-10-08: keep legacy's rule exactly. An order is credited to
-- the expert who was the customer's referrer WHEN IT WAS PLACED (legacy
-- `orders.reagent_id`, set once at creation), so a customer moving to another
-- expert later never changes past sales or commission. The app used to credit
-- an order to the customer's CURRENT referrer, which disagreed with legacy for
-- 8 of 49 experts on the imported data.
--
-- No row is deleted and no existing column changes.
-- ============================================================================
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "salesAgentId" TEXT;
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "referrerCustomerId" TEXT;
DO $$ BEGIN
ALTER TABLE "Order" ADD CONSTRAINT "Order_salesAgentId_fkey"
FOREIGN KEY ("salesAgentId") REFERENCES "AdminUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "Order" ADD CONSTRAINT "Order_referrerCustomerId_fkey"
FOREIGN KEY ("referrerCustomerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
CREATE INDEX IF NOT EXISTS "Order_salesAgentId_idx" ON "Order"("salesAgentId");
CREATE INDEX IF NOT EXISTS "Order_referrerCustomerId_idx" ON "Order"("referrerCustomerId");
-- Orders placed in the new app before this column existed: the customer's
-- referrer today is the best record there is (these are recent). Orders
-- brought over from legacy (orderNumber 'H-<id>') are NOT guessed here — the
-- import writes their real `orders.reagent_id`.
UPDATE "Order" o
SET "salesAgentId" = c."referredByAgentId"
FROM "Customer" c
WHERE c."id" = o."customerId"
AND o."salesAgentId" IS NULL
AND c."referredByAgentId" IS NOT NULL
AND o."orderNumber" NOT LIKE 'H-%';
COMMENT ON COLUMN "Order"."salesAgentId" IS
'Sales expert credited with this order, fixed when it is placed (legacy orders.reagent_id). Never follows a later change of the customer''s referrer.';
COMMENT ON COLUMN "Order"."referrerCustomerId" IS
'History only: legacy orders.reagent_id when it named a customer who is not a sales expert. Never set for new orders.';
@@ -0,0 +1,9 @@
-- «اجرت» admin screen (owner ruling 2026-10-08): its permission for the
-- seeded super-admin user type. A fresh seed grants every rid in ALL_RIDS;
-- an existing database gets the new one here. Data only, idempotent.
-- The formula itself needs no row: until an admin approves one, the server
-- uses legacy's formula (pricing/wage-formula.ts LEGACY_WAGE_FORMULA).
INSERT INTO "permission" ("rid", "userTypeId")
SELECT 'config.wage', 85
WHERE EXISTS (SELECT 1 FROM "user_type" WHERE "id" = 85)
ON CONFLICT DO NOTHING;
@@ -0,0 +1,14 @@
-- PERM-01 (owner approval 2026-10-08): legacy's two dashboard permissions
-- «آمار عمده فروشی» (dashboard.omde) and «آمار تک فروشی» (dashboard.tak), and
-- their tree parent "dashboard", became the single "dashboard.view" when the two
-- dashboards became one. A database seeded before that change still holds the
-- old rows: nothing reads them and the permission screen cannot show or revoke
-- them. Whoever held one keeps the dashboard through "dashboard.view", then the
-- old rows go. Data only, idempotent.
INSERT INTO "permission" ("rid", "userTypeId")
SELECT DISTINCT 'dashboard.view', "userTypeId"
FROM "permission"
WHERE "rid" IN ('dashboard', 'dashboard.omde', 'dashboard.tak')
ON CONFLICT DO NOTHING;
DELETE FROM "permission" WHERE "rid" IN ('dashboard', 'dashboard.omde', 'dashboard.tak');
@@ -0,0 +1,33 @@
-- PERS-02 (owner approval 2026-10-08): cities become a tree again, as in legacy
-- (country > province > city > district, henza `cities.parent`). The flat
-- `province` label is turned into parent rows, nothing is lost, then dropped.
ALTER TABLE "City" ADD COLUMN "parentId" TEXT;
-- One province row per distinct label, named as legacy names them
-- («استان تهران»), unless a city row already carries that name.
INSERT INTO "City" ("id", "name")
SELECT 'cprov' || substr(md5(p.pname), 1, 20), p.pname
FROM (
SELECT DISTINCT
CASE WHEN btrim("province") LIKE 'استان%' THEN btrim("province") ELSE 'استان ' || btrim("province") END AS pname
FROM "City"
WHERE "province" IS NOT NULL AND btrim("province") <> ''
) p
WHERE NOT EXISTS (SELECT 1 FROM "City" c WHERE c."name" = p.pname);
UPDATE "City" c
SET "parentId" = (
SELECT p."id" FROM "City" p
WHERE p."id" <> c."id"
AND p."name" = CASE WHEN btrim(c."province") LIKE 'استان%' THEN btrim(c."province") ELSE 'استان ' || btrim(c."province") END
ORDER BY p."id"
LIMIT 1
)
WHERE c."province" IS NOT NULL AND btrim(c."province") <> '';
ALTER TABLE "City" DROP COLUMN "province";
CREATE INDEX "City_parentId_idx" ON "City"("parentId");
ALTER TABLE "City" ADD CONSTRAINT "City_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "City"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,30 @@
-- Catalogue paging indexes.
--
-- Owner, 2026-10-12: «دیتاها در اپ واقعی تا 16 هزار رکورد میرود و گرفتن همه یک
-- جا اشتباه است، حتما باید پیج نیشن داشته باشد.» Step 1 of
-- docs/CATALOG-PAGINATION-PLAN.md: without these, server-side paging would walk
-- the same full table scans the unpaged query did.
--
-- `Product` carried NO index whatsoever, so every GET /catalog/products was a
-- sequential scan, and so was each per-product include.
--
-- IF NOT EXISTS throughout: these run under BOTH migration paths (D15) — the
-- Prisma CLI in development and the app's own runner in the deploy image.
--
-- CREATE INDEX (not CONCURRENTLY): the app's runner wraps each migration in a
-- transaction, which CONCURRENTLY cannot join. On a 16k-row table this locks
-- writes for a few hundred milliseconds, which is acceptable; a table large
-- enough to matter would need the index built by hand outside a migration.
-- Matches the storefront list exactly: filter isActive, order by createdAt.
CREATE INDEX IF NOT EXISTS "Product_isActive_createdAt_idx" ON "Product" ("isActive", "createdAt");
-- The category and brand filters of the search screen.
CREATE INDEX IF NOT EXISTS "Product_categoryId_idx" ON "Product" ("categoryId");
CREATE INDEX IF NOT EXISTS "Product_brandId_idx" ON "Product" ("brandId");
-- `include: { variants: { where: { isActive: true } } }`, once per product.
CREATE INDEX IF NOT EXISTS "ProductVariant_productId_isActive_idx" ON "ProductVariant" ("productId", "isActive");
-- `where: { images: { some: {} } }` plus the gallery ordered by sortOrder.
CREATE INDEX IF NOT EXISTS "ProductImage_productId_sortOrder_idx" ON "ProductImage" ("productId", "sortOrder");
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
export * from "./index"
+1
View File
@@ -0,0 +1 @@
module.exports = { ...require('.') }
+9
View File
@@ -0,0 +1,9 @@
class PrismaClient {
constructor() {
throw new Error(
'@prisma/client/deno/edge did not initialize yet. Please run "prisma generate" and try to import it again.',
)
}
}
export { PrismaClient }
+1
View File
@@ -0,0 +1 @@
export * from "./default"
File diff suppressed because one or more lines are too long
+854
View File
@@ -0,0 +1,854 @@
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('@prisma/client/runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 5.22.0
* Query Engine version: 605197351a3c8bdd595af2d2a9bc3025bca48ea2
*/
Prisma.prismaVersion = {
client: "5.22.0",
engine: "605197351a3c8bdd595af2d2a9bc3025bca48ea2"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.NotFoundError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`NotFoundError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.CityScalarFieldEnum = {
id: 'id',
name: 'name',
parentId: 'parentId',
percent: 'percent',
percentAux: 'percentAux'
};
exports.Prisma.AdminUserScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
passwordHash: 'passwordHash',
role: 'role',
userTypeId: 'userTypeId',
branchId: 'branchId',
wagePercent: 'wagePercent',
isActive: 'isActive',
createdAt: 'createdAt',
avatarFileId: 'avatarFileId',
address: 'address',
storeName: 'storeName',
pasazhName: 'pasazhName',
memberCode: 'memberCode'
};
exports.Prisma.AppSessionScalarFieldEnum = {
id: 'id',
jti: 'jti',
subjectId: 'subjectId',
subjectType: 'subjectType',
mobile: 'mobile',
iamSessionId: 'iamSessionId',
iamSub: 'iamSub',
ipAddress: 'ipAddress',
userAgent: 'userAgent',
createdAt: 'createdAt',
expiresAt: 'expiresAt',
lastSeenAt: 'lastSeenAt',
revokedAt: 'revokedAt',
revokedReason: 'revokedReason'
};
exports.Prisma.AdminSessionScalarFieldEnum = {
id: 'id',
jti: 'jti',
adminUserId: 'adminUserId',
iamSessionId: 'iamSessionId',
iamSub: 'iamSub',
ipAddress: 'ipAddress',
userAgent: 'userAgent',
createdAt: 'createdAt',
expiresAt: 'expiresAt',
lastSeenAt: 'lastSeenAt',
revokedAt: 'revokedAt',
revokedReason: 'revokedReason'
};
exports.Prisma.SalesExpertProfileScalarFieldEnum = {
id: 'id',
adminUserId: 'adminUserId',
cityId: 'cityId',
gramLevel: 'gramLevel',
phone: 'phone',
nationalCode: 'nationalCode',
tradingCustomerId: 'tradingCustomerId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CustomerScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
nationalCode: 'nationalCode',
passwordHash: 'passwordHash',
cityId: 'cityId',
cityNameRaw: 'cityNameRaw',
kind: 'kind',
phone: 'phone',
address: 'address',
storeName: 'storeName',
pasazhName: 'pasazhName',
zone: 'zone',
memberCode: 'memberCode',
referredByAgentId: 'referredByAgentId',
goldCreditLimitGrams: 'goldCreditLimitGrams',
goldDebtGrams: 'goldDebtGrams',
fiatDebtRial: 'fiatDebtRial',
accountingGoldBalance: 'accountingGoldBalance',
accountingRialBalance: 'accountingRialBalance',
accountingBalanceAt: 'accountingBalanceAt',
makingChargeOffsetPercent: 'makingChargeOffsetPercent',
verificationStep: 'verificationStep',
nationalIdImageUrl: 'nationalIdImageUrl',
businessLicenseUrl: 'businessLicenseUrl',
shopImageUrl: 'shopImageUrl',
verificationRejectionReason: 'verificationRejectionReason',
verificationRejectedAt: 'verificationRejectedAt',
birthDate: 'birthDate',
avatarFileId: 'avatarFileId',
canPlaceOrders: 'canPlaceOrders',
isActive: 'isActive',
isArchived: 'isArchived',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CustomerFavoriteScalarFieldEnum = {
customerId: 'customerId',
productId: 'productId',
createdAt: 'createdAt'
};
exports.Prisma.CustomerAddressScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
cityId: 'cityId',
addressLine: 'addressLine',
isDefault: 'isDefault'
};
exports.Prisma.BranchScalarFieldEnum = {
id: 'id',
name: 'name',
address: 'address',
phone: 'phone',
isActive: 'isActive',
cityId: 'cityId'
};
exports.Prisma.WorkshopScalarFieldEnum = {
id: 'id',
name: 'name',
address: 'address',
phone: 'phone',
supervisorId: 'supervisorId',
isActive: 'isActive'
};
exports.Prisma.CategoryScalarFieldEnum = {
id: 'id',
title: 'title',
type: 'type',
imageUrl: 'imageUrl',
iconUrl: 'iconUrl',
metaTag: 'metaTag',
description: 'description',
parentId: 'parentId'
};
exports.Prisma.BrandScalarFieldEnum = {
id: 'id',
title: 'title'
};
exports.Prisma.CollectionScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
isActive: 'isActive'
};
exports.Prisma.CollectionProductScalarFieldEnum = {
collectionId: 'collectionId',
productId: 'productId'
};
exports.Prisma.ProductScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
categoryId: 'categoryId',
brandId: 'brandId',
productCode: 'productCode',
showType: 'showType',
isActive: 'isActive',
createdAt: 'createdAt',
accountingStockWeight: 'accountingStockWeight',
accountingStockCount: 'accountingStockCount',
accountingStockAt: 'accountingStockAt'
};
exports.Prisma.ProductImageScalarFieldEnum = {
id: 'id',
productId: 'productId',
url: 'url',
sortOrder: 'sortOrder',
role: 'role'
};
exports.Prisma.ProductVariantScalarFieldEnum = {
id: 'id',
productId: 'productId',
title: 'title',
sku: 'sku',
rashWeightGrams: 'rashWeightGrams',
defaultWagePercent: 'defaultWagePercent',
qty: 'qty',
isPreOrder: 'isPreOrder',
isActive: 'isActive',
imageId: 'imageId'
};
exports.Prisma.OrderScalarFieldEnum = {
id: 'id',
orderNumber: 'orderNumber',
customerId: 'customerId',
branchId: 'branchId',
channel: 'channel',
status: 'status',
totalGoldWeightGrams: 'totalGoldWeightGrams',
wageWeightGrams: 'wageWeightGrams',
flatRialCharge: 'flatRialCharge',
isSuspendedOverCreditLimit: 'isSuspendedOverCreditLimit',
deliveredAt: 'deliveredAt',
salesAgentId: 'salesAgentId',
referrerCustomerId: 'referrerCustomerId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.OrderItemScalarFieldEnum = {
id: 'id',
orderId: 'orderId',
productVariantId: 'productVariantId',
quantity: 'quantity',
rashWeightGramsSnapshot: 'rashWeightGramsSnapshot',
wagePercentSnapshot: 'wagePercentSnapshot',
discountPercentSnapshot: 'discountPercentSnapshot',
computedWeightGrams: 'computedWeightGrams',
flatRialCharge: 'flatRialCharge'
};
exports.Prisma.OrderStatusEventScalarFieldEnum = {
id: 'id',
orderId: 'orderId',
fromStatus: 'fromStatus',
toStatus: 'toStatus',
note: 'note',
actorAdminUserId: 'actorAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.PreOrderScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
cityId: 'cityId',
workshopId: 'workshopId',
status: 'status',
designImageUrl: 'designImageUrl',
colorName: 'colorName',
sizeSpec: 'sizeSpec',
stoneColor: 'stoneColor',
minaColor: 'minaColor',
estimatedWeightGrams: 'estimatedWeightGrams',
estimatedPriceRial: 'estimatedPriceRial',
description: 'description',
convertedOrderId: 'convertedOrderId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
id: 'id',
preOrderId: 'preOrderId',
changes: 'changes',
actorAdminUserId: 'actorAdminUserId',
ipAddress: 'ipAddress',
createdAt: 'createdAt'
};
exports.Prisma.LedgerEntryScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
currency: 'currency',
direction: 'direction',
sourceType: 'sourceType',
amount: 'amount',
karatPerMille: 'karatPerMille',
pricePerGramRialAtEntry: 'pricePerGramRialAtEntry',
orderId: 'orderId',
confirmed: 'confirmed',
confirmedAt: 'confirmedAt',
postedByAdminUserId: 'postedByAdminUserId',
labName: 'labName',
hallmark: 'hallmark',
accountName: 'accountName',
bankName: 'bankName',
transferTo: 'transferTo',
originalValue: 'originalValue',
priceRial: 'priceRial',
receivedAt: 'receivedAt',
note: 'note',
createdAt: 'createdAt'
};
exports.Prisma.GoldPriceQuoteScalarFieldEnum = {
id: 'id',
karatPerMille: 'karatPerMille',
pricePerGramRial: 'pricePerGramRial',
source: 'source',
quotedAt: 'quotedAt'
};
exports.Prisma.WalletBonusScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
orderId: 'orderId',
goldGrams: 'goldGrams',
grantedAt: 'grantedAt',
expiresAt: 'expiresAt',
consumedAt: 'consumedAt',
isActive: 'isActive'
};
exports.Prisma.UserTypeScalarFieldEnum = {
id: 'id',
title: 'title',
isActive: 'isActive',
createdAt: 'createdAt'
};
exports.Prisma.PermissionScalarFieldEnum = {
rid: 'rid',
userTypeId: 'userTypeId'
};
exports.Prisma.CommentScalarFieldEnum = {
id: 'id',
productId: 'productId',
customerId: 'customerId',
body: 'body',
status: 'status',
replyToId: 'replyToId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SettingScalarFieldEnum = {
key: 'key',
value: 'value',
updatedAt: 'updatedAt'
};
exports.Prisma.AccountingLinkScalarFieldEnum = {
id: 'id',
provider: 'provider',
entityType: 'entityType',
entityId: 'entityId',
externalCode: 'externalCode',
externalName: 'externalName',
origin: 'origin',
snapshot: 'snapshot',
syncedAt: 'syncedAt',
createdAt: 'createdAt'
};
exports.Prisma.AccountingOutboxScalarFieldEnum = {
id: 'id',
provider: 'provider',
kind: 'kind',
entityId: 'entityId',
status: 'status',
attempts: 'attempts',
nextAttemptAt: 'nextAttemptAt',
lastError: 'lastError',
externalRef: 'externalRef',
progress: 'progress',
lastRequest: 'lastRequest',
lastResponse: 'lastResponse',
sentAt: 'sentAt',
resolvedById: 'resolvedById',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.AccountingIssueScalarFieldEnum = {
id: 'id',
provider: 'provider',
kind: 'kind',
externalCode: 'externalCode',
entityId: 'entityId',
message: 'message',
data: 'data',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
resolvedAt: 'resolvedAt'
};
exports.Prisma.PromotionScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
scope: 'scope',
usage: 'usage',
discountPercent: 'discountPercent',
isActive: 'isActive',
platform: 'platform',
startsAt: 'startsAt',
endsAt: 'endsAt',
productId: 'productId',
categoryId: 'categoryId',
customerId: 'customerId',
brandId: 'brandId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.PromotionExclusionScalarFieldEnum = {
id: 'id',
type: 'type',
itemId: 'itemId',
createdAt: 'createdAt'
};
exports.Prisma.BranchAllocationScalarFieldEnum = {
id: 'id',
branchId: 'branchId',
allocatorAdminUserId: 'allocatorAdminUserId',
note: 'note',
createdAt: 'createdAt'
};
exports.Prisma.BranchAllocationItemScalarFieldEnum = {
id: 'id',
allocationId: 'allocationId',
productId: 'productId',
weightGrams: 'weightGrams'
};
exports.Prisma.SmsMessageScalarFieldEnum = {
id: 'id',
audience: 'audience',
recipients: 'recipients',
body: 'body',
sentCount: 'sentCount',
sentByAdminUserId: 'sentByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.NotificationScalarFieldEnum = {
id: 'id',
title: 'title',
body: 'body',
audience: 'audience',
sentByAdminUserId: 'sentByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.NotificationRecipientScalarFieldEnum = {
id: 'id',
notificationId: 'notificationId',
customerId: 'customerId',
readAt: 'readAt'
};
exports.Prisma.NewsletterSubscriberScalarFieldEnum = {
id: 'id',
email: 'email',
mobile: 'mobile',
createdAt: 'createdAt'
};
exports.Prisma.CustomerActivityLogScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
action: 'action',
detail: 'detail',
createdAt: 'createdAt'
};
exports.Prisma.UploadedFileScalarFieldEnum = {
id: 'id',
url: 'url',
originalName: 'originalName',
mimeType: 'mimeType',
sizeBytes: 'sizeBytes',
purpose: 'purpose',
uploadedByAdminUserId: 'uploadedByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.CustomerOtpScalarFieldEnum = {
id: 'id',
mobile: 'mobile',
customerId: 'customerId',
codeHash: 'codeHash',
expiresAt: 'expiresAt',
attempts: 'attempts',
consumedAt: 'consumedAt',
requestIp: 'requestIp',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.JsonNullValueInput = {
JsonNull: Prisma.JsonNull
};
exports.Prisma.NullableJsonNullValueInput = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.JsonNullValueFilter = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull,
AnyNull: Prisma.AnyNull
};
exports.AdminRole = exports.$Enums.AdminRole = {
SUPER_ADMIN: 'SUPER_ADMIN',
SYSTEM_APPROVER: 'SYSTEM_APPROVER',
SALES_EXPERT: 'SALES_EXPERT',
WORKSHOP_SUPERVISOR: 'WORKSHOP_SUPERVISOR',
ACCOUNTANT: 'ACCOUNTANT'
};
exports.CustomerKind = exports.$Enums.CustomerKind = {
SALES_AGENT: 'SALES_AGENT',
APP_SHOPKEEPER: 'APP_SHOPKEEPER',
WALK_IN_SHOPKEEPER: 'WALK_IN_SHOPKEEPER',
BANKER: 'BANKER',
WORKSHOP_SUPERVISOR: 'WORKSHOP_SUPERVISOR',
RETAIL: 'RETAIL'
};
exports.VerificationStep = exports.$Enums.VerificationStep = {
UNVERIFIED: 'UNVERIFIED',
STEP_ONE_SUBMITTED: 'STEP_ONE_SUBMITTED',
STEP_TWO_SUBMITTED: 'STEP_TWO_SUBMITTED',
VERIFIED: 'VERIFIED',
REJECTED: 'REJECTED'
};
exports.ProductImageRole = exports.$Enums.ProductImageRole = {
FEATURED: 'FEATURED',
FEATURED_MOBILE: 'FEATURED_MOBILE',
GALLERY: 'GALLERY',
GALLERY_MOBILE: 'GALLERY_MOBILE'
};
exports.OrderChannel = exports.$Enums.OrderChannel = {
RETAIL: 'RETAIL',
WHOLESALE: 'WHOLESALE'
};
exports.OrderStatus = exports.$Enums.OrderStatus = {
SUSPENDED: 'SUSPENDED',
PLACED: 'PLACED',
ADMIN_APPROVED: 'ADMIN_APPROVED',
SOURCING: 'SOURCING',
PREPARING: 'PREPARING',
DELIVERED_TO_AGENT: 'DELIVERED_TO_AGENT',
DELIVERED_TO_CUSTOMER: 'DELIVERED_TO_CUSTOMER',
CANCELLED: 'CANCELLED'
};
exports.PreOrderStatus = exports.$Enums.PreOrderStatus = {
PLACED: 'PLACED',
ADMIN_APPROVED: 'ADMIN_APPROVED',
WORKSHOP_APPROVED: 'WORKSHOP_APPROVED',
READY_TO_SHIP: 'READY_TO_SHIP',
DELIVERED: 'DELIVERED',
CANCELLED: 'CANCELLED'
};
exports.LedgerCurrency = exports.$Enums.LedgerCurrency = {
GOLD_GRAM: 'GOLD_GRAM',
RIAL: 'RIAL'
};
exports.LedgerDirection = exports.$Enums.LedgerDirection = {
DEBIT: 'DEBIT',
CREDIT: 'CREDIT'
};
exports.LedgerSourceType = exports.$Enums.LedgerSourceType = {
ORDER_STOCK: 'ORDER_STOCK',
ORDER_PRE_ORDER: 'ORDER_PRE_ORDER',
REPAYMENT_MELTED_GOLD: 'REPAYMENT_MELTED_GOLD',
REPAYMENT_TRANSFER: 'REPAYMENT_TRANSFER',
REPAYMENT_CHEQUE: 'REPAYMENT_CHEQUE',
REPAYMENT_CASH: 'REPAYMENT_CASH',
WALLET_BONUS: 'WALLET_BONUS',
ADJUSTMENT: 'ADJUSTMENT'
};
exports.CommentStatus = exports.$Enums.CommentStatus = {
PENDING: 'PENDING',
APPROVED: 'APPROVED',
REJECTED: 'REJECTED'
};
exports.AccountingEntityType = exports.$Enums.AccountingEntityType = {
CUSTOMER: 'CUSTOMER',
PRODUCT: 'PRODUCT'
};
exports.AccountingOutboxKind = exports.$Enums.AccountingOutboxKind = {
CUSTOMER_PUSH: 'CUSTOMER_PUSH',
PRODUCT_PUSH: 'PRODUCT_PUSH',
ORDER_DOCUMENT: 'ORDER_DOCUMENT',
ORDER_DOCUMENT_DELETE: 'ORDER_DOCUMENT_DELETE'
};
exports.AccountingOutboxStatus = exports.$Enums.AccountingOutboxStatus = {
PENDING: 'PENDING',
SENDING: 'SENDING',
SENT: 'SENT',
FAILED: 'FAILED',
NEEDS_REVIEW: 'NEEDS_REVIEW',
RESOLVED: 'RESOLVED'
};
exports.PromotionScope = exports.$Enums.PromotionScope = {
PRODUCT: 'PRODUCT',
CATEGORY: 'CATEGORY',
CUSTOMER: 'CUSTOMER',
BRAND: 'BRAND'
};
exports.PromotionUsage = exports.$Enums.PromotionUsage = {
ONCE: 'ONCE',
UNTIL_END_DATE: 'UNTIL_END_DATE'
};
exports.MessageAudience = exports.$Enums.MessageAudience = {
SINGLE_NUMBER: 'SINGLE_NUMBER',
MULTIPLE_NUMBERS: 'MULTIPLE_NUMBERS',
ALL_CUSTOMERS: 'ALL_CUSTOMERS',
WHOLESALE_CUSTOMERS: 'WHOLESALE_CUSTOMERS',
RETAIL_CUSTOMERS: 'RETAIL_CUSTOMERS',
SALES_AGENTS: 'SALES_AGENTS'
};
exports.Prisma.ModelName = {
City: 'City',
AdminUser: 'AdminUser',
AppSession: 'AppSession',
AdminSession: 'AdminSession',
SalesExpertProfile: 'SalesExpertProfile',
Customer: 'Customer',
CustomerFavorite: 'CustomerFavorite',
CustomerAddress: 'CustomerAddress',
Branch: 'Branch',
Workshop: 'Workshop',
Category: 'Category',
Brand: 'Brand',
Collection: 'Collection',
CollectionProduct: 'CollectionProduct',
Product: 'Product',
ProductImage: 'ProductImage',
ProductVariant: 'ProductVariant',
Order: 'Order',
OrderItem: 'OrderItem',
OrderStatusEvent: 'OrderStatusEvent',
PreOrder: 'PreOrder',
PreOrderStatusEvent: 'PreOrderStatusEvent',
LedgerEntry: 'LedgerEntry',
GoldPriceQuote: 'GoldPriceQuote',
WalletBonus: 'WalletBonus',
UserType: 'UserType',
Permission: 'Permission',
Comment: 'Comment',
Setting: 'Setting',
AccountingLink: 'AccountingLink',
AccountingOutbox: 'AccountingOutbox',
AccountingIssue: 'AccountingIssue',
Promotion: 'Promotion',
PromotionExclusion: 'PromotionExclusion',
BranchAllocation: 'BranchAllocation',
BranchAllocationItem: 'BranchAllocationItem',
SmsMessage: 'SmsMessage',
Notification: 'Notification',
NotificationRecipient: 'NotificationRecipient',
NewsletterSubscriber: 'NewsletterSubscriber',
CustomerActivityLog: 'CustomerActivityLog',
UploadedFile: 'UploadedFile',
CustomerOtp: 'CustomerOtp'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+97
View File
@@ -0,0 +1,97 @@
{
"name": "prisma-client-2865f0245bc905fafa9ec2b299c03c4ad7a157188246b5449e2f920f2b3a6c3d",
"main": "index.js",
"types": "index.d.ts",
"browser": "index-browser.js",
"exports": {
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.js",
"default": "./wasm.js"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.js",
"default": "./runtime/library.js"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.js",
"default": "./runtime/binary.js"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "5.22.0",
"sideEffects": false
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
export * from "./index"
+854
View File
@@ -0,0 +1,854 @@
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('@prisma/client/runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 5.22.0
* Query Engine version: 605197351a3c8bdd595af2d2a9bc3025bca48ea2
*/
Prisma.prismaVersion = {
client: "5.22.0",
engine: "605197351a3c8bdd595af2d2a9bc3025bca48ea2"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.NotFoundError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`NotFoundError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.CityScalarFieldEnum = {
id: 'id',
name: 'name',
parentId: 'parentId',
percent: 'percent',
percentAux: 'percentAux'
};
exports.Prisma.AdminUserScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
passwordHash: 'passwordHash',
role: 'role',
userTypeId: 'userTypeId',
branchId: 'branchId',
wagePercent: 'wagePercent',
isActive: 'isActive',
createdAt: 'createdAt',
avatarFileId: 'avatarFileId',
address: 'address',
storeName: 'storeName',
pasazhName: 'pasazhName',
memberCode: 'memberCode'
};
exports.Prisma.AppSessionScalarFieldEnum = {
id: 'id',
jti: 'jti',
subjectId: 'subjectId',
subjectType: 'subjectType',
mobile: 'mobile',
iamSessionId: 'iamSessionId',
iamSub: 'iamSub',
ipAddress: 'ipAddress',
userAgent: 'userAgent',
createdAt: 'createdAt',
expiresAt: 'expiresAt',
lastSeenAt: 'lastSeenAt',
revokedAt: 'revokedAt',
revokedReason: 'revokedReason'
};
exports.Prisma.AdminSessionScalarFieldEnum = {
id: 'id',
jti: 'jti',
adminUserId: 'adminUserId',
iamSessionId: 'iamSessionId',
iamSub: 'iamSub',
ipAddress: 'ipAddress',
userAgent: 'userAgent',
createdAt: 'createdAt',
expiresAt: 'expiresAt',
lastSeenAt: 'lastSeenAt',
revokedAt: 'revokedAt',
revokedReason: 'revokedReason'
};
exports.Prisma.SalesExpertProfileScalarFieldEnum = {
id: 'id',
adminUserId: 'adminUserId',
cityId: 'cityId',
gramLevel: 'gramLevel',
phone: 'phone',
nationalCode: 'nationalCode',
tradingCustomerId: 'tradingCustomerId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CustomerScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
nationalCode: 'nationalCode',
passwordHash: 'passwordHash',
cityId: 'cityId',
cityNameRaw: 'cityNameRaw',
kind: 'kind',
phone: 'phone',
address: 'address',
storeName: 'storeName',
pasazhName: 'pasazhName',
zone: 'zone',
memberCode: 'memberCode',
referredByAgentId: 'referredByAgentId',
goldCreditLimitGrams: 'goldCreditLimitGrams',
goldDebtGrams: 'goldDebtGrams',
fiatDebtRial: 'fiatDebtRial',
accountingGoldBalance: 'accountingGoldBalance',
accountingRialBalance: 'accountingRialBalance',
accountingBalanceAt: 'accountingBalanceAt',
makingChargeOffsetPercent: 'makingChargeOffsetPercent',
verificationStep: 'verificationStep',
nationalIdImageUrl: 'nationalIdImageUrl',
businessLicenseUrl: 'businessLicenseUrl',
shopImageUrl: 'shopImageUrl',
verificationRejectionReason: 'verificationRejectionReason',
verificationRejectedAt: 'verificationRejectedAt',
birthDate: 'birthDate',
avatarFileId: 'avatarFileId',
canPlaceOrders: 'canPlaceOrders',
isActive: 'isActive',
isArchived: 'isArchived',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CustomerFavoriteScalarFieldEnum = {
customerId: 'customerId',
productId: 'productId',
createdAt: 'createdAt'
};
exports.Prisma.CustomerAddressScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
cityId: 'cityId',
addressLine: 'addressLine',
isDefault: 'isDefault'
};
exports.Prisma.BranchScalarFieldEnum = {
id: 'id',
name: 'name',
address: 'address',
phone: 'phone',
isActive: 'isActive',
cityId: 'cityId'
};
exports.Prisma.WorkshopScalarFieldEnum = {
id: 'id',
name: 'name',
address: 'address',
phone: 'phone',
supervisorId: 'supervisorId',
isActive: 'isActive'
};
exports.Prisma.CategoryScalarFieldEnum = {
id: 'id',
title: 'title',
type: 'type',
imageUrl: 'imageUrl',
iconUrl: 'iconUrl',
metaTag: 'metaTag',
description: 'description',
parentId: 'parentId'
};
exports.Prisma.BrandScalarFieldEnum = {
id: 'id',
title: 'title'
};
exports.Prisma.CollectionScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
isActive: 'isActive'
};
exports.Prisma.CollectionProductScalarFieldEnum = {
collectionId: 'collectionId',
productId: 'productId'
};
exports.Prisma.ProductScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
categoryId: 'categoryId',
brandId: 'brandId',
productCode: 'productCode',
showType: 'showType',
isActive: 'isActive',
createdAt: 'createdAt',
accountingStockWeight: 'accountingStockWeight',
accountingStockCount: 'accountingStockCount',
accountingStockAt: 'accountingStockAt'
};
exports.Prisma.ProductImageScalarFieldEnum = {
id: 'id',
productId: 'productId',
url: 'url',
sortOrder: 'sortOrder',
role: 'role'
};
exports.Prisma.ProductVariantScalarFieldEnum = {
id: 'id',
productId: 'productId',
title: 'title',
sku: 'sku',
rashWeightGrams: 'rashWeightGrams',
defaultWagePercent: 'defaultWagePercent',
qty: 'qty',
isPreOrder: 'isPreOrder',
isActive: 'isActive',
imageId: 'imageId'
};
exports.Prisma.OrderScalarFieldEnum = {
id: 'id',
orderNumber: 'orderNumber',
customerId: 'customerId',
branchId: 'branchId',
channel: 'channel',
status: 'status',
totalGoldWeightGrams: 'totalGoldWeightGrams',
wageWeightGrams: 'wageWeightGrams',
flatRialCharge: 'flatRialCharge',
isSuspendedOverCreditLimit: 'isSuspendedOverCreditLimit',
deliveredAt: 'deliveredAt',
salesAgentId: 'salesAgentId',
referrerCustomerId: 'referrerCustomerId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.OrderItemScalarFieldEnum = {
id: 'id',
orderId: 'orderId',
productVariantId: 'productVariantId',
quantity: 'quantity',
rashWeightGramsSnapshot: 'rashWeightGramsSnapshot',
wagePercentSnapshot: 'wagePercentSnapshot',
discountPercentSnapshot: 'discountPercentSnapshot',
computedWeightGrams: 'computedWeightGrams',
flatRialCharge: 'flatRialCharge'
};
exports.Prisma.OrderStatusEventScalarFieldEnum = {
id: 'id',
orderId: 'orderId',
fromStatus: 'fromStatus',
toStatus: 'toStatus',
note: 'note',
actorAdminUserId: 'actorAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.PreOrderScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
cityId: 'cityId',
workshopId: 'workshopId',
status: 'status',
designImageUrl: 'designImageUrl',
colorName: 'colorName',
sizeSpec: 'sizeSpec',
stoneColor: 'stoneColor',
minaColor: 'minaColor',
estimatedWeightGrams: 'estimatedWeightGrams',
estimatedPriceRial: 'estimatedPriceRial',
description: 'description',
convertedOrderId: 'convertedOrderId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
id: 'id',
preOrderId: 'preOrderId',
changes: 'changes',
actorAdminUserId: 'actorAdminUserId',
ipAddress: 'ipAddress',
createdAt: 'createdAt'
};
exports.Prisma.LedgerEntryScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
currency: 'currency',
direction: 'direction',
sourceType: 'sourceType',
amount: 'amount',
karatPerMille: 'karatPerMille',
pricePerGramRialAtEntry: 'pricePerGramRialAtEntry',
orderId: 'orderId',
confirmed: 'confirmed',
confirmedAt: 'confirmedAt',
postedByAdminUserId: 'postedByAdminUserId',
labName: 'labName',
hallmark: 'hallmark',
accountName: 'accountName',
bankName: 'bankName',
transferTo: 'transferTo',
originalValue: 'originalValue',
priceRial: 'priceRial',
receivedAt: 'receivedAt',
note: 'note',
createdAt: 'createdAt'
};
exports.Prisma.GoldPriceQuoteScalarFieldEnum = {
id: 'id',
karatPerMille: 'karatPerMille',
pricePerGramRial: 'pricePerGramRial',
source: 'source',
quotedAt: 'quotedAt'
};
exports.Prisma.WalletBonusScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
orderId: 'orderId',
goldGrams: 'goldGrams',
grantedAt: 'grantedAt',
expiresAt: 'expiresAt',
consumedAt: 'consumedAt',
isActive: 'isActive'
};
exports.Prisma.UserTypeScalarFieldEnum = {
id: 'id',
title: 'title',
isActive: 'isActive',
createdAt: 'createdAt'
};
exports.Prisma.PermissionScalarFieldEnum = {
rid: 'rid',
userTypeId: 'userTypeId'
};
exports.Prisma.CommentScalarFieldEnum = {
id: 'id',
productId: 'productId',
customerId: 'customerId',
body: 'body',
status: 'status',
replyToId: 'replyToId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SettingScalarFieldEnum = {
key: 'key',
value: 'value',
updatedAt: 'updatedAt'
};
exports.Prisma.AccountingLinkScalarFieldEnum = {
id: 'id',
provider: 'provider',
entityType: 'entityType',
entityId: 'entityId',
externalCode: 'externalCode',
externalName: 'externalName',
origin: 'origin',
snapshot: 'snapshot',
syncedAt: 'syncedAt',
createdAt: 'createdAt'
};
exports.Prisma.AccountingOutboxScalarFieldEnum = {
id: 'id',
provider: 'provider',
kind: 'kind',
entityId: 'entityId',
status: 'status',
attempts: 'attempts',
nextAttemptAt: 'nextAttemptAt',
lastError: 'lastError',
externalRef: 'externalRef',
progress: 'progress',
lastRequest: 'lastRequest',
lastResponse: 'lastResponse',
sentAt: 'sentAt',
resolvedById: 'resolvedById',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.AccountingIssueScalarFieldEnum = {
id: 'id',
provider: 'provider',
kind: 'kind',
externalCode: 'externalCode',
entityId: 'entityId',
message: 'message',
data: 'data',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
resolvedAt: 'resolvedAt'
};
exports.Prisma.PromotionScalarFieldEnum = {
id: 'id',
title: 'title',
description: 'description',
scope: 'scope',
usage: 'usage',
discountPercent: 'discountPercent',
isActive: 'isActive',
platform: 'platform',
startsAt: 'startsAt',
endsAt: 'endsAt',
productId: 'productId',
categoryId: 'categoryId',
customerId: 'customerId',
brandId: 'brandId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.PromotionExclusionScalarFieldEnum = {
id: 'id',
type: 'type',
itemId: 'itemId',
createdAt: 'createdAt'
};
exports.Prisma.BranchAllocationScalarFieldEnum = {
id: 'id',
branchId: 'branchId',
allocatorAdminUserId: 'allocatorAdminUserId',
note: 'note',
createdAt: 'createdAt'
};
exports.Prisma.BranchAllocationItemScalarFieldEnum = {
id: 'id',
allocationId: 'allocationId',
productId: 'productId',
weightGrams: 'weightGrams'
};
exports.Prisma.SmsMessageScalarFieldEnum = {
id: 'id',
audience: 'audience',
recipients: 'recipients',
body: 'body',
sentCount: 'sentCount',
sentByAdminUserId: 'sentByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.NotificationScalarFieldEnum = {
id: 'id',
title: 'title',
body: 'body',
audience: 'audience',
sentByAdminUserId: 'sentByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.NotificationRecipientScalarFieldEnum = {
id: 'id',
notificationId: 'notificationId',
customerId: 'customerId',
readAt: 'readAt'
};
exports.Prisma.NewsletterSubscriberScalarFieldEnum = {
id: 'id',
email: 'email',
mobile: 'mobile',
createdAt: 'createdAt'
};
exports.Prisma.CustomerActivityLogScalarFieldEnum = {
id: 'id',
customerId: 'customerId',
action: 'action',
detail: 'detail',
createdAt: 'createdAt'
};
exports.Prisma.UploadedFileScalarFieldEnum = {
id: 'id',
url: 'url',
originalName: 'originalName',
mimeType: 'mimeType',
sizeBytes: 'sizeBytes',
purpose: 'purpose',
uploadedByAdminUserId: 'uploadedByAdminUserId',
createdAt: 'createdAt'
};
exports.Prisma.CustomerOtpScalarFieldEnum = {
id: 'id',
mobile: 'mobile',
customerId: 'customerId',
codeHash: 'codeHash',
expiresAt: 'expiresAt',
attempts: 'attempts',
consumedAt: 'consumedAt',
requestIp: 'requestIp',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.JsonNullValueInput = {
JsonNull: Prisma.JsonNull
};
exports.Prisma.NullableJsonNullValueInput = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.JsonNullValueFilter = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull,
AnyNull: Prisma.AnyNull
};
exports.AdminRole = exports.$Enums.AdminRole = {
SUPER_ADMIN: 'SUPER_ADMIN',
SYSTEM_APPROVER: 'SYSTEM_APPROVER',
SALES_EXPERT: 'SALES_EXPERT',
WORKSHOP_SUPERVISOR: 'WORKSHOP_SUPERVISOR',
ACCOUNTANT: 'ACCOUNTANT'
};
exports.CustomerKind = exports.$Enums.CustomerKind = {
SALES_AGENT: 'SALES_AGENT',
APP_SHOPKEEPER: 'APP_SHOPKEEPER',
WALK_IN_SHOPKEEPER: 'WALK_IN_SHOPKEEPER',
BANKER: 'BANKER',
WORKSHOP_SUPERVISOR: 'WORKSHOP_SUPERVISOR',
RETAIL: 'RETAIL'
};
exports.VerificationStep = exports.$Enums.VerificationStep = {
UNVERIFIED: 'UNVERIFIED',
STEP_ONE_SUBMITTED: 'STEP_ONE_SUBMITTED',
STEP_TWO_SUBMITTED: 'STEP_TWO_SUBMITTED',
VERIFIED: 'VERIFIED',
REJECTED: 'REJECTED'
};
exports.ProductImageRole = exports.$Enums.ProductImageRole = {
FEATURED: 'FEATURED',
FEATURED_MOBILE: 'FEATURED_MOBILE',
GALLERY: 'GALLERY',
GALLERY_MOBILE: 'GALLERY_MOBILE'
};
exports.OrderChannel = exports.$Enums.OrderChannel = {
RETAIL: 'RETAIL',
WHOLESALE: 'WHOLESALE'
};
exports.OrderStatus = exports.$Enums.OrderStatus = {
SUSPENDED: 'SUSPENDED',
PLACED: 'PLACED',
ADMIN_APPROVED: 'ADMIN_APPROVED',
SOURCING: 'SOURCING',
PREPARING: 'PREPARING',
DELIVERED_TO_AGENT: 'DELIVERED_TO_AGENT',
DELIVERED_TO_CUSTOMER: 'DELIVERED_TO_CUSTOMER',
CANCELLED: 'CANCELLED'
};
exports.PreOrderStatus = exports.$Enums.PreOrderStatus = {
PLACED: 'PLACED',
ADMIN_APPROVED: 'ADMIN_APPROVED',
WORKSHOP_APPROVED: 'WORKSHOP_APPROVED',
READY_TO_SHIP: 'READY_TO_SHIP',
DELIVERED: 'DELIVERED',
CANCELLED: 'CANCELLED'
};
exports.LedgerCurrency = exports.$Enums.LedgerCurrency = {
GOLD_GRAM: 'GOLD_GRAM',
RIAL: 'RIAL'
};
exports.LedgerDirection = exports.$Enums.LedgerDirection = {
DEBIT: 'DEBIT',
CREDIT: 'CREDIT'
};
exports.LedgerSourceType = exports.$Enums.LedgerSourceType = {
ORDER_STOCK: 'ORDER_STOCK',
ORDER_PRE_ORDER: 'ORDER_PRE_ORDER',
REPAYMENT_MELTED_GOLD: 'REPAYMENT_MELTED_GOLD',
REPAYMENT_TRANSFER: 'REPAYMENT_TRANSFER',
REPAYMENT_CHEQUE: 'REPAYMENT_CHEQUE',
REPAYMENT_CASH: 'REPAYMENT_CASH',
WALLET_BONUS: 'WALLET_BONUS',
ADJUSTMENT: 'ADJUSTMENT'
};
exports.CommentStatus = exports.$Enums.CommentStatus = {
PENDING: 'PENDING',
APPROVED: 'APPROVED',
REJECTED: 'REJECTED'
};
exports.AccountingEntityType = exports.$Enums.AccountingEntityType = {
CUSTOMER: 'CUSTOMER',
PRODUCT: 'PRODUCT'
};
exports.AccountingOutboxKind = exports.$Enums.AccountingOutboxKind = {
CUSTOMER_PUSH: 'CUSTOMER_PUSH',
PRODUCT_PUSH: 'PRODUCT_PUSH',
ORDER_DOCUMENT: 'ORDER_DOCUMENT',
ORDER_DOCUMENT_DELETE: 'ORDER_DOCUMENT_DELETE'
};
exports.AccountingOutboxStatus = exports.$Enums.AccountingOutboxStatus = {
PENDING: 'PENDING',
SENDING: 'SENDING',
SENT: 'SENT',
FAILED: 'FAILED',
NEEDS_REVIEW: 'NEEDS_REVIEW',
RESOLVED: 'RESOLVED'
};
exports.PromotionScope = exports.$Enums.PromotionScope = {
PRODUCT: 'PRODUCT',
CATEGORY: 'CATEGORY',
CUSTOMER: 'CUSTOMER',
BRAND: 'BRAND'
};
exports.PromotionUsage = exports.$Enums.PromotionUsage = {
ONCE: 'ONCE',
UNTIL_END_DATE: 'UNTIL_END_DATE'
};
exports.MessageAudience = exports.$Enums.MessageAudience = {
SINGLE_NUMBER: 'SINGLE_NUMBER',
MULTIPLE_NUMBERS: 'MULTIPLE_NUMBERS',
ALL_CUSTOMERS: 'ALL_CUSTOMERS',
WHOLESALE_CUSTOMERS: 'WHOLESALE_CUSTOMERS',
RETAIL_CUSTOMERS: 'RETAIL_CUSTOMERS',
SALES_AGENTS: 'SALES_AGENTS'
};
exports.Prisma.ModelName = {
City: 'City',
AdminUser: 'AdminUser',
AppSession: 'AppSession',
AdminSession: 'AdminSession',
SalesExpertProfile: 'SalesExpertProfile',
Customer: 'Customer',
CustomerFavorite: 'CustomerFavorite',
CustomerAddress: 'CustomerAddress',
Branch: 'Branch',
Workshop: 'Workshop',
Category: 'Category',
Brand: 'Brand',
Collection: 'Collection',
CollectionProduct: 'CollectionProduct',
Product: 'Product',
ProductImage: 'ProductImage',
ProductVariant: 'ProductVariant',
Order: 'Order',
OrderItem: 'OrderItem',
OrderStatusEvent: 'OrderStatusEvent',
PreOrder: 'PreOrder',
PreOrderStatusEvent: 'PreOrderStatusEvent',
LedgerEntry: 'LedgerEntry',
GoldPriceQuote: 'GoldPriceQuote',
WalletBonus: 'WalletBonus',
UserType: 'UserType',
Permission: 'Permission',
Comment: 'Comment',
Setting: 'Setting',
AccountingLink: 'AccountingLink',
AccountingOutbox: 'AccountingOutbox',
AccountingIssue: 'AccountingIssue',
Promotion: 'Promotion',
PromotionExclusion: 'PromotionExclusion',
BranchAllocation: 'BranchAllocation',
BranchAllocationItem: 'BranchAllocationItem',
SmsMessage: 'SmsMessage',
Notification: 'Notification',
NotificationRecipient: 'NotificationRecipient',
NewsletterSubscriber: 'NewsletterSubscriber',
CustomerActivityLog: 'CustomerActivityLog',
UploadedFile: 'UploadedFile',
CustomerOtp: 'CustomerOtp'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+27
View File
@@ -0,0 +1,27 @@
# Prisma Client &middot; [![npm version](https://img.shields.io/npm/v/@prisma/client.svg?style=flat)](https://www.npmjs.com/package/@prisma/client) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/prisma/prisma/blob/main/CONTRIBUTING.md) [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue)](https://github.com/prisma/prisma/blob/main/LICENSE) [![Discord](https://img.shields.io/discord/937751382725886062?label=Discord)](https://pris.ly/discord)
Prisma Client JS is an **auto-generated query builder** that enables **type-safe** database access and **reduces boilerplate**. You can use it as an alternative to traditional ORMs such as Sequelize, TypeORM or SQL query builders like knex.js.
It is part of the [Prisma](https://www.prisma.io/) ecosystem. Prisma provides database tools for data access, declarative data modeling, schema migrations and visual data management. Learn more in the main [`prisma`](https://github.com/prisma/prisma/) repository or read the [documentation](https://www.prisma.io/docs/).
## Getting started
Follow one of these guides to get started with Prisma Client JS:
- [Quickstart](https://www.prisma.io/docs/getting-started/quickstart) (5 min)
- [Set up a new project with Prisma (SQL migrations)](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-sql) (15 min)
- [Set up a new project with Prisma (Prisma Migrate)](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-prisma-migrate) (15 min)
- [Add Prisma to an existing project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project) (15 min)
Alternatively you can explore the ready-to-run [examples](https://github.com/prisma/prisma-examples/) (REST, GraphQL, gRPC, plain JavaScript and TypeScript demos, ...) or watch the [demo videos](https://www.youtube.com/watch?v=0RhtQgIs-TE&list=PLn2e1F9Rfr6k9PnR_figWOcSHgc_erDr5&index=1) (1-2 min per video).
## Contributing
Refer to our [contribution guidelines](https://github.com/prisma/prisma/blob/main/CONTRIBUTING.md) and [Code of Conduct for contributors](https://github.com/prisma/prisma/blob/main/CODE_OF_CONDUCT.md).
## Tests Status
- Prisma Tests Status:
[![CI](https://github.com/prisma/prisma/actions/workflows/test.yml/badge.svg)](https://github.com/prisma/prisma/actions/workflows/test.yml)
- Ecosystem Tests Status:
[![Actions Status](https://github.com/prisma/ecosystem-tests/workflows/test/badge.svg)](https://github.com/prisma/ecosystem-tests/actions)
+1
View File
@@ -0,0 +1 @@
export * from '.prisma/client/default'
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
...require('.prisma/client/default'),
}
+1
View File
@@ -0,0 +1 @@
export * from '.prisma/client/edge'
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
// https://github.com/prisma/prisma/pull/12907
...require('.prisma/client/edge'),
}
+1
View File
@@ -0,0 +1 @@
export * from './scripts/default-index'
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
// https://github.com/prisma/prisma/pull/12907
...require('./scripts/default-index'),
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
const prisma = require('.prisma/client/index-browser')
module.exports = prisma
+1
View File
@@ -0,0 +1 @@
export * from '.prisma/client/default'
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
// https://github.com/prisma/prisma/pull/12907
...require('.prisma/client/default'),
}
+280
View File
@@ -0,0 +1,280 @@
{
"name": "@prisma/client",
"version": "5.22.0",
"description": "Prisma Client is an auto-generated, type-safe and modern JavaScript/TypeScript ORM for Node.js that's tailored to your data. Supports PostgreSQL, CockroachDB, MySQL, MariaDB, SQL Server, SQLite & MongoDB databases.",
"keywords": [
"ORM",
"Prisma",
"prisma2",
"Prisma Client",
"client",
"query",
"query-builder",
"database",
"db",
"JavaScript",
"JS",
"TypeScript",
"TS",
"SQL",
"SQLite",
"pg",
"Postgres",
"PostgreSQL",
"CockroachDB",
"MySQL",
"MariaDB",
"MSSQL",
"SQL Server",
"SQLServer",
"MongoDB",
"react-native"
],
"main": "default.js",
"types": "default.d.ts",
"browser": "index-browser.js",
"exports": {
"./package.json": "./package.json",
".": {
"require": {
"types": "./default.d.ts",
"node": "./default.js",
"edge-light": "./default.js",
"workerd": "./default.js",
"worker": "./default.js",
"browser": "./index-browser.js"
},
"import": {
"types": "./default.d.ts",
"node": "./default.js",
"edge-light": "./default.js",
"workerd": "./default.js",
"worker": "./default.js",
"browser": "./index-browser.js"
},
"default": "./default.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.js",
"default": "./wasm.js"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.js",
"default": "./runtime/library.js"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.js",
"default": "./runtime/binary.js"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"license": "Apache-2.0",
"engines": {
"node": ">=16.13"
},
"homepage": "https://www.prisma.io",
"repository": {
"type": "git",
"url": "https://github.com/prisma/prisma.git",
"directory": "packages/client"
},
"author": "Tim Suchanek <suchanek@prisma.io>",
"bugs": "https://github.com/prisma/prisma/issues",
"files": [
"README.md",
"runtime",
"!runtime/*.map",
"scripts",
"generator-build",
"edge.js",
"edge.d.ts",
"wasm.js",
"wasm.d.ts",
"index.js",
"index.d.ts",
"react-native.js",
"react-native.d.ts",
"default.js",
"default.d.ts",
"index-browser.js",
"extension.js",
"extension.d.ts",
"sql.d.ts",
"sql.js",
"sql.mjs"
],
"devDependencies": {
"@cloudflare/workers-types": "4.20240614.0",
"@codspeed/benchmark.js-plugin": "3.1.1",
"@faker-js/faker": "8.4.1",
"@fast-check/jest": "1.8.2",
"@inquirer/prompts": "5.0.5",
"@jest/create-cache-key-function": "29.7.0",
"@jest/globals": "29.7.0",
"@jest/test-sequencer": "29.7.0",
"@libsql/client": "0.8.0",
"@neondatabase/serverless": "0.9.3",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "1.25.1",
"@opentelemetry/instrumentation": "0.52.1",
"@opentelemetry/resources": "1.25.1",
"@opentelemetry/sdk-trace-base": "1.25.1",
"@opentelemetry/semantic-conventions": "1.25.1",
"@planetscale/database": "1.18.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/mini-proxy": "0.9.5",
"@prisma/query-engine-wasm": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@snaplet/copycat": "0.17.3",
"@swc-node/register": "1.10.9",
"@swc/core": "1.6.13",
"@swc/jest": "0.2.36",
"@timsuchanek/copy": "1.4.5",
"@types/debug": "4.1.12",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.5.12",
"@types/js-levenshtein": "1.1.3",
"@types/mssql": "9.1.5",
"@types/node": "18.19.31",
"@types/pg": "8.11.6",
"arg": "5.0.2",
"benchmark": "2.1.4",
"ci-info": "4.0.0",
"decimal.js": "10.4.3",
"detect-runtime": "1.0.4",
"env-paths": "2.2.1",
"esbuild": "0.23.0",
"execa": "5.1.1",
"expect-type": "0.19.0",
"flat-map-polyfill": "0.3.8",
"fs-extra": "11.1.1",
"get-stream": "6.0.1",
"globby": "11.1.0",
"indent-string": "4.0.0",
"jest": "29.7.0",
"jest-extended": "4.0.2",
"jest-junit": "16.0.0",
"jest-serializer-ansi-escapes": "3.0.0",
"jest-snapshot": "29.7.0",
"js-levenshtein": "1.1.6",
"kleur": "4.1.5",
"klona": "2.0.6",
"mariadb": "3.3.1",
"memfs": "4.9.3",
"mssql": "11.0.1",
"new-github-issue-url": "0.2.1",
"node-fetch": "3.3.2",
"p-retry": "4.6.2",
"pg": "8.11.5",
"pkg-up": "3.1.0",
"pluralize": "8.0.0",
"resolve": "1.22.8",
"rimraf": "3.0.2",
"simple-statistics": "7.8.5",
"sort-keys": "4.2.0",
"source-map-support": "0.5.21",
"sql-template-tag": "5.2.1",
"stacktrace-parser": "0.1.10",
"strip-ansi": "6.0.1",
"strip-indent": "3.0.0",
"ts-node": "10.9.2",
"ts-pattern": "5.2.0",
"tsd": "0.31.1",
"typescript": "5.4.5",
"undici": "5.28.4",
"wrangler": "3.62.0",
"zx": "7.2.3",
"@prisma/adapter-d1": "5.22.0",
"@prisma/adapter-libsql": "5.22.0",
"@prisma/adapter-neon": "5.22.0",
"@prisma/adapter-pg": "5.22.0",
"@prisma/adapter-planetscale": "5.22.0",
"@prisma/driver-adapter-utils": "5.22.0",
"@prisma/adapter-pg-worker": "5.22.0",
"@prisma/debug": "5.22.0",
"@prisma/engines": "5.22.0",
"@prisma/fetch-engine": "5.22.0",
"@prisma/generator-helper": "5.22.0",
"@prisma/get-platform": "5.22.0",
"@prisma/instrumentation": "5.22.0",
"@prisma/internals": "5.22.0",
"@prisma/migrate": "5.22.0",
"@prisma/pg-worker": "5.22.0"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
},
"sideEffects": false,
"scripts": {
"dev": "DEV=true tsx helpers/build.ts",
"build": "tsx helpers/build.ts",
"test": "dotenv -e ../../.db.env -- jest --silent",
"test:e2e": "dotenv -e ../../.db.env -- tsx tests/e2e/_utils/run.ts",
"test:functional": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts",
"test:memory": "dotenv -e ../../.db.env -- tsx helpers/memory-tests.ts",
"test:functional:code": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts --no-types",
"test:functional:types": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts --types-only",
"test-notypes": "dotenv -e ../../.db.env -- jest --testPathIgnorePatterns src/__tests__/types/types.test.ts",
"generate": "node scripts/postinstall.js",
"postinstall": "node scripts/postinstall.js",
"new-test": "tsx ./helpers/new-test/new-test.ts"
}
}
@@ -0,0 +1 @@
export * from '.prisma/client/react-native'
@@ -0,0 +1,3 @@
module.exports = {
...require('.prisma/client/react-native'),
}
@@ -0,0 +1 @@
export * from "./library"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,365 @@
declare class AnyNull extends NullTypesEnumValue {
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
}
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: Runtime;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type Runtime = "edge-routine" | "workerd" | "deno" | "lagon" | "react-native" | "netlify" | "electron" | "node" | "bun" | "edge-light" | "fastly" | "unknown";
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+176
View File
@@ -0,0 +1,176 @@
'use strict'
const isObject = (val) => val !== null && typeof val === 'object' && !Array.isArray(val)
// this is a modified version of https://github.com/chalk/ansi-regex (MIT License)
const ANSI_REGEX =
/* eslint-disable-next-line no-control-regex */
/[\u001b\u009b][[\]#;?()]*(?:(?:(?:[^\W_]*;?[^\W_]*)\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g
const create = () => {
const colors = { enabled: true, visible: true, styles: {}, keys: {} }
if ('FORCE_COLOR' in process.env) {
colors.enabled = process.env.FORCE_COLOR !== '0'
}
const ansi = (style) => {
let open = (style.open = `\u001b[${style.codes[0]}m`)
let close = (style.close = `\u001b[${style.codes[1]}m`)
let regex = (style.regex = new RegExp(`\\u001b\\[${style.codes[1]}m`, 'g'))
style.wrap = (input, newline) => {
if (input.includes(close)) input = input.replace(regex, close + open)
let output = open + input + close
// see https://github.com/chalk/chalk/pull/92, thanks to the
// chalk contributors for this fix. However, we've confirmed that
// this issue is also present in Windows terminals
return newline ? output.replace(/\r*\n/g, `${close}$&${open}`) : output
}
return style
}
const wrap = (style, input, newline) => {
return typeof style === 'function' ? style(input) : style.wrap(input, newline)
}
const style = (input, stack) => {
if (input === '' || input == null) return ''
if (colors.enabled === false) return input
if (colors.visible === false) return ''
let str = '' + input
let nl = str.includes('\n')
let n = stack.length
if (n > 0 && stack.includes('unstyle')) {
stack = [...new Set(['unstyle', ...stack])].reverse()
}
while (n-- > 0) str = wrap(colors.styles[stack[n]], str, nl)
return str
}
const define = (name, codes, type) => {
colors.styles[name] = ansi({ name, codes })
let keys = colors.keys[type] || (colors.keys[type] = [])
keys.push(name)
Reflect.defineProperty(colors, name, {
configurable: true,
enumerable: true,
set(value) {
colors.alias(name, value)
},
get() {
let color = (input) => style(input, color.stack)
Reflect.setPrototypeOf(color, colors)
color.stack = this.stack ? this.stack.concat(name) : [name]
return color
},
})
}
define('reset', [0, 0], 'modifier')
define('bold', [1, 22], 'modifier')
define('dim', [2, 22], 'modifier')
define('italic', [3, 23], 'modifier')
define('underline', [4, 24], 'modifier')
define('inverse', [7, 27], 'modifier')
define('hidden', [8, 28], 'modifier')
define('strikethrough', [9, 29], 'modifier')
define('black', [30, 39], 'color')
define('red', [31, 39], 'color')
define('green', [32, 39], 'color')
define('yellow', [33, 39], 'color')
define('blue', [34, 39], 'color')
define('magenta', [35, 39], 'color')
define('cyan', [36, 39], 'color')
define('white', [37, 39], 'color')
define('gray', [90, 39], 'color')
define('grey', [90, 39], 'color')
define('bgBlack', [40, 49], 'bg')
define('bgRed', [41, 49], 'bg')
define('bgGreen', [42, 49], 'bg')
define('bgYellow', [43, 49], 'bg')
define('bgBlue', [44, 49], 'bg')
define('bgMagenta', [45, 49], 'bg')
define('bgCyan', [46, 49], 'bg')
define('bgWhite', [47, 49], 'bg')
define('blackBright', [90, 39], 'bright')
define('redBright', [91, 39], 'bright')
define('greenBright', [92, 39], 'bright')
define('yellowBright', [93, 39], 'bright')
define('blueBright', [94, 39], 'bright')
define('magentaBright', [95, 39], 'bright')
define('cyanBright', [96, 39], 'bright')
define('whiteBright', [97, 39], 'bright')
define('bgBlackBright', [100, 49], 'bgBright')
define('bgRedBright', [101, 49], 'bgBright')
define('bgGreenBright', [102, 49], 'bgBright')
define('bgYellowBright', [103, 49], 'bgBright')
define('bgBlueBright', [104, 49], 'bgBright')
define('bgMagentaBright', [105, 49], 'bgBright')
define('bgCyanBright', [106, 49], 'bgBright')
define('bgWhiteBright', [107, 49], 'bgBright')
colors.ansiRegex = ANSI_REGEX
colors.hasColor = colors.hasAnsi = (str) => {
colors.ansiRegex.lastIndex = 0
return typeof str === 'string' && str !== '' && colors.ansiRegex.test(str)
}
colors.alias = (name, color) => {
let fn = typeof color === 'string' ? colors[color] : color
if (typeof fn !== 'function') {
throw new TypeError('Expected alias to be the name of an existing color (string) or a function')
}
if (!fn.stack) {
Reflect.defineProperty(fn, 'name', { value: name })
colors.styles[name] = fn
fn.stack = [name]
}
Reflect.defineProperty(colors, name, {
configurable: true,
enumerable: true,
set(value) {
colors.alias(name, value)
},
get() {
let color = (input) => style(input, color.stack)
Reflect.setPrototypeOf(color, colors)
color.stack = this.stack ? this.stack.concat(fn.stack) : fn.stack
return color
},
})
}
colors.theme = (custom) => {
if (!isObject(custom)) throw new TypeError('Expected theme to be an object')
for (let name of Object.keys(custom)) {
colors.alias(name, custom[name])
}
return colors
}
colors.alias('unstyle', (str) => {
if (typeof str === 'string' && str !== '') {
colors.ansiRegex.lastIndex = 0
return str.replace(colors.ansiRegex, '')
}
return ''
})
colors.alias('noop', (str) => str)
colors.none = colors.clear = colors.noop
colors.stripColor = colors.unstyle
colors.define = define
return colors
}
module.exports = create()
module.exports.create = create
@@ -0,0 +1,9 @@
class PrismaClient {
constructor() {
throw new Error(
'@prisma/client/deno/edge did not initialize yet. Please run "prisma generate" and try to import it again.',
)
}
}
export { PrismaClient }
@@ -0,0 +1,110 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as runtime from '@prisma/client/runtime/library'
/**
* ## Prisma Client ʲˢ
*
* Type-safe database client for TypeScript & Node.js
* @example
* ```
* const prisma = new Prisma()
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*
*
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client).
*/
export declare const PrismaClient: any
/**
* ## Prisma Client ʲˢ
*
* Type-safe database client for TypeScript & Node.js
* @example
* ```
* const prisma = new Prisma()
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*
*
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client).
*/
export declare type PrismaClient = any
export declare class PrismaClientExtends<
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
> {
$extends: { extArgs: ExtArgs } & (<
R extends runtime.Types.Extensions.UserArgs['result'] = {},
M extends runtime.Types.Extensions.UserArgs['model'] = {},
Q extends runtime.Types.Extensions.UserArgs['query'] = {},
C extends runtime.Types.Extensions.UserArgs['client'] = {},
Args extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.InternalArgs<R, M, {}, C>,
>(
args:
| ((client: PrismaClientExtends<ExtArgs>) => { $extends: { extArgs: Args } })
| { name?: string }
| { result?: R & runtime.Types.Extensions.UserArgs['result'] }
| { model?: M & runtime.Types.Extensions.UserArgs['model'] }
| { query?: Q & runtime.Types.Extensions.UserArgs['query'] }
| { client?: C & runtime.Types.Extensions.UserArgs['client'] },
) => PrismaClientExtends<Args & ExtArgs> & Args['client'])
$transaction<R>(
fn: (prisma: Omit<this, runtime.ITXClientDenyList>) => Promise<R>,
options?: { maxWait?: number; timeout?: number; isolationLevel?: string },
): Promise<R>
$transaction<P extends Prisma.PrismaPromise<any>[]>(
arg: [...P],
options?: { isolationLevel?: string },
): Promise<runtime.Types.Utils.UnwrapTuple<P>>
}
export declare const dmmf: any
export declare type dmmf = any
/**
* Get the type of the value, that the Promise holds.
*/
export declare type PromiseType<T extends PromiseLike<any>> = T extends PromiseLike<infer U> ? U : T
/**
* Get the return type of a function which returns a Promise.
*/
export declare type PromiseReturnType<T extends (...args: any) => Promise<any>> = PromiseType<ReturnType<T>>
export namespace Prisma {
export type TransactionClient = any
export function defineExtension<
R extends runtime.Types.Extensions.UserArgs['result'] = {},
M extends runtime.Types.Extensions.UserArgs['model'] = {},
Q extends runtime.Types.Extensions.UserArgs['query'] = {},
C extends runtime.Types.Extensions.UserArgs['client'] = {},
Args extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.InternalArgs<R, M, {}, C>,
>(
args:
| ((client: PrismaClientExtends) => { $extends: { extArgs: Args } })
| { name?: string }
| { result?: R & runtime.Types.Extensions.UserArgs['result'] }
| { model?: M & runtime.Types.Extensions.UserArgs['model'] }
| { query?: Q & runtime.Types.Extensions.UserArgs['query'] }
| { client?: C & runtime.Types.Extensions.UserArgs['client'] },
): (client: any) => PrismaClientExtends<Args>
export type Extension = runtime.Types.Extensions.UserArgs
export import getExtensionContext = runtime.Extensions.getExtensionContext
export import Args = runtime.Types.Public.Args
export import Payload = runtime.Types.Public.Payload
export import Result = runtime.Types.Public.Result
export import Exact = runtime.Types.Public.Exact
export import PrismaPromise = runtime.Types.Public.PrismaPromise
export const prismaVersion: {
client: string
engine: string
}
}
@@ -0,0 +1,65 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/scripts/default-index.ts
var default_index_exports = {};
__export(default_index_exports, {
Prisma: () => Prisma,
PrismaClient: () => PrismaClient,
default: () => default_index_default
});
module.exports = __toCommonJS(default_index_exports);
// ../../node_modules/.pnpm/@prisma+engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2/node_modules/@prisma/engines-version/package.json
var prisma = {
enginesVersion: "605197351a3c8bdd595af2d2a9bc3025bca48ea2"
};
// package.json
var version = "5.22.0";
// src/runtime/utils/clientVersion.ts
var clientVersion = version;
// src/scripts/default-index.ts
var PrismaClient = class {
constructor() {
throw new Error('@prisma/client did not initialize yet. Please run "prisma generate" and try to import it again.');
}
};
function defineExtension(ext) {
if (typeof ext === "function") {
return ext;
}
return (client) => client.$extends(ext);
}
function getExtensionContext(that) {
return that;
}
var Prisma = {
defineExtension,
getExtensionContext,
prismaVersion: { client: clientVersion, engine: prisma.enginesVersion }
};
var default_index_default = { Prisma };
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Prisma,
PrismaClient
});
@@ -0,0 +1,5 @@
export function getPostInstallTrigger(): string
export const UNABLE_TO_FIND_POSTINSTALL_TRIGGER__EMPTY_STRING
export const UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING
export const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR
export const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR
@@ -0,0 +1,410 @@
// @ts-check
const childProcess = require('child_process')
const { promisify } = require('util')
const fs = require('fs')
const path = require('path')
const c = require('./colors')
const exec = promisify(childProcess.exec)
function debug(message, ...optionalParams) {
if (process.env.DEBUG && process.env.DEBUG === 'prisma:postinstall') {
console.log(message, ...optionalParams)
}
}
/**
* Adds `package.json` to the end of a path if it doesn't already exist'
* @param {string} pth
*/
function addPackageJSON(pth) {
if (pth.endsWith('package.json')) return pth
return path.join(pth, 'package.json')
}
/**
* Looks up for a `package.json` which is not `@prisma/cli` or `prisma` and returns the directory of the package
* @param {string | null} startPath - Path to Start At
* @param {number} limit - Find Up limit
* @returns {string | null}
*/
function findPackageRoot(startPath, limit = 10) {
if (!startPath || !fs.existsSync(startPath)) return null
let currentPath = startPath
// Limit traversal
for (let i = 0; i < limit; i++) {
const pkgPath = addPackageJSON(currentPath)
if (fs.existsSync(pkgPath)) {
try {
const pkg = require(pkgPath)
if (pkg.name && !['@prisma/cli', 'prisma'].includes(pkg.name)) {
return pkgPath.replace('package.json', '')
}
} catch {} // eslint-disable-line no-empty
}
currentPath = path.join(currentPath, '../')
}
return null
}
/**
* The `postinstall` hook of client sets up the ground and env vars for the `prisma generate` command,
* and runs it, showing a warning if the schema is not found.
* - initializes the ./node_modules/.prisma/client folder with the default index(-browser).js/index.d.ts,
* which define a `PrismaClient` class stub that throws an error if instantiated before the `prisma generate`
* command is successfully executed.
* - sets the path of the root of the project (TODO: to verify) to the `process.env.PRISMA_GENERATE_IN_POSTINSTALL`
* variable, or `'true'` if the project root cannot be found.
* - runs `prisma generate`, passing through additional information about the command that triggered the generation,
* which is useful for debugging/telemetry. It tries to use the local `prisma` package if it is installed, otherwise it
* falls back to the global `prisma` package. If neither options are available, it warns the user to install `prisma` first.
*/
async function main() {
if (process.env.INIT_CWD) {
process.chdir(process.env.INIT_CWD) // necessary, because npm chooses __dirname as process.cwd()
// in the postinstall hook
}
await createDefaultGeneratedThrowFiles()
// TODO: consider using the `which` package
const localPath = getLocalPackagePath()
// Only execute if !localpath
const installedGlobally = localPath ? undefined : await isInstalledGlobally()
// this is needed, so we can find the correct schemas in yarn workspace projects
const root = findPackageRoot(localPath)
process.env.PRISMA_GENERATE_IN_POSTINSTALL = root ? root : 'true'
debug({
localPath,
installedGlobally,
init_cwd: process.env.INIT_CWD,
PRISMA_GENERATE_IN_POSTINSTALL: process.env.PRISMA_GENERATE_IN_POSTINSTALL,
})
try {
if (localPath) {
await run('node', [localPath, 'generate', '--postinstall', doubleQuote(getPostInstallTrigger())])
return
}
if (installedGlobally) {
await run('prisma', ['generate', '--postinstall', doubleQuote(getPostInstallTrigger())])
return
}
} catch (e) {
// if exit code = 1 do not print
if (e && e !== 1) {
console.error(e)
}
debug(e)
}
if (!localPath && !installedGlobally) {
console.error(
`${c.yellow(
'warning',
)} In order to use "@prisma/client", please install Prisma CLI. You can install it with "npm add -D prisma".`,
)
}
}
function getLocalPackagePath() {
try {
const packagePath = require.resolve('prisma/package.json')
if (packagePath) {
return require.resolve('prisma')
}
} catch (e) {} // eslint-disable-line no-empty
// TODO: consider removing this
try {
const packagePath = require.resolve('@prisma/cli/package.json')
if (packagePath) {
return require.resolve('@prisma/cli')
}
} catch (e) {} // eslint-disable-line no-empty
return null
}
async function isInstalledGlobally() {
try {
const result = await exec('prisma -v')
if (result.stdout.includes('@prisma/client')) {
return true
} else {
console.error(`${c.yellow('warning')} You still have the ${c.bold('prisma')} cli (Prisma 1) installed globally.
Please uninstall it with either ${c.green('npm remove -g prisma')} or ${c.green('yarn global remove prisma')}.`)
}
} catch (e) {
return false
}
}
if (!process.env.PRISMA_SKIP_POSTINSTALL_GENERATE) {
main()
.catch((e) => {
if (e.stderr) {
if (e.stderr.includes(`Can't find schema.prisma`)) {
console.error(
`${c.yellow('warning')} @prisma/client needs a ${c.bold('schema.prisma')} to function, but couldn't find it.
Please either create one manually or use ${c.bold('prisma init')}.
Once you created it, run ${c.bold('prisma generate')}.
To keep Prisma related things separate, we recommend creating it in a subfolder called ${c.underline(
'./prisma',
)} like so: ${c.underline('./prisma/schema.prisma')}\n`,
)
} else {
console.error(e.stderr)
}
} else {
console.error(e)
}
process.exit(0)
})
.finally(() => {
debug(`postinstall trigger: ${getPostInstallTrigger()}`)
})
}
function run(cmd, params, cwd = process.cwd()) {
const child = childProcess.spawn(cmd, params, {
stdio: ['pipe', 'inherit', 'inherit'],
cwd,
})
return new Promise((resolve, reject) => {
child.on('close', () => {
resolve(undefined)
})
child.on('exit', (code) => {
if (code === 0) {
resolve(undefined)
} else {
reject(code)
}
})
child.on('error', () => {
reject()
})
})
}
/**
* Copies our default "throw" files into the default generation folder. These
* files are dummy and informative because they just throw an error to let the
* user know that they have forgotten to run `prisma generate` or that they
* don't have a a schema file yet. We only add these files at the default
* location `node_modules/.prisma/client`.
*/
async function createDefaultGeneratedThrowFiles() {
try {
const dotPrismaClientDir = path.join(__dirname, '../../../.prisma/client')
const denoPrismaClientDir = path.join(__dirname, '../../../.prisma/client/deno')
await makeDir(dotPrismaClientDir)
await makeDir(denoPrismaClientDir)
const defaultFileConfig = {
js: path.join(__dirname, 'default-index.js'),
ts: path.join(__dirname, 'default-index.d.ts'),
}
/**
* @type {Record<string, { js?: string; ts?: string } | undefined>}
*/
const defaultFiles = {
index: defaultFileConfig,
edge: defaultFileConfig,
default: defaultFileConfig,
wasm: defaultFileConfig,
'index-browser': {
js: path.join(__dirname, 'default-index.js'),
ts: undefined,
},
'deno/edge': {
js: undefined,
ts: path.join(__dirname, 'default-deno-edge.ts'),
},
}
for (const file of Object.keys(defaultFiles)) {
const { js, ts } = defaultFiles[file] ?? {}
const dotPrismaJsFilePath = path.join(dotPrismaClientDir, `${file}.js`)
const dotPrismaTsFilePath = path.join(dotPrismaClientDir, `${file}.d.ts`)
if (js && !fs.existsSync(dotPrismaJsFilePath) && fs.existsSync(js)) {
await fs.promises.copyFile(js, dotPrismaJsFilePath)
}
if (ts && !fs.existsSync(dotPrismaTsFilePath) && fs.existsSync(ts)) {
await fs.promises.copyFile(ts, dotPrismaTsFilePath)
}
}
} catch (e) {
console.error(e)
}
}
// TODO: can this be replaced some utility eg. mkdir
function makeDir(input) {
const make = async (pth) => {
try {
await fs.promises.mkdir(pth)
return pth
} catch (error) {
if (error.code === 'EPERM') {
throw error
}
if (error.code === 'ENOENT') {
if (path.dirname(pth) === pth) {
throw new Error(`operation not permitted, mkdir '${pth}'`)
}
if (error.message.includes('null bytes')) {
throw error
}
await make(path.dirname(pth))
return make(pth)
}
try {
const stats = await fs.promises.stat(pth)
if (!stats.isDirectory()) {
throw new Error('The path is not a directory')
}
} catch (_) {
throw error
}
return pth
}
}
return make(path.resolve(input))
}
/**
* Get the command that triggered this postinstall script being run. If there is
* an error while attempting to get this value then the string constant
* 'ERROR_WHILE_FINDING_POSTINSTALL_TRIGGER' is returned.
* This information is just necessary for telemetry.
* This is passed to `prisma generate` as a string like `--postinstall value`.
*/
function getPostInstallTrigger() {
/*
npm_config_argv` is not officially documented so here are our research notes
`npm_config_argv` is available to the postinstall script when the containing package has been installed by npm into some project.
An example of its value:
```
npm_config_argv: '{"remain":["../test"],"cooked":["add","../test"],"original":["add","../test"]}',
```
We are interesting in the data contained in the "original" field.
Trivia/Note: `npm_config_argv` is not available when running e.g. `npm install` on the containing package itself (e.g. when working on it)
Yarn mimics this data and environment variable. Here is an example following `yarn add` for the same package:
```
npm_config_argv: '{"remain":[],"cooked":["add"],"original":["add","../test"]}'
```
Other package managers like `pnpm` have not been tested.
*/
const maybe_npm_config_argv_string = process.env.npm_config_argv
if (maybe_npm_config_argv_string === undefined) {
return UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING
}
let npm_config_argv
try {
npm_config_argv = JSON.parse(maybe_npm_config_argv_string)
} catch (e) {
return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR}: ${maybe_npm_config_argv_string}`
}
if (typeof npm_config_argv !== 'object' || npm_config_argv === null) {
return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR}: ${maybe_npm_config_argv_string}`
}
const npm_config_argv_original_arr = npm_config_argv.original
if (!Array.isArray(npm_config_argv_original_arr)) {
return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR}: ${maybe_npm_config_argv_string}`
}
const npm_config_argv_original = npm_config_argv_original_arr.filter((arg) => arg !== '').join(' ')
const command =
npm_config_argv_original === ''
? getPackageManagerName()
: [getPackageManagerName(), npm_config_argv_original].join(' ')
return command
}
/**
* Wrap double quotes around the given string.
*/
function doubleQuote(x) {
return `"${x}"`
}
/**
* Get the package manager name currently being used. If parsing fails, then the following pattern is returned:
* UNKNOWN_NPM_CONFIG_USER_AGENT(<string received>).
*/
function getPackageManagerName() {
const userAgent = process.env.npm_config_user_agent
if (!userAgent) return 'MISSING_NPM_CONFIG_USER_AGENT'
const name = parsePackageManagerName(userAgent)
if (!name) return `UNKNOWN_NPM_CONFIG_USER_AGENT(${userAgent})`
return name
}
/**
* Parse package manager name from useragent. If parsing fails, `null` is returned.
*/
function parsePackageManagerName(userAgent) {
let packageManager = null
// example: 'yarn/1.22.4 npm/? node/v13.11.0 darwin x64'
// References:
// - https://pnpm.io/only-allow-pnpm
// - https://github.com/cameronhunter/npm-config-user-agent-parser
if (userAgent) {
const matchResult = userAgent.match(/^([^/]+)\/.+/)
if (matchResult) {
packageManager = matchResult[1].trim()
}
}
return packageManager
}
// prettier-ignore
const UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING'
// prettier-ignore
const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR'
// prettier-ignore
const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR'
// expose for testing
exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING = UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING
exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR = UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR
exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR = UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR
exports.getPostInstallTrigger = getPostInstallTrigger
+1
View File
@@ -0,0 +1 @@
export * from '.prisma/client/sql'
+4
View File
@@ -0,0 +1,4 @@
'use strict'
module.exports = {
...require('.prisma/client/sql'),
}
+1
View File
@@ -0,0 +1 @@
export * from '../../.prisma/client/sql/index.mjs'
+1
View File
@@ -0,0 +1 @@
export * from '.prisma/client/wasm'
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
// https://github.com/prisma/prisma/pull/12907
...require('.prisma/client/wasm'),
}