fix: skip soft deletes for ALL entities with filters applied

More robust fix based on user feedback: instead of hardcoding specific
entity types, now dynamically checks if ANY filters were applied during
the sync and skips soft deletes accordingly.

Previous approach (hardcoded):
- Maintained list of specific entities to skip
- Easy to miss new entities (like billing_items bug)
- Required manual updates when adding new filters

New approach (dynamic):
- Tracks whether params.filter has any values
- Applies to ALL entities with ANY filter type:
  * Date filters (tickets, tasks, time_entries, billing_items)
  * Status filters (contracts, projects)
  * Active filters (companies, resources, contacts, etc.)
- Future-proof: automatically handles new filtered entities

Logic:
- hasAppliedFilters = params.filter && params.filter.length > 0
- if (!isIncremental && !hasAppliedFilters) → perform soft deletes
- if (!isIncremental && hasAppliedFilters) → skip soft deletes

This prevents deleting records that exist outside any filter criteria,
not just date ranges.
This commit is contained in:
root 2026-01-24 09:57:01 -05:00
parent 5b7532ee59
commit 31c2d94a1b

View file

@ -136,6 +136,9 @@ export class EntitySyncService {
params.filter = filters;
}
}
// Track whether any filters were applied - if so, skip soft deletes
const hasAppliedFilters = params.filter && params.filter.length > 0;
// Fetch data from Autotask with pagination
entityLogger.phase(SyncPhase.FETCHING, 'Fetching records from Autotask API');
@ -345,16 +348,11 @@ export class EntitySyncService {
entityLogger.info('Upserted records to PostgreSQL', { upsertedCount });
// For full sync, soft delete records not in the fetched set
// IMPORTANT: Only delete for entities without date filters to avoid deleting records outside sync window
// IMPORTANT: Skip soft deletes if ANY filters were applied (date, status, active, etc.)
// because we cannot know what records exist outside the filter criteria
let deletedCount = 0;
const hasDateFilter = entity === EntityType.TICKETS ||
entity === EntityType.TASKS ||
entity === EntityType.TIME_ENTRIES ||
entity === EntityType.BILLING_ITEMS ||
entity === EntityType.PROJECTS ||
entity === EntityType.CONTRACTS;
if (!isIncremental && !hasDateFilter) {
if (!isIncremental && !hasAppliedFilters) {
entityLogger.phase(SyncPhase.DELETING, 'Checking for records to soft delete');
syncProgressTracker.updateProgress(trackingId, { phase: 'deleting' });
@ -367,8 +365,8 @@ export class EntitySyncService {
entityLogger.warn('Soft delete failed, continuing sync', {}, err);
// Don't throw - soft delete failure shouldn't fail the entire sync
}
} else if (!isIncremental && hasDateFilter) {
entityLogger.info('Skipping soft-delete for date-filtered sync (would delete records outside sync window)');
} else if (!isIncremental && hasAppliedFilters) {
entityLogger.info('Skipping soft-delete because filters were applied (would delete records outside filter criteria)');
}
// Calculate added vs updated (simplified - actual count would require tracking)