524789facc
- 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>
278 lines
8.0 KiB
Plaintext
278 lines
8.0 KiB
Plaintext
// Doc_manager — модель данных
|
||
// Полиморфная таблица documents для contract/invoice/act/upd.
|
||
// organization_id присутствует на всех owner-scoped таблицах (single-tenant v1, готово к multi-tenant).
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
enum DocType {
|
||
contract
|
||
invoice
|
||
act
|
||
upd
|
||
}
|
||
|
||
enum DocStatus {
|
||
draft
|
||
issued
|
||
sent
|
||
partially_paid
|
||
paid
|
||
cancelled
|
||
signed
|
||
}
|
||
|
||
enum VatRate {
|
||
none
|
||
vat_0 @map("0")
|
||
vat_5 @map("5")
|
||
vat_7 @map("7")
|
||
vat_10 @map("10")
|
||
vat_20 @map("20")
|
||
}
|
||
|
||
enum ClientKind {
|
||
ul // юр.лицо
|
||
ip // ИП
|
||
fl // физ.лицо
|
||
}
|
||
|
||
enum TochkaEnv {
|
||
sandbox
|
||
prod
|
||
}
|
||
|
||
enum PaymentKind {
|
||
incoming
|
||
incoming_sbp
|
||
incoming_sbp_b2b
|
||
outgoing
|
||
}
|
||
|
||
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
|
||
|
||
clients Client[]
|
||
servicesCatalog ServiceCatalog[]
|
||
templates DocumentTemplate[]
|
||
documents Document[]
|
||
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 {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
kind ClientKind
|
||
name String
|
||
inn String?
|
||
kpp String?
|
||
address String?
|
||
email String?
|
||
phone String?
|
||
contactPerson String?
|
||
requisitesJson Json?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
documents Document[]
|
||
|
||
@@index([organizationId])
|
||
@@index([organizationId, name])
|
||
}
|
||
|
||
model ServiceCatalog {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
name String
|
||
unit String // "шт", "час", "мес"
|
||
defaultPriceCents BigInt @default(0)
|
||
defaultVat VatRate @default(none)
|
||
notes String?
|
||
archivedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
lines DocumentLine[]
|
||
|
||
@@index([organizationId])
|
||
@@index([organizationId, archivedAt])
|
||
}
|
||
|
||
model DocumentTemplate {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
docType DocType
|
||
name String
|
||
body Json
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([organizationId, docType])
|
||
}
|
||
|
||
model Document {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
docType DocType
|
||
number String
|
||
issuedAt DateTime?
|
||
status DocStatus @default(draft)
|
||
clientId String? @db.Uuid
|
||
client Client? @relation(fields: [clientId], references: [id])
|
||
parentDocumentId String? @db.Uuid
|
||
parent Document? @relation("DocumentChildren", fields: [parentDocumentId], references: [id])
|
||
children Document[] @relation("DocumentChildren")
|
||
body Json
|
||
totalCents BigInt @default(0)
|
||
vatCents BigInt @default(0)
|
||
currency String @default("RUB")
|
||
tochkaDocumentId String?
|
||
tochkaEnvironment TochkaEnv?
|
||
pdfPath String?
|
||
createdBy String? // sub из JWT auth.queo.ru
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
lines DocumentLine[]
|
||
payments Payment[]
|
||
|
||
@@unique([organizationId, docType, number])
|
||
@@index([organizationId, clientId, issuedAt(sort: Desc)])
|
||
@@index([organizationId, status])
|
||
@@index([tochkaDocumentId])
|
||
}
|
||
|
||
model DocumentLine {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
documentId String @db.Uuid
|
||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||
position Int
|
||
serviceId String? @db.Uuid
|
||
service ServiceCatalog? @relation(fields: [serviceId], references: [id])
|
||
name String
|
||
// Количество хранится как milli-units (тысячные), чтобы поддержать дробные количества без float.
|
||
qtyMilli BigInt @default(1000)
|
||
unit String
|
||
priceCents BigInt
|
||
vat VatRate @default(none)
|
||
sumCents BigInt
|
||
|
||
@@index([documentId])
|
||
@@index([serviceId])
|
||
}
|
||
|
||
model Payment {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
documentId String? @db.Uuid
|
||
document Document? @relation(fields: [documentId], references: [id])
|
||
tochkaPaymentId String @unique
|
||
kind PaymentKind
|
||
amountCents BigInt
|
||
payerInn String?
|
||
payerName String?
|
||
purpose String?
|
||
paidAt DateTime?
|
||
raw Json
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([organizationId, paidAt(sort: Desc)])
|
||
@@index([documentId])
|
||
}
|
||
|
||
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
|
||
customerCode String
|
||
accountCode String?
|
||
expiresAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([organizationId, environment])
|
||
}
|
||
|
||
model WebhookEvent {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
receivedAt DateTime @default(now())
|
||
source String // 'tochka'
|
||
eventType String
|
||
raw Json
|
||
processedAt DateTime?
|
||
error String?
|
||
dedupeKey String @unique
|
||
|
||
@@index([source, eventType, receivedAt])
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(uuid()) @db.Uuid
|
||
organizationId String @db.Uuid
|
||
organization Organization @relation(fields: [organizationId], references: [id])
|
||
actorSub String?
|
||
action String
|
||
entity String
|
||
entityId String
|
||
diff Json?
|
||
at DateTime @default(now())
|
||
|
||
@@index([organizationId, at(sort: Desc)])
|
||
@@index([entity, entityId])
|
||
}
|