feat: Morning NOC Summary adaptive card for Teams
- Add MorningSummaryService with Zabbix aggregation and adaptive card builder - Add webhook delivery system with Teams incoming webhooks - Add admin UI at /admin/morning-summary for webhook/config management - Add API routes: /send, /test, /webhooks, /webhooks/[id], /config, /history - Register morning-summary cron job in SyncScheduler (Mon-Fri 6:30 AM) - Add outages_only filter (Unavailable triggers only) - Fix host resolution: use getTriggerEnabledHosts to exclude disabled hosts - Fix resolved events: event.get value:1 scoped to window with r_eventid filter - Remove emojis from fact rows and section headers in card - Remove Open Zabbix button (duplicate of View Problems) - Add migrations: morning_summary_config + morning_summaries tables - Add outages_only column to morning_summary_config
This commit is contained in:
parent
19605f82aa
commit
c518eefdb2
61 changed files with 11236 additions and 237 deletions
|
|
@ -51,6 +51,12 @@ export function mapAutotaskToDatabase(
|
|||
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;
|
||||
|
|
@ -518,6 +524,41 @@ function mapContract(data: any): Record<string, any> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Autotask Service (catalog item) entity
|
||||
*/
|
||||
function mapAutotaskService(data: any): Record<string, any> {
|
||||
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<string, any> {
|
||||
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
|
||||
*/
|
||||
|
|
@ -558,45 +599,32 @@ function mapBillingItem(data: any): Record<string, any> {
|
|||
* Map Time Entry entity
|
||||
*/
|
||||
function mapTimeEntry(data: any): Record<string, any> {
|
||||
// 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,
|
||||
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_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,
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,11 +65,27 @@ export function getAllEntitiesInOrder(): EntityType[] {
|
|||
EntityType.TASKS,
|
||||
EntityType.CONFIGURATION_ITEMS,
|
||||
EntityType.CONTRACTS,
|
||||
EntityType.CONTRACT_SERVICES,
|
||||
EntityType.AUTOTASK_SERVICES,
|
||||
EntityType.BILLING_ITEMS,
|
||||
EntityType.TIME_ENTRIES,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build filter for contract services (requires contractID filter — fetch all via contractIDs)
|
||||
* @returns Query filter array for active contract services
|
||||
*/
|
||||
export function buildContractServicesFilter(): Array<{ field: string; op: string; value: any }> {
|
||||
return [
|
||||
{
|
||||
field: 'contractID',
|
||||
op: 'gt',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name for entity type
|
||||
* @param entity Entity type
|
||||
|
|
@ -102,6 +118,8 @@ export function getAutotaskEntityName(entity: EntityType): string {
|
|||
[EntityType.CONFIGURATION_ITEMS]: 'ConfigurationItems',
|
||||
[EntityType.CONTACTS]: 'Contacts',
|
||||
[EntityType.CONTRACTS]: 'Contracts',
|
||||
[EntityType.CONTRACT_SERVICES]: 'ContractServices',
|
||||
[EntityType.AUTOTASK_SERVICES]: 'Services',
|
||||
[EntityType.TIME_ENTRIES]: 'TimeEntries',
|
||||
[EntityType.TICKET_NOTES]: 'TicketNotes',
|
||||
};
|
||||
|
|
@ -141,6 +159,8 @@ export function getLastModifiedField(entity: EntityType): string {
|
|||
[EntityType.CONFIGURATION_ITEMS]: 'lastModifiedTime',
|
||||
[EntityType.CONTACTS]: 'lastModifiedDate',
|
||||
[EntityType.CONTRACTS]: 'lastModifiedDateTime',
|
||||
[EntityType.CONTRACT_SERVICES]: 'lastModifiedDate',
|
||||
[EntityType.AUTOTASK_SERVICES]: 'lastModifiedDate',
|
||||
[EntityType.BILLING_ITEMS]: 'itemDate',
|
||||
[EntityType.TIME_ENTRIES]: 'dateWorked',
|
||||
[EntityType.TICKET_NOTES]: 'lastActivityDate',
|
||||
|
|
@ -171,6 +191,8 @@ export function getActiveField(entity: EntityType): string | null {
|
|||
[EntityType.CONFIGURATION_ITEMS]: 'isActive',
|
||||
[EntityType.CONTACTS]: 'isActive',
|
||||
[EntityType.CONTRACTS]: null, // Use status field instead
|
||||
[EntityType.CONTRACT_SERVICES]: null,
|
||||
[EntityType.AUTOTASK_SERVICES]: 'isActive',
|
||||
[EntityType.BILLING_ITEMS]: null,
|
||||
[EntityType.TIME_ENTRIES]: null, // Time entries don't have active status
|
||||
[EntityType.TICKET_NOTES]: null, // Ticket notes don't have active status
|
||||
|
|
@ -258,7 +280,9 @@ export function buildDateRangeFilter(
|
|||
[EntityType.TIME_ENTRIES]: 'createDate', // TimeEntry uses createDate for filtering
|
||||
[EntityType.PROJECTS]: 'startDateTime',
|
||||
[EntityType.BILLING_ITEMS]: 'itemDate',
|
||||
[EntityType.CONTRACTS]: 'startDate', // Contracts use startDate
|
||||
[EntityType.CONTRACTS]: 'startDate',
|
||||
[EntityType.CONTRACT_SERVICES]: null,
|
||||
[EntityType.AUTOTASK_SERVICES]: null,
|
||||
[EntityType.COMPANIES]: null,
|
||||
[EntityType.RESOURCES]: null,
|
||||
[EntityType.CONTACTS]: null,
|
||||
|
|
@ -456,6 +480,8 @@ export function getEntityDisplayName(entity: EntityType): string {
|
|||
[EntityType.CONFIGURATION_ITEMS]: 'Configuration Items',
|
||||
[EntityType.CONTACTS]: 'Contacts',
|
||||
[EntityType.CONTRACTS]: 'Contracts',
|
||||
[EntityType.CONTRACT_SERVICES]: 'Contract Services',
|
||||
[EntityType.AUTOTASK_SERVICES]: 'Autotask Services',
|
||||
[EntityType.TIME_ENTRIES]: 'Time Entries',
|
||||
[EntityType.TICKET_NOTES]: 'Ticket Notes',
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue