2026-10-10 09:03:53 +03:30
|
|
|
-- 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.
|
2026-10-27 17:35:25 +03:30
|
|
|
-- Statements are idempotent so a DB that already has these objects (db push /
|
|
|
|
|
-- prior migrate) still records this file without crashing the API boot.
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
DO $$ BEGIN
|
|
|
|
|
CREATE TYPE "AccountingEntityType" AS ENUM ('CUSTOMER', 'PRODUCT');
|
|
|
|
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
DO $$ BEGIN
|
|
|
|
|
CREATE TYPE "AccountingOutboxKind" AS ENUM ('CUSTOMER_PUSH', 'PRODUCT_PUSH', 'ORDER_DOCUMENT', 'ORDER_DOCUMENT_DELETE');
|
|
|
|
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
DO $$ BEGIN
|
|
|
|
|
CREATE TYPE "AccountingOutboxStatus" AS ENUM ('PENDING', 'SENDING', 'SENT', 'FAILED', 'NEEDS_REVIEW', 'RESOLVED');
|
|
|
|
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "accountingBalanceAt" TIMESTAMP(3);
|
|
|
|
|
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "accountingGoldBalance" DECIMAL(14,4);
|
|
|
|
|
ALTER TABLE "Customer" ADD COLUMN IF NOT EXISTS "accountingRialBalance" DECIMAL(20,2);
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "accountingStockAt" TIMESTAMP(3);
|
|
|
|
|
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "accountingStockCount" DECIMAL(14,4);
|
|
|
|
|
ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "accountingStockWeight" DECIMAL(14,4);
|
2026-10-10 09:03:53 +03:30
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
CREATE TABLE IF NOT EXISTS "AccountingLink" (
|
2026-10-10 09:03:53 +03:30
|
|
|
"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")
|
|
|
|
|
);
|
|
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
CREATE TABLE IF NOT EXISTS "AccountingOutbox" (
|
2026-10-10 09:03:53 +03:30
|
|
|
"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")
|
|
|
|
|
);
|
|
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
CREATE TABLE IF NOT EXISTS "AccountingIssue" (
|
2026-10-10 09:03:53 +03:30
|
|
|
"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")
|
|
|
|
|
);
|
|
|
|
|
|
2026-10-27 17:35:25 +03:30
|
|
|
CREATE INDEX IF NOT EXISTS "AccountingLink_entityType_entityId_idx" ON "AccountingLink"("entityType", "entityId");
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "AccountingLink_provider_entityType_entityId_key" ON "AccountingLink"("provider", "entityType", "entityId");
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "AccountingLink_provider_entityType_externalCode_key" ON "AccountingLink"("provider", "entityType", "externalCode");
|
|
|
|
|
CREATE INDEX IF NOT EXISTS "AccountingOutbox_status_nextAttemptAt_idx" ON "AccountingOutbox"("status", "nextAttemptAt");
|
|
|
|
|
CREATE INDEX IF NOT EXISTS "AccountingOutbox_kind_entityId_idx" ON "AccountingOutbox"("kind", "entityId");
|
|
|
|
|
CREATE INDEX IF NOT EXISTS "AccountingIssue_resolvedAt_idx" ON "AccountingIssue"("resolvedAt");
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "AccountingIssue_provider_kind_externalCode_key" ON "AccountingIssue"("provider", "kind", "externalCode");
|
2026-10-10 09:03:53 +03:30
|
|
|
|
|
|
|
|
-- 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;
|