feat: add time entry manager, controller, and API routes
This commit is contained in:
+332
-44
@@ -124,10 +124,10 @@ enum SyncJobStatus {
|
||||
}
|
||||
|
||||
model SyncJobRun {
|
||||
id String @id @default(uuid())
|
||||
jobType SyncJobType
|
||||
status SyncJobStatus @default(QUEUED)
|
||||
triggeredBy String @default("system")
|
||||
id String @id @default(uuid())
|
||||
jobType SyncJobType
|
||||
status SyncJobStatus @default(QUEUED)
|
||||
triggeredBy String @default("system")
|
||||
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
@@ -250,6 +250,7 @@ model CorporateLocation {
|
||||
opportunities Opportunity[]
|
||||
serviceBoards ServiceTicketBoard[]
|
||||
productDataRecords ProductData[]
|
||||
timeEntries TimeEntry[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -307,6 +308,9 @@ model Company {
|
||||
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])
|
||||
@@ -384,12 +388,15 @@ model Contact {
|
||||
memberId Int?
|
||||
companyId Int?
|
||||
|
||||
company Company? @relation(fields: [companyId], references: [id])
|
||||
opportunities Opportunity[]
|
||||
company Company? @relation(fields: [companyId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
opportunities Opportunity[]
|
||||
serviceTickets ServiceTicket[]
|
||||
activities Activity[]
|
||||
timeEntries TimeEntry[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model CatalogItemType {
|
||||
@@ -606,7 +613,7 @@ model ProductData {
|
||||
qty Float @default(1)
|
||||
internalNote String?
|
||||
shortDescription String?
|
||||
description 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?
|
||||
@@ -692,7 +699,8 @@ model ServiceTicket {
|
||||
|
||||
// ------ 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.
|
||||
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.
|
||||
@@ -739,12 +747,12 @@ model ServiceTicket {
|
||||
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])
|
||||
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])
|
||||
@@ -1104,9 +1112,9 @@ model ScheduleStatus {
|
||||
|
||||
name String
|
||||
description String? // Optima Only field, not synced to CW
|
||||
color String?
|
||||
color String?
|
||||
|
||||
softFlag Boolean @default(false)
|
||||
softFlag Boolean @default(false)
|
||||
defaultFlag Boolean @default(false)
|
||||
|
||||
schedules Schedule[]
|
||||
@@ -1122,15 +1130,15 @@ model ScheduleType {
|
||||
id Int @unique
|
||||
uid String @id @default(uuid())
|
||||
|
||||
name String
|
||||
description String? // Optima Only field, not synced to CW
|
||||
displayColor String?
|
||||
name String
|
||||
description String? // Optima Only field, not synced to CW
|
||||
displayColor String?
|
||||
|
||||
tableReference String?
|
||||
moduleId String? @db.Char(2)
|
||||
moduleId String? @db.Char(2)
|
||||
scheduleTypeId String? @db.Char(1)
|
||||
|
||||
systemFlag Boolean @default(false)
|
||||
systemFlag Boolean @default(false)
|
||||
displayFlag Boolean @default(false)
|
||||
|
||||
schedules Schedule[]
|
||||
@@ -1143,15 +1151,15 @@ model ScheduleType {
|
||||
}
|
||||
|
||||
model ScheduleSpan {
|
||||
id Int @id @default(autoincrement())
|
||||
id Int @id @default(autoincrement())
|
||||
scheduleSpanId String? @db.Char(1)
|
||||
spanDesc String? @db.Char(20)
|
||||
spanDesc String? @db.Char(20)
|
||||
|
||||
schedules Schedule[]
|
||||
}
|
||||
|
||||
model Schedule {
|
||||
id Int @unique
|
||||
id Int @unique
|
||||
uid String @id @default(uuid())
|
||||
|
||||
name String
|
||||
@@ -1159,24 +1167,24 @@ model Schedule {
|
||||
|
||||
memberId String?
|
||||
|
||||
closedFlag Boolean @default(false)
|
||||
reminderFlag Boolean @default(false)
|
||||
allDayFlag Boolean @default(false)
|
||||
closedFlag Boolean @default(false)
|
||||
reminderFlag Boolean @default(false)
|
||||
allDayFlag Boolean @default(false)
|
||||
acknowledgementFlag Boolean @default(false)
|
||||
meetingFlag Boolean @default(false)
|
||||
recurringFlag 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)
|
||||
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])
|
||||
@@ -1189,9 +1197,274 @@ model Schedule {
|
||||
|
||||
updatedById String?
|
||||
createdById String?
|
||||
closedById 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?
|
||||
|
||||
closedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
@@ -1269,12 +1542,12 @@ model GeneratedQuotes {
|
||||
}
|
||||
|
||||
model TaxCode {
|
||||
id Int @unique
|
||||
id Int @unique
|
||||
uid String @id @default(uuid())
|
||||
|
||||
opportunities Opportunity[]
|
||||
|
||||
code String @unique
|
||||
code String @unique
|
||||
codeCaption String
|
||||
description String?
|
||||
|
||||
@@ -1307,3 +1580,18 @@ model CwMember {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user