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>
4174 lines
159 KiB
Plaintext
4174 lines
159 KiB
Plaintext
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")
|
||
}
|