feat: Projects + stronger LLM placeholder substitution

Backend:
- Project model with defaults: defaultClient, defaultTemplate, defaultBankAccount
- Document gets optional projectId (FK + index)
- Migration 2_projects: enum ProjectStatus + Project table + Document.projectId
- API: /api/projects CRUD, GET /api/projects/:id/documents
- documents/routes filter by projectId

LLM prompt:
- Concrete preamble example: «ООО «...», в лице директора ... , ИП ..., ОГРНИП ...»
  → {{customer.name}}, {{customer.signatoryPosition}} {{customer.signatoryName}},
    {{executor.name}}, {{executor.ogrn}}
- Expanded placeholder list (signatoryName/Position для customer, ogrn etc)
- Side-role detection: «Заказчик» vs «Исполнитель» map to customer/executor

Frontend:
- /projects (list) and /projects/:id (defaults form + documents list under project)
- Nav: «Проекты» first
- DocumentEdit reads projectId from URL, presets default client from project,
  saves projectId with the document

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
admin
2026-05-01 13:17:07 +03:00
parent 973d85c1dd
commit b2c221e643
11 changed files with 661 additions and 17 deletions
@@ -0,0 +1,37 @@
-- CreateEnum
CREATE TYPE "ProjectStatus" AS ENUM ('active', 'completed', 'cancelled');
-- CreateTable
CREATE TABLE "Project" (
"id" UUID NOT NULL,
"organizationId" UUID NOT NULL,
"name" TEXT NOT NULL,
"status" "ProjectStatus" NOT NULL DEFAULT 'active',
"defaultClientId" UUID,
"defaultTemplateId" UUID,
"defaultBankAccountId" UUID,
"notes" TEXT,
"archivedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "Project_organizationId_archivedAt_idx" ON "Project"("organizationId", "archivedAt");
CREATE INDEX "Project_organizationId_status_idx" ON "Project"("organizationId", "status");
ALTER TABLE "Project" ADD CONSTRAINT "Project_organizationId_fkey"
FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Project" ADD CONSTRAINT "Project_defaultClientId_fkey"
FOREIGN KEY ("defaultClientId") REFERENCES "Client"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "Project" ADD CONSTRAINT "Project_defaultTemplateId_fkey"
FOREIGN KEY ("defaultTemplateId") REFERENCES "DocumentTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "Project" ADD CONSTRAINT "Project_defaultBankAccountId_fkey"
FOREIGN KEY ("defaultBankAccountId") REFERENCES "BankAccount"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AlterTable: добавить projectId на Document
ALTER TABLE "Document" ADD COLUMN "projectId" UUID;
CREATE INDEX "Document_organizationId_projectId_idx" ON "Document"("organizationId", "projectId");
ALTER TABLE "Document" ADD CONSTRAINT "Document_projectId_fkey"
FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+38
View File
@@ -55,6 +55,12 @@ enum PaymentKind {
outgoing
}
enum ProjectStatus {
active
completed
cancelled
}
model Organization {
id String @id @default(uuid()) @db.Uuid
name String
@@ -82,6 +88,31 @@ model Organization {
tochkaCredentials TochkaCredential[]
auditLog AuditLog[]
bankAccounts BankAccount[]
projects Project[]
}
model Project {
id String @id @default(uuid()) @db.Uuid
organizationId String @db.Uuid
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
status ProjectStatus @default(active)
// Дефолты — подставляются при создании документов внутри проекта
defaultClientId String? @db.Uuid
defaultClient Client? @relation(fields: [defaultClientId], references: [id])
defaultTemplateId String? @db.Uuid
defaultTemplate DocumentTemplate? @relation(fields: [defaultTemplateId], references: [id])
defaultBankAccountId String? @db.Uuid
defaultBankAccount BankAccount? @relation(fields: [defaultBankAccountId], references: [id])
notes String?
archivedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
documents Document[]
@@index([organizationId, archivedAt])
@@index([organizationId, status])
}
model BankAccount {
@@ -100,6 +131,7 @@ model BankAccount {
updatedAt DateTime @updatedAt
tochkaCredentials TochkaCredential[]
defaultForProjects Project[]
@@index([organizationId])
}
@@ -121,6 +153,7 @@ model Client {
updatedAt DateTime @updatedAt
documents Document[]
defaultForProjects Project[]
@@index([organizationId])
@@index([organizationId, name])
@@ -155,6 +188,8 @@ model DocumentTemplate {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
defaultForProjects Project[]
@@index([organizationId, docType])
}
@@ -162,6 +197,8 @@ model Document {
id String @id @default(uuid()) @db.Uuid
organizationId String @db.Uuid
organization Organization @relation(fields: [organizationId], references: [id])
projectId String? @db.Uuid
project Project? @relation(fields: [projectId], references: [id])
docType DocType
number String
issuedAt DateTime?
@@ -188,6 +225,7 @@ model Document {
@@unique([organizationId, docType, number])
@@index([organizationId, clientId, issuedAt(sort: Desc)])
@@index([organizationId, status])
@@index([organizationId, projectId])
@@index([tochkaDocumentId])
}