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:
admin
2026-05-01 11:08:26 +03:00
parent b28c0463b3
commit 524789facc
15 changed files with 909 additions and 200 deletions
+27
View File
@@ -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