metallkart-erp/prisma/schema.prisma
louis de5e4de0f9 fix(backend): resolve all 23 TypeScript errors — 2 real bugs found (SPEC-BACKEND-FIX-TS)
8 error categories fixed across 11 files:
- TenderStatus enum: added CANCELLED (real bug — order cancel crashed for tenders)
- creatorId→createdById: 6 sites in copilot engagement tools (real bug — ownership checks always false)
- Order.constructor Prisma 5 clash: removed select constraints in estimates+labor-report
- Prisma Json null: DbNull for Json? fields, InputJsonValue for Record<string,unknown>
- Anthropic SDK ContentBlock: TextBlock→TextBlockParam, ToolUseBlock→ToolUseBlockParam
- Role[] vs Role: estimates.service approveFinancial+delete accept union type
- ActionValueClass: string→enum in discharge-kpi CatalogEntry
- SendEmailOptions: added required html field in claim-draft

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-01 22:48:15 +04:00

4174 lines
159 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
}
// STD-1A — Statut produit standard
enum ProductStatus {
DRAFT
ACTIVE
ARCHIVED
}
// 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 MessagingChannel {
MAX
TELEGRAM
}
enum PairingStatus {
PENDING
USED
EXPIRED
REVOKED
}
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
CANCELLED
}
// 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)
additionalRoles Role[] @default([]) @map("additional_roles")
active Boolean @default(true)
phone String?
telegramId String? @map("telegram_id")
weeklyReportSubscribed Boolean @default(true) @map("weekly_report_subscribed")
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")
productsCreated Product[] @relation("ProductCreator")
pricingsCreated ProductMaterialPricing[] @relation("PricingCreator")
pricingChangesRequested ProductMaterialPricing[] @relation("PricingChangeRequester")
pricingsApproved ProductMaterialPricing[] @relation("PricingApprover")
pricingHistoryActions ProductMaterialPricingHistory[] @relation("PricingHistoryActor")
mcpErpContextAudits McpErpContextAudit[]
aiAdvisorConversations AIAdvisorConversation[]
aiAdvisorArticleSuggestionsReviewed AIAdvisorArticleSuggestion[] @relation("SuggestionReviewer")
aiAdvisorQuotas AIAdvisorQuota[]
builderFeaturesRequested AIConstructorFeature[] @relation("FeatureRequestedBy")
builderFeaturesReviewed AIConstructorFeature[] @relation("FeatureReviewedBy")
builderFeatureHistoryActions AIConstructorFeatureHistory[]
builderReverts AIConstructorRevert[]
builderRevertItemDecisions AIConstructorRevertItem[]
builderQuotas AIConstructorBuilderQuota[]
bugsReported BugReport[] @relation("BugReporter")
bugsAssigned BugReport[] @relation("BugAssignee")
announcements UserAnnouncement[]
// Agent Август (M0A)
agentActionsReceived AgentAction[] @relation("AgentActionsForUser")
agentEngagements AgentEngagement[] @relation("AgentEngagements")
knowledgeArticles KnowledgeArticle[] @relation("KnowledgeArticlesAuthored")
// Copilote Август (M3)
conversationSessions ConversationSession[] @relation("ConversationSessions")
// Engagement Layer (Bloc 1A)
tasksCreated Task[] @relation("TaskCreatedBy")
tasksAssigned Task[] @relation("TaskAssignee")
decisionsToDecide Decision[] @relation("DecisionDecider")
decisionsEscalatedTo Decision[] @relation("DecisionEscalateTo")
decisionsCreated Decision[] @relation("DecisionCreatedBy")
decisionsDecided Decision[] @relation("DecisionDecidedBy")
decisionLogActions DecisionLog[] @relation("DecisionLogActor")
remindersTargeted Reminder[] @relation("ReminderTarget")
userMemories UserMemory[] @relation("UserMemories")
agendaCalendarConfig AgendaCalendarConfig? @relation("AgendaCalendarConfig")
chatPreference ChatPreference? @relation("ChatPreferences")
augustSilence AugustSilenceState? @relation("AugustSilence")
lessonsActed AgentLesson[] @relation("LessonActor")
lessonsPromoted AgentLesson[] @relation("LessonPromoter")
notificationDeliveries NotificationDelivery[] @relation("NotificationDeliveries")
pushSubscriptions PushSubscription[] @relation("UserPushSubscriptions")
laborConfigsUpdated LaborConfig[] @relation("LaborConfigUpdater")
workLogsVerified WorkLog[] @relation("WorkLogVerifier")
workLogsCreated WorkLog[] @relation("WorkLogCreator")
@@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")
usesEdo Boolean @default(false) @map("uses_edo")
edoProvider String? @map("edo_provider")
preferredChannel MessagingChannel? @map("preferred_channel")
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
contacts SupplierContact[]
tagLinks SupplierTagLink[]
channelLinks SupplierChannelLink[] @relation("SupplierChannelLinks")
pairingCodes ChannelPairingCode[] @relation("SupplierPairingCodes")
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")
productPricings ProductMaterialPricing[] @relation("SupplierPricings")
@@index([status])
@@index([inn])
@@index([deletedAt])
@@map("suppliers")
}
model SupplierChannelLink {
id Int @id @default(autoincrement())
supplierId Int @map("supplier_id")
channel MessagingChannel
externalChatId String @map("external_chat_id")
displayName String? @map("display_name")
linkedAt DateTime @default(now()) @map("linked_at")
active Boolean @default(true)
supplier Supplier @relation("SupplierChannelLinks", fields: [supplierId], references: [id])
@@unique([channel, externalChatId])
@@index([supplierId])
@@map("supplier_channel_links")
}
model SupplierNotificationLog {
id Int @id @default(autoincrement())
supplierId Int @map("supplier_id")
event String
refType String @map("ref_type")
refId Int @map("ref_id")
channel String
createdAt DateTime @default(now()) @map("created_at")
@@unique([supplierId, event, refType, refId])
@@index([supplierId])
@@map("supplier_notification_logs")
}
model ChannelPairingCode {
id Int @id @default(autoincrement())
code String @unique
supplierId Int @map("supplier_id")
channel MessagingChannel
expiresAt DateTime @map("expires_at")
usedAt DateTime? @map("used_at")
usedByChatId String? @map("used_by_chat_id")
status PairingStatus @default(PENDING)
createdAt DateTime @default(now()) @map("created_at")
supplier Supplier @relation("SupplierPairingCodes", fields: [supplierId], references: [id])
@@index([supplierId])
@@index([status])
@@map("channel_pairing_codes")
}
enum MaxMsgStatus {
PENDING
SENT
FAILED
}
model MaxOutboundMessage {
id Int @id @default(autoincrement())
chatId String @map("chat_id")
text String
status MaxMsgStatus @default(PENDING)
error String?
createdAt DateTime @default(now()) @map("created_at")
sentAt DateTime? @map("sent_at")
@@index([status])
@@map("max_outbound_messages")
}
model MaxInboundMessage {
id Int @id @default(autoincrement())
channel String
type String
chatId String @map("chat_id")
text String
receivedAt DateTime @default(now()) @map("received_at")
processed Boolean @default(false)
@@index([processed])
@@map("max_inbound_messages")
}
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?
accountantName String? @map("accountant_name")
accountantPhone String? @map("accountant_phone")
accountantEmail String? @map("accountant_email")
usesEdo Boolean @default(false) @map("uses_edo")
edoProvider String? @map("edo_provider")
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")
}
// STD-1A — Catalogue produits standards
model Product {
id Int @id @default(autoincrement())
name String @db.VarChar(300)
code String @unique @db.VarChar(50)
description String? @db.Text
status ProductStatus @default(DRAFT)
executionUnit ExecutionUnit @default(MAIN_PRODUCTION) @map("execution_unit")
defaultQuantity Int @default(1) @map("default_quantity")
dimensions String?
colorRal String? @map("color_ral")
massKg Decimal? @map("mass_kg") @db.Decimal(10, 4)
imagePath String? @map("image_path")
imageMime String? @map("image_mime")
imageHash String? @map("image_hash")
createdById String @map("created_by_id")
createdBy User @relation("ProductCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
smeta Smeta?
orders Order[]
pricings ProductMaterialPricing[]
@@index([status])
@@index([executionUnit])
@@index([createdAt])
@@map("products")
}
// 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(CUSTOM) @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")
cancelledById String? @map("cancelled_by_id")
thumbnailPath String? @map("thumbnail_path")
notes String?
contractId Int? @map("contract_id")
contract ClientContract? @relation("OrderClientContract", fields: [contractId], references: [id])
productId Int? @map("product_id")
product Product? @relation(fields: [productId], 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[]
laborOperations OrderLaborOperation[]
workLogs WorkLog[]
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([productId])
@@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)
productId Int? @unique @map("product_id")
product Product? @relation(fields: [productId], references: [id])
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[]
laborNodes SmetaLaborNode[]
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")
pricing ProductMaterialPricing?
laborOperations OrderLaborOperation[]
@@index([smetaId])
@@map("smeta_lines")
}
// SPEC-TRUD-1 — Поузловой расчет себестоимости трудозатрат (welding nodes)
model SmetaLaborNode {
id Int @id @default(autoincrement())
smetaId Int @map("smeta_id")
smeta Smeta @relation(fields: [smetaId], references: [id], onDelete: Cascade)
position Int @default(0)
nodeName String @map("node_name")
nodeCount Int @default(1) @map("node_count")
detailCount Int? @map("detail_count")
assemblyNormMin Decimal? @map("assembly_norm_min") @db.Decimal(12, 4)
assemblyTimeH Decimal? @map("assembly_time_h") @db.Decimal(12, 4)
assemblyRate Decimal? @map("assembly_rate") @db.Decimal(12, 2)
assemblyPrice Decimal? @map("assembly_price") @db.Decimal(12, 2)
weldMeters Decimal? @map("weld_meters") @db.Decimal(12, 4)
weldNormMin Decimal? @map("weld_norm_min") @db.Decimal(12, 4)
weldTimeH Decimal? @map("weld_time_h") @db.Decimal(12, 4)
weldRate Decimal? @map("weld_rate") @db.Decimal(12, 2)
weldPrice Decimal? @map("weld_price") @db.Decimal(12, 2)
nodeTotalTime Decimal? @map("node_total_time") @db.Decimal(12, 4)
nodeTotalPrice Decimal? @map("node_total_price") @db.Decimal(12, 2)
conductorTimeMin Decimal? @map("conductor_time_min") @db.Decimal(12, 4)
photoPath String? @map("photo_path")
photoMime String? @map("photo_mime")
anchorRow Int? @map("anchor_row")
createdAt DateTime @default(now()) @map("created_at")
laborOperations OrderLaborOperation[]
@@index([smetaId])
@@map("smeta_labor_nodes")
}
// 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")
seenAt DateTime? @map("seen_at")
acknowledgedAt DateTime? @map("acknowledged_at")
category String? @map("category")
createdAt DateTime @default(now()) @map("created_at")
@@index([documentId])
@@index([recipientId])
@@index([seenAt])
@@index([acknowledgedAt])
@@index([category])
@@index([entityType, entityId])
@@index([actionRequired])
@@map("notifications")
}
model NotificationCategoryConfig {
id Int @id @default(autoincrement())
category String @unique
isCritical Boolean @default(false) @map("is_critical")
requiresAck Boolean @default(false) @map("requires_ack")
displayBanner Boolean @default(false) @map("display_banner")
bannerLevel String @default("warning") @map("banner_level") // info, warning, error
autoExpireDays Int? @map("auto_expire_days")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("notification_category_configs")
}
// 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")
inStockQuantity Decimal? @map("in_stock_quantity") @db.Decimal(12, 2)
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")
deliveryCost Decimal? @map("delivery_cost") @db.Decimal(12, 2)
isTemplate Boolean @default(false) @map("is_template")
templateName String? @map("template_name")
@@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?
paymentTerms String? @map("payment_terms")
desiredDeliveryDays Int? @map("desired_delivery_days")
desiredDeliveryDate DateTime? @map("desired_delivery_date")
tenderComment String? @map("tender_comment")
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)
unitPriceHt Decimal? @map("unit_price_ht") @db.Decimal(12, 2)
vatRate Decimal? @map("vat_rate") @db.Decimal(5, 4)
vatSource String? @map("vat_source")
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? @map("sent_at")
status String @default("PENDING") // PENDING, SENT, RESPONDED, NO_RESPONSE
respondedAt DateTime? @map("responded_at")
dueDate DateTime? @map("due_date")
outgoingMessageId String? @map("outgoing_message_id")
noResponseNotifiedAt DateTime? @map("no_response_notified_at")
notes String?
requirementIds Json? @map("requirement_ids")
inboundEmails InboundEmail[]
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")
poNature String? @map("po_nature") // GOODS | SERVICE
deliveryBySupplier Boolean? @map("delivery_by_supplier")
expectedDocuments ExpectedDocument[]
correlatedEmails InboundEmail[] @relation("InboundEmailPO")
manualInterventionRequired Boolean @default(false) @map("manual_intervention_required")
manualInterventionReason String? @map("manual_intervention_reason")
manualInterventionResolvedBy String? @map("manual_intervention_resolved_by")
manualInterventionResolvedAt DateTime? @map("manual_intervention_resolved_at")
manualInterventionDecision String? @map("manual_intervention_decision")
@@index([status])
@@index([supplierId])
@@index([clientOrderId])
@@index([tenderId])
@@index([createdById])
@@index([expectedDeliveryDate])
@@index([archived])
@@index([expenseCodeId])
@@index([isIndirect])
@@index([supplierContractId])
@@index([manualInterventionRequired])
@@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")
}
// CDC STD-2 — Statut du pricing produit
enum PricingStatus {
ACTIVE
PENDING_APPROVAL
}
// CDC STD-2 — Type de changement audit pricing
enum PricingChangeType {
CREATED
CHANGE_REQUESTED
APPROVED
REJECTED
SUPPLIER_CHANGED
}
// CDC STD-2 — Pricing par ligne smeta orpheline avec workflow d'approbation
model ProductMaterialPricing {
id Int @id @default(autoincrement())
productId Int @map("product_id")
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
smetaLineId Int @unique @map("smeta_line_id")
smetaLine SmetaLine @relation(fields: [smetaLineId], references: [id], onDelete: Cascade)
supplierId Int @map("supplier_id")
supplier Supplier @relation("SupplierPricings", fields: [supplierId], references: [id])
currentUnitPrice Decimal @map("current_unit_price") @db.Decimal(12, 2)
currentDeliveryDays Int? @map("current_delivery_days")
currentSetSince DateTime @default(now()) @map("current_set_since")
status PricingStatus @default(ACTIVE)
pendingUnitPrice Decimal? @map("pending_unit_price") @db.Decimal(12, 2)
pendingDeliveryDays Int? @map("pending_delivery_days")
pendingSupplierId Int? @map("pending_supplier_id")
pendingReason String? @map("pending_reason") @db.VarChar(2000)
pendingRequestedById String? @map("pending_requested_by_id")
pendingRequestedBy User? @relation("PricingChangeRequester", fields: [pendingRequestedById], references: [id])
pendingRequestedAt DateTime? @map("pending_requested_at")
lastApprovedById String? @map("last_approved_by_id")
lastApprovedBy User? @relation("PricingApprover", fields: [lastApprovedById], references: [id])
lastApprovedAt DateTime? @map("last_approved_at")
lastRejectedReason String? @map("last_rejected_reason") @db.VarChar(2000)
createdById String @map("created_by_id")
createdBy User @relation("PricingCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
history ProductMaterialPricingHistory[]
@@index([productId, status])
@@index([supplierId])
@@map("product_material_pricings")
}
// CDC STD-2 — Audit historique des changements de pricing
model ProductMaterialPricingHistory {
id Int @id @default(autoincrement())
pricingId Int @map("pricing_id")
pricing ProductMaterialPricing @relation(fields: [pricingId], references: [id], onDelete: Cascade)
unitPriceBefore Decimal? @map("unit_price_before") @db.Decimal(12, 2)
unitPriceAfter Decimal? @map("unit_price_after") @db.Decimal(12, 2)
deliveryDaysBefore Int? @map("delivery_days_before")
deliveryDaysAfter Int? @map("delivery_days_after")
supplierIdBefore Int? @map("supplier_id_before")
supplierIdAfter Int? @map("supplier_id_after")
changeType PricingChangeType
changeReason String? @map("change_reason") @db.VarChar(2000)
changedById String @map("changed_by_id")
changedBy User @relation("PricingHistoryActor", fields: [changedById], references: [id])
changedAt DateTime @default(now()) @map("changed_at")
variationPercent Decimal? @map("variation_percent") @db.Decimal(6, 2)
@@index([pricingId, changedAt])
@@index([changeType])
@@map("product_material_pricing_history")
}
// SPEC HELP-MODULE-P0 — Articles d'aide intégrés
enum HelpCategory {
GLOSSARY
WORKFLOW
FAQ
MODULE_REF
INTRO
AI_MODULES
}
model HelpArticle {
id Int @id @default(autoincrement())
slug String @unique
titleRu String @map("title_ru")
bodyMd String @map("body_md") @db.Text
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")
}
model McpErpContextAudit {
id Int @id @default(autoincrement())
toolName String @map("tool_name")
arguments Json?
resultSize Int? @map("result_size")
durationMs Int @map("duration_ms")
callerId String? @map("caller_id")
caller User? @relation(fields: [callerId], references: [id])
error String?
createdAt DateTime @default(now()) @map("created_at")
@@index([createdAt])
@@index([toolName])
@@map("mcp_erp_context_audit")
}
model AIAdvisorConversation {
id Int @id @default(autoincrement())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
title String?
contextPage String? @map("context_page")
archived Boolean @default(false)
archivedAt DateTime? @map("archived_at")
totalTokensIn Int @default(0) @map("total_tokens_in")
totalTokensOut Int @default(0) @map("total_tokens_out")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
messages AIAdvisorMessage[]
suggestions AIAdvisorArticleSuggestion[]
@@index([userId, archived, updatedAt(sort: Desc)])
@@map("ai_advisor_conversations")
}
model AIAdvisorMessage {
id Int @id @default(autoincrement())
conversationId Int @map("conversation_id")
conversation AIAdvisorConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
role String
content String @db.Text
contextPage String? @map("context_page")
tokensInput Int? @map("tokens_input")
tokensOutput Int? @map("tokens_output")
toolsUsed Json? @map("tools_used")
modelUsed String? @map("model_used")
durationMs Int? @map("duration_ms")
status String @default("COMPLETE")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
@@index([conversationId, createdAt])
@@map("ai_advisor_messages")
}
model AIAdvisorArticleSuggestion {
id Int @id @default(autoincrement())
conversationId Int @map("conversation_id")
conversation AIAdvisorConversation @relation(fields: [conversationId], references: [id])
messageId Int? @map("message_id")
action String
targetArticleId Int? @map("target_article_id")
proposedTitle String? @map("proposed_title")
proposedContent String @map("proposed_content") @db.Text
status String @default("PENDING")
reviewedById String? @map("reviewed_by_id")
reviewedBy User? @relation("SuggestionReviewer", fields: [reviewedById], references: [id])
reviewedAt DateTime? @map("reviewed_at")
reviewerNotes String? @map("reviewer_notes")
createdAt DateTime @default(now()) @map("created_at")
@@index([status, createdAt])
@@map("ai_advisor_article_suggestions")
}
model AIAdvisorQuota {
id Int @id @default(autoincrement())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
monthYear String @map("month_year")
advisorRequestsUsed Int @default(0) @map("advisor_requests_used")
advisorRequestsLimit Int @default(200) @map("advisor_requests_limit")
tokensInputUsed Int @default(0) @map("tokens_input_used")
tokensOutputUsed Int @default(0) @map("tokens_output_used")
costEstimateCents Int @default(0) @map("cost_estimate_cents")
maxCostCents Int @default(0) @map("max_cost_cents")
@@unique([userId, monthYear])
@@index([monthYear])
@@map("ai_advisor_quota")
}
// ─── AI CONSTRUCTOR (BUILDER) ────────────────────────────────────────
enum AIConstructorFeatureStatus {
DRAFT
ANALYZING
AWAITING_DESIGN_REVIEW
GENERATING
DEPLOYING
TESTING
AWAITING_FINAL_REVIEW
APPROVED
MERGING
MERGED
REVERTED
REJECTED
FAILED
ABANDONED
}
enum AIConstructorFeatureType {
FIELD_ADD
TABLE_NEW
WORKFLOW
}
enum AIConstructorRevertItemAction {
CONSERVER
ARCHIVER
EXPORTER
CORRIGER
}
enum AIConstructorRevertStatus {
COLLECTING
AWAITING_DECISIONS
COMMITTING
COMPLETED
ABORTED
}
model AIConstructorFeature {
id Int @id @default(autoincrement())
title String
description String @db.Text
requestedById String @map("requested_by_id")
requestedBy User @relation("FeatureRequestedBy", fields: [requestedById], references: [id])
status AIConstructorFeatureStatus @default(DRAFT)
type AIConstructorFeatureType?
branchName String? @map("branch_name")
stagingPort Int? @map("staging_port")
stagingDbName String? @map("staging_db_name")
stagingExpiresAt DateTime? @map("staging_expires_at")
stagingUrlFrontend String? @map("staging_url_frontend")
stagingUrlBackend String? @map("staging_url_backend")
stagingContainerBackend String? @map("staging_container_backend")
stagingContainerFrontend String? @map("staging_container_frontend")
generatedSpec String? @map("generated_spec") @db.Text
filesAdded Json? @map("files_added")
filesModified Json? @map("files_modified")
vitestResults Json? @map("vitest_results")
playwrightResults Json? @map("playwright_results")
reviewedById String? @map("reviewed_by_id")
reviewedBy User? @relation("FeatureReviewedBy", fields: [reviewedById], references: [id])
reviewedAt DateTime? @map("reviewed_at")
reviewerNotes String? @map("reviewer_notes") @db.Text
mergedAt DateTime? @map("merged_at")
mergedCommitSha String? @map("merged_commit_sha")
mergeAdvisoryLockKey BigInt? @map("merge_advisory_lock_key")
preSnapshotDataPath String? @map("pre_snapshot_data_path")
preSnapshotTablesImpacted String[] @map("pre_snapshot_tables_impacted")
branchDeletedAt DateTime? @map("branch_deleted_at")
validationResult Json? @map("validation_result")
retryCount Int @default(0) @map("retry_count")
totalTokensIn Int @default(0) @map("total_tokens_in")
totalTokensOut Int @default(0) @map("total_tokens_out")
costEstimateCents Int @default(0) @map("cost_estimate_cents")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
history AIConstructorFeatureHistory[]
reverts AIConstructorRevert[]
@@index([requestedById, status, createdAt(sort: Desc)])
@@index([status])
@@map("ai_constructor_features")
}
model AIConstructorFeatureHistory {
id Int @id @default(autoincrement())
featureId Int @map("feature_id")
feature AIConstructorFeature @relation(fields: [featureId], references: [id], onDelete: Cascade)
action String
fromStatus AIConstructorFeatureStatus? @map("from_status")
toStatus AIConstructorFeatureStatus? @map("to_status")
actorId String? @map("actor_id")
actor User? @relation(fields: [actorId], references: [id])
payload Json?
notes String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
@@index([featureId, createdAt])
@@map("ai_constructor_feature_history")
}
model AIConstructorRevert {
id Int @id @default(autoincrement())
featureId Int @unique @map("feature_id")
feature AIConstructorFeature @relation(fields: [featureId], references: [id])
status AIConstructorRevertStatus @default(COLLECTING)
startedAt DateTime @default(now()) @map("started_at")
committedAt DateTime? @map("committed_at")
tablesImpacted String[] @map("tables_impacted")
itemsTotal Int @default(0) @map("items_total")
itemsDecided Int @default(0) @map("items_decided")
gitRevertCommitSha String? @map("git_revert_commit_sha")
initiatedById String @map("initiated_by_id")
initiatedBy User @relation(fields: [initiatedById], references: [id])
errorMessage String? @map("error_message")
notes String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
items AIConstructorRevertItem[]
@@index([featureId])
@@index([status])
@@map("ai_constructor_reverts")
}
model AIConstructorRevertItem {
id Int @id @default(autoincrement())
revertId Int @map("revert_id")
revert AIConstructorRevert @relation(fields: [revertId], references: [id], onDelete: Cascade)
entityType String @map("entity_type")
entityId String @map("entity_id")
entitySnapshot Json @map("entity_snapshot")
action AIConstructorRevertItemAction?
decidedAt DateTime? @map("decided_at")
decidedById String? @map("decided_by_id")
decidedBy User? @relation(fields: [decidedById], references: [id])
exportPath String? @map("export_path")
correctionNote String? @map("correction_note") @db.Text
appliedAt DateTime? @map("applied_at")
@@index([revertId])
@@index([entityType, entityId])
@@map("ai_constructor_revert_items")
}
model AIConstructorBuilderQuota {
id Int @id @default(autoincrement())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
monthYear String @map("month_year")
featuresRequestsUsed Int @default(0) @map("features_requests_used")
featuresRequestsLimit Int @default(20) @map("features_requests_limit")
tokensInputUsed Int @default(0) @map("tokens_input_used")
tokensOutputUsed Int @default(0) @map("tokens_output_used")
costEstimateCents Int @default(0) @map("cost_estimate_cents")
maxCostCents Int @default(0) @map("max_cost_cents")
@@unique([userId, monthYear])
@@index([monthYear])
@@map("ai_constructor_builder_quota")
}
// ══════════════════════════════════════════════════════════════════
// BUG REPORTS — Telegram bot feedback channel
// ══════════════════════════════════════════════════════════════════
enum BugSeverity {
LOW
MEDIUM
HIGH
CRITICAL
}
enum BugReportStatus {
NEW
IN_PROGRESS
FIXED
CLOSED
WONTFIX
}
enum BugSource {
WEB_UI
TELEGRAM
EMAIL
MANUAL
COPILOT
}
model BugReport {
id Int @id @default(autoincrement())
title String @default("") @db.VarChar(255)
description String @db.Text
module String?
severity BugSeverity @default(MEDIUM)
status BugReportStatus @default(NEW)
source BugSource @default(TELEGRAM)
sourceMeta Json? @map("source_meta")
screenshotPath String? @map("screenshot_path")
reporterUserId String? @map("reporter_user_id")
reporterName String? @map("reporter_name")
reporterTelegramChatId String? @map("reporter_telegram_chat_id")
reporter User? @relation("BugReporter", fields: [reporterUserId], references: [id])
assigneeUserId String? @map("assignee_user_id")
assignee User? @relation("BugAssignee", fields: [assigneeUserId], references: [id])
notesInternal String? @map("notes_internal") @db.Text
fixedCommitSha String? @map("fixed_commit_sha")
fixedAt DateTime? @map("fixed_at")
closedAt DateTime? @map("closed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@index([status])
@@index([severity])
@@index([createdAt])
@@index([assigneeUserId, status])
@@index([reporterUserId])
@@map("bug_reports")
}
// ══════════════════════════════════════════════════════════════════
// ANNOUNCEMENTS — Multi-channel user notifications (email + Telegram)
// ══════════════════════════════════════════════════════════════════
enum AnnouncementCategory {
ONBOARDING
SYSTEM_UPDATE
MAINTENANCE
FEATURE_RELEASE
REMINDER
}
enum AnnouncementStatus {
SUCCESS
PARTIAL
FAILED
PENDING
}
model UserAnnouncement {
id Int @id @default(autoincrement())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
category AnnouncementCategory
templateSlug String @map("template_slug")
subject String
emailSent Boolean @default(false) @map("email_sent")
telegramSent Boolean @default(false) @map("telegram_sent")
status AnnouncementStatus @default(PENDING)
errorDetails String? @map("error_details") @db.Text
metadata Json?
sentAt DateTime @default(now()) @map("sent_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([userId])
@@index([category])
@@index([status])
@@index([templateSlug])
@@map("user_announcements")
}
// ══════════════════════════════════════════════════════════════════
// SYSTEM JOBS — Idempotent recurring job tracking
// ══════════════════════════════════════════════════════════════════
model SystemJob {
id Int @id @default(autoincrement())
jobName String @unique @map("job_name")
lastRunAt DateTime? @map("last_run_at")
nextRunAt DateTime? @map("next_run_at")
isEnabled Boolean @default(true) @map("is_enabled")
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("system_jobs")
}
// ─── Document Control (AXE 3) ──────────────────────────────────
enum DocType {
UPD
TORG12
INVOICE_SF
SCHET
TN
}
enum ExpectedDocStatus {
EXPECTED
RECEIVED
OVERDUE
WAIVED
}
enum ControlStage {
NOT_RECEIVED
AT_WAREHOUSE
AT_ACHETEUR
DISCREPANCY
AT_COMPTA
REGISTERED
}
enum DiscrepancyKind {
QUANTITY
QUALITY
PRICE
}
enum DiscrepancyStatus {
NOTED
CLAIM_DRAFTED
CLAIM_SENT
AWAITING_UKD
UKD_RECEIVED
UKD_ACCEPTED
RESOLVED
REJECTED
}
model ExpectedDocument {
id Int @id @default(autoincrement())
purchaseOrderId Int @map("purchase_order_id")
purchaseOrder PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade)
docType DocType @map("doc_type")
status ExpectedDocStatus @default(EXPECTED)
dueDate DateTime? @map("due_date")
receivedAt DateTime? @map("received_at")
inboundEmailId Int? @map("inbound_email_id")
waivedReason String? @map("waived_reason")
notifiedAt DateTime? @map("notified_at")
alternativeGroup String? @map("alternative_group")
controlStage ControlStage @default(NOT_RECEIVED) @map("control_stage")
controlStageEnteredAt DateTime? @map("control_stage_entered_at")
internalDueDate DateTime? @map("internal_due_date")
internalStallNotifiedAt DateTime? @map("internal_stall_notified_at")
discrepancies Discrepancy[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([purchaseOrderId, status])
@@index([status])
@@index([controlStage])
@@map("expected_documents")
}
model Discrepancy {
id Int @id @default(autoincrement())
expectedDocumentId Int @map("expected_document_id")
expectedDocument ExpectedDocument @relation(fields: [expectedDocumentId], references: [id], onDelete: Cascade)
kind DiscrepancyKind
status DiscrepancyStatus @default(NOTED)
description String
noticedBy String? @map("noticed_by")
claimSentAt DateTime? @map("claim_sent_at")
ukdReceivedAt DateTime? @map("ukd_received_at")
ukdAcceptedAt DateTime? @map("ukd_accepted_at")
resolvedAt DateTime? @map("resolved_at")
inboundUkdEmailId Int? @map("inbound_ukd_email_id")
claimDraftText String? @map("claim_draft_text") @db.Text
claimSentMessageId String? @map("claim_sent_message_id")
claimUkdDueDate DateTime? @map("claim_ukd_due_date")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([expectedDocumentId])
@@index([status])
@@map("discrepancies")
}
// ─── Price Memory ──────────────────────────────────────────────
enum MatchStatus {
UNMATCHED
PROPOSED
CONFIRMED
REJECTED
}
model CanonicalItem {
id Int @id @default(autoincrement())
canonicalLabel String @unique @map("canonical_label")
family String?
unit String?
attributes Json?
embedding Unsupported("vector(512)")?
observations PriceObservation[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([family])
@@map("canonical_items")
}
model PriceObservation {
id Int @id @default(autoincrement())
rawLabel String @map("raw_label")
supplierId Int? @map("supplier_id")
supplierName String? @map("supplier_name")
unitPriceHt Decimal @map("unit_price_ht") @db.Decimal(12, 2)
currency String @default("RUB")
quantity Decimal? @db.Decimal(12, 4)
unit String?
observedAt DateTime @map("observed_at")
source String
sourceLineId Int @map("source_line_id")
isPaid Boolean @default(false) @map("is_paid")
poStatus String? @map("po_status")
canonicalItemId Int? @map("canonical_item_id")
canonicalItem CanonicalItem? @relation(fields: [canonicalItemId], references: [id])
matchStatus MatchStatus @default(UNMATCHED) @map("match_status")
matchScore Float? @map("match_score")
matchMethod String? @map("match_method")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([source, sourceLineId])
@@index([supplierId])
@@index([observedAt])
@@index([matchStatus])
@@index([canonicalItemId])
@@map("price_observations")
}
// ─── Inbound Email Ingestion ───────────────────────────────────
model InboundEmail {
id Int @id @default(autoincrement())
mailbox String
uid Int
uidValidity Int @default(0) @map("uid_validity")
messageId String? @map("message_id")
inReplyTo String? @map("in_reply_to")
references String?
fromAddress String @map("from_address")
fromName String? @map("from_name")
subject String?
receivedAt DateTime @map("received_at")
bodyText String? @map("body_text")
classification String
classificationMethod String @map("classification_method")
classificationConfidence Float? @map("classification_confidence")
routingStatus String @default("NEW") @map("routing_status")
tenderDispatchId Int? @map("tender_dispatch_id")
tenderDispatch TenderDispatch? @relation(fields: [tenderDispatchId], references: [id])
correlationStatus String? @map("correlation_status")
correlationCandidates Json? @map("correlation_candidates")
tenderStage String? @map("tender_stage")
responseType String? @map("response_type")
analysisResult Json? @map("analysis_result")
analysisMethod String? @map("analysis_method")
draftOfferId Int? @map("draft_offer_id")
draftStatus String? @map("draft_status")
notifiedAt DateTime? @map("notified_at")
correlatedPurchaseOrderId Int? @map("correlated_purchase_order_id")
correlatedPurchaseOrder PurchaseOrder? @relation("InboundEmailPO", fields: [correlatedPurchaseOrderId], references: [id])
detectedIntent String? @map("detected_intent")
attachments InboundEmailAttachment[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([mailbox, uidValidity, uid])
@@index([classification])
@@index([routingStatus])
@@index([tenderStage])
@@map("inbound_emails")
}
model InboundEmailAttachment {
id Int @id @default(autoincrement())
inboundEmailId Int @map("inbound_email_id")
inboundEmail InboundEmail @relation(fields: [inboundEmailId], references: [id], onDelete: Cascade)
filename String
mimeType String @map("mime_type")
sizeBytes Int @map("size_bytes")
storagePath String @map("storage_path")
kindHint String? @map("kind_hint")
createdAt DateTime @default(now()) @map("created_at")
@@index([inboundEmailId])
@@map("inbound_email_attachments")
}
// ─── Agent Август (M0A) ────────────────────────────────────────
enum AgentActionType {
REMINDER_SENT
ESCALATION_SENT
REPORT_SENT
COPILOT_RESPONSE
KNOWLEDGE_INGESTED
DRY_RUN
}
enum AgentActionStatus {
PENDING
EXECUTED
FAILED
SHADOWED
THROTTLED
KILLED
}
enum KnowledgeArticleStatus {
DRAFT
PUBLISHED
ARCHIVED
}
model AgentAction {
id Int @id @default(autoincrement())
type AgentActionType
status AgentActionStatus @default(PENDING)
targetUserId String?
targetUser User? @relation("AgentActionsForUser", fields: [targetUserId], references: [id])
relatedEntityType String?
relatedEntityId Int?
channel String?
subject String?
body String? @db.Text
triggerJobName String?
intent String?
errorMessage String?
createdAt DateTime @default(now())
executedAt DateTime?
auditLog AgentAuditLog?
engagement AgentEngagement?
@@index([targetUserId])
@@index([type, status])
@@index([createdAt])
@@map("agent_actions")
}
model AgentAuditLog {
id Int @id @default(autoincrement())
agentActionId Int @unique
agentAction AgentAction @relation(fields: [agentActionId], references: [id], onDelete: Cascade)
llmModel String
promptInput String @db.Text
llmOutput String @db.Text
inputTokens Int?
outputTokens Int?
costUsd Decimal? @db.Decimal(10, 6)
retrievedChunkIds Int[] @default([])
toolCalls Json?
createdAt DateTime @default(now())
@@map("agent_audit_logs")
}
model AgentEngagement {
id Int @id @default(autoincrement())
agentActionId Int @unique
agentAction AgentAction @relation(fields: [agentActionId], references: [id], onDelete: Cascade)
targetUserId String
targetUser User @relation("AgentEngagements", fields: [targetUserId], references: [id])
relatedEntityType String
relatedEntityId Int
firstReminderAt DateTime @default(now())
lastReminderAt DateTime @default(now())
reminderCount Int @default(1)
resolved Boolean @default(false)
resolvedAt DateTime?
escalatedAt DateTime?
escalatedToUserId String?
@@index([targetUserId, relatedEntityType, relatedEntityId])
@@index([resolved])
@@map("agent_engagements")
}
model AgentReport {
id String @id @default(uuid())
periodFrom DateTime
periodTo DateTime
aggregatedData Json
contentMarkdown String? @db.Text
sentToUserIds String[] @default([])
sentAt DateTime?
modelUsed String?
costUsd Decimal? @db.Decimal(10, 6)
promptTokens Int?
completionTokens Int?
status AgentReportStatus @default(DRAFT)
generatedAt DateTime @default(now())
generatedByUserId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([periodFrom, periodTo])
@@index([status])
@@index([generatedAt])
@@map("agent_reports")
}
enum AgentReportStatus {
DRAFT
GENERATED
SENT
ARCHIVED
}
model KnowledgeArticle {
id Int @id @default(autoincrement())
title String
content String @db.Text
tags String[] @default([])
category String?
status KnowledgeArticleStatus @default(DRAFT)
authorId String
author User @relation("KnowledgeArticlesAuthored", fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
chunks KnowledgeChunk[]
@@index([status])
@@index([category])
@@map("knowledge_articles")
}
model KnowledgeChunk {
id Int @id @default(autoincrement())
articleId Int
article KnowledgeArticle @relation(fields: [articleId], references: [id], onDelete: Cascade)
chunkIndex Int
content String @db.Text
embedding Unsupported("vector(512)")?
contentTsv Unsupported("tsvector")? @map("content_tsv")
tokenCount Int?
createdAt DateTime @default(now())
@@unique([articleId, chunkIndex])
@@index([articleId])
@@map("knowledge_chunks")
}
// ══════════════════════════════════════════════════════════════════
// M3 — Copilote conversationnel Август
// ══════════════════════════════════════════════════════════════════
enum ConversationStatus {
ACTIVE
ARCHIVED
}
enum ConversationRole {
USER
ASSISTANT
TOOL
}
enum PendingActionStatus {
PROPOSED
CONFIRMED
REJECTED
EXECUTED
FAILED
}
model ConversationSession {
id String @id @default(uuid())
userId String
user User @relation("ConversationSessions", fields: [userId], references: [id])
title String?
status ConversationStatus @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastMessageAt DateTime @default(now())
messages ConversationMessage[]
pendingActions PendingAction[]
@@index([userId, lastMessageAt])
@@index([status])
@@map("conversation_sessions")
}
model ConversationMessage {
id String @id @default(uuid())
sessionId String
session ConversationSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
role ConversationRole
content String @db.Text
toolCalls Json?
toolResults Json?
model String?
inputTokens Int?
outputTokens Int?
createdAt DateTime @default(now())
@@index([sessionId, createdAt])
@@map("conversation_messages")
}
model PendingAction {
id String @id @default(uuid())
sessionId String
session ConversationSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
userId String
toolName String
toolInput Json
toolUseId String
status PendingActionStatus @default(PROPOSED)
summary String?
result Json?
errorMessage String?
createdAt DateTime @default(now())
decidedAt DateTime?
executedAt DateTime?
@@index([sessionId])
@@index([userId, status])
@@map("pending_actions")
}
// ══════════════════════════════════════════════════════════════════
// BLOC 1A — Engagement Layer (Refondation Option C)
// ══════════════════════════════════════════════════════════════════
// --- Enums Engagement Layer ---
enum TaskStatus {
TODO
IN_PROGRESS
BLOCKED
DONE
CANCELLED
}
enum TaskPriority {
LOW
MEDIUM
HIGH
URGENT
}
enum TaskSource {
MANUAL
AUTO_RULE
AGENT_AVGUST
AGENT_SUBAGENT
IMPORTED
}
enum DecisionStatus {
PENDING
DECIDED
ESCALATED
CANCELLED
}
enum DecisionPriority {
LOW
MEDIUM
HIGH
URGENT
}
// Q5 DECISIONS-LOG: OPEN retiré, uniquement BINARY + MULTI_CHOICE
enum DecisionKind {
BINARY
MULTI_CHOICE
}
enum DecisionSource {
MANUAL
AUTO_RULE
AGENT_AVGUST
AGENT_SUBAGENT
}
// SPEC-TRUD-2 — Barème commande (labor cost schedule)
enum LaborPayType {
PIECE
AREA
HOURLY
}
enum WorkerCategory {
SVARCSHIK
SBORSHIK
MALYAR
SLESAR
UNIVERSAL
}
enum LaborPriceSource {
DEFAULT
PAINT_RATE
NODE_PRICE
MANUAL
}
enum LaborSourceType {
SMETA_LINE
LABOR_NODE
SPLIT
}
model LaborConfig {
id Int @id @default(autoincrement())
defaultRatePercent Decimal @default(50) @map("default_rate_percent") @db.Decimal(5, 2)
paintRatePerM2 Decimal @default(65) @map("paint_rate_per_m2") @db.Decimal(12, 2)
hourlyPatterns String[] @default([]) @map("hourly_patterns")
aggregateExcludePatterns String[] @default([]) @map("aggregate_exclude_patterns")
updatedAt DateTime @updatedAt @map("updated_at")
updatedById String? @map("updated_by_id")
updatedBy User? @relation("LaborConfigUpdater", fields: [updatedById], references: [id])
@@map("labor_configs")
}
model OrderLaborOperation {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
sourceType LaborSourceType @map("source_type")
sourceSmetaLineId Int? @map("source_smeta_line_id")
sourceSmetaLine SmetaLine? @relation(fields: [sourceSmetaLineId], references: [id])
sourceLaborNodeId Int? @map("source_labor_node_id")
sourceLaborNode SmetaLaborNode? @relation(fields: [sourceLaborNodeId], references: [id])
parentOperationId Int? @map("parent_operation_id")
parentOperation OrderLaborOperation? @relation("OperationSplits", fields: [parentOperationId], references: [id])
childOperations OrderLaborOperation[] @relation("OperationSplits")
description String
payType LaborPayType @map("pay_type")
workerCategory WorkerCategory @default(UNIVERSAL) @map("worker_category")
priceSource LaborPriceSource @map("price_source")
smetaUnitPrice Decimal? @map("smeta_unit_price") @db.Decimal(12, 2)
smetaTotalPrice Decimal? @map("smeta_total_price") @db.Decimal(12, 2)
quantity Decimal @default(1) @db.Decimal(12, 4)
unit String?
baremeUnitPrice Decimal @map("bareme_unit_price") @db.Decimal(12, 2)
baremeTotalPrice Decimal @map("bareme_total_price") @db.Decimal(12, 2)
configRatePercent Decimal? @map("config_rate_percent") @db.Decimal(5, 2)
configPaintRate Decimal? @map("config_paint_rate") @db.Decimal(12, 2)
position Int @default(0)
justification String?
needsReview Boolean @default(false) @map("needs_review")
workLogs WorkLog[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@map("order_labor_operations")
}
enum DecisionLogEventType {
CREATED
REMINDED
ESCALATED
DECIDED
REOPENED
CANCELLED
}
enum ReminderStatus {
SCHEDULED
SENT
ACKNOWLEDGED
EXPIRED
CANCELLED
}
enum ReminderSource {
MANUAL
AUTO_FROM_TASK
AUTO_FROM_DECISION
AUTO_RULE
AGENT_AVGUST
}
enum NotificationChannel {
ERP_IN_APP
PWA_PUSH
TELEGRAM
EMAIL
}
enum ErpEntityType {
ORDER
PURCHASE_REQUIREMENT
PURCHASE_ORDER
TENDER
BUG_REPORT
CLIENT
SUPPLIER
SMETA
DOCUMENT_FLOW
USER
}
enum AutoRuleTriggerType {
ENTITY_STATUS_CHANGE
ENTITY_CREATED
SCHEDULE_CRON
}
enum EngagementKind {
TASK
DECISION
}
enum UserMemoryCategory {
PREFERENCE
WORK_PATTERN
CONTACT_RELATIONSHIP
DECISION_HISTORY
EXPERTISE_DOMAIN
COMMUNICATION_STYLE
TOOL_USAGE
PROJECT_CONTEXT
GOAL_OBJECTIVE
CONSTRAINT
}
enum UserMemorySource {
OBSERVED
USER_STATED
INFERRED
CONFIGURED
}
enum AgendaView {
DAY
WEEK
MONTH
}
enum DeliveryStatus {
PENDING
SENT
ACKED
FAILED
EXPIRED
AGGREGATED
}
// --- Models Engagement Layer ---
model Task {
id String @id @default(uuid())
title String @db.VarChar(200)
description String? @db.Text
status TaskStatus @default(TODO)
priority TaskPriority @default(MEDIUM)
dueDate DateTime?
createdById String
createdBy User @relation("TaskCreatedBy", fields: [createdById], references: [id])
assigneeId String?
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id])
source TaskSource @default(MANUAL)
autoRuleId String?
autoRule EngagementAutoRule? @relation("TaskFromRule", fields: [autoRuleId], references: [id])
estimatedMinutes Int?
actualMinutes Int?
isPrivate Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
startedAt DateTime?
completedAt DateTime?
dependencies TaskDependency[] @relation("TaskDependencies")
dependents TaskDependency[] @relation("TaskDependents")
entityLinks TaskEntityLink[]
reminders Reminder[] @relation("RemindersForTask")
@@index([assigneeId, status])
@@index([createdById])
@@index([dueDate])
@@index([status, priority])
@@index([source, autoRuleId])
@@map("tasks")
}
model TaskDependency {
id String @id @default(uuid())
taskId String
task Task @relation("TaskDependencies", fields: [taskId], references: [id], onDelete: Cascade)
dependsOnTaskId String
dependsOnTask Task @relation("TaskDependents", fields: [dependsOnTaskId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([taskId, dependsOnTaskId])
@@index([dependsOnTaskId])
@@map("task_dependencies")
}
model TaskEntityLink {
id String @id @default(uuid())
taskId String
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
entityType ErpEntityType
entityId String
createdAt DateTime @default(now())
@@unique([taskId, entityType, entityId])
@@index([entityType, entityId])
@@map("task_entity_links")
}
model Decision {
id String @id @default(uuid())
title String @db.VarChar(200)
description String @db.Text
status DecisionStatus @default(PENDING)
priority DecisionPriority @default(MEDIUM)
kind DecisionKind
options Json?
deciderId String
decider User @relation("DecisionDecider", fields: [deciderId], references: [id])
escalateToId String?
escalateTo User? @relation("DecisionEscalateTo", fields: [escalateToId], references: [id])
escalateAfterHours Int?
createdById String
createdBy User @relation("DecisionCreatedBy", fields: [createdById], references: [id])
source DecisionSource @default(MANUAL)
autoRuleId String?
autoRule EngagementAutoRule? @relation("DecisionFromRule", fields: [autoRuleId], references: [id])
decisionDeadline DateTime
decidedAt DateTime?
decidedById String?
decidedBy User? @relation("DecisionDecidedBy", fields: [decidedById], references: [id])
decidedOptionKey String?
decidedText String? @db.Text
escalatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
entityLinks DecisionEntityLink[]
reminders Reminder[] @relation("RemindersForDecision")
log DecisionLog[]
@@index([deciderId, status])
@@index([escalateToId, escalatedAt])
@@index([decisionDeadline])
@@index([status, priority])
@@map("decisions")
}
model DecisionLog {
id String @id @default(uuid())
decisionId String
decision Decision @relation(fields: [decisionId], references: [id], onDelete: Cascade)
eventType DecisionLogEventType
actorUserId String?
actor User? @relation("DecisionLogActor", fields: [actorUserId], references: [id])
payload Json?
createdAt DateTime @default(now())
@@index([decisionId, createdAt])
@@map("decision_logs")
}
model DecisionEntityLink {
id String @id @default(uuid())
decisionId String
decision Decision @relation(fields: [decisionId], references: [id], onDelete: Cascade)
entityType ErpEntityType
entityId String
createdAt DateTime @default(now())
@@unique([decisionId, entityType, entityId])
@@index([entityType, entityId])
@@map("decision_entity_links")
}
model Reminder {
id String @id @default(uuid())
taskId String?
task Task? @relation("RemindersForTask", fields: [taskId], references: [id], onDelete: Cascade)
decisionId String?
decision Decision? @relation("RemindersForDecision", fields: [decisionId], references: [id], onDelete: Cascade)
scheduledFor DateTime
status ReminderStatus @default(SCHEDULED)
preferredChannel NotificationChannel?
customMessage String? @db.Text
targetUserId String
target User @relation("ReminderTarget", fields: [targetUserId], references: [id])
createdById String?
source ReminderSource @default(MANUAL)
sentAt DateTime?
acknowledgedAt DateTime?
expiredAt DateTime?
failureReason String?
deliveries NotificationDelivery[] @relation("DeliveriesForReminder")
createdAt DateTime @default(now())
@@index([targetUserId, status, scheduledFor])
@@index([scheduledFor, status])
@@map("reminders")
}
model EngagementAutoRule {
id String @id @default(uuid())
name String @db.VarChar(100)
description String? @db.Text
enabled Boolean @default(true)
triggerType AutoRuleTriggerType
triggerConfig Json
engagementKind EngagementKind
engagementTemplate Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
generatedTasks Task[] @relation("TaskFromRule")
generatedDecisions Decision[] @relation("DecisionFromRule")
@@index([triggerType, enabled])
@@index([engagementKind])
@@map("engagement_auto_rules")
}
model UserMemory {
id String @id @default(uuid())
userId String
user User @relation("UserMemories", fields: [userId], references: [id], onDelete: Cascade)
category UserMemoryCategory
factKey String @db.VarChar(100)
factValue Json
confidence Float @default(0.5)
source UserMemorySource
evidenceCount Int @default(1)
firstSeenAt DateTime @default(now())
lastSeenAt DateTime @default(now())
lastReinforced DateTime @default(now())
expiresAt DateTime?
agentSource String?
conversationId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, category, factKey])
@@index([userId, category])
@@index([userId, lastReinforced])
@@index([expiresAt])
@@map("user_memories")
}
model AgendaCalendarConfig {
id String @id @default(uuid())
userId String @unique
user User @relation("AgendaCalendarConfig", fields: [userId], references: [id], onDelete: Cascade)
defaultView AgendaView @default(WEEK)
weekStartsOn Int @default(1)
workingDays Int[] @default([1, 2, 3, 4, 5])
workingHourStart Int @default(8)
workingHourEnd Int @default(18)
showTasks Boolean @default(true)
showDecisions Boolean @default(true)
showReminders Boolean @default(true)
showErpEvents Boolean @default(true)
hiddenCategories String[] @default([])
showCompletedDays Int @default(7)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("agenda_calendar_configs")
}
model ChatPreference {
id String @id @default(uuid())
userId String @unique
user User @relation("ChatPreferences", fields: [userId], references: [id], onDelete: Cascade)
directExecution Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("chat_preferences")
}
model NotificationDelivery {
id String @id @default(uuid())
reminderId String?
reminder Reminder? @relation("DeliveriesForReminder", fields: [reminderId], references: [id], onDelete: Cascade)
notificationId String?
agentActionId String?
sourceType String?
sourceId String?
targetUserId String
target User @relation("NotificationDeliveries", fields: [targetUserId], references: [id])
channel NotificationChannel
payload Json
priority String @default("MEDIUM")
enableEscalation Boolean @default(false) @map("enable_escalation")
status DeliveryStatus @default(PENDING)
sentAt DateTime?
acknowledgedAt DateTime?
failedAt DateTime?
failureReason String?
scheduledFor DateTime? @map("scheduled_for")
externalId String? @map("external_id")
ackToken String? @map("ack_token")
parentDeliveryId String?
parentDelivery NotificationDelivery? @relation("DeliveryEscalation", fields: [parentDeliveryId], references: [id])
childDeliveries NotificationDelivery[] @relation("DeliveryEscalation")
conversationSessionId String?
createdAt DateTime @default(now())
@@index([targetUserId, status, channel])
@@index([reminderId])
@@index([notificationId])
@@index([agentActionId])
@@index([createdAt])
@@index([sourceType, sourceId])
@@index([status, enableEscalation])
@@index([ackToken])
@@map("notification_deliveries")
}
enum LessonStatus {
PROPOSED
CONFIRMED
PROMOTED
PERSONAL
REJECTED
}
model AgentLesson {
id String @id @default(uuid())
status LessonStatus @default(PROPOSED)
eventType String
entityType String
entityId Int
actorUserId String
actor User @relation("LessonActor", fields: [actorUserId], references: [id])
observedAt DateTime @default(now())
lessonText String @db.Text
conditions String? @db.Text
surpriseReason String? @db.Text
actorResponse String? @db.Text
confirmedAt DateTime?
promotedById String?
promotedBy User? @relation("LessonPromoter", fields: [promotedById], references: [id])
promotedAt DateTime?
rejectedReason String?
kbArticleId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([actorUserId])
@@index([status])
@@index([eventType, entityType])
@@map("agent_lessons")
}
enum ActionValueClass {
FORTE
MOYENNE
FAIBLE
}
model ActionCatalog {
id String @id @default(uuid())
actionType String @unique
label String
module String
level Int @default(0)
valueClass ActionValueClass @default(FAIBLE)
isAiCapable Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("action_catalog")
}
enum DetectorRegister {
PLANCHER
ANOMALIE
IMPORTANT
BRUIT_DE_FOND
}
model DetectorConfig {
id String @id @default(uuid())
detectorId String @unique
register DetectorRegister @default(IMPORTANT)
thresholdDays Int @default(2)
coupable Boolean @default(true)
isFixed Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("detector_configs")
}
enum SilenceMode {
NONE
CALM
TOTAL
}
model AugustSilenceState {
id String @id @default(uuid())
userId String @unique
user User @relation("AugustSilence", fields: [userId], references: [id], onDelete: Cascade)
mode SilenceMode @default(NONE)
expiresAt DateTime?
activatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("august_silence_states")
}
model PushSubscription {
id String @id @default(uuid())
userId String
user User @relation("UserPushSubscriptions", fields: [userId], references: [id], onDelete: Cascade)
endpoint String @db.Text
p256dh String @db.VarChar(500)
auth String @db.VarChar(200)
userAgent String? @db.VarChar(500) @map("user_agent")
failureCount Int @default(0) @map("failure_count")
lastUsedAt DateTime? @map("last_used_at")
createdAt DateTime @default(now())
expiresAt DateTime? @map("expires_at")
@@unique([userId, endpoint])
@@index([userId])
@@map("push_subscriptions")
}
// SPEC-TRUD-3: Décompte travail ouvrier
enum WorkLogStatus {
PENDING
VERIFIED
REJECTED
}
model Worker {
id Int @id @default(autoincrement())
name String
nameRu String @map("name_ru")
category WorkerCategory @default(UNIVERSAL)
active Boolean @default(true)
phone String?
workLogs WorkLog[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("workers")
}
model WorkLog {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
operationId Int @map("operation_id")
operation OrderLaborOperation @relation(fields: [operationId], references: [id], onDelete: Cascade)
workerId Int @map("worker_id")
worker Worker @relation(fields: [workerId], references: [id])
workDate DateTime @map("work_date") @db.Date
quantityDone Decimal @map("quantity_done") @db.Decimal(12, 4)
costAmount Decimal @map("cost_amount") @db.Decimal(12, 2)
status WorkLogStatus @default(PENDING)
verifiedById String? @map("verified_by_id")
verifiedBy User? @relation("WorkLogVerifier", fields: [verifiedById], references: [id])
verifiedAt DateTime? @map("verified_at")
rejectionReason String? @map("rejection_reason")
excessJustification String? @map("excess_justification")
createdById String? @map("created_by_id")
createdBy User? @relation("WorkLogCreator", fields: [createdById], references: [id])
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([orderId])
@@index([operationId])
@@index([workerId])
@@index([workDate])
@@map("work_logs")
}