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:
parent
07067bef19
commit
ea3471d38d
36 changed files with 5604 additions and 217 deletions
|
|
@ -103,7 +103,7 @@ export class EntitySyncService {
|
|||
|
||||
// For incremental sync, filter by last sync time
|
||||
// Note: Companies and Resources don't support date-based filtering in Autotask API
|
||||
const supportsIncremental = entity !== EntityType.COMPANIES && entity !== EntityType.RESOURCES;
|
||||
const supportsIncremental = entity !== EntityType.COMPANIES && entity !== EntityType.RESOURCES && entity !== EntityType.COMPANY_TEAMS;
|
||||
|
||||
if (isIncremental && supportsIncremental) {
|
||||
try {
|
||||
|
|
@ -415,6 +415,41 @@ export class EntitySyncService {
|
|||
}
|
||||
}
|
||||
|
||||
// After contacts sync, backfill primary_contact_id and billing_contact_id on companies
|
||||
if (entity === EntityType.CONTACTS) {
|
||||
try {
|
||||
const primaryResult = await postgresClient.query(
|
||||
`UPDATE companies c
|
||||
SET primary_contact_id = (
|
||||
SELECT id FROM contacts
|
||||
WHERE company_id = c.id AND primary_contact = true AND is_deleted = false
|
||||
ORDER BY id LIMIT 1
|
||||
)
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM contacts
|
||||
WHERE company_id = c.id AND primary_contact = true AND is_deleted = false
|
||||
)`
|
||||
);
|
||||
entityLogger.info('Backfilled companies.primary_contact_id', { updatedCount: (primaryResult as any).rowCount ?? 0 });
|
||||
|
||||
const billingResult = await postgresClient.query(
|
||||
`UPDATE companies c
|
||||
SET billing_contact_id = (
|
||||
SELECT id FROM contacts
|
||||
WHERE company_id = c.id AND billing_contact = true AND is_deleted = false
|
||||
ORDER BY id LIMIT 1
|
||||
)
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM contacts
|
||||
WHERE company_id = c.id AND billing_contact = true AND is_deleted = false
|
||||
)`
|
||||
);
|
||||
entityLogger.info('Backfilled companies.billing_contact_id', { updatedCount: (billingResult as any).rowCount ?? 0 });
|
||||
} catch (err) {
|
||||
entityLogger.warn('Contact FK backfill on companies failed', { error: String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
// For full sync, soft delete records not in the fetched set
|
||||
// IMPORTANT: Skip soft deletes if ANY filters were applied (date, status, active, etc.)
|
||||
// because we cannot know what records exist outside the filter criteria.
|
||||
|
|
@ -817,6 +852,13 @@ export class EntitySyncService {
|
|||
return await this.syncEntity(EntityType.CONTACTS, isIncremental);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Company Teams (TAMs, CSMs, co-managed resources assigned to a company)
|
||||
*/
|
||||
async syncCompanyTeams(isIncremental: boolean = false): Promise<EntitySyncStats> {
|
||||
return await this.syncEntity(EntityType.COMPANY_TEAMS, isIncremental);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Contracts
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue