// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init generator client { provider = "prisma-client" output = "../generated/prisma" } datasource db { provider = "postgresql" } enum PhoneType { DIRECT MOBILE HOME COMPANY // Main company line, not direct to contact SITE // Main site line, not direct to contact } enum FaxType { FAX // Direct fax line to contact COMPANY // Main company fax line, not direct to contact SITE // Main site fax line, not direct to contact } enum BillingMethod { ACTUAL_RATES FIXED_FEE NOT_TO_EXCEED // Requires "Bill Ticket Seperately" OVERRIDE_RATE // Shows hourly rate field } enum BillingType { STANDARD PROJECT } // By human nature, there are only two genders. enum GenderType { MALE FEMALE } enum USState { AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA WA WV WI WY } enum Country { US } enum OpportunityInterest { HOT WARM COLD } // ---- Sync Job Tracking ---- enum SyncJobType { FULL_SYNC INCREMENTAL_SYNC } enum SyncJobStatus { QUEUED RUNNING COMPLETED FAILED TIMED_OUT } model SyncJobRun { id String @id @default(uuid()) jobType SyncJobType status SyncJobStatus @default(QUEUED) triggeredBy String @default("system") startedAt DateTime? completedAt DateTime? errorSummary String? steps SyncStepLog[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model SyncStepLog { id String @id @default(uuid()) syncJobRunId String syncJobRun SyncJobRun @relation(fields: [syncJobRunId], references: [id], onDelete: Cascade) tableName String syncMode String // "full" or "incremental" recordsProcessed Int @default(0) recordsInserted Int @default(0) recordsSkipped Int @default(0) recordsFailed Int @default(0) recordsDeleted Int @default(0) sampleErrors Json @default("[]") durationMs Int @default(0) createdAt DateTime @default(now()) } model Session { id String @id @default(uuid()) sessionKey String @unique @default(cuid()) userId String expires DateTime refreshTokenGenerated Boolean @default(false) refreshedAt DateTime? invalidatedAt DateTime? user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model User { id String @id @default(uuid()) roles Role[] permissions String? login String @unique firstName String? lastName String? email String @unique image String? title String? active Boolean @default(true) hidden Boolean @default(false) cwIdentifier String? @unique cwMemberId Int? @unique cwMember CwMember? @relation(fields: [cwMemberId], references: [cwMemberId]) userId String? @unique token String? sessions Session[] companiesDeleted Company[] @relation("DeletedBy") companiesEntered Company[] @relation("EnteredBy") opportunities Opportunity[] @relation("PrimarySalesRep") opportunitiesSecondary Opportunity[] @relation("SecondarySalesRep") generatedQuotes GeneratedQuotes[] companyAddresses CompanyAddress[] updatedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt serviceTicketsOwned ServiceTicket[] @relation("ServiceTicketOwner") serviceTicketsClosed ServiceTicket[] @relation("ServiceTicketClosedBy") serviceTicketsCreated ServiceTicket[] @relation("ServiceTicketCreatedBy") serviceTicketsUpdated ServiceTicket[] @relation("ServiceTicketUpdatedBy") serviceTicketNotes ServiceTicketNote[] @relation("ServiceTicketNoteAuthor") } model Role { id String @id @default(uuid()) title String moniker String @unique // e.g. admin, super_admin, moderator permissions String users User[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CorporateLocation { id Int @unique uid String @id @default(uuid()) name String description String? updatedById String? addressLine1 String? addressLine2 String? city String? state USState? zipCode String? country Country? inactiveFlag Boolean @default(false) // Optima Only field, not synced to CW opportunities Opportunity[] serviceBoards ServiceTicketBoard[] productDataRecords ProductData[] timeEntries TimeEntry[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model InternalDepartment { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW createdById String? updatedById String? opportunities Opportunity[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model UnifiSite { id String @id @default(cuid()) name String siteId String @unique companyId Int? company Company? @relation(fields: [companyId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Company { id Int @unique uid String @id @default(uuid()) name String phone String? website String? deleteFlag Boolean @default(false) dateDeleted DateTime? taxId String? taxExempt Boolean @default(false) // Optima Only field, not synced to CW enteredById String? deletedById String? contacts Contact[] companyAddresses CompanyAddress[] credentials Credential[] unifiSites UnifiSite[] opportunities Opportunity[] timeEntries TimeEntry[] activities Activity[] deletedBy User? @relation("DeletedBy", fields: [deletedById], references: [cwIdentifier]) enteredBy User? @relation("EnteredBy", fields: [enteredById], references: [cwIdentifier]) deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt serviceTickets ServiceTicket[] billingServiceTickets ServiceTicket[] @relation("BillingCompany") } model CompanyAddress { id Int @unique uid String @id @default(uuid()) name String description String? addressLine1 String? addressLine2 String? city String? state USState? zipCode String? country Country? phone String? fax String? inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) defaultMailFlag Boolean @default(false) defaultBillFlag Boolean @default(false) defaultShipFlag Boolean @default(false) updatedById String? updatedBy User? @relation(fields: [updatedById], references: [cwIdentifier]) companyId Int company Company @relation(fields: [companyId], references: [id]) contacts Contact[] oppportunities Opportunity[] serviceTickets ServiceTicket[] billingTickets ServiceTicket[] @relation("BillingAddress") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Contact { id Int @unique uid String @id @default(uuid()) active Boolean @default(true) default Boolean @default(false) firstName String lastName String nickname String? title String? gender GenderType? birthday DateTime? email String? phone String? phoneExtension String? phoneType PhoneType? companyAddressId Int? companyAddress CompanyAddress? @relation(fields: [companyAddressId], references: [id]) memberId Int? companyId Int? company Company? @relation(fields: [companyId], references: [id]) opportunities Opportunity[] serviceTickets ServiceTicket[] activities Activity[] timeEntries TimeEntry[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CatalogItemType { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CatalogCategory { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) catalogSubcategories CatalogSubcategory[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CatalogSubcategory { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW categoryId Int category CatalogCategory @relation(fields: [categoryId], references: [id]) items CatalogItem[] inactiveFlag Boolean @default(false) updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CatalogManufacturer { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) items CatalogItem[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model WarehouseBin { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW minQuantity Int maxQuantity Int inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) proeductInventories ProductInventory[] warehouse Warehouse? @relation(fields: [warehouseId], references: [id]) warehouseId Int? updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ProductInventory { id Int @unique uid String @id @default(uuid()) qtyOnHand Int @default(0) warehouseBinId Int warehouseBin WarehouseBin @relation(fields: [warehouseBinId], references: [id]) itemId Int? item CatalogItem? @relation(fields: [itemId], references: [id]) warehouse Warehouse? @relation(fields: [warehouseId], references: [id]) warehouseId Int? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Warehouse { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) lockedFlag Boolean @default(false) minimumStock MinimumStockByWarehouse[] inventory ProductInventory[] bins WarehouseBin[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model MinimumStockByWarehouse { id Int @unique uid String @id @default(uuid()) minQty Int @default(0) warehouseId Int warehouse Warehouse @relation(fields: [warehouseId], references: [id]) itemId Int? item CatalogItem? @relation(fields: [itemId], references: [id]) updatedById String? enteredById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CatalogItem { id Int @unique uid String @id @default(uuid()) identifier String? @unique name String description String? customerDescription String? internalNotes String? linkedItems CatalogItem[] @relation("LinkedItems") linkedTo CatalogItem[] @relation("LinkedItems") subcategoryId Int subcategory CatalogSubcategory @relation(fields: [subcategoryId], references: [id]) manufacturerId Int? manufacturer CatalogManufacturer? @relation(fields: [manufacturerId], references: [id]) partNumber String? vendorName String? vendorSku String? vendorCwId Int? price Float cost Float inventory ProductInventory[] minimumStockByWarehouses MinimumStockByWarehouse[] productDataRecords ProductData[] inactive Boolean @default(false) salesTaxable Boolean @default(true) onHand Int @default(0) cwLastUpdated DateTime? // IV_Class_ID from CW: 'S' = Service/Labor, 'I' = Inventory, 'N' = Non-Inventory classId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ProductData { id Int @unique uid String @id @default(uuid()) qty Float @default(1) internalNote String? shortDescription String? description String? sequenceNumber Int? // This is the sequence number of the product on the ticket, which may be different from the sequence number of the product in the catalog, and is important for maintaining the order of products on the ticket as they were added. procurementNotes String? productNarrative String? unitPrice Float @default(0) unitCost Float @default(0) listPrice Float @default(0) // The original price of the product before any discounts or promotions are applied, which may be different from the unit price if there are any overrides or promotions applied at the ticket level. discount Float @default(0) // The discount amount applied to this product, which may be different from the discount on the catalog item if there are any overrides or promotions applied at the ticket level. recurringRevenue Float @default(0) // For subscription products, the recurring revenue amount, which may be different from the unit price if there are any discounts or promotions applied. recurringCost Float @default(0) // For subscription products, the recurring cost amount, which may be different from the unit cost if there are any discounts or promotions applied. qtyPicked Int @default(0) // How many of this product have been taken out of inventory qtyShipped Int @default(0) // How many of this product have been recieved by the customer cancelReason String? // If this product was canceled or removed from the ticket after being added, what was the reason for that? cancelQty Float? // If this product was canceled or removed from the ticket after being added, how many were canceled or removed? // ------ Flag Fields ------ billableFlag Boolean @default(true) taxableFlag Boolean @default(true) invoiceFlag Boolean @default(true) recurringFlag Boolean @default(false) // Is this product a subscription or recurring revenue product? poApprovedFlag Boolean @default(false) // Was the purchase of this product approved as part of a purchase order process? calcPriceFlag Boolean @default(true) // Should the price of this product be calculated based on the catalog item price and any overrides, or should it use the unitPrice as is? calcCostFlag Boolean @default(true) // Should the cost of this product be calculated based on the catalog item cost and any overrides, or should it use the unitCost as is? cancelFlag Boolean @default(false) // Has this product been canceled or removed from the ticket after being added, but we want to keep the record of it for historical and reporting purposes? // ------ Relational Fields ------ catalogItemId Int corporateLocationId Int serviceTicketId Int? opportunityId Int? serviceTicket ServiceTicket? @relation(fields: [serviceTicketId], references: [id]) opportunity Opportunity? @relation(fields: [opportunityId], references: [id]) catalogItem CatalogItem @relation(fields: [catalogItemId], references: [id]) corporateLocation CorporateLocation @relation(fields: [corporateLocationId], references: [id]) updatedById String? createdById String? closedById String? cancelById String closedAt DateTime? cancelledAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // // SERVICE TICKETS // model ServiceTicket { id Int @unique uid String @id @default(uuid()) // ------ Core ticket fields ------ summary String notes ServiceTicketNote[] addressLine1 String? addressLine2 String? city String? state USState? zipCode String? country Country? contactName String? phone String? phoneExtension String? phoneType PhoneType? email String? // ------ Billing and invoicing fields ------ products ProductData[] // The products used on this ticket, which may be important for billing and invoicing, as well as reporting and analytics. timeEntries TimeEntry[] // The time entries logged against this ticket. poNumber String? billCompleteFlag Boolean @default(false) // Bill after ticket is closed, not allowing any billing while open. billUnapprovedFlag Boolean @default(false) // Can this ticket bill unapproved work or expenses? billingAmount Float @default(0.00) billingMethod BillingMethod @default(ACTUAL_RATES) timeBillableFlag Boolean @default(true) // Is the time spent on this ticket billable? expenseBillableFlag Boolean @default(true) // Are the expenses incurred on this ticket billable? productBillableFlag Boolean @default(true) // Are the products used on this ticket billable? timeInvoiceableFlag Boolean @default(true) // Should the billable time on this ticket be included on invoices? expenseInvoiceableFlag Boolean @default(true) // Should the billable expenses on this ticket be included on invoices? productInvoiceableFlag Boolean @default(true) // Should the billable products on this ticket be included on invoices? dateRequested DateTime? // The date the customer requested service, which may be different from the date the ticket was created in the system. billingType BillingType @default(STANDARD) // (CUSTOM FIELD) Standard billing or project billing, which may have different rules for how the ticket is billed. billingInstructions String? // (CUSTOM FIELD) Any special instructions for billing this ticket, which may be important for project billing or non-standard billing arrangements. // ------ Flag Fields ------ rejectedFlag Boolean @default(false) // More used for denoting if a ticket was rejected by some automation. closedFlag Boolean @default(false) redFlag Boolean @default(false) // Carry over from CW, used for visibility and filtering. publishFlag Boolean @default(false) // Should this ticket be visible to the customer in the portal or any other means? // ------ Relational Fields ------ ticketOwnerId String? serviceTicketBoardId Int? severityId Int impactId Int priorityId Int sourceId Int locationId Int parentId Int? companyId Int? contactId Int? companyAddressId Int? billingCompanyId Int? billingAddressId Int? severity ServiceTicketSeverity @relation(fields: [severityId], references: [id]) impact ServiceTicketImpact @relation(fields: [impactId], references: [id]) priority ServiceTicketPriority @relation(fields: [priorityId], references: [id]) source ServiceTicketSource @relation(fields: [sourceId], references: [id]) location ServiceTicketLocation @relation(fields: [locationId], references: [id]) serviceTicketBoard ServiceTicketBoard? @relation(fields: [serviceTicketBoardId], references: [id]) ticketOwner User? @relation("ServiceTicketOwner", fields: [ticketOwnerId], references: [cwIdentifier]) company Company? @relation(fields: [companyId], references: [id]) contact Contact? @relation(fields: [contactId], references: [id]) companyAddress CompanyAddress? @relation(fields: [companyAddressId], references: [id]) billingCompany Company? @relation("BillingCompany", fields: [billingCompanyId], references: [id]) billingAddress CompanyAddress? @relation("BillingAddress", fields: [billingAddressId], references: [id]) // ------ Audit Fields ------ createdById String? updatedById String? closedById String? closedBy User? @relation("ServiceTicketClosedBy", fields: [closedById], references: [id]) createdBy User? @relation("ServiceTicketCreatedBy", fields: [createdById], references: [id]) updatedBy User? @relation("ServiceTicketUpdatedBy", fields: [updatedById], references: [id]) rejectedAt DateTime? closedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ServiceTicketNote { id Int @unique uid String @id @default(uuid()) notes String notesMd String authorId String author User @relation("ServiceTicketNoteAuthor", fields: [authorId], references: [id]) problemFlag Boolean @default(false) // Is this note describing the problem? resolutionFlag Boolean @default(false) // Is this note describing the resolution? internalAnalysisFlag Boolean @default(false) // Is this note describing the internal analysis of the issue, such as root cause analysis or technical details that may not be relevant to the customer? internalMemberFlag Boolean @default(false) // Is this note meant to be seen by internal team members only, not visible to the customer? createdByParentFlag Boolean @default(false) // Is this note created by the parent entity. mergedFlag Boolean @default(false) bundledFlag Boolean @default(false) serviceTicketId Int serviceTicket ServiceTicket @relation(fields: [serviceTicketId], references: [id]) createdById String? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ServiceTicketType { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ServiceTicketBoard { id Int @unique uid String @id @default(uuid()) name String // Does this generate revenue for the company? timeBillableFlag Boolean @default(false) expenseBillableFlag Boolean @default(false) productBillableFlag Boolean @default(false) // Can the customer see this on their invoice? timeInvoiceableFlag Boolean @default(false) expenseInvoiceableFlag Boolean @default(false) productInvoiceableFlag Boolean @default(false) // These are auto assignment rule flags // If/When I implement a system for auto assignement, // these flags will determine which fields are considered // for auto assignment when a ticket is created without an assignee. // These are a carry over from CW. autoAssignNewFlag Boolean @default(false) autoAssignEmailCreatedFlag Boolean @default(false) autoAssignPortalCreatedFlag Boolean @default(false) projectFlag Boolean @default(false) lockDescriptionFlag Boolean @default(false) // Should we lock the description field on tickets in this board after creation? emailContactFlag Boolean @default(false) // Should we email the contact when a ticket is updated? emailResourceFlag Boolean @default(false) // Should we email the assigned resource(s) when a ticket is updated? resolutionSortOrder String @default("D") @db.Char(1) internalAnalysisSortOrder String @default("D") @db.Char(1) locationId Int location CorporateLocation @relation(fields: [locationId], references: [id]) serviceTickets ServiceTicket[] createdById String? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ServiceTicketLocation { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW defaultFlag Boolean @default(false) serviceTickets ServiceTicket[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ServiceTicketSource { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) serviceTickets ServiceTicket[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // How much does the issue affect the customer or their business operations model ServiceTicketImpact { id Int @unique uid String @id @default(uuid()) name String description String? defaultFlag Boolean @default(false) serviceTickets ServiceTicket[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // How soon does the issue need to be addressed/resolved model ServiceTicketPriority { id Int @unique uid String @id @default(uuid()) name String color String? // Hex color code for priority, e.g. "#FF0000" for red description String? // Optima Only field, not synced to CW defaultFlag Boolean @default(false) serviceTickets ServiceTicket[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // How bad is the Technical Issue model ServiceTicketSeverity { id Int @unique uid String @id @default(uuid()) name String description String? defaultFlag Boolean @default(false) serviceTickets ServiceTicket[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("ServiceTicketServerity") } // This data is populated asynchronously after a ticket is closed, // so we can keep the critical path of closing a ticket fast and not // dependent on any additional data processing. This will also allow // us to be able to store the data AS IT was at the time of closing, // without worrying about any additional updates that may come in after the fact. model ServiceTicketFinalData { id String @id @default(uuid()) } model OpportunityStage { id Int @unique uid String @id @default(uuid()) name String seqNbr Int? funnelColor String? updatedById String? opportunities Opportunity[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model OpportunityType { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) opportunities Opportunity[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model OpportunityStatus { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) wonFlag Boolean @default(false) lostFlag Boolean @default(false) closeFlag Boolean @default(false) updatedById String? createdById String? opportunities Opportunity[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Opportunity { id Int @unique uid String @id @default(uuid()) name String notes String? oppNarrative String? // A long form text field for the story of the opportunity, which may include details about the customer's needs, the proposed solution, and any other relevant information that doesn't fit into the structured fields. generatedQuotes GeneratedQuotes[] typeId Int? type OpportunityType? @relation(fields: [typeId], references: [id]) stageId Int? stage OpportunityStage? @relation(fields: [stageId], references: [id]) statusId Int? status OpportunityStatus? @relation(fields: [statusId], references: [id]) taxCodeId Int? taxCode TaxCode? @relation(fields: [taxCodeId], references: [id]) interest OpportunityInterest? probability Float @default(0) source String? // Sales rep references primarySalesRepId String? primarySalesRep User? @relation("PrimarySalesRep", fields: [primarySalesRepId], references: [cwIdentifier]) secondarySalesRepId String? secondarySalesRep User? @relation("SecondarySalesRep", fields: [secondarySalesRepId], references: [cwIdentifier]) // Company / contact / site companyId Int? company Company? @relation(fields: [companyId], references: [id]) contactId Int? contact Contact? @relation(fields: [contactId], references: [id]) siteId Int? site CompanyAddress? @relation(fields: [siteId], references: [id]) customerPO String? // Location / department locationId Int? location CorporateLocation? @relation(fields: [locationId], references: [id]) departmentId Int? department InternalDepartment? @relation(fields: [departmentId], references: [id]) // Dates expectedCloseDate DateTime? pipelineChangeDate DateTime? dateBecameLead DateTime? closedDate DateTime? closedFlag Boolean @default(false) closedById String? // Local product sequence — array of CW forecast item IDs in display order. // When present, fetchProducts() uses this order instead of CW sequenceNumber. productSequence Int[] @default([]) products ProductData[] updatedBy String eneteredBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ------ Schedule / Calendar -------- model ScheduleStatus { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW color String? softFlag Boolean @default(false) defaultFlag Boolean @default(false) schedules Schedule[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ScheduleType { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW displayColor String? tableReference String? moduleId String? @db.Char(2) scheduleTypeId String? @db.Char(1) systemFlag Boolean @default(false) displayFlag Boolean @default(false) schedules Schedule[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ScheduleSpan { id Int @id @default(autoincrement()) scheduleSpanId String? @db.Char(1) spanDesc String? @db.Char(20) schedules Schedule[] } model Schedule { id Int @unique uid String @id @default(uuid()) name String description String? memberId String? closedFlag Boolean @default(false) reminderFlag Boolean @default(false) allDayFlag Boolean @default(false) acknowledgementFlag Boolean @default(false) meetingFlag Boolean @default(false) recurringFlag Boolean @default(false) billableFlag Boolean @default(false) acknowledgedById String? acknowledgedAt DateTime? startDate DateTime? endDate DateTime? hoursScheduled Float? duration Int? // The number of days in between the start and end date. hoursPerDay Float? reminderMinutes Int? @default(15) statusId Int? status ScheduleStatus? @relation(fields: [statusId], references: [id]) typeId Int? type ScheduleType? @relation(fields: [typeId], references: [id]) scheduleSpanId Int? scheduleSpan ScheduleSpan? @relation(fields: [scheduleSpanId], references: [id]) updatedById String? createdById String? closedById String? closedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ------- Activities ------- model Activity { id Int @unique uid String @id @default(uuid()) subject String // Activity Title/Summary notes ActivityNotes? startTime DateTime //Full Date Time Value endTime DateTime //Full Date Time Value assignToId String assignedById String enteredBy String automated Boolean @default(false) closedFlag Boolean @default(false) notifyCompleteFlag Boolean @default(false) // Should we send a notification to the person assigned when activity is completed. notificationSentFlat Boolean @default(false) // Tracks to see if the completion notification has already been sent out. opportunityId String? serviceTicketId String? contactId Int? companyId Int? activityTypeId Int? activityStatusId Int? contact Contact? @relation(fields: [contactId], references: [id]) company Company? @relation(fields: [companyId], references: [id]) activityType ActivityType? @relation(fields: [activityTypeId], references: [id]) activityStatus ActivityStatus? @relation(fields: [activityStatusId], references: [id]) timeEntries TimeEntry[] createdById String? updatedById String? closedById String? closedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ActivityNotes { id Int @unique uid String @id @default(uuid()) notes String @default("") activityId Int? @unique activity Activity? @relation(fields: [activityId], references: [id]) internalAnalysisFlag Boolean @default(false) // Does this note describing the internal analysis of the activity, such as root cause analysis or technical details that may not be relevant to the customer? enteredById String? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ActivityType { id Int @unique uid String @id @default(uuid()) name String @db.VarChar(15) // "SO_Activity_Type_ID" in CW description String inactiveFlag Boolean @default(false) historyFlag Boolean @default(false) // Is this activity type just for historical record keeping, and should not be used for new activities? defaultFlag Boolean @default(false) importFlag Boolean @default(false) emailFlag Boolean @default(false) memoFlag Boolean @default(false) pointsValue Int? activities Activity[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ActivityStatus { id Int @unique uid String @id @default(uuid()) name String description String? // Optima Only field, not synced to CW closedFlag Boolean @default(false) inactiveFlag Boolean @default(false) defaultFlag Boolean @default(false) spawnFollowupFlag Boolean @default(false) // Should creating an activity with this status automatically spawn a follow-up activity with a different type and/or status? activities Activity[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ------- Time Entries ------- model TimeEntry { id Int @unique uid String @id @default(uuid()) // The CW member identifier of the person who logged this time memberId String? // Relational Values serviceTicketId Int? activityId Int? projectId Int? // TODO: Implement this. chargeCodeId Int? companyId Int statusId Int? locationId Int? contactId Int? contact Contact? @relation(fields: [contactId], references: [id]) location CorporateLocation? @relation(fields: [locationId], references: [id]) status TimeEntryStatus? @relation(fields: [statusId], references: [id]) chargeCode TimeEntryChargeCode? @relation(fields: [chargeCodeId], references: [id]) company Company @relation(fields: [companyId], references: [id]) serviceTicket ServiceTicket? @relation(fields: [serviceTicketId], references: [id]) activity Activity? @relation(fields: [activityId], references: [id]) // ------ Time Fields ------ dateStart DateTime? timeStart DateTime? timeEnd DateTime? // ------ Notes ------ notes String? // Customer-visible notes about what was done notesMd String? // Markdown version of notes internalNote String? // Internal notes not visible to the customer // ------ Hours ------ billableHours Float? // How many hours are being billed to the customer actualHours Float? // How many hours were actually worked invoicedHours Float? // How many hours have been included on an invoice deductedHours Float? // How many hours were deducted from billing, but not necessarily the same as actual hours if there are any adjustments or overrides. // ------ Rates ------ hourlyRate Float? // The rate at which this time is billed to the customer effectiveRate Float? // The actual effective rate after any agreement or override adjustments // ------ Flag Fields ------ issueFlag Boolean @default(false) mergedFlag Boolean @default(false) // Has this time entry been merged with another time entry, such as when multiple time entries are combined into one for billing purposes? invoiceFlag Boolean @default(false) // Has this time entry been included on an invoice? billableFlag Boolean @default(true) // Should this time entry be billed to the customer? documentFlag Boolean @default(false) // Is there a document associated with this time entry, such as a receipt for an expense or a report of the work done? teProblemFlag Boolean @default(false) // Does this note describe the problem? teResolutionFlag Boolean @default(false) // Does this note describe the resolution? teInternalAnalysisFlag Boolean @default(false) // Does this note contain internal analysis? // ------ Audit Fields ------ chargeToRecId Int? // The record ID of the entity that this time entry should be charged to, which may be used for billing and reporting purposes, and may be different from the service ticket or activity it is associated with. chargeToType String? @db.VarChar(13) createdById String? updatedById String? originalAuthorId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TimeEntryStatus { id Int @unique uid String @id @default(uuid()) statusId Int @unique description String? @db.VarChar(50) action String? timeEntries TimeEntry[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TimeEntryChargeCode { id Int @unique uid String @id @default(uuid()) chargeCodeId Int @unique description String? @db.VarChar(50) expenseFlag Boolean @default(false) timeFlag Boolean @default(true) billableFlag Boolean @default(true) invoiceFlag Boolean @default(true) timeEntries TimeEntry[] updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TimeActivityClass { id Int @unique uid String @id @default(uuid()) description String? @db.VarChar(50) hourlyRate Float? inactiveFlag Boolean @default(false) taxExemptFlag Boolean @default(false) createdById String? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TimeActivityType { id Int @unique uid String @id @default(uuid()) description String? @db.VarChar(50) minHours Float? maxHours Float? rate Float? @default(1) costMultiplier Float @default(1) inactiveFlag Boolean @default(false) invoiceFlag Boolean @default(false) billableFlag Boolean @default(false) utilizationFlag Boolean @default(false) defaultFlag Boolean @default(false) multiplierFlag Boolean @default(false) createdById String? updatedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CredentialType { id String @id @default(cuid()) name String @unique permissionScope String icon String? fields Json credentials Credential[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model SecureValue { id String @id @default(cuid()) name String content String // Encrypted content hash String // Hash of the original content for integrity verification and Search credentialId String credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Credential { id String @id @default(cuid()) name String notes String? subCredentialOfId String? subCredentialOf Credential? @relation("SubCredentials", fields: [subCredentialOfId], references: [id], onDelete: Cascade) subCredentials Credential[] @relation("SubCredentials") typeId String type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade) fields Json companyId String company Company @relation(fields: [companyId], references: [uid], onDelete: Cascade) securevalues SecureValue[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model GeneratedQuotes { id String @id @default(uuid()) quoteRegenData Json @default("{}") // Store any additional data needed for quote regeneration, such as product details, pricing, etc. quoteRegenParams Json @default("{}") // Store parameters used for quote regeneration, such as template ID, formatting options, etc. quoteRegenHash String @unique @default("") downloads Json @default("[]") // Array of download records with timestamp and user info quoteFile Bytes quoteFileName String opportunityId String opportunity Opportunity @relation(fields: [opportunityId], references: [uid], onDelete: Cascade) createdById String? createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TaxCode { id Int @unique uid String @id @default(uuid()) opportunities Opportunity[] code String @unique codeCaption String description String? rate Float? defaultFlag Boolean @default(false) createdBy String updatedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CwMember { id String @id @default(cuid()) cwMemberId Int @unique identifier String @unique firstName String lastName String officeEmail String? inactiveFlag Boolean @default(false) apiKey String? user User? cwLastUpdated DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CwMemberType { id Int @unique uid String @id @default(uuid()) description String? @db.VarChar(30) inactiveFlag Boolean @default(false) updatedById String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }