metallkart-erp/prisma/schema.prisma
2026-05-10 23:23:52 +03:00

2260 lines
92 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ══════════════════════════════════════════════════════════════════
// ENUMS
// ══════════════════════════════════════════════════════════════════
// CDC §9.1 — 15 rôles métier
enum Role {
ADMIN // Louis-André — tous accès
DIRECTION_FIN // Максим — validation financière
DIRECTION_OPS // Никита — validation opérationnelle
COMMERCIAL // Васильева, Мартьянов
INGENIEUR_CHEF // Ежов Т.О. — сметы, КД
INGENIEUR_PTO // Данг Ле Хай — documentation technique
ADM_PRODUCTION // Лебедева — plan production
ACHETEUR // Иванов — achats
RESP_ENTREPOT // Наталья — réception, stocks
COMPTABLE // Банкина Т.А., Банкина Анна
MAITRE_PROD // Алидин — fabrication
MAITRE_ZAGOT // Блинов — fabrication
TECHNOLOGUE // Илья — laser
MAGASINIER // Ежов Олег — склад заготовок
USER // lecture seule
}
// CDC §5.1 — Cycle de vie commande (10 statuts + CANCELLED)
enum OrderStatus {
DRAFT // Brouillon
AWAITING_ESTIMATE // En attente смета
AWAITING_VALIDATION // En attente validation financière
VALIDATED // Validé (finances OK)
LAUNCHED // Lancé en production
IN_PRODUCTION // En production
QUALITY_CONTROL // En contrôle qualité
SHIPPED // Expédié
DELIVERED // Livré
ARCHIVED // Archivé
CANCELLED // Annulé
}
// CDC §5.2 — Type de commande
enum OrderType {
STANDARD
CUSTOM
}
// CDC §5.2 — Unité d'exécution (исполнитель)
enum ExecutionUnit {
MAIN_PRODUCTION // Основное производство
PREPARATION // Заготовительный участок
SEVER // Участок Север
TLT // ТЛТ
EXTERNAL // Внешний подрядчик
}
// CDC §5.3 — Motif révision
enum RevisionReason {
PROTOTYPE_REJECTED
CLIENT_REQUEST
TECHNICAL_ERROR
OTHER
}
// CDC §5.3 — Statut révision commande
enum RevisionStatus {
DRAFT
PENDING_FINANCIAL
PENDING_PRODUCTION
APPROVED
REJECTED
}
// CDC §4.1 — Catégorie demande de paiement
enum PaymentCategory {
DIRECT_PROJECT
PRODUCTION_SUPPLY
ADMINISTRATIVE
EXCEPTIONAL
}
// CDC §4.1 — Type de récurrence
enum RecurrenceType {
MONTHLY
ANNUAL
}
// CDC §4.1 — Statut approbation
enum ApprovalStatus {
PENDING
APPROVED
REJECTED
INFO_REQUESTED
ESCALATED
}
// CDC §4.1 — Niveau d'alerte budget
enum AlertLevel {
NONE
YELLOW
RED
}
// CDC §4.3 — Type de document
enum DocumentType {
CONTRACT
UPD
INVOICE
SMETA
SPECIFICATION
PASSPORT_ZAKAZA
DELIVERY_NOTE
OTHER
APPENDIX
}
// CDC §4.3 — Statut suivi document (contrat/УПД)
enum DocumentTrackingStatus {
SENT
REMINDER_1
REMINDER_2
REMINDER_3
ESCALATED
RECEIVED
CLOSED
}
// CDC §4.3 — Canal d'envoi document
enum DocumentChannel {
EDO
EMAIL
PAPER
}
// CDC §2 — Statut fournisseur
enum SupplierStatus {
ACTIVE
INACTIVE
DISPUTE
}
enum SupplierContractStatus {
DRAFT
SIGNED
EXPIRED
}
enum SupplierPaymentTrigger {
PREPAYMENT
ON_DELIVERY
POST_DELIVERY
CUSTOM_DATE
}
// CDC §4.4 — Type de contact fournisseur
enum SupplierContactType {
COMMERCIAL
ACCOUNTANT
}
// CDC §6.1 — Catégorie matériau
enum MaterialCategory {
LASER_FLAT
LASER_TUBE
TUBE_BENDING
ROD_BENDING
CNC_MILLING
GALVANIC
MACHINING_EXT
MESH_WELDING
SPRING_COILING
METAL
FASTENERS
COMPONENTS
PAINT
}
enum ReceptionStatus {
DRAFT
VERIFIED
ACCOUNTED
DISCREPANCY
CANCELLED
}
// SPEC 13A-1 — Type de ligne смета
enum SmetaLineType {
SERVICE
MATERIAL
LABOR
}
// CDC §6.1 — Statut besoin d'achat
enum PurchaseRequirementStatus {
DRAFT
CONFIRMED
IN_TENDER
ORDERED
RECEIVED
CANCELLED
}
// CDC §6.1 — Priorité besoin d'achat
enum PurchasePriority {
LOW
NORMAL
HIGH
URGENT
}
// CDC §6.1 — Statut tender
enum TenderStatus {
DRAFT
SENT
COMPARING
AWARDED
CLOSED
}
// CDC §6.1 — Statut commande fournisseur
enum PurchaseOrderStatus {
DRAFT
CONFIRMED
SENT
SHIPPED
RECEIVED
PARTIAL
CANCELLED
}
// CDC §7.2 — Statut entrée trésorerie
enum CashflowStatus {
PLANNED
CONFIRMED
PAID
CANCELLED
}
// CDC §4.2 — Statut validation смета
// CDC §7.2 — Type trésorerie
enum CashflowType {
INCOME
EXPENSE
}
// CDC §7.2 — Source entrée trésorerie
enum CashflowSource {
ERP
C1_IMPORT
MANUAL
}
// CDC §4.2 — Statut validation смета (workflow 2 validateurs)
enum EstimateStatus {
DRAFT
SUBMITTED
FINANCIAL_REVIEW
TECHNICAL_REVIEW
REVISION_REQUESTED
APPROVED
LOCKED
SUPERSEDED
}
// ACHATS-A — Statut lot d'achat
enum PurchaseLotStatus {
PLANNED
AWAITING_VALIDATION
VALIDATED
ORDERING
ORDERED
DELIVERED
CANCELLED
}
// CDC §5.2 révisé — Type de photo commande
enum PhotoType {
EXPEDITION
PRODUCT_FINISHED
PROTOTYPE
QUALITY_CHECK
OTHER
}
// CDC §5 — Priorité notification
enum NotificationPriority {
LOW
NORMAL
HIGH
URGENT
}
// Phase 1.5 — Direction document (client ou fournisseur)
enum DocumentDirection {
SUPPLIER
CLIENT
}
// CDC §7.2 révisé — Source relevé bancaire
enum BankStatementSource {
IMPORT_1C
MANUAL
}
// CDC §7.2 révisé — Statut vérification relevé
enum VerificationStatus {
PENDING
VERIFIED
ERROR
}
// CDC §6.1 révisé — Mode paiement ТЛТ
enum TltPaymentMode {
IMMEDIATE
DEFERRED
}
// SPEC 24F — Types de surcoûts revision smeta
enum AdditionalCostType {
WASTED_MATERIAL
CANCELLATION_FEE
URGENT_REORDER
PRICE_INCREASE
OTHER
}
// SPEC 24F — Statut validation surcoût
enum AdditionalCostStatus {
PENDING
AC_APPROVED
AC_REJECTED
}
// SPEC 24F — Statut approbation revision smeta
enum RevisionApprovalStatus {
REV_PENDING
PARTIALLY_APPROVED
REV_APPROVED
REV_REJECTED
}
// SPEC 24F — Type de diff entre lignes smeta
enum SmetaDiffType {
ADDED
REMOVED
QTY_CHANGED
PRICE_CHANGED
CATEGORY_CHANGED
UNIT_CHANGED
}
enum PaymentType {
PREPAYMENT_FULL
INSTALLMENT
POST_DELIVERY
}
enum InvoiceStatus {
PENDING_VALIDATION
APPROVED_AWAITING_PAYMENT
PARTIALLY_PAID
PAID
}
enum PaymentTrigger {
ORDER_DATE
INVOICE_DATE
SHIPMENT_DATE
PRODUCTION_READY
CUSTOM_DATE
}
enum ForecastStatus {
EXTRAPOLATED
CONFIRMED
MODIFIED
REALIZED
}
enum ForecastSourceType {
AUTO_GENERATED
MANUAL_ADD
RESIDUAL
BACKFILLED
}
enum ForecastTriggerPhase {
ORDER_CREATED
SMETA_LOCKED
PRODUCTION_LAUNCHED
SHIPMENT_SIGNED
MANUAL
}
enum ClientInstallmentStatus {
PENDING
PARTIALLY_PAID
PAID
CANCELLED
}
enum ClientInvoiceStatus {
DRAFT
ISSUED
CANCELLED
}
enum DeliveryLotStatus {
DRAFT
SHIPPED
SIGNED_BY_CLIENT
DELIVERED
CANCELLED
}
enum ContractStatus {
DRAFT
ACTIVE
EXPIRED
CANCELLED
}
enum PlannedDeliveryLotStatus {
PLANNED
PARTIALLY_SHIPPED
FULLY_SHIPPED
CANCELLED
}
// ══════════════════════════════════════════════════════════════════
// MODELS
// ══════════════════════════════════════════════════════════════════
// CDC §9.1 — Utilisateurs avec rôles RBAC
model User {
id String @id @default(uuid())
email String @unique
password String
name String
nameRu String? @map("name_ru")
role Role @default(USER)
active Boolean @default(true)
phone String?
telegramId String? @map("telegram_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
ordersManaged Order[] @relation("OrderManager")
orderRevisions OrderRevision[] @relation("RevisionInitiator")
paymentRequests PaymentRequest[] @relation("PaymentRequester")
opsApprovals PaymentRequest[] @relation("OpsApprover")
finApprovals PaymentRequest[] @relation("FinApprover")
documentsResponsible Document[]
orderPhotos OrderPhoto[]
auditLogs AuditLog[]
smetasSubmitted Smeta[] @relation("SmetaSubmitter")
smetasValidated Smeta[] @relation("SmetaValidator")
estimateComments EstimateComment[] @relation("EstimateCommentAuthor")
notifications Notification[]
statusChanges OrderStatusHistory[] @relation("StatusChangeUser")
recurringPayments RecurringPayment[] @relation("RecurringPaymentCreator")
confirmedRequirements PurchaseRequirement[] @relation("RequirementConfirmer")
tendersCreated Tender[] @relation("TenderCreator")
purchaseOrdersCreated PurchaseOrder[] @relation("POCreator")
archivedRequirements PurchaseRequirement[] @relation("RequirementArchiver")
revisionAckRequirements PurchaseRequirement[] @relation("RequirementRevisionAck")
uploadedFiles DocumentFile[] @relation("UploadedFiles")
lineAwards TenderLineAward[] @relation("LineAwarder")
smetaRevisionsApproved SmetaRevision[] @relation("SmetaRevisionApprover")
smetaRevisionsCreated SmetaRevision[] @relation("SmetaRevisionCreator")
additionalCostsApproved AdditionalCost[] @relation("AdditionalCostApprover")
additionalCostsCreated AdditionalCost[] @relation("AdditionalCostCreator")
budgetFinValidations BudgetValidation[] @relation("BudgetFinValidator")
budgetTechValidations BudgetValidation[] @relation("BudgetTechValidator")
poInvoicesUploaded POInvoice[] @relation("POInvoiceUploadedBy")
prBudgetOverrides PurchaseRequirement[] @relation("PRBudgetOverride")
notificationPreferences UserNotificationPreference[]
fileTrackingConfigs FileTrackingConfig[]
forecastRebalanceLogs ForecastRebalanceLog[]
receptionsCreated PurchaseLineReception[] @relation("ReceptionCreator")
receptionsVerified PurchaseLineReception[] @relation("ReceptionVerifier")
receptionsAccounted PurchaseLineReception[] @relation("ReceptionAccountant")
orderCategoryNotes OrderCategoryNote[] @relation("OrderCategoryNoteCreator")
ordersAsConstructor Order[] @relation("OrderConstructor")
helpArticlesCreated HelpArticle[] @relation("HelpArticleCreatedBy")
@@index([role])
@@index([active])
@@map("users")
}
// CDC §4.4 — Base fournisseurs unifiée
model Supplier {
id Int @id @default(autoincrement())
companyName String @map("company_name")
inn String @unique
kpp String?
ogrn String?
legalAddress String? @map("legal_address")
contactName String? @map("contact_name")
contactPhone String? @map("contact_phone")
contactEmail String? @map("contact_email")
accountantName String? @map("accountant_name")
accountantPhone String? @map("accountant_phone")
accountantEmail String? @map("accountant_email")
productTags String[] @map("product_tags")
paymentDelayDays Int? @map("payment_delay_days")
deliveryDelayDays Int? @map("delivery_delay_days")
discountPercent Decimal? @map("discount_percent") @db.Decimal(5, 2)
notes String?
status SupplierStatus @default(ACTIVE)
isInternal Boolean @default(false) @map("is_internal")
isRegular Boolean @default(false) @map("is_regular")
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
contacts SupplierContact[]
tagLinks SupplierTagLink[]
paymentRequests PaymentRequest[]
documents Document[] @relation("SupplierDocuments")
tenderOffers TenderOffer[]
purchaseOrders PurchaseOrder[]
cashflowEntries CashflowEntry[] @relation("SupplierCashflow")
recurringPayments RecurringPayment[]
hintedRequirements PurchaseRequirement[] @relation("SupplierHint")
preferredForPRs PurchaseRequirement[] @relation("PRPreferredSupplier")
tenderDispatches TenderDispatch[] @relation("TenderDispatches")
tenderLineAwards TenderLineAward[] @relation("TenderLineAwards")
deliveryNotes SupplierDeliveryNote[] @relation("SupplierDNs")
contracts SupplierContract[] @relation("SupplierContracts")
@@index([status])
@@index([inn])
@@index([deletedAt])
@@map("suppliers")
}
model SupplierContract {
id Int @id @default(autoincrement())
supplierId Int @map("supplier_id")
supplier Supplier @relation("SupplierContracts", fields: [supplierId], references: [id])
contractNumber String @map("contract_number")
contractDate DateTime @map("contract_date") @db.Date
startDate DateTime @map("start_date") @db.Date
endDate DateTime? @map("end_date") @db.Date
status SupplierContractStatus @default(DRAFT)
notes String?
documents Document[] @relation("SupplierContractDocuments")
terms SupplierContractPaymentTerm[]
purchaseOrders PurchaseOrder[]
signedAt DateTime? @map("signed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([supplierId, contractNumber])
@@index([supplierId])
@@index([status])
@@map("supplier_contracts")
}
model SupplierContractPaymentTerm {
id Int @id @default(autoincrement())
contractId Int @map("contract_id")
contract SupplierContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
percent Decimal @db.Decimal(5, 2)
trigger SupplierPaymentTrigger
offsetDays Int @default(0) @map("offset_days")
label String
sortOrder Int @default(0) @map("sort_order")
@@index([contractId])
@@map("supplier_contract_payment_terms")
}
// CDC §4.4 — Contacts fournisseurs (opérationnels et comptables)
model SupplierContact {
id Int @id @default(autoincrement())
supplierId Int @map("supplier_id")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
type SupplierContactType
name String
phone String?
email String?
position String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([supplierId])
@@map("supplier_contacts")
}
// CDC §4.4 — Tags fournisseurs (extensibles)
model SupplierTag {
id Int @id @default(autoincrement())
name String @unique
label String?
isIndirect Boolean @default(false) @map("is_indirect")
createdAt DateTime @default(now()) @map("created_at")
// Relations
supplierLinks SupplierTagLink[]
@@map("supplier_tags")
}
// CDC §4.4 — Liaison fournisseur-tag (N-N)
model SupplierTagLink {
supplierId Int @map("supplier_id")
tagId Int @map("tag_id")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
tag SupplierTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([supplierId, tagId])
@@map("supplier_tag_links")
}
// CDC — Client
model Client {
id Int @id @default(autoincrement())
companyName String @map("company_name")
inn String? @unique
kpp String?
abbreviation String? @unique
contactName String? @map("contact_name")
phone String?
email String?
address String?
isActive Boolean @default(true) @map("is_active")
notes String?
onecGuid String? @unique @map("onec_guid")
onecSyncedAt DateTime? @map("onec_synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
orders Order[]
documents Document[] @relation("ClientDocuments")
cashflowEntries CashflowEntry[] @relation("ClientCashflow")
contracts ClientContract[]
@@index([isActive])
@@map("clients")
}
// CDC §5.1-5.2 — Fiche de commande
model Order {
id Int @id @default(autoincrement())
orderCode String @unique @map("order_code")
clientId Int @map("client_id")
client Client @relation(fields: [clientId], references: [id])
managerId String? @map("manager_id")
manager User? @relation("OrderManager", fields: [managerId], references: [id])
status OrderStatus @default(DRAFT)
orderType OrderType @default(STANDARD) @map("order_type")
executionUnit ExecutionUnit @default(MAIN_PRODUCTION) @map("execution_unit")
productName String @map("product_name")
quantity Int @default(1)
dimensions String?
colorRal String? @map("color_ral")
massKg Decimal? @map("mass_kg") @db.Decimal(10, 4)
contractNumber String? @map("contract_number")
paymentTerms Json? @map("payment_terms")
kdFolderPath String? @map("kd_folder_path")
smetaFolderPath String? @map("smeta_folder_path")
specFolderPath String? @map("spec_folder_path")
launchDate DateTime? @map("launch_date")
deliveryDate DateTime? @map("delivery_date")
cancelReason String? @map("cancel_reason")
cancelledAt DateTime? @map("cancelled_at")
thumbnailPath String? @map("thumbnail_path")
notes String?
contractId Int? @map("contract_id")
contract ClientContract? @relation("OrderClientContract", fields: [contractId], references: [id])
constructorId String? @map("constructor_id")
constructor User? @relation("OrderConstructor", fields: [constructorId], references: [id])
planningComment String? @map("planning_comment") @db.VarChar(300)
productionStatusText String? @map("production_status_text") @db.VarChar(200)
executorText String? @map("executor_text") @db.VarChar(100)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
smetas Smeta[]
revisions OrderRevision[]
financialTracking FinancialTracking?
purchaseRequirements PurchaseRequirement[]
purchaseOrders PurchaseOrder[]
paymentRequests PaymentRequest[]
photos OrderPhoto[]
documents Document[] @relation("OrderDocuments")
cashflowEntries CashflowEntry[] @relation("OrderCashflow")
statusHistory OrderStatusHistory[]
smetaRevisions SmetaRevision[]
additionalCosts AdditionalCost[]
budgetValidation BudgetValidation?
clientInvoices ClientInvoice[]
plannedDeliveryLots PlannedDeliveryLot[]
deliveryLots DeliveryLot[]
clientInstallments ClientInstallment[]
forecastInstallments ForecastInstallment[]
categoryNotes OrderCategoryNote[]
productImagePath String? @map("product_image_path")
productImageMime String? @map("product_image_mime")
productImageHash String? @map("product_image_hash")
productImageSource String? @map("product_image_source")
@@index([status])
@@index([clientId])
@@index([managerId])
@@index([constructorId])
@@index([executionUnit])
@@index([createdAt])
@@map("orders")
}
// CDC §5.1 — Historique transitions de statut commande
model OrderStatusHistory {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
fromStatus OrderStatus @map("from_status")
toStatus OrderStatus @map("to_status")
changedById String @map("changed_by_id")
changedBy User @relation("StatusChangeUser", fields: [changedById], references: [id])
comment String?
createdAt DateTime @default(now()) @map("created_at")
@@index([orderId])
@@index([createdAt])
@@map("order_status_history")
}
// CDC §5.3 — Gestion des révisions
model OrderRevision {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
version Int
reason RevisionReason
description String?
status RevisionStatus @default(DRAFT)
// Deltas financiers
deltaServices Decimal @default(0) @map("delta_services") @db.Decimal(12, 2)
deltaMaterials Decimal @default(0) @map("delta_materials") @db.Decimal(12, 2)
deltaLabor Decimal @default(0) @map("delta_labor") @db.Decimal(12, 2)
deltaSellingPrice Decimal @default(0) @map("delta_selling_price") @db.Decimal(12, 2)
additionalPurchasesDesc String? @map("additional_purchases_desc")
delayImpactDays Int? @map("delay_impact_days")
drawingChanges String? @map("drawing_changes")
generatesNewPurchases Boolean @default(false) @map("generates_new_purchases")
// Initiateur
initiatorId String @map("initiator_id")
initiator User @relation("RevisionInitiator", fields: [initiatorId], references: [id])
// Approbation financière (DIRECTION_FIN)
finApproverId String? @map("fin_approver_id")
finApprovalComment String? @map("fin_approval_comment")
finApprovedAt DateTime? @map("fin_approved_at")
// Approbation production (DIRECTION_OPS)
opsApproverId String? @map("ops_approver_id")
opsApprovalComment String? @map("ops_approval_comment")
opsApprovedAt DateTime? @map("ops_approved_at")
// Rejet
rejectedById String? @map("rejected_by_id")
rejectionComment String? @map("rejection_comment")
rejectedAt DateTime? @map("rejected_at")
// Timestamps
submittedAt DateTime? @map("submitted_at")
approvedAt DateTime? @map("approved_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([orderId, version])
@@index([orderId])
@@index([status])
@@index([initiatorId])
@@map("order_revisions")
}
// CDC §4.2, §7.1 — Budget commande (смета) avec workflow validation
model Smeta {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
version Int @default(1)
status EstimateStatus @default(DRAFT)
filePath String? @map("file_path")
totalServices Decimal @default(0) @map("total_services") @db.Decimal(12, 2)
totalMaterials Decimal @default(0) @map("total_materials") @db.Decimal(12, 2)
totalLabor Decimal @default(0) @map("total_labor") @db.Decimal(12, 2)
supplyOverhead Decimal @default(0) @map("supply_overhead") @db.Decimal(12, 2)
margin Decimal @default(0) @db.Decimal(12, 2)
tradeSurcharge Decimal @default(0) @map("trade_surcharge") @db.Decimal(12, 2)
sellingPrice Decimal @default(0) @map("selling_price") @db.Decimal(12, 2)
isLocked Boolean @default(false) @map("is_locked")
submittedById String? @map("submitted_by_id")
submittedBy User? @relation("SmetaSubmitter", fields: [submittedById], references: [id])
submittedAt DateTime? @map("submitted_at")
validatedById String? @map("validated_by_id")
validatedBy User? @relation("SmetaValidator", fields: [validatedById], references: [id])
validatedAt DateTime? @map("validated_at")
lockedAt DateTime? @map("locked_at")
previousVersionId Int? @map("previous_version_id")
previousVersion Smeta? @relation("SmetaVersions", fields: [previousVersionId], references: [id])
nextVersions Smeta[] @relation("SmetaVersions")
deltaJson Json? @map("delta_json")
importSource String? @map("import_source") // 'EXCEL' | 'MANUAL' | null
importedAt DateTime? @map("imported_at")
importedFile String? @map("imported_file") // nom du fichier importé
manualEdits Json? @map("manual_edits") // log des éditions manuelles post-import
specData Json? @map("spec_data") // parsed spec lines from СПЕЦИФИКАЦИЯ (for PR generation at LAUNCH)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
comments EstimateComment[]
lines SmetaLine[]
revisionsAsOld SmetaRevision[] @relation("OldSmeta")
revisionsAsNew SmetaRevision[] @relation("NewSmeta")
@@unique([orderId, version])
@@index([orderId])
@@index([status])
@@map("smetas")
}
// CDC §4.2 — Commentaires de validation смета
model EstimateComment {
id Int @id @default(autoincrement())
smetaId Int @map("smeta_id")
smeta Smeta @relation(fields: [smetaId], references: [id], onDelete: Cascade)
authorId String @map("author_id")
author User @relation("EstimateCommentAuthor", fields: [authorId], references: [id])
content String
createdAt DateTime @default(now()) @map("created_at")
@@index([smetaId])
@@map("estimate_comments")
}
// SPEC 13A-1 — Lignes détaillées смета (import Excel)
model SmetaLine {
id Int @id @default(autoincrement())
smetaId Int @map("smeta_id")
smeta Smeta @relation(fields: [smetaId], references: [id], onDelete: Cascade)
lineType SmetaLineType @map("line_type")
lineCategory String? @map("line_category")
description String
unit String?
quantity Decimal? @db.Decimal(12, 4)
normHours Decimal? @map("norm_hours") @db.Decimal(12, 4)
unitPrice Decimal? @map("unit_price") @db.Decimal(12, 2)
totalPrice Decimal @default(0) @map("total_price") @db.Decimal(12, 2)
position Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
@@index([smetaId])
@@map("smeta_lines")
}
// CDC §4.1 — Demandes de paiement
model PaymentRequest {
id Int @id @default(autoincrement())
requestNumber String @unique @map("request_number")
requesterId String @map("requester_id")
requester User @relation("PaymentRequester", fields: [requesterId], references: [id])
supplierId Int? @map("supplier_id")
supplier Supplier? @relation(fields: [supplierId], references: [id])
orderId Int? @map("order_id")
order Order? @relation(fields: [orderId], references: [id])
purchaseOrderId Int? @map("purchase_order_id")
purchaseOrder PurchaseOrder? @relation("POPaymentRequests", fields: [purchaseOrderId], references: [id])
amount Decimal @db.Decimal(12, 2)
category PaymentCategory
expenseCodeId Int? @map("expense_code_id")
expenseCode ExpenseCode? @relation("PRExpenseCode", fields: [expenseCodeId], references: [id])
description String
invoiceFilePath String? @map("invoice_file_path")
status ApprovalStatus @default(PENDING)
opsApproverId String? @map("ops_approver_id")
opsApprover User? @relation("OpsApprover", fields: [opsApproverId], references: [id])
opsApprovedAt DateTime? @map("ops_approved_at")
finApproverId String? @map("fin_approver_id")
finApprover User? @relation("FinApprover", fields: [finApproverId], references: [id])
finApprovedAt DateTime? @map("fin_approved_at")
rejectionComment String? @map("rejection_comment")
budgetCheckResult String? @map("budget_check_result")
budgetOverrunPercent Decimal? @map("budget_overrun_percent") @db.Decimal(5, 2)
paidAt DateTime? @map("paid_at")
dueDate DateTime? @map("due_date")
isRecurring Boolean @default(false) @map("is_recurring")
recurrenceType RecurrenceType? @map("recurrence_type")
recurrenceEndDate DateTime? @map("recurrence_end_date")
parentRecurringId Int? @map("parent_recurring_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
poInvoice POInvoice?
installment POInstallment?
@@index([status])
@@index([category])
@@index([requesterId])
@@index([orderId])
@@index([purchaseOrderId])
@@index([createdAt])
@@index([isRecurring])
@@map("payment_requests")
}
// CDC §4.1.1 — Budgets mensuels
model MonthlyBudget {
id Int @id @default(autoincrement())
category PaymentCategory
expenseCodeId Int @map("expense_code_id")
expenseCode ExpenseCode @relation("MBExpenseCode", fields: [expenseCodeId], references: [id])
month Int
year Int
allocatedAmount Decimal @map("allocated_amount") @db.Decimal(12, 2)
consumedAmount Decimal @default(0) @map("consumed_amount") @db.Decimal(12, 2)
alertThreshold Decimal @default(70) @map("alert_threshold") @db.Decimal(5, 2)
budgetMode String @default("FIXED") @map("budget_mode")
productionVolumeBase Decimal? @map("production_volume_base") @db.Decimal(12, 2)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([category, expenseCodeId, month, year])
@@map("monthly_budgets")
}
// SPEC-39A — Codes budgétaires en base (remplace enum ExpenseCodeLegacy)
model ExpenseCode {
id Int @id @default(autoincrement())
code String @unique
labelRu String @map("label_ru")
groupCode String @map("group_code")
groupLabel String @map("group_label")
parentCategory PaymentCategory
scope String @default("INDIRECT_ADMIN")
isSystem Boolean @default(true) @map("is_system")
isActive Boolean @default(true) @map("is_active")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
paymentRequests PaymentRequest[] @relation("PRExpenseCode")
monthlyBudgets MonthlyBudget[] @relation("MBExpenseCode")
recurringPayments RecurringPayment[] @relation("RPExpenseCode")
purchaseOrders PurchaseOrder[]
purchaseRequirements PurchaseRequirement[] @relation("RequirementExpenseCode")
cashflowEntries CashflowEntry[] @relation("CFExpenseCode")
smetaMappings SmetaBudgetMapping[] @relation("MappingExpenseCode")
@@index([groupCode])
@@index([parentCategory])
@@index([scope])
@@index([isActive])
@@map("expense_codes")
}
// SPEC-40D — Mapping des champs smeta vers les codes budg\u00e9taires (ventilation %)
model SmetaBudgetMapping {
id Int @id @default(autoincrement())
smetaField String @map("smeta_field")
expenseCodeId Int @map("expense_code_id")
expenseCode ExpenseCode @relation("MappingExpenseCode", fields: [expenseCodeId], references: [id])
percent Decimal @db.Decimal(5, 2)
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([smetaField, expenseCodeId])
@@index([smetaField])
@@map("smeta_budget_mappings")
}
// CDC §4.1 — Modèle de paiement récurrent (dépenses administratives)
model RecurringPayment {
id Int @id @default(autoincrement())
templateName String @map("template_name")
supplierId Int? @map("supplier_id")
supplier Supplier? @relation(fields: [supplierId], references: [id])
amount Decimal @db.Decimal(12, 2)
category PaymentCategory @default(ADMINISTRATIVE)
expenseCodeId Int @map("expense_code_id")
expenseCode ExpenseCode @relation("RPExpenseCode", fields: [expenseCodeId], references: [id])
recurrenceType RecurrenceType @map("recurrence_type")
dayOfMonth Int @default(1) @map("day_of_month")
nextGenerationDate DateTime @map("next_generation_date")
isActive Boolean @default(true) @map("is_active")
createdById String @map("created_by_id")
createdBy User @relation("RecurringPaymentCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([isActive])
@@index([nextGenerationDate])
@@map("recurring_payments")
}
// CDC §4.3 — Suivi documentaire
model Document {
id Int @id @default(autoincrement())
documentType DocumentType @map("document_type")
documentReference String? @map("document_reference")
title String
filePath String? @map("file_path")
orderId Int? @map("order_id")
order Order? @relation("OrderDocuments", fields: [orderId], references: [id])
supplierId Int? @map("supplier_id")
supplier Supplier? @relation("SupplierDocuments", fields: [supplierId], references: [id])
clientId Int? @map("client_id")
client Client? @relation("ClientDocuments", fields: [clientId], references: [id])
sentAt DateTime? @map("sent_at")
sentChannel DocumentChannel? @map("sent_channel")
returnExpectedAt DateTime? @map("return_expected_at")
returnActualAt DateTime? @map("return_actual_at")
responsibleId String? @map("responsible_id")
responsible User? @relation(fields: [responsibleId], references: [id])
trackingStatus DocumentTrackingStatus? @map("tracking_status")
nextReminderAt DateTime? @map("next_reminder_at")
escalatedAt DateTime? @map("escalated_at")
receivedAt DateTime? @map("received_at")
closedAt DateTime? @map("closed_at")
reminderCount Int @default(0) @map("reminder_count")
lastReminderAt DateTime? @map("last_reminder_at")
comment String?
direction DocumentDirection?
onecGuid String? @unique @map("onec_guid")
onecSyncedAt DateTime? @map("onec_synced_at")
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Self-relation for APPENDIX documents
parentDocumentId Int? @map("parent_document_id")
parentDocument Document? @relation("DocumentParent", fields: [parentDocumentId], references: [id])
childDocuments Document[] @relation("DocumentParent")
// FK cashflow client
clientContractId Int? @map("client_contract_id")
clientContract ClientContract? @relation(fields: [clientContractId], references: [id])
deliveryLotId Int? @map("delivery_lot_id")
deliveryLot DeliveryLot? @relation(fields: [deliveryLotId], references: [id])
clientInvoiceId Int? @map("client_invoice_id")
clientInvoice ClientInvoice? @relation(fields: [clientInvoiceId], references: [id])
supplierDeliveryNoteId Int? @map("supplier_delivery_note_id")
supplierDeliveryNote SupplierDeliveryNote? @relation("SDNDocuments", fields: [supplierDeliveryNoteId], references: [id])
supplierContractId Int? @map("supplier_contract_id")
supplierContract SupplierContract? @relation("SupplierContractDocuments", fields: [supplierContractId], references: [id])
// Relations
notifications Notification[]
files DocumentFile[] @relation("DocumentFiles")
lastFileReminderAt DateTime? @map("last_file_reminder_at")
fileReminderCount Int @default(0) @map("file_reminder_count")
@@index([documentType])
@@index([orderId])
@@index([supplierId])
@@index([returnExpectedAt])
@@index([trackingStatus])
@@index([nextReminderAt])
@@index([direction])
@@index([parentDocumentId])
@@index([deletedAt])
@@index([clientContractId])
@@index([deliveryLotId])
@@index([clientInvoiceId])
@@index([supplierDeliveryNoteId])
@@index([supplierContractId])
@@index([lastFileReminderAt])
@@map("documents")
}
// CDC §13A — Fichiers attachés aux documents
model DocumentFile {
id Int @id @default(autoincrement())
documentId Int @map("document_id")
document Document @relation("DocumentFiles", fields: [documentId], references: [id])
originalName String @map("original_name")
storedPath String @map("stored_path")
mimeType String @map("mime_type")
fileSize Int @map("file_size")
version Int @default(1)
isLatest Boolean @default(true) @map("is_latest")
uploadedById String @map("uploaded_by_id")
uploadedBy User @relation("UploadedFiles", fields: [uploadedById], references: [id])
comment String?
checksum String?
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([documentId])
@@index([documentId, isLatest])
@@index([uploadedById])
@@map("document_files")
}
// CDC §5 — Notifications centralisées
model Notification {
id Int @id @default(autoincrement())
documentId Int? @map("document_id")
document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
recipientId String @map("recipient_id")
recipient User @relation(fields: [recipientId], references: [id])
type String // REMINDER_1, REMINDER_2, REMINDER_3, ESCALATION, RECEIVED, ALERT, TASK, INFO
message String
entityType String? @map("entity_type") // ORDER, PAYMENT_REQUEST, SMETA, DOCUMENT, PURCHASE_ORDER, FINANCIAL_ALERT, SYSTEM
entityId Int? @map("entity_id")
priority NotificationPriority @default(NORMAL)
actionRequired Boolean @default(false) @map("action_required")
actionUrl String? @map("action_url")
expiresAt DateTime? @map("expires_at")
readAt DateTime? @map("read_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([documentId])
@@index([recipientId])
@@index([readAt])
@@index([entityType, entityId])
@@index([actionRequired])
@@map("notifications")
}
// CDC §4.3 — Configuration délais relance (configurable)
model ReminderConfig {
id Int @id @default(autoincrement())
key String @unique // reminder_1_days, reminder_2_days, reminder_3_days
value Int // nombre de jours
label String? // description lisible
@@map("reminder_configs")
}
// CDC §6.1 étape 1 — Besoins d'achat
model PurchaseRequirement {
id Int @id @default(autoincrement())
orderId Int? @map("order_id")
order Order? @relation(fields: [orderId], references: [id], onDelete: Cascade)
materialCategory MaterialCategory @map("material_category")
description String
quantity Decimal @db.Decimal(12, 2)
originalQuantity Decimal? @map("original_quantity") @db.Decimal(12, 2)
unit String @default("шт")
estimatedPrice Decimal? @map("estimated_price") @db.Decimal(12, 2)
status PurchaseRequirementStatus @default(DRAFT)
priority PurchasePriority @default(NORMAL)
requiredByDate DateTime? @map("required_by_date")
sourceType String? @map("source_type")
sourceId Int? @map("source_id")
confirmedById String? @map("confirmed_by_id")
confirmedBy User? @relation("RequirementConfirmer", fields: [confirmedById], references: [id])
confirmedAt DateTime? @map("confirmed_at")
quantityPerUnit Decimal? @map("quantity_per_unit") @db.Decimal(12, 4)
sourceReference String? @map("source_reference")
budgetAmount Decimal? @map("budget_amount") @db.Decimal(12, 2)
notes String?
extraPurchaseReason String? @map("extra_purchase_reason")
supplierHintId Int? @map("supplier_hint_id")
supplierHint Supplier? @relation("SupplierHint", fields: [supplierHintId], references: [id])
tenderId Int? @map("tender_id")
tender Tender? @relation(fields: [tenderId], references: [id])
archived Boolean @default(false)
archivedAt DateTime? @map("archived_at")
archivedById String? @map("archived_by_id")
archivedBy User? @relation("RequirementArchiver", fields: [archivedById], references: [id])
deletedAt DateTime? @map("deleted_at")
inStock Boolean @default(false) @map("in_stock")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// ACHATS-A — champs lotissement
totalQuantity Decimal? @map("total_quantity") @db.Decimal(10, 3)
selectedSupplierId Int? @map("selected_supplier_id")
negotiatedPrice Decimal? @map("negotiated_price") @db.Decimal(10, 2)
lots PurchaseLot[]
offerLines TenderOfferLine[]
lineAwards TenderLineAward[]
poLines PurchaseOrderLine[] @relation("POLineToPR")
// SPEC 29A — revision alert fields
revisionAlert Boolean @default(false) @map("revision_alert")
revisionNote String? @map("revision_note")
revisionAckBy String? @map("revision_ack_by")
revisionAckAt DateTime? @map("revision_ack_at")
revisionAckUser User? @relation("RequirementRevisionAck", fields: [revisionAckBy], references: [id])
// SPEC 39DE-v2 — PR indirects
isIndirect Boolean @default(false) @map("is_indirect")
expenseCodeId Int? @map("expense_code_id")
expenseCode ExpenseCode? @relation("RequirementExpenseCode", fields: [expenseCodeId], references: [id])
budgetPeriod String? @map("budget_period")
preferredSupplierId Int? @map("preferred_supplier_id")
preferredSupplier Supplier? @relation("PRPreferredSupplier", fields: [preferredSupplierId], references: [id])
purchaseOrderId Int? @map("purchase_order_id")
purchaseOrder PurchaseOrder? @relation("PRtoPO", fields: [purchaseOrderId], references: [id])
budgetOverrideBy String? @map("budget_override_by")
budgetOverrideByUser User? @relation("PRBudgetOverride", fields: [budgetOverrideBy], references: [id])
budgetOverrideAt DateTime? @map("budget_override_at")
@@index([orderId])
@@index([materialCategory])
@@index([status])
@@index([priority])
@@index([materialCategory, status])
@@index([archived])
@@index([isIndirect])
@@index([expenseCodeId])
@@index([revisionAlert])
@@map("purchase_requirements")
}
// ACHATS-A — Lotissement des besoins d'achat
model PurchaseLot {
id Int @id @default(autoincrement())
requirementId Int? @map("requirement_id")
requirement PurchaseRequirement? @relation(fields: [requirementId], references: [id], onDelete: Cascade)
lotNumber Int @map("lot_number")
quantity Decimal @db.Decimal(15, 2)
plannedDate DateTime? @map("planned_date")
paymentDate DateTime? @map("payment_date")
deliveryDate DateTime? @map("delivery_date")
deliveryDays Int? @map("delivery_days")
status PurchaseLotStatus @default(PLANNED)
validatedByFinId String? @map("validated_by_fin_id")
validatedByTechId String? @map("validated_by_tech_id")
validatedAt DateTime? @map("validated_at")
purchaseOrderId Int? @map("purchase_order_id")
purchaseOrder PurchaseOrder? @relation(fields: [purchaseOrderId], references: [id])
invoices POInvoice[]
lotLines PurchaseLotLine[]
receptions PurchaseLineReception[]
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([requirementId, lotNumber])
@@index([requirementId])
@@index([status])
@@index([purchaseOrderId])
@@map("purchase_lots")
}
model PurchaseLotLine {
id Int @id @default(autoincrement())
lotId Int @map("lot_id")
lot PurchaseLot @relation(fields: [lotId], references: [id], onDelete: Cascade)
poLineId Int @map("po_line_id")
poLine PurchaseOrderLine @relation(fields: [poLineId], references: [id], onDelete: Cascade)
quantity Decimal @db.Decimal(12, 2)
createdAt DateTime @default(now()) @map("created_at")
@@unique([lotId, poLineId])
@@index([lotId])
@@index([poLineId])
@@map("purchase_lot_lines")
}
// CDC §6.1 étapes 2-5 — Mini-appel d'offres
model Tender {
id Int @id @default(autoincrement())
referenceCode String @unique @map("reference_code")
status TenderStatus @default(DRAFT)
description String?
createdById String @map("created_by_id")
createdBy User @relation("TenderCreator", fields: [createdById], references: [id])
deadline DateTime?
awardedOfferId Int? @unique @map("awarded_offer_id")
awardedOffer TenderOffer? @relation("AwardedOffer", fields: [awardedOfferId], references: [id])
awardedAt DateTime? @map("awarded_at")
closedReason String? @map("closed_reason")
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
requirements PurchaseRequirement[]
offers TenderOffer[]
purchaseOrders PurchaseOrder[] @relation("TenderPurchaseOrders")
dispatches TenderDispatch[]
lineAwards TenderLineAward[]
@@index([status])
@@index([createdById])
@@map("tenders")
}
// CDC §6.1 étape 3-4 — Offres fournisseurs
model TenderOffer {
id Int @id @default(autoincrement())
tenderId Int @map("tender_id")
tender Tender @relation(fields: [tenderId], references: [id], onDelete: Cascade)
supplierId Int @map("supplier_id")
supplier Supplier @relation(fields: [supplierId], references: [id])
proposedPrice Decimal? @map("proposed_price") @db.Decimal(12, 2)
deliveryDays Int? @map("delivery_days")
conditions String?
receivedAt DateTime @default(now()) @map("received_at")
isSelected Boolean @default(false) @map("is_selected")
receivedVia String? @map("received_via")
validUntil DateTime? @map("valid_until")
notes String?
ranking Int?
attachmentPaths String[] @default([]) @map("attachment_paths")
// Relations
awardedTender Tender? @relation("AwardedOffer")
lines TenderOfferLine[]
lineAwards TenderLineAward[]
@@index([tenderId])
@@index([supplierId])
@@map("tender_offers")
}
// SPEC 20A — Détail ligne par ligne des offres fournisseurs
model TenderOfferLine {
id Int @id @default(autoincrement())
offerId Int @map("offer_id")
offer TenderOffer @relation(fields: [offerId], references: [id], onDelete: Cascade)
requirementId Int @map("requirement_id")
requirement PurchaseRequirement @relation(fields: [requirementId], references: [id])
unitPrice Decimal? @map("unit_price") @db.Decimal(12, 2)
quantity Decimal @db.Decimal(12, 2)
unit String @default("шт")
totalLinePrice Decimal? @map("total_line_price") @db.Decimal(12, 2)
available Boolean @default(true)
notes String?
createdAt DateTime @default(now()) @map("created_at")
@@index([offerId])
@@index([requirementId])
@@map("tender_offer_lines")
}
// SPEC 20D — Attribution ligne par ligne des tenders
model TenderLineAward {
id Int @id @default(autoincrement())
tenderId Int @map("tender_id")
tender Tender @relation(fields: [tenderId], references: [id], onDelete: Cascade)
requirementId Int @map("requirement_id")
requirement PurchaseRequirement @relation(fields: [requirementId], references: [id])
offerId Int @map("offer_id")
offer TenderOffer @relation(fields: [offerId], references: [id])
supplierId Int @map("supplier_id")
supplier Supplier @relation("TenderLineAwards", fields: [supplierId], references: [id])
unitPrice Decimal @map("unit_price") @db.Decimal(12, 2)
quantity Decimal @db.Decimal(12, 2)
totalPrice Decimal @map("total_price") @db.Decimal(12, 2)
awardedById String @map("awarded_by_id")
awardedBy User @relation("LineAwarder", fields: [awardedById], references: [id])
awardedAt DateTime @default(now()) @map("awarded_at")
purchaseOrderId Int? @map("purchase_order_id")
purchaseOrder PurchaseOrder? @relation(fields: [purchaseOrderId], references: [id])
notes String?
@@unique([tenderId, requirementId])
@@index([tenderId])
@@index([offerId])
@@index([supplierId])
@@index([purchaseOrderId])
@@map("tender_line_awards")
}
// SPEC 19B — Suivi envois consultation fournisseurs
model TenderDispatch {
id Int @id @default(autoincrement())
tenderId Int @map("tender_id")
tender Tender @relation(fields: [tenderId], references: [id], onDelete: Cascade)
supplierId Int @map("supplier_id")
supplier Supplier @relation("TenderDispatches", fields: [supplierId], references: [id])
supplierEmail String @map("supplier_email")
sentAt DateTime @default(now()) @map("sent_at")
status String @default("SENT") // SENT, RESPONDED, NO_RESPONSE
respondedAt DateTime? @map("responded_at")
notes String?
requirementIds Json? @map("requirement_ids") // Array of PR ids this supplier covers
createdAt DateTime @default(now()) @map("created_at")
@@index([tenderId])
@@index([supplierId])
@@map("tender_dispatches")
}
// CDC §6.1 étapes 5-7 — Commande fournisseur
model PurchaseOrder {
id Int @id @default(autoincrement())
orderCode String @unique @map("order_code")
supplierId Int @map("supplier_id")
supplier Supplier @relation(fields: [supplierId], references: [id])
clientOrderId Int? @map("client_order_id")
clientOrder Order? @relation(fields: [clientOrderId], references: [id])
items Json
totalAmount Decimal @map("total_amount") @db.Decimal(12, 2)
qrCodePath String? @map("qr_code_path")
emailSentAt DateTime? @map("email_sent_at")
batchNumber Int? @map("batch_number")
totalBatches Int? @map("total_batches")
status PurchaseOrderStatus @default(DRAFT)
invoiceValidated Boolean @default(false) @map("invoice_validated")
isInternalTlt Boolean @default(false) @map("is_internal_tlt")
tltPaymentMode TltPaymentMode? @map("tlt_payment_mode")
drawingFilePaths String[] @map("drawing_file_paths")
tenderId Int? @map("tender_id")
tender Tender? @relation("TenderPurchaseOrders", fields: [tenderId], references: [id])
createdById String @map("created_by_id")
createdBy User @relation("POCreator", fields: [createdById], references: [id])
expectedDeliveryDate DateTime? @map("expected_delivery_date")
actualDeliveryDate DateTime? @map("actual_delivery_date")
deliveryNotes String? @map("delivery_notes")
paymentStatus String? @map("payment_status")
paidAmount Decimal? @map("paid_amount") @db.Decimal(12, 2)
notes String?
archived Boolean @default(false)
archivedAt DateTime? @map("archived_at")
// SPEC-39A — classification budgétaire
isIndirect Boolean @default(false) @map("is_indirect")
expenseCodeId Int? @map("expense_code_id")
expenseCode ExpenseCode? @relation(fields: [expenseCodeId], references: [id])
budgetPeriod String? @map("budget_period")
budgetOverrideBy String? @map("budget_override_by")
budgetOverrideAt DateTime? @map("budget_override_at")
supplierContractId Int? @map("supplier_contract_id")
supplierContract SupplierContract? @relation(fields: [supplierContractId], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
lines PurchaseOrderLine[]
lots PurchaseLot[]
tenderLineAwards TenderLineAward[]
invoices POInvoice[]
paymentSchedule POPaymentSchedule?
paymentRequests PaymentRequest[] @relation("POPaymentRequests")
sourcePurchaseRequirements PurchaseRequirement[] @relation("PRtoPO")
supplierDeliveryNotes SupplierDeliveryNote[] @relation("PODeliveryNotes")
@@index([status])
@@index([supplierId])
@@index([clientOrderId])
@@index([tenderId])
@@index([createdById])
@@index([expectedDeliveryDate])
@@index([archived])
@@index([expenseCodeId])
@@index([isIndirect])
@@index([supplierContractId])
@@map("purchase_orders")
}
model PurchaseOrderLine {
id Int @id @default(autoincrement())
purchaseOrderId Int @map("purchase_order_id")
purchaseOrder PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade)
description String
material String
quantity Decimal @db.Decimal(12, 2)
unit String @default("шт")
estimatedPrice Decimal? @map("estimated_price") @db.Decimal(12, 2)
finalPrice Decimal? @map("final_price") @db.Decimal(12, 2)
sourceEstimateLineId Int? @map("source_estimate_line_id")
purchaseRequirementId Int? @map("purchase_requirement_id")
purchaseRequirement PurchaseRequirement? @relation("POLineToPR", fields: [purchaseRequirementId], references: [id])
consumedQuantity Decimal? @map("consumed_quantity") @db.Decimal(12, 2)
receivedQuantity Decimal @default(0) @map("received_quantity") @db.Decimal(12, 2)
receivedAt DateTime? @map("received_at")
lotLines PurchaseLotLine[]
receptions PurchaseLineReception[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([purchaseOrderId])
@@index([purchaseRequirementId])
@@map("purchase_order_lines")
}
// CDC §7.1 — Suivi coûts par commande
model FinancialTracking {
id Int @id @default(autoincrement())
orderId Int @unique @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
budgetServices Decimal @default(0) @map("budget_services") @db.Decimal(12, 2)
budgetMaterials Decimal @default(0) @map("budget_materials") @db.Decimal(12, 2)
budgetLabor Decimal @default(0) @map("budget_labor") @db.Decimal(12, 2)
budgetSupply Decimal @default(0) @map("budget_supply") @db.Decimal(12, 2)
committedServices Decimal @default(0) @map("committed_services") @db.Decimal(12, 2)
committedMaterials Decimal @default(0) @map("committed_materials") @db.Decimal(12, 2)
committedLabor Decimal @default(0) @map("committed_labor") @db.Decimal(12, 2)
committedSupply Decimal @default(0) @map("committed_supply") @db.Decimal(12, 2)
actualServices Decimal @default(0) @map("actual_services") @db.Decimal(12, 2)
actualMaterials Decimal @default(0) @map("actual_materials") @db.Decimal(12, 2)
actualLabor Decimal @default(0) @map("actual_labor") @db.Decimal(12, 2)
actualSupply Decimal @default(0) @map("actual_supply") @db.Decimal(12, 2)
alertLevel AlertLevel @default(NONE) @map("alert_level")
appliedRevisionIds Int[] @default([]) @map("applied_revision_ids")
lastRecalculatedAt DateTime? @map("last_recalculated_at")
notes String?
updatedAt DateTime @updatedAt @map("updated_at")
@@map("financial_trackings")
}
// CDC §7.2 — Trésorerie
model CashflowEntry {
id Int @id @default(autoincrement())
date DateTime
type CashflowType
category String
description String
amount Decimal @db.Decimal(14, 2)
orderId Int? @map("order_id")
order Order? @relation("OrderCashflow", fields: [orderId], references: [id])
supplierId Int? @map("supplier_id")
supplier Supplier? @relation("SupplierCashflow", fields: [supplierId], references: [id])
clientId Int? @map("client_id")
client Client? @relation("ClientCashflow", fields: [clientId], references: [id])
isActual Boolean @default(true) @map("is_actual")
source CashflowSource @default(ERP)
status CashflowStatus @default(PLANNED)
purchaseOrderId Int? @map("purchase_order_id")
paymentRequestId Int? @map("payment_request_id")
recurringPaymentId Int? @map("recurring_payment_id")
bankAccountId String? @map("bank_account_id")
expenseCodeId Int? @map("expense_code_id")
expenseCode ExpenseCode? @relation("CFExpenseCode", fields: [expenseCodeId], references: [id])
notes String?
createdById String? @map("created_by_id")
onecGuid String? @unique @map("onec_guid")
onecSyncedAt DateTime? @map("onec_synced_at")
counterparty String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([date])
@@index([type])
@@index([orderId])
@@index([isActual])
@@index([status])
@@index([bankAccountId])
@@index([category])
@@map("cashflow_entries")
}
// CDC §7.2 — Saisie manuelle temporaire 1С (ДЗ, КЗ, avances)
model ManualEntry1C {
id Int @id @default(autoincrement())
type String // DZ, KZ, ADVANCE_RECEIVED
counterparty String
description String
amount Decimal @db.Decimal(14, 2)
dueDate DateTime? @map("due_date")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([type])
@@index([isActive])
@@map("manual_entries_1c")
}
// CDC §6.5 — Table de conversion aciers
model SteelReference {
id Int @id @default(autoincrement())
profileType String @map("profile_type")
section String
weightPerMeter Decimal @map("weight_per_meter") @db.Decimal(10, 4)
@@unique([profileType, section])
@@index([profileType])
@@map("steel_references")
}
// CDC §9 — Journal d'audit
model AuditLog {
id Int @id @default(autoincrement())
userId String? @map("user_id")
user User? @relation(fields: [userId], references: [id])
action String
entityType String @map("entity_type")
entityId String @map("entity_id")
details Json?
ipAddress String? @map("ip_address")
createdAt DateTime @default(now()) @map("created_at")
@@index([userId])
@@index([entityType, entityId])
@@index([createdAt])
@@map("audit_logs")
}
// CDC §5.2 révisé — Фотофиксация commandes
model OrderPhoto {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
photoType PhotoType @map("photo_type")
filePath String @map("file_path")
fileSize Int @map("file_size")
mimeType String @map("mime_type")
description String?
takenById String? @map("taken_by_id")
takenBy User? @relation(fields: [takenById], references: [id])
isRequired Boolean @default(false) @map("is_required")
deletedAt DateTime? @map("deleted_at")
takenAt DateTime @default(now()) @map("taken_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([orderId])
@@index([photoType])
@@map("order_photos")
}
// CDC §7.2 révisé — Банковские выписки
model BankStatement {
id Int @id @default(autoincrement())
statementDate DateTime @map("statement_date")
bankAccountNumber String @map("bank_account_number")
balanceStart Decimal @map("balance_start") @db.Decimal(14, 2)
balanceEnd Decimal @map("balance_end") @db.Decimal(14, 2)
sourceFilePath String? @map("source_file_path")
source BankStatementSource @default(IMPORT_1C)
verificationStatus VerificationStatus @default(PENDING) @map("verification_status")
errorNotes String? @map("error_notes")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([statementDate])
@@index([verificationStatus])
@@map("bank_statements")
}
// 1C OData sync log
model OnecSyncLog {
id Int @id @default(autoincrement())
entity String
status String @default("RUNNING")
startedAt DateTime @default(now()) @map("started_at")
completedAt DateTime? @map("completed_at")
created Int @default(0)
updated Int @default(0)
skipped Int @default(0)
errors Int @default(0)
errorDetails String? @map("error_details")
totalFetched Int @default(0) @map("total_fetched")
@@index([entity, startedAt])
@@map("onec_sync_log")
}
// 1C OData — bank accounts reference
model OnecBankAccount {
id Int @id @default(autoincrement())
onecGuid String @unique @map("onec_guid")
name String
shortName String @map("short_name")
number String?
@@map("onec_bank_accounts")
}
// Mapping MaterialCategory → SupplierTag names (configurable)
model CategoryTagMapping {
id Int @id @default(autoincrement())
category String @unique
tagNames String[] @map("tag_names")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("category_tag_mappings")
}
// 1C OData — cashflow categories (статьи ДДС)
model OnecCashflowCategory {
id Int @id @default(autoincrement())
onecGuid String @unique @map("onec_guid")
name String
erpCategory String? @map("erp_category")
@@map("onec_cashflow_categories")
}
// SPEC 24F — Revision smeta post-lancement
model SmetaRevision {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
oldSmetaId Int @map("old_smeta_id")
oldSmeta Smeta @relation("OldSmeta", fields: [oldSmetaId], references: [id])
newSmetaId Int @map("new_smeta_id")
newSmeta Smeta @relation("NewSmeta", fields: [newSmetaId], references: [id])
diffReport Json @map("diff_report")
status RevisionApprovalStatus @default(REV_PENDING)
decisions Json?
approvedById String? @map("approved_by_id")
approvedBy User? @relation("SmetaRevisionApprover", fields: [approvedById], references: [id])
approvedAt DateTime? @map("approved_at")
createdById String @map("created_by_id")
createdBy User @relation("SmetaRevisionCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
additionalCosts AdditionalCost[]
@@index([orderId])
@@index([oldSmetaId])
@@index([newSmetaId])
@@map("smeta_revisions")
}
// SPEC 24F — Surcoûts liés aux revisions smeta
model AdditionalCost {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
revisionId Int? @map("revision_id")
revision SmetaRevision? @relation(fields: [revisionId], references: [id])
type AdditionalCostType
description String
amount Decimal @db.Decimal(12, 2)
currency String @default("RUB")
relatedPRId Int? @map("related_pr_id")
relatedPOId Int? @map("related_po_id")
status AdditionalCostStatus @default(PENDING)
approvedById String? @map("approved_by_id")
approvedBy User? @relation("AdditionalCostApprover", fields: [approvedById], references: [id])
approvedAt DateTime? @map("approved_at")
createdById String @map("created_by_id")
createdBy User @relation("AdditionalCostCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
@@index([orderId])
@@index([revisionId])
@@map("additional_costs")
}
// SPEC 27C — Budget consolidé + double validation
model BudgetValidation {
id Int @id @default(autoincrement())
orderId Int @unique @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
totalEstimated Decimal @map("total_estimated") @db.Decimal(12, 2)
totalAwarded Decimal @map("total_awarded") @db.Decimal(12, 2)
totalPO Decimal @map("total_po") @db.Decimal(12, 2)
variance Decimal @db.Decimal(12, 2)
variancePercent Decimal @map("variance_percent") @db.Decimal(10, 2)
status BudgetValidationStatus @default(PENDING)
finValidatedBy String? @map("fin_validated_by")
finValidator User? @relation("BudgetFinValidator", fields: [finValidatedBy], references: [id])
finValidatedAt DateTime? @map("fin_validated_at")
finComment String? @map("fin_comment")
techValidatedBy String? @map("tech_validated_by")
techValidator User? @relation("BudgetTechValidator", fields: [techValidatedBy], references: [id])
techValidatedAt DateTime? @map("tech_validated_at")
techComment String? @map("tech_comment")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([status])
@@map("budget_validations")
}
enum BudgetValidationStatus {
PENDING
FIN_APPROVED
TECH_APPROVED
FULLY_APPROVED
REJECTED
}
// ─── POInvoice (facture fournisseur liée à un PO) ─────────────────
model POInvoice {
id Int @id @default(autoincrement())
purchaseOrderId Int @map("purchase_order_id")
purchaseOrder PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade)
lotId Int? @map("lot_id")
lot PurchaseLot? @relation(fields: [lotId], references: [id])
invoiceNumber String? @map("invoice_number")
amount Decimal? @db.Decimal(12, 2)
status InvoiceStatus @default(PENDING_VALIDATION)
filePath String @map("file_path")
fileName String @map("file_name")
dueDate DateTime? @map("due_date")
paidAt DateTime? @map("paid_at")
paidAmount Decimal? @map("paid_amount") @db.Decimal(12, 2)
paymentRequestId Int? @unique @map("payment_request_id")
paymentRequest PaymentRequest? @relation(fields: [paymentRequestId], references: [id])
uploadedById String @map("uploaded_by_id")
uploadedBy User @relation("POInvoiceUploadedBy", fields: [uploadedById], references: [id])
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([purchaseOrderId])
@@index([lotId])
@@index([status])
@@map("po_invoices")
}
// ─── POPaymentSchedule (conditions de paiement d'un PO) ───────────
model POPaymentSchedule {
id Int @id @default(autoincrement())
purchaseOrderId Int @unique @map("purchase_order_id")
purchaseOrder PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade)
paymentType PaymentType @map("payment_type")
postDeliveryDays Int? @map("post_delivery_days")
postpayMode PostpayMode @default(SIMPLE) @map("postpay_mode")
source POPaymentScheduleSource @default(MANUAL)
sourceContractId Int? @map("source_contract_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
installments POInstallment[]
@@map("po_payment_schedules")
}
// ─── POInstallment (tranches de paiement) ─────────────────────────
model POInstallment {
id Int @id @default(autoincrement())
scheduleId Int @map("schedule_id")
schedule POPaymentSchedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
percentage Decimal @db.Decimal(5, 2)
amount Decimal? @db.Decimal(12, 2)
dueDate DateTime? @map("due_date")
label String?
paidAt DateTime? @map("paid_at")
paidAmount Decimal? @map("paid_amount") @db.Decimal(12, 2)
sortOrder Int @default(0) @map("sort_order")
isPostDelivery Boolean @default(false) @map("is_post_delivery")
paymentRequestId Int? @unique @map("payment_request_id")
paymentRequest PaymentRequest? @relation(fields: [paymentRequestId], references: [id])
@@index([scheduleId])
@@map("po_installments")
}
model UserNotificationPreference {
id Int @id @default(autoincrement())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
category String // PAYMENT_DUE, PR_APPROVE, PR_CONFIRM, TENDER_OFFER, SMETA_REVISION, PO_DELIVERY, LOT_ORDER_DUE
inApp Boolean @default(true) @map("in_app")
email Boolean @default(false)
telegram Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, category])
@@map("user_notification_preferences")
}
// ══════════════════════════════════════════════════════════════════
// CASHFLOW ENTRÉES CLIENTS (S42C)
// ══════════════════════════════════════════════════════════════════
model PaymentTemplate {
id Int @id @default(autoincrement())
name String
description String?
isActive Boolean @default(true)
terms PaymentTemplateTerm[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("payment_templates")
}
model PaymentTemplateTerm {
id Int @id @default(autoincrement())
templateId Int @map("template_id")
template PaymentTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
percent Decimal @db.Decimal(5, 2)
trigger PaymentTrigger
offsetDays Int @default(0) @map("offset_days")
label String
sortOrder Int @default(0) @map("sort_order")
@@map("payment_template_terms")
}
model ClientInvoice {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id])
invoiceNumber String @unique @map("invoice_number")
invoiceDate DateTime @map("invoice_date") @db.Date
amount Decimal @db.Decimal(14, 2)
status ClientInvoiceStatus @default(DRAFT)
notes String?
installments ClientInstallment[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
documents Document[]
@@index([orderId])
@@map("client_invoices")
}
model PlannedDeliveryLot {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id])
label String @db.VarChar(120)
plannedDate DateTime @map("planned_date") @db.Date
quantity Decimal @default(1) @db.Decimal(14, 4)
amountSnapshot Decimal? @map("amount_snapshot") @db.Decimal(14, 2)
sortOrder Int @default(0) @map("sort_order")
status PlannedDeliveryLotStatus @default(PLANNED)
notes String?
deliveryLot DeliveryLot? @relation("PlannedToActual")
forecasts ForecastInstallment[] @relation("PlannedLotForecasts")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@map("planned_delivery_lots")
}
model DeliveryLot {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id])
updNumber String @unique @map("upd_number")
updDate DateTime @map("upd_date") @db.Date
amount Decimal @db.Decimal(14, 2)
status DeliveryLotStatus @default(DRAFT)
notes String?
signedByClientAt DateTime? @map("signed_by_client_at")
plannedLotId Int? @unique @map("planned_lot_id")
plannedLot PlannedDeliveryLot? @relation("PlannedToActual", fields: [plannedLotId], references: [id], onDelete: SetNull)
installments ClientInstallment[] @relation("DeliveryLotInstallments")
documents Document[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@map("delivery_lots")
}
model ClientInstallment {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id])
invoiceId Int? @map("invoice_id")
invoice ClientInvoice? @relation(fields: [invoiceId], references: [id])
shipmentId Int? @map("shipment_id")
shipment DeliveryLot? @relation("DeliveryLotInstallments", fields: [shipmentId], references: [id])
contractTermId Int? @map("contract_term_id")
contractTerm ContractPaymentTerm? @relation(fields: [contractTermId], references: [id], onDelete: SetNull)
forecastLink ForecastInstallment? @relation("ForecastLink")
amount Decimal @db.Decimal(14, 2)
dueDate DateTime @map("due_date") @db.Date
trigger PaymentTrigger
label String
status ClientInstallmentStatus @default(PENDING)
notes String?
payments ClientPaymentReceived[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@index([invoiceId])
@@index([shipmentId])
@@index([contractTermId])
@@index([dueDate])
@@map("client_installments")
}
model ClientPaymentReceived {
id Int @id @default(autoincrement())
installmentId Int @map("installment_id")
installment ClientInstallment @relation(fields: [installmentId], references: [id])
amount Decimal @db.Decimal(14, 2)
receivedAt DateTime @map("received_at") @db.Date
bankReference String? @map("bank_reference")
onecId String? @map("onec_id")
notes String?
createdAt DateTime @default(now()) @map("created_at")
@@index([installmentId])
@@index([onecId])
@@map("client_payments_received")
}
model ForecastInstallment {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
contractTermId Int? @map("contract_term_id")
contractTerm ContractPaymentTerm? @relation(fields: [contractTermId], references: [id], onDelete: SetNull)
expectedDate DateTime @map("expected_date") @db.Date
expectedAmount Decimal @map("expected_amount") @db.Decimal(14, 2)
realizedAmount Decimal @default(0) @map("realized_amount") @db.Decimal(14, 2)
status ForecastStatus @default(EXTRAPOLATED)
sourceType ForecastSourceType @default(AUTO_GENERATED) @map("source_type")
triggerPhase ForecastTriggerPhase @default(ORDER_CREATED) @map("trigger_phase")
wasManuallyEdited Boolean @default(false) @map("was_manually_edited")
linkedInstallmentId Int? @unique @map("linked_installment_id")
linkedInstallment ClientInstallment? @relation("ForecastLink", fields: [linkedInstallmentId], references: [id], onDelete: SetNull)
parentForecastId Int? @map("parent_forecast_id")
parentForecast ForecastInstallment? @relation("ForecastResidualChain", fields: [parentForecastId], references: [id], onDelete: SetNull)
childForecasts ForecastInstallment[] @relation("ForecastResidualChain")
rebalancedFromLinkId Int? @map("rebalanced_from_link_id")
rebalancedFromLink ForecastInstallment? @relation("RebalanceChain", fields: [rebalancedFromLinkId], references: [id])
rebalancedTargets ForecastInstallment[] @relation("RebalanceChain")
rebalanceDelta Decimal? @map("rebalance_delta") @db.Decimal(14, 2)
rebalanceLogsAsTarget ForecastRebalanceLog[] @relation("RebalanceLogTarget")
rebalanceLogsAsSource ForecastRebalanceLog[] @relation("RebalanceLogSource")
plannedLotId Int? @map("planned_lot_id")
plannedLot PlannedDeliveryLot? @relation("PlannedLotForecasts", fields: [plannedLotId], references: [id], onDelete: SetNull)
label String?
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@index([expectedDate])
@@index([status])
@@index([rebalancedFromLinkId])
@@index([plannedLotId])
@@map("forecast_installments")
}
model ClientContract {
id Int @id @default(autoincrement())
clientId Int @map("client_id")
client Client @relation(fields: [clientId], references: [id])
contractNumber String @map("contract_number")
contractDate DateTime @map("contract_date") @db.Date
startDate DateTime @map("start_date") @db.Date
endDate DateTime? @map("end_date") @db.Date
status ContractStatus @default(DRAFT)
notes String?
terms ContractPaymentTerm[]
orders Order[] @relation("OrderClientContract")
documents Document[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([clientId, contractNumber])
@@index([clientId])
@@index([status])
@@map("client_contracts")
}
model ContractPaymentTerm {
id Int @id @default(autoincrement())
contractId Int @map("contract_id")
contract ClientContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
percent Decimal @db.Decimal(5, 2)
trigger PaymentTrigger
offsetDays Int @default(0) @map("offset_days")
label String
sortOrder Int @default(0) @map("sort_order")
forecasts ForecastInstallment[]
installments ClientInstallment[]
@@index([contractId])
@@map("contract_payment_terms")
}
// ══════════════════════════════════════════════════════════════════
// S42K — Configuration du tracking documents sans fichier
// ══════════════════════════════════════════════════════════════════
model FileTrackingConfig {
id String @id @default("singleton")
clientContractThresholdDays Int @default(15) @map("client_contract_threshold_days")
deliveryLotThresholdDays Int @default(10) @map("delivery_lot_threshold_days")
clientInvoiceThresholdDays Int @default(3) @map("client_invoice_threshold_days")
purchaseOrderThresholdDays Int @default(7) @map("purchase_order_threshold_days")
supplierContractThresholdDays Int @default(15) @map("supplier_contract_threshold_days")
escalationThresholdDays Int @default(30) @map("escalation_threshold_days")
reminderIntervalDays Int @default(7) @map("reminder_interval_days")
enabled Boolean @default(true)
updatedAt DateTime @updatedAt @map("updated_at")
updatedById String? @map("updated_by_id")
updatedBy User? @relation(fields: [updatedById], references: [id])
@@map("file_tracking_configs")
}
model ForecastRebalanceLog {
id String @id @default(uuid())
targetId Int @map("target_id")
target ForecastInstallment @relation("RebalanceLogTarget", fields: [targetId], references: [id])
sourceId Int @map("source_id")
source ForecastInstallment @relation("RebalanceLogSource", fields: [sourceId], references: [id])
delta Decimal @db.Decimal(14, 2)
appliedAt DateTime @default(now()) @map("applied_at")
reversedAt DateTime? @map("reversed_at")
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
@@index([targetId, reversedAt])
@@index([sourceId, reversedAt])
@@map("forecast_rebalance_logs")
}
// S42-PLANNING-1 — Réceptions au niveau position PO
model PurchaseLineReception {
id Int @id @default(autoincrement())
poLineId Int @map("po_line_id")
poLine PurchaseOrderLine @relation(fields: [poLineId], references: [id], onDelete: Cascade)
lotId Int? @map("lot_id")
lot PurchaseLot? @relation(fields: [lotId], references: [id])
receivedQty Decimal @map("received_qty") @db.Decimal(12, 2)
receivedAt DateTime @map("received_at")
transportDocNum String? @map("transport_doc_num")
documentMissing Boolean @default(false) @map("document_missing")
qualityNotes String? @map("quality_notes")
status ReceptionStatus @default(DRAFT)
createdById String @map("created_by_id")
createdBy User @relation("ReceptionCreator", fields: [createdById], references: [id])
verifiedById String? @map("verified_by_id")
verifiedBy User? @relation("ReceptionVerifier", fields: [verifiedById], references: [id])
verifiedAt DateTime? @map("verified_at")
accountedById String? @map("accounted_by_id")
accountedBy User? @relation("ReceptionAccountant", fields: [accountedById], references: [id])
accountedAt DateTime? @map("accounted_at")
discrepancyNotes String? @map("discrepancy_notes")
supplierDeliveryNoteId Int? @map("supplier_delivery_note_id")
supplierDeliveryNote SupplierDeliveryNote? @relation("SDNReceptions", fields: [supplierDeliveryNoteId], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([poLineId])
@@index([lotId])
@@index([status])
@@index([documentMissing])
@@index([supplierDeliveryNoteId])
@@map("purchase_line_receptions")
}
// S42-PLANNING-1 — Notes overlay Order × MaterialCategory
model OrderCategoryNote {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
category MaterialCategory
note String @db.VarChar(200)
createdById String @map("created_by_id")
createdBy User @relation("OrderCategoryNoteCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([orderId, category])
@@index([orderId])
@@map("order_category_notes")
}
enum HolidayType {
FIXED
WORKING_SATURDAY
}
model PublicHoliday {
id Int @id @default(autoincrement())
date DateTime @unique @db.Date
name String
holidayType HolidayType @default(FIXED) @map("holiday_type")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("public_holidays")
}
enum SupplierDeliveryNoteStatus {
DRAFT
SIGNED
CANCELLED
}
enum PostpayMode {
SIMPLE
PER_DELIVERY
}
enum POPaymentScheduleSource {
MANUAL
AUTO_FROM_CONTRACT
AUTO_FROM_TLT
}
model SupplierDeliveryNote {
id Int @id @default(autoincrement())
number String
signedDate DateTime @map("signed_date") @db.Date
supplierId Int @map("supplier_id")
supplier Supplier @relation("SupplierDNs", fields: [supplierId], references: [id])
purchaseOrderId Int? @map("purchase_order_id")
purchaseOrder PurchaseOrder? @relation("PODeliveryNotes", fields: [purchaseOrderId], references: [id])
status SupplierDeliveryNoteStatus @default(DRAFT)
notes String?
receptions PurchaseLineReception[] @relation("SDNReceptions")
documents Document[] @relation("SDNDocuments")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([supplierId, number])
@@index([purchaseOrderId])
@@index([status])
@@map("supplier_delivery_notes")
}
// SPEC HELP-MODULE-P0 — Articles d'aide intégrés
enum HelpCategory {
GLOSSARY
WORKFLOW
FAQ
MODULE_REF
INTRO
}
model HelpArticle {
id Int @id @default(autoincrement())
slug String @unique
titleRu String @map("title_ru")
bodyMd String @db.Text @map("body_md")
category HelpCategory
tags String[] @default([])
isPublished Boolean @default(true) @map("is_published")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
createdById String? @map("created_by_id")
createdBy User? @relation("HelpArticleCreatedBy", fields: [createdById], references: [id])
@@index([category, isPublished])
@@index([isPublished])
@@map("help_articles")
}