feat: gate treasury as an org module and group money inputs

Link optional invoice and cheque settlements to cash accounts, and let superadmin enable the finance module per organization.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-18 19:33:09 +03:30
co-authored by Cursor
parent ab538b8304
commit 8981a552fc
25 changed files with 509 additions and 33 deletions
@@ -0,0 +1,37 @@
ALTER TABLE "Payment" ADD COLUMN "financialAccountId" TEXT;
ALTER TABLE "Cheque" ADD COLUMN "financialAccountId" TEXT;
ALTER TABLE "FinancialMovement" ADD COLUMN "paymentId" TEXT;
ALTER TABLE "FinancialMovement" ADD COLUMN "chequeId" TEXT;
CREATE INDEX "FinancialMovement_paymentId_idx" ON "FinancialMovement"("paymentId");
CREATE INDEX "FinancialMovement_chequeId_idx" ON "FinancialMovement"("chequeId");
CREATE TABLE "FinancialAuditLog" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"entityType" TEXT NOT NULL,
"entityId" TEXT NOT NULL,
"action" TEXT NOT NULL,
"before" JSONB,
"after" JSONB,
"actorUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FinancialAuditLog_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "FinancialAuditLog_organizationId_entityType_entityId_createdAt_idx" ON "FinancialAuditLog"("organizationId", "entityType", "entityId", "createdAt");
CREATE TABLE "FinancialAttachment" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"documentId" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"storageKey" TEXT NOT NULL,
"createdById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FinancialAttachment_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "FinancialAttachment_documentId_idx" ON "FinancialAttachment"("documentId");
ALTER TABLE "FinancialAuditLog" ADD CONSTRAINT "FinancialAuditLog_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FinancialAttachment" ADD CONSTRAINT "FinancialAttachment_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FinancialAttachment" ADD CONSTRAINT "FinancialAttachment_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "FinancialDocument"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+41
View File
@@ -211,6 +211,8 @@ model Organization {
financialPayrolls FinancialPayroll[]
financialRecurringItems FinancialRecurringItem[]
financialReconciliations FinancialReconciliation[]
financialAuditLogs FinancialAuditLog[]
financialAttachments FinancialAttachment[]
wooCommerceConnections WooCommerceConnection[]
montifyConnections MontifyConnection[]
voipConnections VoipConnection[]
@@ -1181,6 +1183,8 @@ model Payment {
method PaymentMethod @default(transfer)
paidAt DateTime @default(now())
notes String?
/// اگر پر باشد، این پرداخت یک‌بار در صندوق نشسته است
financialAccountId String?
installmentId String? @unique
installment InvoiceInstallment? @relation(fields: [installmentId], references: [id], onDelete: SetNull)
cheque Cheque?
@@ -1840,6 +1844,8 @@ model Cheque {
/// in = دریافتی، out = پرداختی
direction String @default("in")
reminderNotifiedAt DateTime?
/// حساب صندوق برای لحظهٔ وصول؛ تا قبل از وصول موجودی را عوض نمی‌کند
financialAccountId String?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -2071,6 +2077,7 @@ model FinancialDocument {
voidedAt DateTime?
voidReason String?
movements FinancialMovement[]
attachments FinancialAttachment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -2094,6 +2101,8 @@ model FinancialMovement {
installment FinancialInstallment? @relation(fields: [installmentId], references: [id], onDelete: SetNull)
payrollId String?
payroll FinancialPayroll? @relation(fields: [payrollId], references: [id], onDelete: SetNull)
paymentId String?
chequeId String?
/// in | out
direction String
amount Decimal @db.Decimal(18, 2)
@@ -2113,6 +2122,38 @@ model FinancialMovement {
@@index([receivableId])
@@index([installmentId])
@@index([payrollId])
@@index([paymentId])
@@index([chequeId])
}
model FinancialAuditLog {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
entityType String
entityId String
action String
before Json?
after Json?
actorUserId String?
createdAt DateTime @default(now())
@@index([organizationId, entityType, entityId, createdAt])
}
model FinancialAttachment {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
documentId String
document FinancialDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
fileName String
mimeType String
storageKey String
createdById String?
createdAt DateTime @default(now())
@@index([documentId])
}
model FinancialPayroll {