Add comprehensive field support for Resources entity including: - Name fields: middleInitial, namePrefix, nameSuffix - Contact: emailAddress2, emailAddress3, homePhone - Employment: accountingReferenceID, payrollType, internalCost - System: emailTypeCode, numberFormat, timeFormat, dateFormat - Demographics: gender - Security: licenseType, securityLevel - Location: defaultServiceDeskRoleID Changes: - Migration 015: Add new columns to resources table - Updated entity mapper to map all Autotask Resource fields - Expanded TypeScript Resource interface with all fields - Maintains backward compatibility with existing field names This ensures all data exposed by the Autotask Resources API is now captured and stored in the database.
577 lines
20 KiB
TypeScript
577 lines
20 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,
|
|
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.first_response_date_time,
|
|
resolution_plan_date_time: data.resolution_plan_date_time,
|
|
resolved_date_time: data.resolved_date_time,
|
|
first_response_assigned_resource_id: data.first_response_assigned_resource_id,
|
|
first_response_initiating_resource_id: data.first_response_initiating_resource_id,
|
|
project_id: data.project_id,
|
|
opportunity_id: data.opportunity_id,
|
|
change_approval_board: data.change_approval_board,
|
|
change_approval_status: data.change_approval_status,
|
|
change_approval_type: data.change_approval_type,
|
|
change_info_field1: data.change_info_field1,
|
|
change_info_field2: data.change_info_field2,
|
|
change_info_field3: data.change_info_field3,
|
|
change_info_field4: data.change_info_field4,
|
|
change_info_field5: data.change_info_field5,
|
|
contract_id: data.contract_id,
|
|
monitor_id: data.monitor_id,
|
|
monitor_type_id: data.monitor_type_id,
|
|
ticket_type: data.ticket_type,
|
|
ticket_category: data.ticket_category,
|
|
service_level_agreement_id: data.service_level_agreement_id,
|
|
resolution: data.resolution,
|
|
purchase_order_number: data.purchase_order_number,
|
|
ticket_completion_date: data.ticket_completion_date,
|
|
last_activity_person_type: data.last_activity_person_type,
|
|
last_activity_resource_id: data.last_activity_resource_id,
|
|
current_service_thermometer_rating: data.current_service_thermometer_rating,
|
|
previous_service_thermometer_rating: data.previous_service_thermometer_rating,
|
|
service_thermometer_temperature: data.service_thermometer_temperature,
|
|
api_vendor_id: data.api_vendor_id,
|
|
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> {
|
|
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: data.resourceType,
|
|
payroll_identifier: data.payrollIdentifier,
|
|
pay_roll_identifier: data.payrollIdentifier, // Keep for backward compatibility
|
|
payroll_type: data.payrollType,
|
|
accounting_reference_id: data.accountingReferenceID,
|
|
internal_cost: data.internalCost,
|
|
hire_date: data.hireDate,
|
|
|
|
// Location and availability
|
|
location_id: data.locationID,
|
|
travel_availability_pct: data.travelAvailabilityPct,
|
|
default_service_desk_role_id: data.defaultServiceDeskRoleID,
|
|
|
|
// System preferences
|
|
email_type_code: data.emailTypeCode,
|
|
number_format: data.numberFormat,
|
|
time_format: data.timeFormat,
|
|
date_format: data.dateFormat,
|
|
|
|
// Demographics and security
|
|
gender: data.gender,
|
|
license_type: data.licenseType,
|
|
security_level: 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));
|
|
}
|