/** * Entity Mapper * Maps Autotask API responses to PostgreSQL database schema format */ import { EntityType } from '../types/sync'; /** * Map Autotask field names to PostgreSQL column names * Autotask uses camelCase, PostgreSQL uses snake_case */ export function toSnakeCase(str: string): string { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } /** * Map Autotask API response to database format * @param entity Entity type * @param data Autotask API response data * @returns Mapped data for PostgreSQL */ export function mapAutotaskToDatabase( entity: EntityType, data: any ): Record { // Entity-specific mappings use ORIGINAL data (not snake_cased) let mapped: Record; switch (entity) { case EntityType.COMPANIES: mapped = mapCompany(data); break; case EntityType.TICKETS: mapped = mapTicket(data); break; case EntityType.TASKS: mapped = mapTask(data); break; case EntityType.PROJECTS: mapped = mapProject(data); break; case EntityType.PROJECT_PHASES: mapped = mapProjectPhase(data); break; case EntityType.RESOURCES: mapped = mapResource(data); break; case EntityType.CONFIGURATION_ITEMS: mapped = mapConfigurationItem(data); break; case EntityType.CONTACTS: mapped = mapContact(data); break; case EntityType.CONTRACTS: mapped = mapContract(data); break; case EntityType.CONTRACT_SERVICES: mapped = mapContractService(data); break; case EntityType.AUTOTASK_SERVICES: mapped = mapAutotaskService(data); break; case EntityType.BILLING_ITEMS: mapped = mapBillingItem(data); break; case EntityType.TIME_ENTRIES: mapped = mapTimeEntry(data); break; case EntityType.TICKET_NOTES: mapped = mapTicketNote(data); break; case EntityType.STATUSES: case EntityType.ISSUE_TYPES: case EntityType.SUB_ISSUE_TYPES: case EntityType.WORK_TYPES: mapped = mapPicklist(data); break; case EntityType.TAG_GROUPS: mapped = mapTagGroup(data); break; case EntityType.TAGS: mapped = mapTag(data); break; default: // Fallback: auto-convert camelCase to snake_case mapped = {}; for (const [key, value] of Object.entries(data)) { const snakeKey = toSnakeCase(key); mapped[snakeKey] = value; } } // Add sync timestamp mapped.synced_at = new Date(); // Ensure is_deleted is false for new/updated records if (mapped.is_deleted === undefined) { mapped.is_deleted = false; } return mapped; } /** * Map Company entity */ function mapCompany(data: any): Record { // Convert userDefinedFields array → JSONB object { name: value } const udfs: Record = {}; if (Array.isArray(data.userDefinedFields)) { for (const udf of data.userDefinedFields) { udfs[udf.name] = udf.value ?? null; } } return { id: data.id, company_name: data.companyName || data.name, company_number: data.companyNumber, phone: data.phone, fax: data.fax, web_address: data.webAddress, address1: data.address1, address2: data.address2, city: data.city, state: data.state, postal_code: data.postalCode, country_id: data.countryID, is_active: data.isActive !== undefined ? data.isActive : true, company_type: data.companyType, company_category_id: data.companyCategoryID, owner_resource_id: data.ownerResourceID, territory_id: data.territoryID, market_segment_id: data.marketSegmentID, competitor_id: data.competitorID, parent_company_id: data.parentCompanyID, billing_address1: data.billingAddress1, billing_address2: data.billingAddress2, billing_city: data.billToCity, billing_state: data.billToState, billing_postal_code: data.billToZipCode, bill_to_country_id: data.billToCountryID, bill_to_address_to_use: data.billToAddressToUse, bill_to_attention: data.billToAttention, additional_address_information: data.additionalAddressInformation, bill_to_additional_address_info: data.billToAdditionalAddressInformation, tax_id: data.taxID, tax_exempt: data.isTaxExempt || false, tax_region_id: data.taxRegionID, currency_id: data.currencyID, invoice_method: data.invoiceMethod, invoice_template_id: data.invoiceTemplateID, invoice_email_message_id: data.invoiceEmailMessageID, quote_template_id: data.quoteTemplateID, purchase_order_template_id: data.purchaseOrderTemplateID, key_account_icon: data.keyAccountIcon, is_client_portal_active: data.isClientPortalActive || false, is_task_fire_active: data.isTaskFireActive || false, is_comanaged: data.isEnabledForComanaged || false, sic_code: data.sicCode, stock_market: data.stockMarket, stock_symbol: data.stockSymbol, alternate_phone1: data.alternatePhone1, alternate_phone2: data.alternatePhone2, survey_company_rating: data.surveyCompanyRating, asset_value: data.assetValue, last_activity_date: data.lastActivityDate, last_tracked_modification_date_time: data.lastTrackedModifiedDateTime, api_vendor_id: data.apiVendorID, create_date: data.createDate, created_by_resource_id: data.createdByResourceID, classification: data.classification ?? null, bill_to_company_location_id: data.billToCompanyLocationID ?? null, impersonator_creator_resource_id: data.impersonatorCreatorResourceID ?? null, invoice_non_contract_items_to_parent_company: data.invoiceNonContractItemsToParentCompany ?? null, quote_email_message_id: data.quoteEmailMessageID ?? null, user_defined_fields: Object.keys(udfs).length > 0 ? JSON.stringify(udfs) : null, is_deleted: data.isDeleted || false, }; } /** * Map Ticket entity */ function mapTicket(data: any): Record { return { id: data.id, company_id: data.companyID, ticket_number: data.ticketNumber, title: data.title, description: data.description, status: data.status, priority: data.priority, queue_id: data.queueID, issue_type: data.issueType, sub_issue_type: data.subIssueType, source: data.source, assigned_resource_id: data.assignedResourceID, assigned_resource_role_id: data.assignedResourceRoleID, contact_id: data.contactID, account_physical_location_id: data.companyLocationID, due_date_time: data.dueDateTime, estimated_hours: data.estimatedHours, completed_date: data.completedDate, create_date: data.createDate, created_by_contact_id: data.createdByContactID, creator_resource_id: data.creatorResourceID, creator_type: data.creatorType, billing_code_id: data.billingCodeID, configuration_item_id: data.configurationItemID, last_activity_date: data.lastActivityDate, last_customer_notification_date_time: data.lastCustomerNotificationDateTime, last_customer_visible_activity_date_time: data.lastCustomerVisibleActivityDateTime, first_response_date_time: data.firstResponseDateTime, resolution_plan_date_time: data.resolutionPlanDateTime, resolved_date_time: data.resolvedDateTime, first_response_assigned_resource_id: data.firstResponseAssignedResourceID, first_response_initiating_resource_id: data.firstResponseInitiatingResourceID, project_id: data.projectID, opportunity_id: data.opportunityID, change_approval_board: data.changeApprovalBoard, change_approval_status: data.changeApprovalStatus, change_approval_type: data.changeApprovalType, change_info_field1: data.changeInfoField1, change_info_field2: data.changeInfoField2, change_info_field3: data.changeInfoField3, change_info_field4: data.changeInfoField4, change_info_field5: data.changeInfoField5, contract_id: data.contractID, monitor_id: data.monitorID, monitor_type_id: data.monitorTypeID, ticket_type: data.ticketType, ticket_category: data.ticketCategory, service_level_agreement_id: data.serviceLevelAgreementID, resolution: data.resolution, purchase_order_number: data.purchaseOrderNumber, ticket_completion_date: data.ticketCompletionDate, last_activity_person_type: data.lastActivityPersonType, last_activity_resource_id: data.lastActivityResourceID, current_service_thermometer_rating: data.currentServiceThermometerRating, previous_service_thermometer_rating: data.previousServiceThermometerRating, service_thermometer_temperature: data.serviceThermometerTemperature, api_vendor_id: data.apiVendorID, problem_ticket_id: data.problemTicketId, rma_status: data.rmaStatus, rma_type: data.rmaType, service_level_agreement_paused_next_event_hours: data.serviceLevelAgreementPausedNextEventHours, is_assigned_to_comanaged: data.isAssignedToComanaged || false, is_visible_to_comanaged: data.isVisibleToComanaged || false, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map TicketNote entity */ function mapTicketNote(data: any): Record { return { id: data.id, ticket_id: data.ticketID, title: data.title, description: data.description, note_type: data.noteType, publish: data.publish, creator_resource_id: data.creatorResourceID, creator_type: data.creatorType, last_activity_date: data.lastActivityDate, create_date_time: data.createDateTime, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Task entity */ function mapTask(data: any): Record { return { id: data.id, title: data.title, description: data.description, status: data.status, priority: data.priority, assigned_resource_id: data.assignedResourceID, assigned_resource_role_id: data.assignedResourceRoleID, department_id: data.departmentID, estimated_hours: data.estimatedHours, remaining_hours: data.remainingHours, hours_to_be_scheduled: data.hoursToBeScheduled, start_date_time: data.startDateTime, end_date_time: data.endDateTime, completed_date_time: data.completedDateTime, create_date_time: data.createDateTime, creator_resource_id: data.creatorResourceID, completed_by_resource_id: data.completedByResourceID, last_activity_date_time: data.lastActivityDateTime, project_id: data.projectID, ticket_id: data.ticketID, phase_id: data.phaseID, allocation_code_id: data.allocationCodeID, task_type: data.taskType, task_is_billable: data.taskIsBillable !== undefined ? data.taskIsBillable : true, task_number: data.taskNumber, purchase_order_number: data.purchaseOrderNumber, can_client_portal_user_complete_task: data.canClientPortalUserCompleteTask || false, creator_type: data.creatorType, task_category_id: data.taskCategoryID, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Project entity */ function mapProject(data: any): Record { return { id: data.id, company_id: data.companyID, project_name: data.projectName, project_number: data.projectNumber, description: data.description, start_date_time: data.start_date_time, end_date_time: data.end_date_time, estimated_time: data.estimated_time, actual_hours: data.actual_hours, estimated_sale_cost: data.estimated_sale_cost, labor_estimated_costs: data.labor_estimated_costs, labor_estimated_revenue: data.labor_estimated_revenue, project_cost_estimated_margin_percentage: data.project_cost_estimated_margin_percentage, status: data.status, type: data.type, project_lead_resource_id: data.project_lead_resource_id, account_executive_resource_id: data.account_executive_resource_id, owner_resource_id: data.owner_resource_id, creator_resource_id: data.creator_resource_id, completed_percentage: data.completed_percentage, completed_date_time: data.completed_date_time, duration: data.duration, original_estimated_revenue: data.original_estimated_revenue, estimated_time_cost: data.estimated_time_cost, purchase_order_number: data.purchase_order_number, business_division_subdivision_id: data.business_division_subdivision_id, line_of_business_id: data.line_of_business_id, department: data.department, last_activity_date_time: data.last_activity_date_time, last_activity_person_type: data.last_activity_person_type, last_activity_resource_id: data.last_activity_resource_id, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map ProjectPhase entity */ function mapProjectPhase(data: any): Record { return { id: data.id, project_id: data.projectID, title: data.title, description: data.description, phase_number: data.phaseNumber, estimated_hours: data.estimatedHours, start_date_time: data.startDate, due_date: data.dueDate, create_date_time: data.createDate, last_activity_date_time: data.lastActivityDateTime, parent_phase_id: data.parentPhaseID, is_scheduled: data.isScheduled, creator_resource_id: data.creatorResourceID, external_id: data.externalID, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Resource entity */ function mapResource(data: any): Record { // Helper to safely parse integer values (returns null for non-numeric strings) const safeInt = (value: any): number | null => { if (value === null || value === undefined || value === '') return null; const num = typeof value === 'number' ? value : parseInt(value, 10); return isNaN(num) ? null : num; }; return { id: data.id, // Name fields first_name: data.firstName, last_name: data.lastName, middle_initial: data.middleInitial, name_prefix: data.namePrefix, name_suffix: data.nameSuffix, // Contact information email: data.email || data.emailAddress, email_address: data.emailAddress, email_address2: data.emailAddress2, email_address3: data.emailAddress3, office_phone: data.officePhone, mobile_phone: data.mobilePhone, home_phone: data.homePhone, office_extension: data.officeExtension, // System fields user_name: data.userName, title: data.title, is_active: data.isActive !== undefined ? data.isActive : true, // Employment details resource_type: safeInt(data.resourceType), payroll_identifier: data.payrollIdentifier, pay_roll_identifier: data.payrollIdentifier, // Keep for backward compatibility payroll_type: safeInt(data.payrollType), accounting_reference_id: data.accountingReferenceID, internal_cost: data.internalCost, hire_date: data.hireDate, // Location and availability location_id: safeInt(data.locationID), travel_availability_pct: data.travelAvailabilityPct, default_service_desk_role_id: safeInt(data.defaultServiceDeskRoleID), // System preferences email_type_code: safeInt(data.emailTypeCode), number_format: data.numberFormat, time_format: data.timeFormat, date_format: data.dateFormat, // Demographics and security gender: safeInt(data.gender), license_type: safeInt(data.licenseType), security_level: safeInt(data.securityLevel), // Ratings survey_resource_rating: data.surveyResourceRating, // Audit fields synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Configuration Item entity */ function mapConfigurationItem(data: any): Record { const mapped: Record = { id: data.id, company_id: data.companyID, product_id: data.productID, reference_title: data.referenceTitle, reference_number: data.referenceNumber, serial_number: data.serialNumber, install_date: data.installDate, warranty_expiration_date: data.warrantyExpirationDate, is_active: data.isActive !== undefined ? data.isActive : true, daily_cost: data.dailyCost, hourly_cost: data.hourlyCost, monthly_cost: data.monthlyCost, per_use_cost: data.perUseCost, setup_fee: data.setupFee, contact_id: data.contactID, location_id: data.locationID, vendor_id: data.vendorID, installed_by_id: data.installedByID, installed_by_contact_id: data.installedByContactID, parent_configuration_item_id: data.parentConfigurationItemID, notes: data.notes, create_date: data.createDate, created_by_person_id: data.createdByPersonID, last_modified_time: data.lastModifiedTime, last_activity_person_type: data.lastActivityPersonType, impersonator_creator_resource_id: data.impersonatorCreatorResourceID, configuration_item_category_id: data.configurationItemCategoryID, configuration_item_type: data.type, device_type: data.deviceType, rmm_device_uid: data.rmmDeviceUID, api_vendor_id: data.apiVendorID, // Datto fields datto_availability: data.dattoAvailability, datto_device_memory_megabytes: data.dattoDeviceMemoryMegabytes, datto_drives_errors: data.dattoDrivesErrors, datto_hostname: data.dattoHostname, datto_internal_ip: data.dattoInternalIP, datto_kernel_version_id: data.dattoKernelVersionID, datto_last_check_in_date_time: data.dattoLastCheckInDateTime, datto_nic_speed_kilobits_per_second: data.dattoNicSpeedKilobitsPerSecond, datto_number_of_agents: data.dattoNumberOfAgents, datto_number_of_drives: data.dattoNumberOfDrives, datto_number_of_logical_volumes: data.dattoNumberOfLogicalVolumes, datto_number_of_volumes: data.dattoNumberOfVolumes, datto_off_site_storage_used_bytes: data.dattoOffSiteStorageUsedBytes, datto_os_version_id: data.dattoOsVersionID, datto_percentage_used: data.dattoPercentageUsed, datto_protected_kilobytes: data.dattoProtectedKilobytes, datto_remote_ip: data.dattoRemoteIP, datto_serial_number: data.dattoSerialNumber, datto_uptime_seconds: data.dattoUptimeSeconds, datto_used_kilobytes: data.dattoUsedKilobytes, datto_z_pool_percentage: data.dattoZPoolPercentage, device_networking_id: data.deviceNetworkingID, last_backup_date: data.lastBackupDateTime || data.lastBackupDate, last_backup_status: data.lastBackupStatus, os_version_id: data.osVersionID, service_id: data.serviceID, service_bundle_id: data.serviceBundleID, snmp_location: data.snmpLocation, snmp_name: data.snmpName, snmp_contact: data.snmpContact, // RMM audit fields rmm_device_audit_architecture_id: data.rmmDeviceAuditArchitectureID, rmm_device_audit_display_adaptor_id: data.rmmDeviceAuditDisplayAdaptorID, rmm_device_audit_domain_id: data.rmmDeviceAuditDomainID, rmm_device_audit_external_ip_address: data.rmmDeviceAuditExternalIPAddress, rmm_device_audit_hostname: data.rmmDeviceAuditHostname, rmm_device_audit_ip_address: data.rmmDeviceAuditIPAddress, rmm_device_audit_mac_address: data.rmmDeviceAuditMacAddress, rmm_device_audit_manufacturer_id: data.rmmDeviceAuditManufacturerID, rmm_device_audit_missing_patch_count: data.rmmDeviceAuditMissingPatchCount, rmm_device_audit_mobile_network_operator_id: data.rmmDeviceAuditMobileNetworkOperatorID, rmm_device_audit_mobile_number: data.rmmDeviceAuditMobileNumber, rmm_device_audit_model_id: data.rmmDeviceAuditModelID, rmm_device_audit_motherboard_id: data.rmmDeviceAuditMotherboardID, rmm_device_audit_operating_system_id: data.rmmDeviceAuditOperatingSystemID, rmm_device_audit_processor_id: data.rmmDeviceAuditProcessorID, rmm_device_audit_service_pack_id: data.rmmDeviceAuditServicePackID, rmm_device_audit_snmp_contact: data.rmmDeviceAuditSNMPContact, rmm_device_audit_snmp_location: data.rmmDeviceAuditSNMPLocation, rmm_device_audit_snmp_name: data.rmmDeviceAuditSNMPName, rmm_device_audit_software_status_id: data.rmmDeviceAuditSoftwareStatusID, rmm_device_audit_storage_bytes: data.rmmDeviceAuditStorageBytes, rmm_open_alert_count: data.rmmOpenAlertCount, rmm_device_audit_description: data.rmmDeviceAuditDescription, rmm_device_audit_device_type_id: data.rmmDeviceAuditDeviceTypeID, rmm_device_audit_last_user: data.rmmDeviceAuditLastUser, rmm_device_audit_memory_bytes: data.rmmDeviceAuditMemoryBytes, source_cost_id: data.sourceCostID, source_cost_type: data.sourceCostType, }; // Store all UDFs as JSONB if (data.userDefinedFields && Array.isArray(data.userDefinedFields)) { const udfMap: Record = {}; for (const udf of data.userDefinedFields) { if (udf.name != null) { udfMap[String(udf.name)] = udf.value ?? null; } } mapped.udfs = Object.keys(udfMap).length > 0 ? udfMap : null; // Also extract the named backup_type_udf for backward compat const backupUdf = data.userDefinedFields.find( (udf: any) => udf.name === 'Backup Type' || String(udf.name) === '29693319' ); if (backupUdf?.value) { mapped.backup_type_udf = backupUdf.value; } } return mapped; } /** * Map Contact entity */ function mapContact(data: any): Record { // Convert userDefinedFields array → JSONB object { name: value } const udfs: Record = {}; if (Array.isArray(data.userDefinedFields)) { for (const udf of data.userDefinedFields) { udfs[udf.name] = udf.value ?? null; } } return { id: data.id, company_id: data.companyID, first_name: data.firstName, last_name: data.lastName, middle_initial: data.middleInitial, title: data.title, email_address: data.emailAddress, email_address2: data.emailAddress2, email_address3: data.emailAddress3, phone: data.phone, extension: data.extension, alternate_phone: data.alternatePhone, mobile_phone: data.mobilePhone, fax: data.faxNumber, address_line: data.addressLine, address_line1: data.addressLine1, city: data.city, state: data.state, zip_code: data.zipCode, country_id: data.countryID, company_location_id: data.companyLocationID, is_active: data.isActive !== undefined ? data.isActive : true, name_prefix: data.namePrefix, name_suffix: data.nameSuffix, facebook_url: data.facebookUrl, twitter_url: data.twitterUrl, linked_in_url: data.linkedInUrl, primary_contact: data.primaryContact || false, billing_contact: data.billingContact || false, solicitation_opt_out: data.solicitationOptOut || false, solicitation_opt_out_time: data.solicitationOptOutTime, survey_opt_out: data.surveyOptOut || false, is_opted_out_from_bulk_email: data.isOptedOutFromBulkEmail || false, bulk_email_opt_out_time: data.bulkEmailOptOutTime, receives_email_notifications: data.receivesEmailNotifications !== undefined ? data.receivesEmailNotifications : true, room_number: data.roomNumber, note: data.note, external_id: data.externalID, api_vendor_id: data.apiVendorID, impersonator_creator_resource_id: data.impersonatorCreatorResourceID, create_date: data.createDate, last_activity_date: data.lastActivityDate, last_modified_date: data.lastModifiedDate, user_defined_fields: Object.keys(udfs).length > 0 ? JSON.stringify(udfs) : null, is_deleted: data.isDeleted || false, }; } /** * Map Contract entity */ function mapContract(data: any): Record { return { id: data.id, company_id: data.companyID, contract_name: data.contractName, contract_number: data.contractNumber, description: data.description, start_date: data.start_date, end_date: data.end_date, time_reporting_requires_start_and_stop_times: data.time_reporting_requires_start_and_stop_times, service_level_agreement_id: data.service_level_agreement_id, contract_type: data.contract_type, contract_category: data.contract_category, status: data.status, business_division_subdivision_id: data.business_division_subdivision_id, contact_id: data.contact_id, contact_name: data.contact_name, billing_preference: data.billing_preference, purchase_order_number: data.purchase_order_number, setup_fee: data.setup_fee, setup_fee_allocation_code_id: data.setup_fee_allocation_code_id, estimated_cost: data.estimated_cost, estimated_hours: data.estimated_hours, estimated_revenue: data.estimated_revenue, over_budget_dollar_amount: data.over_budget_dollar_amount, over_budget_hours: data.over_budget_hours, contract_period_type: data.contract_period_type, opportunity_id: data.opportunity_id, renewed_contract_id: data.renewed_contract_id, is_default_contract: data.is_default_contract || false, internal_currency_setup_fee: data.internal_currency_setup_fee, internal_currency_over_budget_dollar_amount: data.internal_currency_over_budget_dollar_amount, internal_currency_estimated_cost: data.internal_currency_estimated_cost, internal_currency_estimated_revenue: data.internal_currency_estimated_revenue, exclusion_contract_id: data.exclusion_contract_id, internal_currency_monthly_revenue: data.internal_currency_monthly_revenue, internal_currency_quarterly_revenue: data.internal_currency_quarterly_revenue, internal_currency_semi_annual_revenue: data.internal_currency_semi_annual_revenue, internal_currency_yearly_revenue: data.internal_currency_yearly_revenue, internal_currency_one_time_revenue: data.internal_currency_one_time_revenue, compliance: data.compliance, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Autotask Service (catalog item) entity */ function mapAutotaskService(data: any): Record { return { id: data.id, name: data.name, description: data.description, unit_price: data.unitPrice, unit_cost: data.unitCost, period_type: data.periodType, is_active: data.isActive !== undefined ? data.isActive : true, synced_at: new Date(), is_deleted: false, }; } /** * Map Contract Service entity */ function mapContractService(data: any): Record { return { id: data.id, contract_id: data.contractID, service_id: data.serviceID, unit_price: data.unitPrice, unit_cost: data.unitCost, adjusted_price: data.internalCurrencyAdjustedPrice, invoice_description: data.invoiceDescription, internal_currency_price: data.internalCurrencyUnitPrice, synced_at: new Date(), is_deleted: false, }; } /** * Map Billing Item entity */ function mapBillingItem(data: any): Record { return { id: data.id, company_id: data.companyID, product_id: data.productID, description: data.description, quantity: data.quantity, rate: data.rate, total_amount: data.total_amount, line_discount_dollars: data.line_discount_dollars, line_discount_percent: data.line_discount_percent, tax_category_id: data.tax_category_id, internal_currency_line_discount_dollars: data.internal_currency_line_discount_dollars, allocation_code_id: data.allocation_code_id, invoice_id: data.invoice_id, vendor_id: data.vendor_id, expense_item: data.expense_item || false, task_id: data.task_id, ticket_id: data.ticket_id, project_id: data.project_id, our_cost: data.our_cost, list_price: data.list_price, unit_cost: data.unit_cost, unit_price: data.unit_price, extended_price: data.extended_price, tax_dollars: data.tax_dollars, internal_currency_unit_price: data.internal_currency_unit_price, internal_currency_total_amount: data.internal_currency_total_amount, synced_at: data.synced_at, is_deleted: data.is_deleted || false, }; } /** * Map Time Entry entity */ function mapTimeEntry(data: any): Record { // Autotask uses isNonBillable; derive billable from it const isNonBillable = data.isNonBillable; const billable = isNonBillable != null ? !isNonBillable : null; return { id: data.id, resource_id: data.resourceID, ticket_id: data.ticketID, task_id: data.taskID, contract_id: data.contractID, contract_service_id: data.contractServiceID, contract_service_bundle_id: data.contractServiceBundleID, entry_date: data.dateWorked, hours_worked: data.hoursWorked, hours_to_bill: data.hoursToBill, notes: data.summaryNotes, internal_notes: data.internalNotes, type: data.timeEntryType, start_date_time: data.startDateTime, end_date_time: data.endDateTime, billable, non_billable: isNonBillable, allocation_code_id: data.billingCodeID, approved_by_resource_id: data.billingApprovalResourceID, approved_date_time: data.billingApprovalDateTime, role_id: data.roleID, synced_at: new Date(), is_deleted: false, }; } /** * Map TagGroup entity */ function mapTagGroup(data: any): Record { return { id: data.id, label: data.label, display_color: data.displayColor, is_active: data.isActive !== undefined ? data.isActive : true, is_system: data.isSystem || false, }; } /** * Map Tag entity */ function mapTag(data: any): Record { return { id: data.id, label: data.label, tag_group_id: data.tagGroupID, is_active: data.isActive !== undefined ? data.isActive : true, is_system: data.isSystem || false, is_excluded_from_automatic_tagging: data.isExcludedFromAutomaticTagging || false, create_date_time: data.createDateTime, last_modified_date_time: data.lastModifiedDateTime, }; } /** * Map Picklist entity (statuses, issue types, etc.) */ function mapPicklist(data: any): Record { return { value: data.value, label: data.label || data.name, is_active: data.isActive !== undefined ? data.isActive : true, is_system: data.isSystem || false, sort_order: data.sortOrder, parent_value: data.parentValue, }; } /** * Batch map multiple entities */ export function mapAutotaskBatch( entity: EntityType, items: any[] ): Record[] { return items.map(item => mapAutotaskToDatabase(entity, item)); }