feat: product design surveys, cart expiry/buy-later, and catalog polish

Add like/dislike surveys with majority alerts for manufacturing decisions, clearer accounting mapping copy, and related cart/catalog admin-app improvements.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mahmood
2026-11-12 17:55:52 +03:30
co-authored by Cursor
parent 032fa800d9
commit 7dd74ff4f7
74 changed files with 6351 additions and 893 deletions
@@ -0,0 +1,13 @@
-- Product detail opens in the customer app, for the admin «آمار» screen.
CREATE TABLE "ProductView" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ProductView_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "ProductView_productId_createdAt_idx" ON "ProductView"("productId", "createdAt");
CREATE INDEX "ProductView_createdAt_idx" ON "ProductView"("createdAt");
ALTER TABLE "ProductView" ADD CONSTRAINT "ProductView_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
-- Product labels from the «برچسب‌ها» list. IF NOT EXISTS: some databases
-- already carry this column from an earlier, unmerged migration.
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "tags" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];
UPDATE "Product" SET "tags" = ARRAY[]::TEXT[] WHERE "tags" IS NULL;
ALTER TABLE "Product" ALTER COLUMN "tags" SET DEFAULT ARRAY[]::TEXT[];
ALTER TABLE "Product" ALTER COLUMN "tags" SET NOT NULL;
CREATE INDEX IF NOT EXISTS "Product_tags_idx" ON "Product" USING GIN ("tags");
@@ -0,0 +1,13 @@
-- Server-side customer cart for daily expiry + reminder SMS (cart.expiry).
CREATE TABLE "CustomerCart" (
"customerId" TEXT NOT NULL,
"items" JSONB NOT NULL DEFAULT '[]',
"reminderSentForClearAt" TIMESTAMP(3),
"clearedAt" TIMESTAMP(3),
"updatedAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CustomerCart_pkey" PRIMARY KEY ("customerId")
);
ALTER TABLE "CustomerCart" ADD CONSTRAINT "CustomerCart_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,12 @@
-- Deferred «خرید بعدی» list — separate from CustomerCart so daily expiry
-- and order-clear never touch it, and stock is never reserved.
CREATE TABLE "CustomerBuyLater" (
"customerId" TEXT NOT NULL,
"items" JSONB NOT NULL DEFAULT '[]',
"updatedAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CustomerBuyLater_pkey" PRIMARY KEY ("customerId")
);
ALTER TABLE "CustomerBuyLater" ADD CONSTRAINT "CustomerBuyLater_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,49 @@
-- Product design survey (like/dislike before manufacturing).
CREATE TYPE "ProductSurveyStatus" AS ENUM ('PENDING_REVIEW', 'OPEN', 'CLOSED', 'APPROVED', 'REJECTED');
CREATE TYPE "ProductSurveyVoteValue" AS ENUM ('LIKE', 'DISLIKE');
CREATE TABLE "ProductSurvey" (
"id" TEXT NOT NULL,
"title" TEXT,
"imageUrl" TEXT NOT NULL,
"suggestedWeightGrams" DECIMAL(14,4) NOT NULL,
"status" "ProductSurveyStatus" NOT NULL DEFAULT 'PENDING_REVIEW',
"createdByCustomerId" TEXT,
"createdByAdminId" TEXT,
"publishedAt" TIMESTAMP(3),
"decidedAt" TIMESTAMP(3),
"decidedByAdminId" TEXT,
"decisionNote" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProductSurvey_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "ProductSurveyVote" (
"id" TEXT NOT NULL,
"surveyId" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"value" "ProductSurveyVoteValue" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProductSurveyVote_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "ProductSurvey_status_idx" ON "ProductSurvey"("status");
CREATE INDEX "ProductSurvey_createdByCustomerId_idx" ON "ProductSurvey"("createdByCustomerId");
CREATE INDEX "ProductSurvey_createdAt_idx" ON "ProductSurvey"("createdAt");
CREATE INDEX "ProductSurveyVote_customerId_idx" ON "ProductSurveyVote"("customerId");
CREATE UNIQUE INDEX "ProductSurveyVote_surveyId_customerId_key" ON "ProductSurveyVote"("surveyId", "customerId");
ALTER TABLE "ProductSurvey" ADD CONSTRAINT "ProductSurvey_createdByCustomerId_fkey" FOREIGN KEY ("createdByCustomerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "ProductSurvey" ADD CONSTRAINT "ProductSurvey_createdByAdminId_fkey" FOREIGN KEY ("createdByAdminId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "ProductSurvey" ADD CONSTRAINT "ProductSurvey_decidedByAdminId_fkey" FOREIGN KEY ("decidedByAdminId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "ProductSurveyVote" ADD CONSTRAINT "ProductSurveyVote_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "ProductSurvey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ProductSurveyVote" ADD CONSTRAINT "ProductSurveyVote_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
INSERT INTO "permission" ("rid", "userTypeId")
SELECT 'product.survey', 85
WHERE EXISTS (SELECT 1 FROM "user_type" WHERE "id" = 85)
ON CONFLICT DO NOTHING;