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:
@@ -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;
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user