4553f63deb
- monorepo (npm workspaces): apps/api (Fastify+Prisma+TS), apps/web (Vite+React+TS), packages/shared (zod schemas) - SSO via auth.queo.ru: jose+JWKS plugin, requireDocPermission(viewer|user|admin) - DEV_BYPASS_AUTH for local development (hard-checked off in production) - M2: organization upsert, clients CRUD with search, services catalog with soft-delete - BigInt -> Number serializer for Prisma money columns - Embedded Postgres + npm run dev:demo for one-command local boot - Docker compose for queoserver: postgres + api + web (nginx as ingress proxying /api -> api:3030) - First migration 0_init committed (prisma migrate diff) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
251 lines
6.8 KiB
Plaintext
251 lines
6.8 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
|
|
inn String
|
|
kpp String?
|
|
ogrn String?
|
|
legalAddress String?
|
|
bankName String?
|
|
bankBik String?
|
|
bankAccount String?
|
|
signatoryName String?
|
|
signatoryPosition String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
clients Client[]
|
|
servicesCatalog ServiceCatalog[]
|
|
templates DocumentTemplate[]
|
|
documents Document[]
|
|
payments Payment[]
|
|
tochkaCredentials TochkaCredential[]
|
|
auditLog AuditLog[]
|
|
}
|
|
|
|
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])
|
|
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])
|
|
}
|