feat: add time entry manager, controller, and API routes
This commit is contained in:
+376
-110
@@ -666,6 +666,7 @@ model Member {
|
||||
approvedOpportunities Opportunity[] @relation("OpportunityApprovedBy")
|
||||
rejectedOpportunities Opportunity[] @relation("OpportunityRejectedBy")
|
||||
opportunityMembers OpportunityMember[] @relation("OpportunityMemberToMember")
|
||||
memberType MemberType? @relation(fields: [memberTypeRecId], references: [memberTypeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
|
||||
@@map("Member")
|
||||
@@schema("dbo")
|
||||
@@ -1740,10 +1741,13 @@ model ActivityType {
|
||||
description String @map("Description") @db.NVarChar(50)
|
||||
hoursMin Decimal? @map("Hours_Min") @db.Decimal(18, 2)
|
||||
hoursMax Decimal? @map("Hours_Max") @db.Decimal(18, 2)
|
||||
defaultFlag Boolean @map("Default_Flag")
|
||||
multiplierFlag Boolean @map("Multiplier_Flag")
|
||||
rate Decimal? @map("Rate") @db.Decimal(18, 2)
|
||||
rateType String? @map("Rate_Type") @db.Char(1)
|
||||
|
||||
defaultFlag Boolean @map("Default_Flag")
|
||||
multiplierFlag Boolean @map("Multiplier_Flag")
|
||||
|
||||
rate Decimal? @map("Rate") @db.Decimal(18, 2)
|
||||
rateType String? @map("Rate_Type") @db.Char(1)
|
||||
|
||||
inactiveFlag Boolean @map("Inactive_Flag")
|
||||
invoiceFlag Boolean @map("Invoice_Flag")
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
@@ -2069,45 +2073,63 @@ model Country {
|
||||
// =====================
|
||||
|
||||
model SoActivity {
|
||||
soActivityRecId Int @id @map("SO_Activity_Recid")
|
||||
opportunityRecId Int? @map("Opportunity_Recid")
|
||||
assignTo String @map("Assign_To") @db.NVarChar(15)
|
||||
assignedBy String @map("Assigned_By") @db.NVarChar(15)
|
||||
companyRecId Int? @map("Company_RecID")
|
||||
soActivityTypeRecId Int? @map("SO_Activity_Type_RecID")
|
||||
subject String? @map("Subject") @db.NVarChar(100)
|
||||
soReferenceRecId Int? @map("SO_Reference_RecID")
|
||||
dateEntered DateTime @map("Date_Entered") @db.DateTime
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
contactRecId Int? @map("Contact_RecID")
|
||||
contactName String? @map("Contact_Name") @db.NVarChar(62)
|
||||
closeFlag Boolean @map("Close_Flag")
|
||||
dateClosed DateTime? @map("Date_Closed") @db.DateTime
|
||||
closedBy String? @map("Closed_By") @db.NVarChar(15)
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime
|
||||
notifyCompleteFlag Boolean @map("Notify_Complete_Flag")
|
||||
notificationSentFlag Boolean @map("Notification_Sent_Flag")
|
||||
srServiceRecId Int? @map("SR_Service_RecID")
|
||||
agrHeaderRecId Int? @map("AGR_Header_RecID")
|
||||
marketingCampaignRecId Int? @map("Marketing_Campaign_RecID")
|
||||
assignToRecId Int @map("assignto_recid")
|
||||
assignByRecId Int? @map("assignby_recid")
|
||||
mobileGuid String @map("Mobile_Guid") @db.UniqueIdentifier
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
dateTimeStart DateTime? @map("Date_Time_Start") @db.DateTime
|
||||
dateTimeEnd DateTime? @map("Date_Time_End") @db.DateTime
|
||||
automated Boolean @map("Automated")
|
||||
dateTimeStartUtc DateTime? @map("Date_Time_Start_UTC") @db.SmallDateTime
|
||||
dateTimeEndUtc DateTime? @map("Date_Time_End_UTC") @db.SmallDateTime
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime
|
||||
lastUpdatedUTC DateTime @map("Last_Update_UTC") @db.DateTime
|
||||
dateClosedUtc DateTime? @map("Date_Closed_UTC") @db.DateTime
|
||||
soActStatusRecId Int @map("so_act_status_recid")
|
||||
currencyRecId Int @map("Currency_RecID")
|
||||
id String? @map("Id") @db.UniqueIdentifier
|
||||
subject String? @map("Subject") @db.NVarChar(100)
|
||||
|
||||
opportunity Opportunity? @relation(fields: [opportunityRecId], references: [opportunityRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
soActivityRecId Int @id @map("SO_Activity_Recid")
|
||||
opportunityRecId Int? @map("Opportunity_Recid")
|
||||
companyRecId Int? @map("Company_RecID")
|
||||
|
||||
assignTo String @map("Assign_To") @db.NVarChar(15)
|
||||
assignedBy String @map("Assigned_By") @db.NVarChar(15)
|
||||
|
||||
soActivityTypeRecId Int? @map("SO_Activity_Type_RecID")
|
||||
soReferenceRecId Int? @map("SO_Reference_RecID")
|
||||
|
||||
dateEntered DateTime @map("Date_Entered") @db.DateTime
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
|
||||
contactRecId Int? @map("Contact_RecID")
|
||||
contactName String? @map("Contact_Name") @db.NVarChar(62)
|
||||
|
||||
closeFlag Boolean @map("Close_Flag")
|
||||
dateClosed DateTime? @map("Date_Closed") @db.DateTime
|
||||
closedBy String? @map("Closed_By") @db.NVarChar(15)
|
||||
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime
|
||||
|
||||
notifyCompleteFlag Boolean @map("Notify_Complete_Flag")
|
||||
notificationSentFlag Boolean @map("Notification_Sent_Flag")
|
||||
|
||||
srServiceRecId Int? @map("SR_Service_RecID")
|
||||
|
||||
agrHeaderRecId Int? @map("AGR_Header_RecID")
|
||||
marketingCampaignRecId Int? @map("Marketing_Campaign_RecID")
|
||||
|
||||
assignToRecId Int @map("assignto_recid")
|
||||
assignByRecId Int? @map("assignby_recid")
|
||||
|
||||
mobileGuid String @map("Mobile_Guid") @db.UniqueIdentifier
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
|
||||
dateTimeStart DateTime? @map("Date_Time_Start") @db.DateTime
|
||||
dateTimeEnd DateTime? @map("Date_Time_End") @db.DateTime
|
||||
|
||||
automated Boolean @map("Automated")
|
||||
|
||||
dateTimeStartUtc DateTime? @map("Date_Time_Start_UTC") @db.SmallDateTime
|
||||
dateTimeEndUtc DateTime? @map("Date_Time_End_UTC") @db.SmallDateTime
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime
|
||||
lastUpdatedUTC DateTime @map("Last_Update_UTC") @db.DateTime
|
||||
dateClosedUtc DateTime? @map("Date_Closed_UTC") @db.DateTime
|
||||
|
||||
soActStatusRecId Int @map("so_act_status_recid")
|
||||
currencyRecId Int @map("Currency_RecID")
|
||||
|
||||
id String? @map("Id") @db.UniqueIdentifier
|
||||
|
||||
opportunity Opportunity? @relation(fields: [opportunityRecId], references: [opportunityRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
notes SoActivityNotes?
|
||||
|
||||
@@map("SO_Activity")
|
||||
@@schema("dbo")
|
||||
@@ -2284,83 +2306,84 @@ model ScheduleStatus {
|
||||
}
|
||||
|
||||
model ScheduleType {
|
||||
scheduleTypeRecId Int @id @map("Schedule_Type_RecID")
|
||||
tableReference String? @map("Table_Reference") @db.NVarChar(50)
|
||||
description String? @map("Description") @db.NVarChar(50)
|
||||
displayColor String? @map("Display_Color") @db.NVarChar(30)
|
||||
moduleId String? @map("Module_ID") @db.Char(2)
|
||||
systemFlag Boolean @map("System_Flag")
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
displayFlag Boolean @map("Display_Flag")
|
||||
xrefMbrTable String? @map("Xref_Mbr_Table") @db.NVarChar(50)
|
||||
scheduleTypeId String? @map("Schedule_Type_ID") @db.Char(1)
|
||||
teChargeCodeRecId Int? @map("TE_Charge_Code_RecID")
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
scheduleTypeRecId Int @id @map("Schedule_Type_RecID")
|
||||
tableReference String? @map("Table_Reference") @db.NVarChar(50)
|
||||
description String? @map("Description") @db.NVarChar(50)
|
||||
displayColor String? @map("Display_Color") @db.NVarChar(30)
|
||||
moduleId String? @map("Module_ID") @db.Char(2)
|
||||
systemFlag Boolean @map("System_Flag")
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
displayFlag Boolean @map("Display_Flag")
|
||||
xrefMbrTable String? @map("Xref_Mbr_Table") @db.NVarChar(50)
|
||||
scheduleTypeId String? @map("Schedule_Type_ID") @db.Char(1)
|
||||
teChargeCodeRecId Int? @map("TE_Charge_Code_RecID")
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
|
||||
schedules Schedule[]
|
||||
schedules Schedule[]
|
||||
teChargeCode TeChargeCode? @relation(fields: [teChargeCodeRecId], references: [teChargeCodeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
|
||||
@@map("Schedule_Type")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model Schedule {
|
||||
scheduleRecId Int @id @map("Schedule_RecID")
|
||||
recId Int? @map("RecID")
|
||||
scheduleTypeRecId Int @map("Schedule_Type_RecID")
|
||||
memberId String? @map("Member_ID") @db.NVarChar(15)
|
||||
dateTimeStart DateTime? @map("Date_Time_Start") @db.DateTime
|
||||
dateTimeEnd DateTime? @map("Date_Time_End") @db.DateTime
|
||||
closeFlag Boolean @map("close_flag")
|
||||
hoursEstimated Decimal? @map("Hours_Estimated") @db.Decimal(18, 2)
|
||||
lastUpdate DateTime? @map("Last_Update") @db.DateTime
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
syncable Boolean @map("Syncable")
|
||||
lastSync DateTime? @map("Last_Sync") @db.DateTime
|
||||
exchangeGuid String? @map("Exchange_GUID") @db.VarChar(4000)
|
||||
reminderFlag Boolean @map("Reminder_Flag")
|
||||
reminderMinutes Int? @map("Reminder_Minutes")
|
||||
allDayFlag Boolean @map("All_Day_Flag")
|
||||
duration Int? @map("Duration")
|
||||
enteredByRecId Int? @map("Entered_By_RecID")
|
||||
xrefMbrRecId Int? @map("Xref_Mbr_RecID")
|
||||
percentSched Int? @map("Percent_Sched")
|
||||
hoursSched Decimal? @map("Hours_Sched") @db.Decimal(18, 2)
|
||||
scheduleStatusRecId Int? @map("Schedule_Status_RecID")
|
||||
hoursPerDay Decimal? @map("Hours_Per_Day") @db.Decimal(18, 2)
|
||||
ackFlag Boolean? @map("Ack_Flag")
|
||||
ackMemberRecId Int? @map("Ack_Member_RecID")
|
||||
ackDate DateTime? @map("Ack_Date") @db.DateTime
|
||||
closeMemberRecId Int? @map("Close_Member_RecID")
|
||||
closeDate DateTime? @map("Close_Date") @db.DateTime
|
||||
billableFlag Boolean? @map("Billable_Flag")
|
||||
dateEntered DateTime? @map("Date_Entered") @db.DateTime
|
||||
mobileGuid String @map("Mobile_Guid") @db.UniqueIdentifier
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
scheduleSpanRecId Int? @map("Schedule_Span_RecID")
|
||||
meetingFlag Boolean? @map("Meeting_Flag")
|
||||
recurringFlag Boolean? @map("Recurring_Flag")
|
||||
ackDateUtc DateTime? @map("Ack_Date_UTC") @db.DateTime
|
||||
dateEnteredUtc DateTime? @map("Date_Entered_UTC") @db.DateTime
|
||||
lastUpdateUtc DateTime? @map("Last_Update_UTC") @db.DateTime
|
||||
closeDateUtc DateTime? @map("Close_Date_UTC") @db.DateTime
|
||||
enteredBy String? @map("Entered_By") @db.NVarChar(15)
|
||||
acknowledgedBy String? @map("Acknowledged_By") @db.NVarChar(15)
|
||||
closedBy String? @map("Closed_By") @db.NVarChar(15)
|
||||
dateTimeStartUtc DateTime? @map("Date_Time_Start_UTC") @db.SmallDateTime
|
||||
dateTimeEndUtc DateTime? @map("Date_Time_End_UTC") @db.SmallDateTime
|
||||
scheduleDesc String? @map("Schedule_Desc") @db.NVarChar(250)
|
||||
privateFlag Boolean @map("Private_Flag")
|
||||
notifyType String? @map("NotifyType") @db.NVarChar(2)
|
||||
scheduleRecId Int @id @map("Schedule_RecID")
|
||||
recId Int? @map("RecID")
|
||||
scheduleTypeRecId Int @map("Schedule_Type_RecID")
|
||||
memberId String? @map("Member_ID") @db.NVarChar(15)
|
||||
dateTimeStart DateTime? @map("Date_Time_Start") @db.DateTime
|
||||
dateTimeEnd DateTime? @map("Date_Time_End") @db.DateTime
|
||||
closeFlag Boolean @map("close_flag")
|
||||
hoursEstimated Decimal? @map("Hours_Estimated") @db.Decimal(18, 2)
|
||||
lastUpdate DateTime? @map("Last_Update") @db.DateTime
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
syncable Boolean @map("Syncable")
|
||||
lastSync DateTime? @map("Last_Sync") @db.DateTime
|
||||
exchangeGuid String? @map("Exchange_GUID") @db.VarChar(4000)
|
||||
reminderFlag Boolean @map("Reminder_Flag")
|
||||
reminderMinutes Int? @map("Reminder_Minutes")
|
||||
allDayFlag Boolean @map("All_Day_Flag")
|
||||
duration Int? @map("Duration")
|
||||
enteredByRecId Int? @map("Entered_By_RecID")
|
||||
xrefMbrRecId Int? @map("Xref_Mbr_RecID")
|
||||
percentSched Int? @map("Percent_Sched")
|
||||
hoursSched Decimal? @map("Hours_Sched") @db.Decimal(18, 2)
|
||||
scheduleStatusRecId Int? @map("Schedule_Status_RecID")
|
||||
hoursPerDay Decimal? @map("Hours_Per_Day") @db.Decimal(18, 2)
|
||||
ackFlag Boolean? @map("Ack_Flag")
|
||||
ackMemberRecId Int? @map("Ack_Member_RecID")
|
||||
ackDate DateTime? @map("Ack_Date") @db.DateTime
|
||||
closeMemberRecId Int? @map("Close_Member_RecID")
|
||||
closeDate DateTime? @map("Close_Date") @db.DateTime
|
||||
billableFlag Boolean? @map("Billable_Flag")
|
||||
dateEntered DateTime? @map("Date_Entered") @db.DateTime
|
||||
mobileGuid String @map("Mobile_Guid") @db.UniqueIdentifier
|
||||
srLocationRecId Int? @map("SR_Location_RecID")
|
||||
scheduleSpanRecId Int? @map("Schedule_Span_RecID")
|
||||
meetingFlag Boolean? @map("Meeting_Flag")
|
||||
recurringFlag Boolean? @map("Recurring_Flag")
|
||||
ackDateUtc DateTime? @map("Ack_Date_UTC") @db.DateTime
|
||||
dateEnteredUtc DateTime? @map("Date_Entered_UTC") @db.DateTime
|
||||
lastUpdateUtc DateTime? @map("Last_Update_UTC") @db.DateTime
|
||||
closeDateUtc DateTime? @map("Close_Date_UTC") @db.DateTime
|
||||
enteredBy String? @map("Entered_By") @db.NVarChar(15)
|
||||
acknowledgedBy String? @map("Acknowledged_By") @db.NVarChar(15)
|
||||
closedBy String? @map("Closed_By") @db.NVarChar(15)
|
||||
dateTimeStartUtc DateTime? @map("Date_Time_Start_UTC") @db.SmallDateTime
|
||||
dateTimeEndUtc DateTime? @map("Date_Time_End_UTC") @db.SmallDateTime
|
||||
scheduleDesc String? @map("Schedule_Desc") @db.NVarChar(250)
|
||||
privateFlag Boolean @map("Private_Flag")
|
||||
notifyType String? @map("NotifyType") @db.NVarChar(2)
|
||||
|
||||
details ScheduleDetail[]
|
||||
status ScheduleStatus? @relation(fields: [scheduleStatusRecId], references: [scheduleStatusRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
type ScheduleType @relation(fields: [scheduleTypeRecId], references: [scheduleTypeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
span ScheduleSpan? @relation(fields: [scheduleSpanRecId], references: [scheduleSpanRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
status ScheduleStatus? @relation(fields: [scheduleStatusRecId], references: [scheduleStatusRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
type ScheduleType @relation(fields: [scheduleTypeRecId], references: [scheduleTypeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
span ScheduleSpan? @relation(fields: [scheduleSpanRecId], references: [scheduleSpanRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
|
||||
@@map("Schedule")
|
||||
@@schema("dbo")
|
||||
@@ -2398,3 +2421,246 @@ model ScheduleDetail {
|
||||
@@map("Schedule_Detail")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
// =====================
|
||||
// TIME
|
||||
// =====================
|
||||
|
||||
model TimeEntry {
|
||||
companyRecId Int @map("Company_RecID")
|
||||
timeRecId Int @map("Time_RecID")
|
||||
enteredBy String? @map("Entered_By") @db.NVarChar(15)
|
||||
memberId String? @map("Member_ID") @db.NVarChar(15)
|
||||
|
||||
dateStart DateTime? @map("Date_Start") @db.DateTime
|
||||
timeStart DateTime? @map("Time_Start") @db.DateTime
|
||||
timeEnd DateTime? @map("Time_End") @db.DateTime
|
||||
hourlyRate Float? @map("Hourly_Rate") @db.SmallMoney
|
||||
hoursBill Decimal @map("Hours_Bill") @db.Decimal(18, 2)
|
||||
|
||||
invoiceFlag Boolean @map("Invoice_Flag")
|
||||
|
||||
lastUpdate DateTime? @map("Last_Update") @db.DateTime
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
|
||||
pmProjectRecId Int? @map("PM_Project_RecID")
|
||||
|
||||
hoursActual Decimal @map("Hours_Actual") @db.Decimal(18, 2)
|
||||
billableFlag Boolean @map("Billable_Flag")
|
||||
billingLogRecId Int? @map("Billing_Log_RecID")
|
||||
|
||||
srServiceRecId Int? @map("SR_Service_RecID")
|
||||
|
||||
activityTypeRecId Int? @map("Activity_Type_RecID")
|
||||
activityClassRecId Int? @map("Activity_Class_RecID")
|
||||
|
||||
teStatusId Int @map("TE_Status_ID") @db.SmallInt
|
||||
timeSheetRecId Int? @map("Time_Sheet_RecID")
|
||||
teChargeCodeRecId Int? @map("TE_Charge_Code_RecID")
|
||||
|
||||
billingUnitRecId Int @map("Billing_Unit_RecID")
|
||||
ownerLevelRecId Int @map("Owner_Level_RecID")
|
||||
|
||||
memberRecId Int? @map("Member_RecID")
|
||||
|
||||
hoursInvoiced Decimal? @map("Hours_Invoiced") @db.Decimal(18, 2)
|
||||
adjustment Decimal @map("Adjustment") @db.Decimal(18, 2)
|
||||
|
||||
memberTypeRecId Int? @map("Member_Type_RecID")
|
||||
|
||||
agrAmount Decimal @map("Agr_Amount") @db.Decimal(18, 2)
|
||||
agrHeaderRecId Int? @map("Agr_Header_RecID")
|
||||
agrAdjustment Decimal? @map("Agr_Adjustment") @db.Decimal(18, 2)
|
||||
agrHours Decimal? @map("Agr_Hours") @db.Decimal(18, 2)
|
||||
agrMonth Int? @map("Agr_Month") @db.SmallInt
|
||||
agrYear Int? @map("Agr_Year") @db.SmallInt
|
||||
|
||||
standardRate Decimal? @map("Standard_Rate") @db.Decimal(18, 2)
|
||||
effectiveRate Decimal? @map("Effective_Rate") @db.Decimal(18, 2)
|
||||
|
||||
hoursDeduct Decimal? @map("Hours_Deduct") @db.Decimal(18, 2)
|
||||
|
||||
contactRecId Int? @map("Contact_RecID")
|
||||
|
||||
soActivityRecId Int? @map("SO_Activity_RecID")
|
||||
mobileGuid String @map("Mobile_GUID") @db.UniqueIdentifier
|
||||
billingSr Int? @map("billing_sr")
|
||||
|
||||
signatureRecId Int? @map("Signature_RecID")
|
||||
signatureHours Decimal? @map("Signature_Hours") @db.Decimal(18, 2)
|
||||
|
||||
exchangeHref String? @map("exchange_href") @db.NVarChar(300)
|
||||
teProblemFlag Boolean @map("TE_Problem_Flag")
|
||||
teResolutionFlag Boolean @map("TE_Resolution_Flag")
|
||||
teInternalAnalysisFlag Boolean @map("TE_InternalAnalysis_Flag")
|
||||
|
||||
documentFlag Boolean @map("Document_Flag")
|
||||
|
||||
dateFormat Int? @map("Date_Format")
|
||||
|
||||
dbTimestamp Bytes @map("DB_Timestamp") @db.VarBinary(8)
|
||||
timeStartUtc DateTime? @map("Time_Start_UTC") @db.DateTime
|
||||
timeEndUtc DateTime? @map("Time_End_UTC") @db.DateTime
|
||||
lastUpdateUtc DateTime? @map("Last_Update_UTC") @db.DateTime
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime
|
||||
|
||||
overageRate Float? @map("Overage_Rate") @db.SmallMoney
|
||||
|
||||
extendedInvoiceAmount Decimal? @map("Extended_Invoice_Amount") @db.Decimal(31, 6)
|
||||
extendedBillAmount Decimal? @map("Extended_Bill_Amount") @db.Decimal(31, 6)
|
||||
|
||||
notificationHistory String @map("Notification_History") @db.VarChar(4000)
|
||||
mergedFlag Boolean @map("Merged_Flag")
|
||||
|
||||
internalNote String? @map("Internal_Note") @db.NVarChar(Max)
|
||||
reference String? @map("Reference") @db.NVarChar(100)
|
||||
|
||||
originalAuthor String? @map("Original_Author") @db.NVarChar(150)
|
||||
|
||||
costPerHour String @map("Cost_Per_Hour") @db.NVarChar(2000)
|
||||
overrideFlag Boolean @map("Override_Flag")
|
||||
issueFlag Boolean @map("Issue_Flag")
|
||||
|
||||
notes String? @map("Notes") @db.NVarChar(4000)
|
||||
notesMarkdown String? @map("Notes_Markdown") @db.NVarChar(Max)
|
||||
|
||||
chargeToRecId Int? @map("Charge_To_RecID")
|
||||
chargeToType String? @map("Charge_To_Type") @db.NVarChar(13)
|
||||
|
||||
teChargeCode TeChargeCode? @relation(fields: [teChargeCodeRecId], references: [teChargeCodeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
memberType MemberType? @relation(fields: [memberTypeRecId], references: [memberTypeRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
|
||||
@@id([companyRecId, timeRecId])
|
||||
@@map("Time_Entry")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model SoActivityType {
|
||||
soActivityTypeRecId Int @id @map("SO_Activity_Type_RecID")
|
||||
soActivityTypeId String? @map("SO_Activity_Type_ID") @db.NVarChar(15)
|
||||
|
||||
description String? @map("Description") @db.NVarChar(50)
|
||||
|
||||
historyFlag Boolean @map("History_Flag")
|
||||
|
||||
updatedBy String @map("Updated_By") @db.NVarChar(15)
|
||||
cleanUpFlag Boolean @map("CleanUp_Flag")
|
||||
defaultFlag Boolean @map("Default_Flag")
|
||||
importFlag Boolean @map("Import_Flag")
|
||||
emailFlag Boolean @map("email_flag")
|
||||
memoFlag Boolean? @map("Memo_Flag")
|
||||
pointsValue Int? @map("Points_Value")
|
||||
inactiveFlag Boolean? @map("Inactive_Flag")
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
|
||||
@@map("SO_Activity_Type")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model SoActStatus {
|
||||
soActStatusRecId Int @id @map("SO_Act_Status_RecID")
|
||||
description String? @map("Description") @db.NVarChar(30)
|
||||
|
||||
defaultFlag Boolean @map("Default_Flag")
|
||||
closedFlag Boolean @map("Closed_Flag")
|
||||
inactiveFlag Boolean @map("Inactive_Flag")
|
||||
spawnFollowupFlag Boolean @map("spawn_followup_flag")
|
||||
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
|
||||
@@map("SO_Act_Status")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model SoActivityNotes {
|
||||
soActivityNotesRecId Int @id @map("SO_Activity_Notes_RecID")
|
||||
soActivityRecId Int @unique @map("SO_Activity_RecID")
|
||||
notes String @map("Notes") @db.NVarChar(Max)
|
||||
internalAnalysisFlag Boolean @map("Internal_Analysis_Flag")
|
||||
dateCreatedUtc DateTime @map("Date_Created_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
updatedBy String @map("Updated_By") @db.NVarChar(15)
|
||||
|
||||
soActivity SoActivity @relation(fields: [soActivityRecId], references: [soActivityRecId], onDelete: NoAction, onUpdate: NoAction)
|
||||
|
||||
@@map("SO_Activity_Notes")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
// =====================
|
||||
// TIME ENTRY LOOKUPS
|
||||
// =====================
|
||||
|
||||
model TeStatus {
|
||||
teStatusRecId Int @id @map("TE_Status_RecID")
|
||||
teStatusId Int @map("TE_Status_ID") @db.SmallInt
|
||||
description String? @map("Description") @db.NVarChar(50)
|
||||
action String? @map("Action") @db.NVarChar(50)
|
||||
lastUpdatedUtc DateTime @map("Last_Updated_UTC") @db.DateTime2
|
||||
localeKeyRecId Int? @map("Locale_Key_RecID")
|
||||
|
||||
@@map("TE_Status")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model TeChargeCode {
|
||||
teChargeCodeRecId Int @id @map("TE_Charge_Code_RecID")
|
||||
|
||||
description String? @map("Description") @db.NVarChar(50)
|
||||
|
||||
companyRecId Int? @map("Company_RecID")
|
||||
ownerLevelRecId Int? @map("Owner_Level_RecID")
|
||||
billingUnitRecId Int? @map("Billing_Unit_RecID")
|
||||
|
||||
activityClassRecId Int? @map("Activity_Class_RecID")
|
||||
activityTypeRecId Int? @map("Activity_Type_RecID")
|
||||
|
||||
expenseFlag Boolean @map("Expense_Flag")
|
||||
timeFlag Boolean @map("Time_Flag")
|
||||
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
billableFlag Boolean @map("Billable_Flag")
|
||||
exTypeFlag Boolean @map("EX_Type_Flag")
|
||||
invoiceFlag Boolean? @map("Invoice_Flag")
|
||||
integrationXref String? @map("Integration_Xref") @db.NVarChar(50)
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
|
||||
scheduleTypes ScheduleType[]
|
||||
timeEntries TimeEntry[]
|
||||
|
||||
@@map("TE_Charge_Code")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
model MemberType {
|
||||
memberTypeRecId Int @id @map("Member_Type_RecID")
|
||||
description String? @map("Description") @db.NVarChar(30)
|
||||
inactiveFlag Boolean @map("Inactive_Flag")
|
||||
updatedBy String? @map("Updated_By") @db.NVarChar(15)
|
||||
lastUpdate DateTime @map("Last_Update") @db.DateTime2
|
||||
lastUpdateUtc DateTime @map("Last_Update_UTC") @db.DateTime2
|
||||
enteredBy String @map("Entered_By") @db.NVarChar(15)
|
||||
dateEnteredUtc DateTime @map("Date_Entered_UTC") @db.DateTime2
|
||||
id String @map("Id") @db.UniqueIdentifier
|
||||
|
||||
members Member[]
|
||||
timeEntries TimeEntry[]
|
||||
|
||||
@@map("Member_Type")
|
||||
@@schema("dbo")
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ export { scheduleTypeTranslation } from "./translations/schedule-type";
|
||||
export { scheduleSpanTranslation } from "./translations/schedule-span";
|
||||
export { scheduleTranslation } from "./translations/schedule";
|
||||
export { taxCodeTranslation } from "./translations/tax-code";
|
||||
export { timeEntryTranslation } from "./translations/time-entry";
|
||||
export { timeEntryStatusTranslation } from "./translations/time-entry-status";
|
||||
export { timeEntryChargeCodeTranslation } from "./translations/time-entry-charge-code";
|
||||
export { timeActivityClassTranslation } from "./translations/time-activity-class";
|
||||
export { timeActivityTypeTranslation } from "./translations/time-activity-type";
|
||||
export { activityTypeTranslation } from "./translations/activity-type";
|
||||
export { activityStatusTranslation } from "./translations/activity-status";
|
||||
export { activityTranslation } from "./translations/activity";
|
||||
export { activityNotesTranslation } from "./translations/activity-notes";
|
||||
export { cwMemberTypeTranslation } from "./translations/cw-member-type";
|
||||
|
||||
// Context type exports
|
||||
export type { TranslationContext } from "./translations/context";
|
||||
|
||||
@@ -37,6 +37,12 @@ import {
|
||||
scheduleTypeTranslation,
|
||||
scheduleSpanTranslation,
|
||||
scheduleTranslation,
|
||||
taxCodeTranslation,
|
||||
timeEntryTranslation,
|
||||
activityTypeTranslation,
|
||||
activityStatusTranslation,
|
||||
activityTranslation,
|
||||
activityNotesTranslation,
|
||||
userTranslation,
|
||||
warehouseBinTranslation,
|
||||
type TranslationContext,
|
||||
@@ -203,6 +209,9 @@ const refreshContextFromApi = async (
|
||||
context.scheduleStatusIds.clear();
|
||||
context.scheduleTypeIds.clear();
|
||||
context.scheduleSpanIds.clear();
|
||||
context.activityTypeIds.clear();
|
||||
context.activityStatusIds.clear();
|
||||
context.activityIds.clear();
|
||||
|
||||
const [
|
||||
users,
|
||||
@@ -215,6 +224,9 @@ const refreshContextFromApi = async (
|
||||
scheduleStatuses,
|
||||
scheduleTypes,
|
||||
scheduleSpans,
|
||||
activityTypes,
|
||||
activityStatuses,
|
||||
activities,
|
||||
] = await Promise.all([
|
||||
apiPrisma.user.findMany({
|
||||
select: {
|
||||
@@ -269,6 +281,21 @@ const refreshContextFromApi = async (
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activityType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activityStatus.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activity.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const defaultOpportunityType = await apiPrisma.opportunityType.findFirst({
|
||||
@@ -346,6 +373,18 @@ const refreshContextFromApi = async (
|
||||
context.scheduleSpanIds.add(span.id);
|
||||
}
|
||||
|
||||
for (const activityType of activityTypes) {
|
||||
context.activityTypeIds.add(activityType.id);
|
||||
}
|
||||
|
||||
for (const activityStatus of activityStatuses) {
|
||||
context.activityStatusIds.add(activityStatus.id);
|
||||
}
|
||||
|
||||
for (const activity of activities) {
|
||||
context.activityIds.add(activity.id);
|
||||
}
|
||||
|
||||
context.defaultOpportunityTypeId = defaultOpportunityType?.id ?? null;
|
||||
|
||||
// Optional context fed from env-provided maps.
|
||||
@@ -813,6 +852,41 @@ const getConfigForTable = (table: string): SyncTableConfig | null => {
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
timeEntry: {
|
||||
sourceModel: "timeEntry",
|
||||
targetModel: "timeEntry",
|
||||
translation: timeEntryTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
soActivityType: {
|
||||
sourceModel: "soActivityType",
|
||||
targetModel: "activityType",
|
||||
translation: activityTypeTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
soActStatus: {
|
||||
sourceModel: "soActStatus",
|
||||
targetModel: "activityStatus",
|
||||
translation: activityStatusTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
soActivity: {
|
||||
sourceModel: "soActivity",
|
||||
targetModel: "activity",
|
||||
translation: activityTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdatedUTC",
|
||||
},
|
||||
soActivityNotes: {
|
||||
sourceModel: "soActivityNotes",
|
||||
targetModel: "activityNotes",
|
||||
translation: activityNotesTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
lastUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
};
|
||||
|
||||
return configMap[table] ?? null;
|
||||
@@ -972,8 +1046,8 @@ export async function syncTableUpdates(
|
||||
uniqueFields.length > 0
|
||||
? formatUniqueConstraintError(error, translatedData)
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "Unknown row sync error";
|
||||
? error.message
|
||||
: "Unknown row sync error";
|
||||
console.error(
|
||||
`Failed row in ${config.sourceModel} -> ${config.targetModel}:`,
|
||||
message
|
||||
@@ -984,8 +1058,7 @@ export async function syncTableUpdates(
|
||||
|
||||
if (failed > sampleErrorsPrinted) {
|
||||
console.error(
|
||||
`${config.sourceModel}: suppressed ${
|
||||
failed - sampleErrorsPrinted
|
||||
`${config.sourceModel}: suppressed ${failed - sampleErrorsPrinted
|
||||
} additional row errors`
|
||||
);
|
||||
}
|
||||
|
||||
+189
-32
@@ -39,6 +39,16 @@ import {
|
||||
scheduleSpanTranslation,
|
||||
scheduleTranslation,
|
||||
taxCodeTranslation,
|
||||
timeEntryTranslation,
|
||||
timeEntryStatusTranslation,
|
||||
timeEntryChargeCodeTranslation,
|
||||
timeActivityClassTranslation,
|
||||
timeActivityTypeTranslation,
|
||||
activityTypeTranslation,
|
||||
activityStatusTranslation,
|
||||
activityTranslation,
|
||||
activityNotesTranslation,
|
||||
cwMemberTypeTranslation,
|
||||
userTranslation,
|
||||
warehouseBinTranslation,
|
||||
type TranslationContext,
|
||||
@@ -114,7 +124,7 @@ const CRITICAL_CW_WATERMARK_OVERLAP_MS =
|
||||
const criticalCwDeltaLimit = Math.max(
|
||||
100,
|
||||
Number.parseInt(process.env.DALPURI_CRITICAL_CW_DELTA_LIMIT ?? "5000", 10) ||
|
||||
5000
|
||||
5000
|
||||
);
|
||||
|
||||
const lastCriticalFullSyncByStep = new Map<string, number>();
|
||||
@@ -346,6 +356,11 @@ const refreshContextFromApi = async (
|
||||
context.scheduleTypeIds.clear();
|
||||
context.scheduleSpanIds.clear();
|
||||
context.taxCodeIds.clear();
|
||||
context.activityTypeIds.clear();
|
||||
context.activityStatusIds.clear();
|
||||
context.activityIds.clear();
|
||||
context.timeEntryChargeCodeIds.clear();
|
||||
context.timeEntryStatusIdByTeStatusId.clear();
|
||||
|
||||
const [
|
||||
users,
|
||||
@@ -363,6 +378,11 @@ const refreshContextFromApi = async (
|
||||
scheduleTypes,
|
||||
scheduleSpans,
|
||||
taxCodes,
|
||||
activityTypes,
|
||||
activityStatuses,
|
||||
activities,
|
||||
timeEntryChargeCodes,
|
||||
timeEntryStatuses,
|
||||
] = await Promise.all([
|
||||
apiPrisma.user.findMany({
|
||||
select: {
|
||||
@@ -442,6 +462,32 @@ const refreshContextFromApi = async (
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activityType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activityStatus.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.activity.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.timeEntryChargeCode.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
}),
|
||||
apiPrisma.timeEntryStatus.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
statusId: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const defaultOpportunityType = await apiPrisma.opportunityType.findFirst({
|
||||
@@ -539,6 +585,26 @@ const refreshContextFromApi = async (
|
||||
context.taxCodeIds.add(taxCode.id);
|
||||
}
|
||||
|
||||
for (const activityType of activityTypes) {
|
||||
context.activityTypeIds.add(activityType.id);
|
||||
}
|
||||
|
||||
for (const activityStatus of activityStatuses) {
|
||||
context.activityStatusIds.add(activityStatus.id);
|
||||
}
|
||||
|
||||
for (const activity of activities) {
|
||||
context.activityIds.add(activity.id);
|
||||
}
|
||||
|
||||
for (const chargeCode of timeEntryChargeCodes) {
|
||||
context.timeEntryChargeCodeIds.add(chargeCode.id);
|
||||
}
|
||||
|
||||
for (const status of timeEntryStatuses) {
|
||||
context.timeEntryStatusIdByTeStatusId.set(status.statusId, status.id);
|
||||
}
|
||||
|
||||
context.defaultOpportunityTypeId = defaultOpportunityType?.id ?? null;
|
||||
|
||||
// Optional context fed from env-provided maps.
|
||||
@@ -645,17 +711,17 @@ const sanitizeUserForeignKeys = (
|
||||
targetModel === "serviceTicketNote"
|
||||
? ["authorId"]
|
||||
: targetModel === "serviceTicket"
|
||||
? ["createdById", "updatedById", "closedById"]
|
||||
: [];
|
||||
? ["createdById", "updatedById", "closedById"]
|
||||
: [];
|
||||
|
||||
const identifierFields =
|
||||
targetModel === "serviceTicket"
|
||||
? ["ticketOwnerId"]
|
||||
: targetModel === "company"
|
||||
? ["enteredById", "deletedById"]
|
||||
: targetModel === "companyAddress"
|
||||
? ["updatedById"]
|
||||
: [];
|
||||
? ["enteredById", "deletedById"]
|
||||
: targetModel === "companyAddress"
|
||||
? ["updatedById"]
|
||||
: [];
|
||||
|
||||
for (const field of userIdFields) {
|
||||
const value = sanitized[field];
|
||||
@@ -1073,8 +1139,7 @@ const reconcileStepDeletes = async (
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Unknown delete error";
|
||||
console.error(
|
||||
`Delete reconcile failed in ${step.name} for ${
|
||||
step.uniqueField
|
||||
`Delete reconcile failed in ${step.name} for ${step.uniqueField
|
||||
}=${formatValue(value)}:`,
|
||||
message
|
||||
);
|
||||
@@ -1085,8 +1150,7 @@ const reconcileStepDeletes = async (
|
||||
|
||||
if (failed > sampleErrorsPrinted) {
|
||||
console.error(
|
||||
`${step.name}: suppressed ${
|
||||
failed - sampleErrorsPrinted
|
||||
`${step.name}: suppressed ${failed - sampleErrorsPrinted
|
||||
} additional delete errors`
|
||||
);
|
||||
}
|
||||
@@ -1097,10 +1161,10 @@ const reconcileStepDeletes = async (
|
||||
type SmartSyncDecision =
|
||||
| { mode: "full"; differences: SmartSyncDifference[] }
|
||||
| {
|
||||
mode: "incremental";
|
||||
sourceIds: number[];
|
||||
differences: SmartSyncDifference[];
|
||||
};
|
||||
mode: "incremental";
|
||||
sourceIds: number[];
|
||||
differences: SmartSyncDifference[];
|
||||
};
|
||||
|
||||
type SmartSyncDifference = {
|
||||
sourceId: number;
|
||||
@@ -1129,10 +1193,8 @@ const logAllSmartSyncDifferences = (
|
||||
? diff.apiUpdatedAt.toISOString()
|
||||
: "null";
|
||||
console.log(
|
||||
` [diff] sourceModel=${step.sourceModel} targetModel=${
|
||||
step.targetModel
|
||||
} id=${diff.sourceId} reason=${
|
||||
diff.reason
|
||||
` [diff] sourceModel=${step.sourceModel} targetModel=${step.targetModel
|
||||
} id=${diff.sourceId} reason=${diff.reason
|
||||
} cwUpdatedAt=${diff.cwUpdatedAt.toISOString()} apiUpdatedAt=${apiUpdated}`
|
||||
);
|
||||
}
|
||||
@@ -1347,8 +1409,8 @@ const syncStep = async (
|
||||
uniqueFields.length > 0
|
||||
? formatUniqueConstraintError(error, translatedData)
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "Unknown row sync error";
|
||||
? error.message
|
||||
: "Unknown row sync error";
|
||||
console.error(
|
||||
`Failed row in ${step.name} (source ${step.sourceModel} -> target ${step.targetModel}):`,
|
||||
message
|
||||
@@ -1393,8 +1455,7 @@ const syncStep = async (
|
||||
|
||||
if (failed > sampleErrorsPrinted) {
|
||||
console.error(
|
||||
`${step.name}: suppressed ${
|
||||
failed - sampleErrorsPrinted
|
||||
`${step.name}: suppressed ${failed - sampleErrorsPrinted
|
||||
} additional row errors`
|
||||
);
|
||||
}
|
||||
@@ -1495,6 +1556,15 @@ export const executeFullDalpuriSync = async (options?: {
|
||||
const isTimedOut = () => Date.now() - syncStartTime > timeoutMs;
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
name: "CW Member Types",
|
||||
sourceModel: "memberType",
|
||||
targetModel: "cwMemberType",
|
||||
translation: cwMemberTypeTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "memberTypeRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "CW Members",
|
||||
sourceModel: "member",
|
||||
@@ -1829,6 +1899,87 @@ export const executeFullDalpuriSync = async (options?: {
|
||||
sourceIdField: "scheduleRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "Time Entry Statuses",
|
||||
sourceModel: "teStatus",
|
||||
targetModel: "timeEntryStatus",
|
||||
translation: timeEntryStatusTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "teStatusRecId",
|
||||
sourceUpdatedField: "lastUpdatedUtc",
|
||||
},
|
||||
{
|
||||
name: "Time Entry Charge Codes",
|
||||
sourceModel: "teChargeCode",
|
||||
targetModel: "timeEntryChargeCode",
|
||||
translation: timeEntryChargeCodeTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "teChargeCodeRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "Time Activity Classes",
|
||||
sourceModel: "activityClass",
|
||||
targetModel: "timeActivityClass",
|
||||
translation: timeActivityClassTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "activityClassRecId",
|
||||
sourceUpdatedField: "lastUpdatedUtc",
|
||||
},
|
||||
{
|
||||
name: "Time Activity Types",
|
||||
sourceModel: "activityType",
|
||||
targetModel: "timeActivityType",
|
||||
translation: timeActivityTypeTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "activityTypeRecId",
|
||||
sourceUpdatedField: "lastUpdatedUtc",
|
||||
},
|
||||
{
|
||||
name: "Time Entries",
|
||||
sourceModel: "timeEntry",
|
||||
targetModel: "timeEntry",
|
||||
translation: timeEntryTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "timeRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "Activity Types",
|
||||
sourceModel: "soActivityType",
|
||||
targetModel: "activityType",
|
||||
translation: activityTypeTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "soActivityTypeRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "Activity Statuses",
|
||||
sourceModel: "soActStatus",
|
||||
targetModel: "activityStatus",
|
||||
translation: activityStatusTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "soActStatusRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
{
|
||||
name: "Activities",
|
||||
sourceModel: "soActivity",
|
||||
targetModel: "activity",
|
||||
translation: activityTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "soActivityRecId",
|
||||
sourceUpdatedField: "lastUpdatedUTC",
|
||||
},
|
||||
{
|
||||
name: "Activity Notes",
|
||||
sourceModel: "soActivityNotes",
|
||||
targetModel: "activityNotes",
|
||||
translation: activityNotesTranslation as unknown as AnyTranslation,
|
||||
uniqueField: "id",
|
||||
sourceIdField: "soActivityNotesRecId",
|
||||
sourceUpdatedField: "lastUpdateUtc",
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -1875,7 +2026,16 @@ export const executeFullDalpuriSync = async (options?: {
|
||||
step.targetModel === "opportunity" ||
|
||||
step.targetModel === "productData" ||
|
||||
step.targetModel === "serviceTicketNote" ||
|
||||
step.targetModel === "schedule"
|
||||
step.targetModel === "schedule" ||
|
||||
step.targetModel === "timeEntryStatus" ||
|
||||
step.targetModel === "timeEntryChargeCode" ||
|
||||
step.targetModel === "timeActivityClass" ||
|
||||
step.targetModel === "timeActivityType" ||
|
||||
step.targetModel === "timeEntry" ||
|
||||
step.targetModel === "activityType" ||
|
||||
step.targetModel === "activityStatus" ||
|
||||
step.targetModel === "activity" ||
|
||||
step.targetModel === "activityNotes"
|
||||
) {
|
||||
try {
|
||||
await refreshContextFromApi(apiPrisma, context);
|
||||
@@ -1925,12 +2085,10 @@ export const executeFullDalpuriSync = async (options?: {
|
||||
? effectiveDecision.sourceIds
|
||||
: undefined;
|
||||
console.log(
|
||||
` [smart-sync]${forceIncremental ? "[forced]" : ""} mode=${
|
||||
effectiveDecision.mode
|
||||
}${
|
||||
effectiveDecision.mode === "incremental"
|
||||
? ` (${effectiveDecision.sourceIds.length} ids)`
|
||||
: ""
|
||||
` [smart-sync]${forceIncremental ? "[forced]" : ""} mode=${effectiveDecision.mode
|
||||
}${effectiveDecision.mode === "incremental"
|
||||
? ` (${effectiveDecision.sourceIds.length} ids)`
|
||||
: ""
|
||||
}`
|
||||
);
|
||||
if (logAllDifferences) {
|
||||
@@ -1978,8 +2136,7 @@ export const executeFullDalpuriSync = async (options?: {
|
||||
if (forceIncremental) {
|
||||
const selected = deleteSteps[0];
|
||||
console.log(
|
||||
`[delete-reconcile] incremental sweep: ${selected.name} (${
|
||||
incrementalDeleteStepIndex + 1
|
||||
`[delete-reconcile] incremental sweep: ${selected.name} (${incrementalDeleteStepIndex + 1
|
||||
}/${steps.length})`
|
||||
);
|
||||
incrementalDeleteStepIndex =
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { SoActivityNotes as CwSoActivityNotes } from "../../generated/prisma/client";
|
||||
import { Translation, skipRow } from "./types";
|
||||
import { TranslationContext } from "./context";
|
||||
|
||||
type ApiActivityNotesRecord = {
|
||||
id: number;
|
||||
notes: string;
|
||||
activityId: number | null;
|
||||
internalAnalysisFlag: boolean;
|
||||
enteredById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const activityNotesTranslation: Translation<
|
||||
CwSoActivityNotes,
|
||||
ApiActivityNotesRecord,
|
||||
TranslationContext
|
||||
> = {
|
||||
values: [
|
||||
{ from: "soActivityNotesRecId", to: "id" },
|
||||
{
|
||||
from: "notes",
|
||||
to: "notes",
|
||||
process: (value: string) => value,
|
||||
},
|
||||
{
|
||||
from: "soActivityRecId",
|
||||
to: "activityId",
|
||||
process: (value: number, context: TranslationContext) => {
|
||||
if (!context.activityIds.has(value)) {
|
||||
skipRow(`Activity ${value} not found in API`);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "internalAnalysisFlag",
|
||||
to: "internalAnalysisFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "enteredBy", to: "enteredById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{ from: "dateCreatedUtc", to: "createdAt" },
|
||||
{ from: "lastUpdateUtc", to: "updatedAt" },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { SoActStatus as CwSoActStatus } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiActivityStatusRecord = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
closedFlag: boolean;
|
||||
inactiveFlag: boolean;
|
||||
defaultFlag: boolean;
|
||||
spawnFollowupFlag: boolean;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const activityStatusTranslation: Translation<
|
||||
CwSoActStatus,
|
||||
ApiActivityStatusRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "soActStatusRecId", to: "id" },
|
||||
{
|
||||
from: "description",
|
||||
to: "name",
|
||||
process: (value) => (value ? value : "Unknown Status"),
|
||||
},
|
||||
{ from: "description", to: "description" },
|
||||
{
|
||||
from: "closedFlag",
|
||||
to: "closedFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "inactiveFlag",
|
||||
to: "inactiveFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "defaultFlag",
|
||||
to: "defaultFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "spawnFollowupFlag",
|
||||
to: "spawnFollowupFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{ from: "dateEnteredUtc", to: "createdAt" },
|
||||
{ from: "lastUpdateUtc", to: "updatedAt" },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { SoActivityType as CwSoActivityType } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiActivityTypeRecord = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
inactiveFlag: boolean;
|
||||
historyFlag: boolean;
|
||||
defaultFlag: boolean;
|
||||
importFlag: boolean;
|
||||
emailFlag: boolean;
|
||||
memoFlag: boolean;
|
||||
pointsValue: number | null;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const activityTypeTranslation: Translation<
|
||||
CwSoActivityType,
|
||||
ApiActivityTypeRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "soActivityTypeRecId", to: "id" },
|
||||
{
|
||||
from: "soActivityTypeId",
|
||||
to: "name",
|
||||
process: (value) => (value ? value : ""),
|
||||
},
|
||||
{
|
||||
from: "description",
|
||||
to: "description",
|
||||
process: (value) => (value ? value : ""),
|
||||
},
|
||||
{
|
||||
from: "inactiveFlag",
|
||||
to: "inactiveFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "historyFlag",
|
||||
to: "historyFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "defaultFlag",
|
||||
to: "defaultFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "importFlag",
|
||||
to: "importFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "emailFlag",
|
||||
to: "emailFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "memoFlag",
|
||||
to: "memoFlag",
|
||||
process: (value) => Boolean(value ?? false),
|
||||
},
|
||||
{ from: "pointsValue", to: "pointsValue" },
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{ from: "dateEnteredUtc", to: "createdAt" },
|
||||
{ from: "lastUpdateUtc", to: "updatedAt" },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
import { SoActivity as CwSoActivity } from "../../generated/prisma/client";
|
||||
import { Translation, skipRow } from "./types";
|
||||
import { TranslationContext } from "./context";
|
||||
|
||||
type ApiActivityRecord = {
|
||||
id: number;
|
||||
subject: string;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
assignToId: string;
|
||||
assignedById: string;
|
||||
enteredBy: string;
|
||||
automated: boolean;
|
||||
closedFlag: boolean;
|
||||
notifyCompleteFlag: boolean;
|
||||
notificationSentFlat: boolean;
|
||||
opportunityId: string | null;
|
||||
serviceTicketId: string | null;
|
||||
contactId: number | null;
|
||||
companyId: number | null;
|
||||
activityTypeId: number | null;
|
||||
activityStatusId: number | null;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
closedById: string | null;
|
||||
closedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const activityTranslation: Translation<
|
||||
CwSoActivity,
|
||||
ApiActivityRecord,
|
||||
TranslationContext
|
||||
> = {
|
||||
values: [
|
||||
{ from: "soActivityRecId", to: "id" },
|
||||
{
|
||||
from: "subject",
|
||||
to: "subject",
|
||||
process: (value) => (value ? value : ""),
|
||||
},
|
||||
{
|
||||
from: "dateTimeStart",
|
||||
to: "startTime",
|
||||
process: (value) => {
|
||||
if (!value) skipRow("Missing dateTimeStart");
|
||||
return value as Date;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "dateTimeEnd",
|
||||
to: "endTime",
|
||||
process: (value) => {
|
||||
if (!value) skipRow("Missing dateTimeEnd");
|
||||
return value as Date;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "assignTo",
|
||||
to: "assignToId",
|
||||
},
|
||||
{
|
||||
from: "assignedBy",
|
||||
to: "assignedById",
|
||||
},
|
||||
{
|
||||
from: "enteredBy",
|
||||
to: "enteredBy",
|
||||
},
|
||||
{
|
||||
from: "automated",
|
||||
to: "automated",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "closeFlag",
|
||||
to: "closedFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "notifyCompleteFlag",
|
||||
to: "notifyCompleteFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "notificationSentFlag",
|
||||
to: "notificationSentFlat",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
// opportunityId is a String? field with no @relation — store as null
|
||||
// until a uid-mapping approach is established
|
||||
from: "opportunityRecId",
|
||||
to: "opportunityId",
|
||||
process: () => null,
|
||||
},
|
||||
{
|
||||
// serviceTicketId is a String? field with no @relation — store as null
|
||||
// until a uid-mapping approach is established
|
||||
from: "srServiceRecId",
|
||||
to: "serviceTicketId",
|
||||
process: () => null,
|
||||
},
|
||||
{
|
||||
from: "contactRecId",
|
||||
to: "contactId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.contactIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "companyRecId",
|
||||
to: "companyId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.companyIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "soActivityTypeRecId",
|
||||
to: "activityTypeId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.activityTypeIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "soActStatusRecId",
|
||||
to: "activityStatusId",
|
||||
process: (value: number, context: TranslationContext) => {
|
||||
if (!context.activityStatusIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{ from: "closedBy", to: "closedById" },
|
||||
{ from: "dateClosedUtc", to: "closedAt" },
|
||||
{ from: "dateEnteredUtc", to: "createdAt" },
|
||||
{ from: "lastUpdatedUTC", to: "updatedAt" },
|
||||
],
|
||||
};
|
||||
@@ -73,6 +73,21 @@ export interface TranslationContext {
|
||||
|
||||
// Set of API TaxCode.id values for FK validation
|
||||
taxCodeIds: Set<number>;
|
||||
|
||||
// Set of API ActivityType.id values for FK validation
|
||||
activityTypeIds: Set<number>;
|
||||
|
||||
// Set of API ActivityStatus.id values for FK validation
|
||||
activityStatusIds: Set<number>;
|
||||
|
||||
// Set of API Activity.id values for FK validation (used by ActivityNotes)
|
||||
activityIds: Set<number>;
|
||||
|
||||
// Set of API TimeEntryChargeCode.id values for FK validation
|
||||
timeEntryChargeCodeIds: Set<number>;
|
||||
|
||||
// Map: CW TE_Status_ID (smallint) -> API TimeEntryStatus.id (rec ID)
|
||||
timeEntryStatusIdByTeStatusId: Map<number, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,5 +118,10 @@ export function createTranslationContext(): TranslationContext {
|
||||
scheduleTypeIds: new Set(),
|
||||
scheduleSpanIds: new Set(),
|
||||
taxCodeIds: new Set(),
|
||||
activityTypeIds: new Set(),
|
||||
activityStatusIds: new Set(),
|
||||
activityIds: new Set(),
|
||||
timeEntryChargeCodeIds: new Set(),
|
||||
timeEntryStatusIdByTeStatusId: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { MemberType as CwMemberType } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiCwMemberTypeRecord = {
|
||||
id: number;
|
||||
description: string | null;
|
||||
inactiveFlag: boolean;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const cwMemberTypeTranslation: Translation<
|
||||
CwMemberType,
|
||||
ApiCwMemberTypeRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "memberTypeRecId", to: "id" },
|
||||
{ from: "description", to: "description" },
|
||||
{
|
||||
from: "inactiveFlag",
|
||||
to: "inactiveFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{
|
||||
from: "dateEnteredUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdateUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ActivityClass as CwActivityClass } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiTimeActivityClassRecord = {
|
||||
id: number;
|
||||
description: string | null;
|
||||
hourlyRate: number | null;
|
||||
inactiveFlag: boolean;
|
||||
taxExemptFlag: boolean;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const timeActivityClassTranslation: Translation<
|
||||
CwActivityClass,
|
||||
ApiTimeActivityClassRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "activityClassRecId", to: "id" },
|
||||
{ from: "description", to: "description" },
|
||||
{
|
||||
from: "hourlyRate",
|
||||
to: "hourlyRate",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "inactiveFlag",
|
||||
to: "inactiveFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "taxExemptFlag",
|
||||
to: "taxExemptFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{
|
||||
from: "dateEnteredUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdatedUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import { ActivityType as CwActivityType } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiTimeActivityTypeRecord = {
|
||||
id: number;
|
||||
description: string | null;
|
||||
minHours: number | null;
|
||||
maxHours: number | null;
|
||||
rate: number | null;
|
||||
costMultiplier: number;
|
||||
inactiveFlag: boolean;
|
||||
invoiceFlag: boolean;
|
||||
billableFlag: boolean;
|
||||
utilizationFlag: boolean;
|
||||
defaultFlag: boolean;
|
||||
multiplierFlag: boolean;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const timeActivityTypeTranslation: Translation<
|
||||
CwActivityType,
|
||||
ApiTimeActivityTypeRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "activityTypeRecId", to: "id" },
|
||||
{ from: "description", to: "description" },
|
||||
{
|
||||
from: "hoursMin",
|
||||
to: "minHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "hoursMax",
|
||||
to: "maxHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "rate",
|
||||
to: "rate",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "costMultiplier",
|
||||
to: "costMultiplier",
|
||||
process: (value) => (value != null ? Number(value) : 1),
|
||||
},
|
||||
{
|
||||
from: "inactiveFlag",
|
||||
to: "inactiveFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "invoiceFlag",
|
||||
to: "invoiceFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "billableFlag",
|
||||
to: "billableFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "utilizationFlag",
|
||||
to: "utilizationFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "defaultFlag",
|
||||
to: "defaultFlag",
|
||||
process: (value) => Boolean(value ?? false),
|
||||
},
|
||||
{
|
||||
from: "multiplierFlag",
|
||||
to: "multiplierFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{
|
||||
from: "dateEnteredUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdatedUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { TeChargeCode as CwTeChargeCode } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiTimeEntryChargeCodeRecord = {
|
||||
id: number;
|
||||
chargeCodeId: number;
|
||||
description: string | null;
|
||||
expenseFlag: boolean;
|
||||
timeFlag: boolean;
|
||||
billableFlag: boolean;
|
||||
invoiceFlag: boolean;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const timeEntryChargeCodeTranslation: Translation<
|
||||
CwTeChargeCode,
|
||||
ApiTimeEntryChargeCodeRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "teChargeCodeRecId", to: "id" },
|
||||
{ from: "teChargeCodeRecId", to: "chargeCodeId" },
|
||||
{ from: "description", to: "description" },
|
||||
{
|
||||
from: "expenseFlag",
|
||||
to: "expenseFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "timeFlag",
|
||||
to: "timeFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "billableFlag",
|
||||
to: "billableFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "invoiceFlag",
|
||||
to: "invoiceFlag",
|
||||
process: (value) => Boolean(value ?? false),
|
||||
},
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{
|
||||
from: "dateEnteredUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdateUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { TeStatus as CwTeStatus } from "../../generated/prisma/client";
|
||||
import { Translation } from "./types";
|
||||
|
||||
type ApiTimeEntryStatusRecord = {
|
||||
id: number;
|
||||
statusId: number;
|
||||
description: string | null;
|
||||
action: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const timeEntryStatusTranslation: Translation<
|
||||
CwTeStatus,
|
||||
ApiTimeEntryStatusRecord
|
||||
> = {
|
||||
values: [
|
||||
{ from: "teStatusRecId", to: "id" },
|
||||
{ from: "teStatusId", to: "statusId" },
|
||||
{ from: "description", to: "description" },
|
||||
{ from: "action", to: "action" },
|
||||
{
|
||||
from: "lastUpdatedUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdatedUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
import { TimeEntry as CwTimeEntry } from "../../generated/prisma/client";
|
||||
import { Translation, skipRow } from "./types";
|
||||
import { TranslationContext } from "./context";
|
||||
|
||||
type ApiTimeEntryRecord = {
|
||||
id: number;
|
||||
uid: string;
|
||||
memberId: string | null;
|
||||
companyId: number;
|
||||
serviceTicketId: number | null;
|
||||
activityId: number | null;
|
||||
chargeCodeId: number | null;
|
||||
statusId: number | null;
|
||||
contactId: number | null;
|
||||
dateStart: Date | null;
|
||||
timeStart: Date | null;
|
||||
timeEnd: Date | null;
|
||||
notes: string | null;
|
||||
notesMd: string | null;
|
||||
internalNote: string | null;
|
||||
billableHours: number | null;
|
||||
actualHours: number | null;
|
||||
invoicedHours: number | null;
|
||||
deductedHours: number | null;
|
||||
hourlyRate: number | null;
|
||||
effectiveRate: number | null;
|
||||
issueFlag: boolean;
|
||||
mergedFlag: boolean;
|
||||
invoiceFlag: boolean;
|
||||
billableFlag: boolean;
|
||||
documentFlag: boolean;
|
||||
teProblemFlag: boolean;
|
||||
teResolutionFlag: boolean;
|
||||
teInternalAnalysisFlag: boolean;
|
||||
chargeToRecId: number | null;
|
||||
chargeToType: string | null;
|
||||
createdById: string | null;
|
||||
updatedById: string | null;
|
||||
originalAuthorId: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export const timeEntryTranslation: Translation<
|
||||
CwTimeEntry,
|
||||
ApiTimeEntryRecord,
|
||||
TranslationContext
|
||||
> = {
|
||||
values: [
|
||||
{ from: "timeRecId", to: "id" },
|
||||
{
|
||||
from: "mobileGuid",
|
||||
to: "uid",
|
||||
process: (value: string) => value,
|
||||
},
|
||||
{ from: "memberId", to: "memberId" },
|
||||
{
|
||||
from: "companyRecId",
|
||||
to: "companyId",
|
||||
process: (value: number, context: TranslationContext) => {
|
||||
if (!context.companyIds.has(value)) return skipRow(`company ${value} not found`);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "srServiceRecId",
|
||||
to: "serviceTicketId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.serviceTicketIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "soActivityRecId",
|
||||
to: "activityId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.activityIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "teChargeCodeRecId",
|
||||
to: "chargeCodeId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.timeEntryChargeCodeIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "teStatusId",
|
||||
to: "statusId",
|
||||
process: (value: number, context: TranslationContext) => {
|
||||
return context.timeEntryStatusIdByTeStatusId.get(value) ?? null;
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "contactRecId",
|
||||
to: "contactId",
|
||||
process: (value: number | null, context: TranslationContext) => {
|
||||
if (!value) return null;
|
||||
if (!context.contactIds.has(value)) return null;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
{ from: "dateStart", to: "dateStart" },
|
||||
{ from: "timeStart", to: "timeStart" },
|
||||
{ from: "timeEnd", to: "timeEnd" },
|
||||
{
|
||||
from: "notes",
|
||||
to: "notes",
|
||||
process: (value: string | null) => value ?? null,
|
||||
},
|
||||
{
|
||||
from: "notesMarkdown",
|
||||
to: "notesMd",
|
||||
process: (value: string | null) => value ?? null,
|
||||
},
|
||||
{ from: "internalNote", to: "internalNote" },
|
||||
{
|
||||
from: "hoursBill",
|
||||
to: "billableHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "hoursActual",
|
||||
to: "actualHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "hoursInvoiced",
|
||||
to: "invoicedHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "hoursDeduct",
|
||||
to: "deductedHours",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "hourlyRate",
|
||||
to: "hourlyRate",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "effectiveRate",
|
||||
to: "effectiveRate",
|
||||
process: (value) => (value != null ? Number(value) : null),
|
||||
},
|
||||
{
|
||||
from: "issueFlag",
|
||||
to: "issueFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "mergedFlag",
|
||||
to: "mergedFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "invoiceFlag",
|
||||
to: "invoiceFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "billableFlag",
|
||||
to: "billableFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "documentFlag",
|
||||
to: "documentFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "teProblemFlag",
|
||||
to: "teProblemFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "teResolutionFlag",
|
||||
to: "teResolutionFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{
|
||||
from: "teInternalAnalysisFlag",
|
||||
to: "teInternalAnalysisFlag",
|
||||
process: (value) => Boolean(value),
|
||||
},
|
||||
{ from: "chargeToRecId", to: "chargeToRecId" },
|
||||
{ from: "chargeToType", to: "chargeToType" },
|
||||
{ from: "enteredBy", to: "createdById" },
|
||||
{ from: "updatedBy", to: "updatedById" },
|
||||
{ from: "originalAuthor", to: "originalAuthorId" },
|
||||
{
|
||||
from: "dateEnteredUtc",
|
||||
to: "createdAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
{
|
||||
from: "lastUpdateUtc",
|
||||
to: "updatedAt",
|
||||
process: (value) => (value as Date | null) ?? new Date(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user