feat: multi-organization support + bank accounts
- Renamed singular /api/organization → CRUD /api/organizations - New BankAccount table with CRUD under /api/organizations/:orgId/bank-accounts - TochkaCredential gets optional bankAccountId for future per-account bank API config - Active organization stored in cookie dm_org, /api/active-organization GET/POST - activeOrgPlugin resolves req._orgId from cookie (or first non-archived as fallback) - Migration 1_multiorg: BankAccount table + data backfill from legacy Organization.bank* fields - Web: new /companies list + /companies/:id with tabs (Реквизиты, Банки и счета, Интеграции stub) - Web: OrgSwitcher dropdown in header (active org + management link) - Removed nav "Реквизиты", "Банк" — replaced by "Компании" - Per-field error highlighting wired up on new forms Existing organization data backfills cleanly: legacy bank* fields stay readable, but new BankAccount becomes the source of truth going forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
-- AlterTable: add shortName, archivedAt to Organization
|
||||
ALTER TABLE "Organization" ADD COLUMN "shortName" TEXT;
|
||||
ALTER TABLE "Organization" ADD COLUMN "archivedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateTable: BankAccount
|
||||
CREATE TABLE "BankAccount" (
|
||||
"id" UUID NOT NULL,
|
||||
"organizationId" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"bankName" TEXT,
|
||||
"bankBik" TEXT,
|
||||
"accountNumber" TEXT,
|
||||
"corrAccount" TEXT,
|
||||
"currency" TEXT NOT NULL DEFAULT 'RUB',
|
||||
"isPrimary" BOOLEAN NOT NULL DEFAULT false,
|
||||
"archivedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BankAccount_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "BankAccount_organizationId_idx" ON "BankAccount"("organizationId");
|
||||
|
||||
ALTER TABLE "BankAccount" ADD CONSTRAINT "BankAccount_organizationId_fkey"
|
||||
FOREIGN KEY ("organizationId") REFERENCES "Organization"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable: add bankAccountId on TochkaCredential
|
||||
ALTER TABLE "TochkaCredential" ADD COLUMN "bankAccountId" UUID;
|
||||
ALTER TABLE "TochkaCredential" ADD CONSTRAINT "TochkaCredential_bankAccountId_fkey"
|
||||
FOREIGN KEY ("bankAccountId") REFERENCES "BankAccount"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Data migration: для каждой организации с заполненным bank* — создать запись BankAccount(isPrimary=true)
|
||||
INSERT INTO "BankAccount" ("id", "organizationId", "name", "bankName", "bankBik", "accountNumber", "currency", "isPrimary", "createdAt", "updatedAt")
|
||||
SELECT
|
||||
gen_random_uuid(),
|
||||
o."id",
|
||||
'Основной счёт',
|
||||
o."bankName",
|
||||
o."bankBik",
|
||||
o."bankAccount",
|
||||
'RUB',
|
||||
true,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM "Organization" o
|
||||
WHERE o."bankName" IS NOT NULL OR o."bankBik" IS NOT NULL OR o."bankAccount" IS NOT NULL;
|
||||
@@ -58,15 +58,19 @@ enum PaymentKind {
|
||||
model Organization {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String
|
||||
shortName String? // короткое имя для бейджей в UI / селектора
|
||||
inn String
|
||||
kpp String?
|
||||
ogrn String?
|
||||
legalAddress String?
|
||||
// Поля bankName/bankBik/bankAccount устарели после введения BankAccount.
|
||||
// Оставлены для обратной совместимости с уже сохранёнными данными.
|
||||
bankName String?
|
||||
bankBik String?
|
||||
bankAccount String?
|
||||
signatoryName String?
|
||||
signatoryPosition String?
|
||||
archivedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -77,6 +81,27 @@ model Organization {
|
||||
payments Payment[]
|
||||
tochkaCredentials TochkaCredential[]
|
||||
auditLog AuditLog[]
|
||||
bankAccounts BankAccount[]
|
||||
}
|
||||
|
||||
model BankAccount {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organizationId String @db.Uuid
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
name String // отображаемое имя, напр. "Точка — основной"
|
||||
bankName String?
|
||||
bankBik String?
|
||||
accountNumber String? // р/счёт (20 цифр)
|
||||
corrAccount String? // к/счёт (20 цифр)
|
||||
currency String @default("RUB")
|
||||
isPrimary Boolean @default(false)
|
||||
archivedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
tochkaCredentials TochkaCredential[]
|
||||
|
||||
@@index([organizationId])
|
||||
}
|
||||
|
||||
model Client {
|
||||
@@ -209,6 +234,8 @@ model TochkaCredential {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organizationId String @db.Uuid
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
bankAccountId String? @db.Uuid
|
||||
bankAccount BankAccount? @relation(fields: [bankAccountId], references: [id])
|
||||
environment TochkaEnv
|
||||
// AES-256-GCM ciphertext (iv|tag|ct), base64
|
||||
jwtEncrypted String
|
||||
|
||||
Reference in New Issue
Block a user