feat: Veeam RPO analysis, comparison, ticket analysis + company teams table

- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
This commit is contained in:
lorentz 2026-04-29 09:16:46 -04:00
parent 07067bef19
commit ea3471d38d
36 changed files with 5604 additions and 217 deletions

View file

@ -83,6 +83,9 @@ export function mapAutotaskToDatabase(
case EntityType.TAGS:
mapped = mapTag(data);
break;
case EntityType.COMPANY_TEAMS:
mapped = mapCompanyTeam(data);
break;
default:
// Fallback: auto-convert camelCase to snake_case
mapped = {};
@ -826,6 +829,19 @@ function mapPicklist(data: any): Record<string, any> {
};
}
/**
* Map CompanyTeam entity
*/
function mapCompanyTeam(data: any): Record<string, any> {
return {
id: data.id,
company_id: data.companyID,
resource_id: data.resourceID,
is_associated_as_comanaged: data.isAssociatedAsComanaged || false,
is_deleted: false,
};
}
/**
* Batch map multiple entities
*/

View file

@ -72,6 +72,7 @@ export function getAllEntitiesInOrder(): EntityType[] {
EntityType.TIME_ENTRIES,
EntityType.TAG_GROUPS,
EntityType.TAGS,
EntityType.COMPANY_TEAMS,
]);
}
@ -130,6 +131,7 @@ export function getAutotaskEntityName(entity: EntityType): string {
[EntityType.PROJECT_PHASES]: 'Phases',
[EntityType.COMPANY_CATEGORIES]: 'CompanyCategories',
[EntityType.COMPANY_TYPES]: 'CompanyTypes',
[EntityType.COMPANY_TEAMS]: 'CompanyTeams',
};
return mapping[entity] || entity;
@ -185,6 +187,7 @@ export function getLastModifiedField(entity: EntityType): string {
[EntityType.PROJECT_PHASES]: 'lastActivityDateTime',
[EntityType.COMPANY_CATEGORIES]: 'lastModifiedDate',
[EntityType.COMPANY_TYPES]: 'lastModifiedDate',
[EntityType.COMPANY_TEAMS]: 'lastModifiedDate', // No date field; incremental not supported
};
return mapping[entity] || 'lastModifiedDate';
@ -222,6 +225,7 @@ export function getActiveField(entity: EntityType): string | null {
[EntityType.PROJECT_PHASES]: null, // No active field on phases
[EntityType.COMPANY_CATEGORIES]: 'isActive',
[EntityType.COMPANY_TYPES]: 'isActive',
[EntityType.COMPANY_TEAMS]: null,
};
return mapping[entity] || null;
@ -319,6 +323,7 @@ export function buildDateRangeFilter(
[EntityType.PROJECT_PHASES]: null,
[EntityType.COMPANY_CATEGORIES]: null,
[EntityType.COMPANY_TYPES]: null,
[EntityType.COMPANY_TEAMS]: null,
};
const dateField = dateFieldMapping[entity];
@ -529,6 +534,7 @@ export function getEntityDisplayName(entity: EntityType): string {
[EntityType.PROJECT_PHASES]: 'Project Phases',
[EntityType.COMPANY_CATEGORIES]: 'Company Categories',
[EntityType.COMPANY_TYPES]: 'Company Types',
[EntityType.COMPANY_TEAMS]: 'Company Teams',
};
return mapping[entity] || entity;