Added 10 critical missing fields to Tickets entity: Database Migration (016): - billing_code_id: Billing code assignment - configuration_item_id: Primary asset/CI - creator_resource_id: Ticket creator - creator_type: Creator type (resource/contact) - problem_ticket_id: Link to problem ticket - rma_status: RMA status tracking - rma_type: RMA type classification - service_level_agreement_paused_next_event_hours: SLA pause tracking - is_assigned_to_comanaged: Co-managed assignment flag - is_visible_to_comanaged: Co-managed visibility flag Entity Mapper Updates: - Added all new fields with correct camelCase mapping - Ensures all Autotask Tickets API fields are captured This completes the Tickets entity to match the full Autotask API specification and should resolve issues with missing ticket data.
594 lines
21 KiB
TypeScript
594 lines
21 KiB
TypeScript
/**
|
|
* 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<string, any> {
|
|
// Entity-specific mappings use ORIGINAL data (not snake_cased)
|
|
let mapped: Record<string, any>;
|
|
|
|
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.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.BILLING_ITEMS:
|
|
mapped = mapBillingItem(data);
|
|
break;
|
|
case EntityType.TIME_ENTRIES:
|
|
mapped = mapTimeEntry(data);
|
|
break;
|
|
case EntityType.STATUSES:
|
|
case EntityType.ISSUE_TYPES:
|
|
case EntityType.SUB_ISSUE_TYPES:
|
|
case EntityType.WORK_TYPES:
|
|
mapped = mapPicklist(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<string, any> {
|
|
return {
|
|
id: data.id,
|
|
company_name: data.companyName || data.name,
|
|
company_number: data.companyNumber,
|
|
phone: data.phone,
|
|
fax: data.fax,
|
|
website: data.webSiteURL,
|
|
address1: data.address1,
|
|
address2: data.address2,
|
|
city: data.city,
|
|
state: data.state,
|
|
postal_code: data.postalCode,
|
|
country: data.country,
|
|
is_active: data.isActive !== undefined ? data.isActive : true,
|
|
company_type: data.companyType,
|
|
owner_resource_id: data.ownerResourceID,
|
|
territory_id: data.territoryID,
|
|
market_segment_id: data.marketSegmentID,
|
|
competitor_id: data.competitorID,
|
|
billing_address1: data.billingAddress1,
|
|
billing_address2: data.billingAddress2,
|
|
billing_city: data.billingCity,
|
|
billing_state: data.billingState,
|
|
billing_postal_code: data.billingPostalCode,
|
|
billing_country: data.billingCountry,
|
|
tax_id: data.taxID,
|
|
tax_exempt: data.taxExempt || false,
|
|
tax_region_id: data.taxRegionID,
|
|
currency_id: data.currencyID,
|
|
invoice_method: data.invoice_method,
|
|
invoice_template_id: data.invoice_template_id,
|
|
quote_template_id: data.quote_template_id,
|
|
key_account_icon: data.key_account_icon,
|
|
last_activity_date: data.last_activity_date,
|
|
last_tracked_modification_date_time: data.last_tracked_modification_date_time || data.last_modified_date,
|
|
api_vendor_id: data.api_vendor_id,
|
|
synced_at: data.synced_at,
|
|
is_deleted: data.is_deleted || false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map Ticket entity
|
|
*/
|
|
function mapTicket(data: any): Record<string, any> {
|
|
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 Task entity
|
|
*/
|
|
function mapTask(data: any): Record<string, any> {
|
|
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<string, any> {
|
|
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 Resource entity
|
|
*/
|
|
function mapResource(data: any): Record<string, any> {
|
|
// 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<string, any> {
|
|
// Configuration items have many fields, map all of them
|
|
const mapped: Record<string, any> = {
|
|
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,
|
|
contact_id: data.contactID,
|
|
configuration_item_type: data.type,
|
|
synced_at: data.synced_at,
|
|
is_deleted: data.is_deleted || false,
|
|
};
|
|
|
|
// Add all other fields dynamically
|
|
const fieldsToInclude = [
|
|
'daily_cost', 'hourly_cost', 'monthly_cost', 'per_use_cost', 'setup_fee',
|
|
'location_id', 'vendor_id', 'installed_by_id', 'installed_by_contact_id',
|
|
'parent_configuration_item_id', 'notes', 'create_date', 'created_by_person_id',
|
|
'last_modified_time', 'last_activity_person_type', 'impersonator_creator_resource_id',
|
|
'configuration_item_category_id', 'configuration_item_type', 'device_type',
|
|
'rmm_device_uid', 'api_vendor_id',
|
|
];
|
|
|
|
// Add Datto RMM fields
|
|
const dattoFields = Object.keys(data).filter(key => key.startsWith('datto_') || key.startsWith('rmm_'));
|
|
fieldsToInclude.push(...dattoFields);
|
|
|
|
fieldsToInclude.forEach(field => {
|
|
if (data[field] !== undefined) {
|
|
mapped[field] = data[field];
|
|
}
|
|
});
|
|
|
|
return mapped;
|
|
}
|
|
|
|
/**
|
|
* Map Contact entity
|
|
*/
|
|
function mapContact(data: any): Record<string, any> {
|
|
return {
|
|
id: data.id,
|
|
company_id: data.companyID,
|
|
first_name: data.firstName,
|
|
last_name: data.lastName,
|
|
title: data.title,
|
|
email_address: data.emailAddress || data.email_address || data.email,
|
|
email_address2: data.emailAddress2 || data.email_address2,
|
|
email_address3: data.emailAddress3 || data.email_address3,
|
|
phone: data.phone,
|
|
extension: data.extension,
|
|
alternate_phone: data.alternate_phone,
|
|
mobile_phone: data.mobile_phone,
|
|
fax: data.faxNumber || data.fax,
|
|
address_line: data.addressLine || data.address_line,
|
|
address_line1: data.addressLine1 || data.address_line1 || data.address_line_1,
|
|
city: data.city,
|
|
state: data.state,
|
|
zip_code: data.zipCode || data.zip_code || data.postal_code,
|
|
country: data.country,
|
|
is_active: data.is_active !== undefined ? data.is_active : true,
|
|
name_prefix: data.name_prefix,
|
|
name_suffix: data.name_suffix,
|
|
facebook_url: data.facebook_url,
|
|
twitter_url: data.twitter_url,
|
|
linked_in_url: data.linked_in_url,
|
|
primary_contact: data.primary_contact || false,
|
|
account_physical_location_id: data.account_physical_location_id,
|
|
solicitation_opt_out: data.solicitation_opt_out || false,
|
|
room_number: data.room_number,
|
|
last_activity_date: data.last_activity_date,
|
|
last_modified_date: data.last_modified_date,
|
|
api_vendor_id: data.api_vendor_id,
|
|
synced_at: data.synced_at,
|
|
is_deleted: data.is_deleted || false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map Contract entity
|
|
*/
|
|
function mapContract(data: any): Record<string, any> {
|
|
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 Billing Item entity
|
|
*/
|
|
function mapBillingItem(data: any): Record<string, any> {
|
|
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<string, any> {
|
|
return {
|
|
id: data.id,
|
|
resource_id: data.resourceID,
|
|
ticket_id: data.ticketID,
|
|
task_id: data.taskID,
|
|
project_id: data.projectID,
|
|
company_id: data.companyID,
|
|
entry_date: data.dateWorked, // Autotask API returns dateWorked
|
|
hours_worked: data.hoursWorked, // Autotask API returns hoursWorked
|
|
notes: data.summaryNotes, // Autotask uses summaryNotes
|
|
internal_notes: data.internalNotes,
|
|
title: data.title,
|
|
type: data.type,
|
|
start_date_time: data.startDateTime,
|
|
end_date_time: data.endDateTime,
|
|
billable: data.billable, // Autotask returns billable
|
|
billing_rate: data.billingRate,
|
|
billing_rate_currency_id: data.billingRateCurrencyID,
|
|
cost_rate: data.costRate,
|
|
cost_rate_currency_id: data.costRateCurrencyID,
|
|
cost: data.cost,
|
|
cost_currency_id: data.costCurrencyID,
|
|
revenue: data.revenue,
|
|
revenue_currency_id: data.revenueCurrencyID,
|
|
margin: data.margin,
|
|
margin_currency_id: data.marginCurrencyID,
|
|
approved: data.approved,
|
|
approved_by_resource_id: data.approvedByResourceID,
|
|
approved_date_time: data.approvedDateTime,
|
|
non_billable: data.nonBillable,
|
|
contract_service_id: data.contractServiceID,
|
|
contract_service_bundle_id: data.contractServiceBundleID,
|
|
role_id: data.roleID,
|
|
department_id: data.departmentID,
|
|
location_id: data.locationID,
|
|
allocation_code_id: data.allocationCodeID,
|
|
imp_project_schedule_id: data.impProjectScheduleID,
|
|
imp_project_schedule_task_id: data.impProjectScheduleTaskID,
|
|
api_vendor_id: data.apiVendorID,
|
|
synced_at: new Date(),
|
|
is_deleted: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map Picklist entity (statuses, issue types, etc.)
|
|
*/
|
|
function mapPicklist(data: any): Record<string, any> {
|
|
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<string, any>[] {
|
|
return items.map(item => mapAutotaskToDatabase(entity, item));
|
|
}
|