generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Org { id String @id @default(uuid()) name String slug String @unique createdAt DateTime @default(now()) machines Machine[] events MachineEvent[] heartbeats MachineHeartbeat[] kpiSnapshots MachineKpiSnapshot[] members OrgUser[] reasonEntries ReasonEntry[] sessions Session[] alertContacts AlertContact[] alertNotifications AlertNotification[] alertPolicies AlertPolicy? downtimeActions DowntimeAction[] locationFinancialOverrides LocationFinancialOverride[] machineFinancialOverrides MachineFinancialOverride[] machineSettings MachineSettings[] workOrders MachineWorkOrder[] financialProfile OrgFinancialProfile? invites OrgInvite[] settings OrgSettings? shifts OrgShift[] productCostOverrides ProductCostOverride[] settingsAudits SettingsAudit[] } model User { id String @id @default(uuid()) email String @unique name String? passwordHash String isActive Boolean @default(true) createdAt DateTime @default(now()) emailVerificationExpiresAt DateTime? @map("email_verification_expires_at") emailVerificationToken String? @unique @map("email_verification_token") emailVerifiedAt DateTime? @map("email_verified_at") phone String? @map("phone") orgs OrgUser[] sessions Session[] alertContacts AlertContact[] alertNotifications AlertNotification[] downtimeActionsCreated DowntimeAction[] @relation("DowntimeActionCreator") downtimeActionsOwned DowntimeAction[] @relation("DowntimeActionOwner") sentInvites OrgInvite[] @relation("OrgInviteInviter") } model OrgUser { id String @id @default(uuid()) orgId String userId String role String @default("MEMBER") createdAt DateTime @default(now()) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([orgId, userId]) @@index([userId]) @@index([orgId]) } model OrgInvite { id String @id @default(uuid()) orgId String @map("org_id") email String role String @default("MEMBER") token String @unique invitedBy String? @map("invited_by") createdAt DateTime @default(now()) @map("created_at") expiresAt DateTime @map("expires_at") acceptedAt DateTime? @map("accepted_at") revokedAt DateTime? @map("revoked_at") inviter User? @relation("OrgInviteInviter", fields: [invitedBy], references: [id]) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId]) @@index([orgId, email]) @@index([expiresAt]) @@map("org_invites") } model Session { id String @id @default(uuid()) orgId String userId String createdAt DateTime @default(now()) lastSeenAt DateTime @default(now()) expiresAt DateTime revokedAt DateTime? ip String? userAgent String? org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([orgId]) @@index([expiresAt]) } model Machine { id String @id @default(uuid()) orgId String name String code String? createdAt DateTime @default(now()) location String? updatedAt DateTime @updatedAt apiKey String? @unique schemaVersion String? @map("schema_version") seq BigInt? @map("seq") tsDevice DateTime @default(now()) @map("ts") tsServer DateTime @default(now()) @map("ts_server") pairingCode String? @unique @map("pairing_code") pairingCodeExpiresAt DateTime? @map("pairing_code_expires_at") pairingCodeUsedAt DateTime? @map("pairing_code_used_at") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) cycles MachineCycle[] events MachineEvent[] heartbeats MachineHeartbeat[] kpiSnapshots MachineKpiSnapshot[] reasonEntries ReasonEntry[] alertNotifications AlertNotification[] downtimeActions DowntimeAction[] financialOverrides MachineFinancialOverride[] settings MachineSettings? workOrders MachineWorkOrder[] settingsAudits SettingsAudit[] @@unique([orgId, name]) @@index([orgId]) } model MachineHeartbeat { id String @id @default(uuid()) orgId String machineId String ts DateTime @default(now()) status String message String? ip String? fwVersion String? schemaVersion String? @map("schema_version") seq BigInt? @map("seq") tsServer DateTime @default(now()) @map("ts_server") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId, machineId, ts]) } model MachineKpiSnapshot { id String @id @default(uuid()) orgId String machineId String ts DateTime @default(now()) workOrderId String? sku String? target Int? good Int? scrap Int? cycleCount Int? goodParts Int? scrapParts Int? cavities Int? cycleTime Float? actualCycle Float? availability Float? performance Float? quality Float? oee Float? trackingEnabled Boolean? productionStarted Boolean? schemaVersion String? @map("schema_version") seq BigInt? @map("seq") tsServer DateTime @default(now()) @map("ts_server") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, machineId, seq], map: "uq_kpi_org_machine_seq") @@index([orgId, machineId, ts]) } model MachineEvent { id String @id @default(uuid()) orgId String machineId String ts DateTime @default(now()) topic String eventType String severity String requiresAck Boolean @default(false) title String description String? data Json? workOrderId String? sku String? schemaVersion String? @map("schema_version") seq BigInt? @map("seq") tsServer DateTime @default(now()) @map("ts_server") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, machineId, seq], map: "uq_event_org_machine_seq") @@index([orgId, machineId, ts]) @@index([orgId, machineId, eventType, ts]) } model MachineCycle { id String @id @default(uuid()) orgId String machineId String ts DateTime @default(now()) cycleCount Int? actualCycleTime Float theoreticalCycleTime Float? workOrderId String? sku String? cavities Int? goodDelta Int? scrapDelta Int? createdAt DateTime @default(now()) schemaVersion String? @map("schema_version") seq BigInt? @map("seq") tsServer DateTime @default(now()) @map("ts_server") machine Machine @relation(fields: [machineId], references: [id]) @@unique([orgId, machineId, ts, cycleCount]) @@unique([orgId, machineId, seq], map: "uq_cycle_org_machine_seq") @@index([orgId, machineId, ts]) @@index([orgId, machineId, cycleCount]) } model MachineWorkOrder { id String @id @default(uuid()) orgId String machineId String workOrderId String sku String? targetQty Int? cycleTime Float? status String @default("PENDING") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt goodParts Int @default(0) @map("good_parts") scrapParts Int @default(0) @map("scrap_parts") cycleCount Int @default(0) @map("cycle_count") mold String? cavitiesTotal Int? @map("cavities_total") cavitiesActive Int? @map("cavities_active") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([machineId, workOrderId]) @@index([orgId, machineId]) @@index([orgId, workOrderId]) @@map("machine_work_orders") } model IngestLog { id String @id @default(uuid()) orgId String? machineId String? endpoint String schemaVersion String? seq BigInt? tsDevice DateTime? tsServer DateTime @default(now()) ok Boolean status Int errorCode String? errorMsg String? body Json? ip String? userAgent String? @@index([endpoint, tsServer]) @@index([machineId, tsServer]) @@index([machineId, seq]) } model OrgSettings { orgId String @id @map("org_id") timezone String @default("UTC") shiftChangeCompMin Int @default(10) @map("shift_change_comp_min") lunchBreakMin Int @default(30) @map("lunch_break_min") stoppageMultiplier Float @default(1.5) @map("stoppage_multiplier") oeeAlertThresholdPct Float @default(90) @map("oee_alert_threshold_pct") macroStoppageMultiplier Float @default(5) @map("macro_stoppage_multiplier") performanceThresholdPct Float @default(85) @map("performance_threshold_pct") qualitySpikeDeltaPct Float @default(5) @map("quality_spike_delta_pct") alertsJson Json? @map("alerts_json") defaultsJson Json? @map("defaults_json") version Int @default(1) updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") shiftScheduleOverridesJson Json? @map("shift_schedule_overrides_json") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@map("org_settings") } model OrgFinancialProfile { orgId String @id @map("org_id") defaultCurrency String @default("USD") @map("default_currency") machineCostPerMin Float? @map("machine_cost_per_min") operatorCostPerMin Float? @map("operator_cost_per_min") ratedRunningKw Float? @map("rated_running_kw") idleKw Float? @map("idle_kw") kwhRate Float? @map("kwh_rate") energyMultiplier Float @default(1.0) @map("energy_multiplier") energyCostPerMin Float? @map("energy_cost_per_min") scrapCostPerUnit Float? @map("scrap_cost_per_unit") rawMaterialCostPerUnit Float? @map("raw_material_cost_per_unit") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@map("org_financial_profiles") } model LocationFinancialOverride { id String @id @default(uuid()) orgId String @map("org_id") location String currency String? machineCostPerMin Float? @map("machine_cost_per_min") operatorCostPerMin Float? @map("operator_cost_per_min") ratedRunningKw Float? @map("rated_running_kw") idleKw Float? @map("idle_kw") kwhRate Float? @map("kwh_rate") energyMultiplier Float? @map("energy_multiplier") energyCostPerMin Float? @map("energy_cost_per_min") scrapCostPerUnit Float? @map("scrap_cost_per_unit") rawMaterialCostPerUnit Float? @map("raw_material_cost_per_unit") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, location]) @@index([orgId]) @@map("location_financial_overrides") } model MachineFinancialOverride { id String @id @default(uuid()) orgId String @map("org_id") machineId String @map("machine_id") currency String? machineCostPerMin Float? @map("machine_cost_per_min") operatorCostPerMin Float? @map("operator_cost_per_min") ratedRunningKw Float? @map("rated_running_kw") idleKw Float? @map("idle_kw") kwhRate Float? @map("kwh_rate") energyMultiplier Float? @map("energy_multiplier") energyCostPerMin Float? @map("energy_cost_per_min") scrapCostPerUnit Float? @map("scrap_cost_per_unit") rawMaterialCostPerUnit Float? @map("raw_material_cost_per_unit") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, machineId]) @@index([orgId]) @@map("machine_financial_overrides") } model ProductCostOverride { id String @id @default(uuid()) orgId String @map("org_id") sku String currency String? rawMaterialCostPerUnit Float? @map("raw_material_cost_per_unit") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, sku]) @@index([orgId]) @@map("product_cost_overrides") } model AlertPolicy { id String @id @default(uuid()) orgId String @unique @map("org_id") policyJson Json @map("policy_json") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId]) @@map("alert_policies") } model AlertContact { id String @id @default(uuid()) orgId String @map("org_id") userId String? @map("user_id") name String roleScope String @map("role_scope") email String? phone String? eventTypes Json? @map("event_types") isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) notifications AlertNotification[] @@unique([orgId, userId]) @@index([orgId]) @@index([orgId, roleScope]) @@map("alert_contacts") } model AlertNotification { id String @id @default(uuid()) orgId String @map("org_id") machineId String @map("machine_id") eventId String @map("event_id") eventType String @map("event_type") ruleId String @map("rule_id") role String channel String contactId String? @map("contact_id") userId String? @map("user_id") sentAt DateTime @default(now()) @map("sent_at") status String error String? contact AlertContact? @relation(fields: [contactId], references: [id]) machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) @@index([orgId, machineId, sentAt]) @@index([orgId, eventId, role, channel]) @@index([userId]) @@index([contactId]) @@map("alert_notifications") } model OrgShift { id String @id @default(uuid()) orgId String @map("org_id") name String startTime String @map("start_time") endTime String @map("end_time") sortOrder Int @map("sort_order") enabled Boolean @default(true) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId]) @@index([orgId, sortOrder]) @@map("org_shifts") } model MachineSettings { machineId String @id @map("machine_id") orgId String @map("org_id") overridesJson Json? @map("overrides_json") updatedAt DateTime @updatedAt @map("updated_at") updatedBy String? @map("updated_by") machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId]) @@map("machine_settings") } model SettingsAudit { id String @id @default(uuid()) orgId String @map("org_id") machineId String? @map("machine_id") actorId String? @map("actor_id") source String payloadJson Json @map("payload_json") createdAt DateTime @default(now()) @map("created_at") machine Machine? @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([orgId, createdAt]) @@index([machineId, createdAt]) @@map("settings_audit") } model ReasonEntry { id String @id @default(uuid()) orgId String machineId String reasonId String @unique kind String episodeId String? durationSeconds Int? episodeEndTs DateTime? scrapEntryId String? scrapQty Int? scrapUnit String? reasonCode String reasonLabel String? reasonText String? capturedAt DateTime workOrderId String? meta Json? schemaVersion Int @default(1) createdAt DateTime @default(now()) machine Machine @relation(fields: [machineId], references: [id], onDelete: Cascade) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) @@unique([orgId, kind, episodeId]) @@unique([orgId, kind, scrapEntryId]) @@index([orgId, machineId, capturedAt]) @@index([orgId, kind, capturedAt]) } model DowntimeAction { id String @id @default(uuid()) orgId String @map("org_id") machineId String? @map("machine_id") reasonCode String? @map("reason_code") hmDay Int? @map("hm_day") hmHour Int? @map("hm_hour") title String notes String? status String @default("open") priority String @default("medium") dueDate DateTime? @map("due_date") reminderAt DateTime? @map("reminder_at") lastReminderAt DateTime? @map("last_reminder_at") completedAt DateTime? @map("completed_at") ownerUserId String? @map("owner_user_id") createdBy String? @map("created_by") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") reminderStage String? @map("reminder_stage") creator User? @relation("DowntimeActionCreator", fields: [createdBy], references: [id]) machine Machine? @relation(fields: [machineId], references: [id]) org Org @relation(fields: [orgId], references: [id], onDelete: Cascade) ownerUser User? @relation("DowntimeActionOwner", fields: [ownerUserId], references: [id]) @@index([orgId]) @@index([orgId, machineId]) @@index([orgId, reasonCode]) @@index([orgId, hmDay, hmHour]) @@index([orgId, status, dueDate]) @@index([ownerUserId]) @@map("downtime_actions") }