[Employer Sync 3.1] Skeleton: EmployersSyncService with stubbed mappers
TL;DR: Empty shell of EmployersSyncService — find_in_batches(1000), per-record begin/rescue, JodGig::TimezoneService watermark, Gig::SyncLog write — with every mapping function stubbed to raise UnimplementedError. Each real mapper lands as its own small PR (4.1–4.4).
Context
The employer sync has the same shape as the existing talent sync: a batch job, an incremental watermark on updated_at, per-record error handling, a Gig::SyncLog row at the end. We want that shell in place first, with every business decision stubbed out — so each mapper can land as its own small, reviewable PR.
Problem
PR #1634 put service, job, mappers, dedup, and bug-fixes all into one 583-line change. It was hard to review with a mid-level engineer. The redo splits them.
Direction
Build only the shell. No real business logic in this PR.
Service — JodGig::Users::EmployersSyncService.execute:
- Read the last successful
Gig::SyncLogfor this origin/destination pair. - Compute the incremental watermark through
JodGig::TimezoneService(the service that branch1642introduces — coordinate). Never+ 8.hours. - Call
select_employers_in_scope(stubbed — returns an empty relation). find_in_batches(batch_size: 1000)— same size as the talent sync.- For each record in a batch, run inside its own
begin / rescue— append failures tofail_log, report to Sentry with the user id, continue. Per-record, not per-batch. - Inside the per-record block: call
map_to_identities_user,upsert_identities_users(single-row), thenmap_to_org_memberships,upsert_org_memberships, thenmap_to_org_outlet_assignments,upsert_org_outlet_assignments. All stubbed for now. - End by writing a
Gig::SyncLogrow withis_successful = fail_log.blank?.
Job — JodGig::Users::EmployersSyncJob:
Sidekiq::Job;sidekiq_options(queue: :low, retry: 0, dead: true, backtrace: 10).- One line:
JodGig::Users::EmployersSyncService.execute().
Scheduler — add the entry in config/sidekiq_scheduler.yml. Cadence is set by issue 5.3 (hourly); for now register it with a cron placeholder noting "set in 5.3."
Stubs. Each of the methods below lives on the service and raises Errors::UnimplementedError with the issue reference:
select_employers_in_scope→ "implemented in 4.1"map_to_identities_user(jodgig_user)→ "implemented in 4.2"upsert_identities_users(rows)→ "implemented in 4.2"map_to_org_memberships(...)→ "implemented in 4.3"upsert_org_memberships(rows)→ "implemented in 4.3"map_to_org_outlet_assignments(...)→ "implemented in 4.4"upsert_org_outlet_assignments(rows)→ "implemented in 4.4"resolve_owner_per_company(...)→ "implemented in 4.3"
Gig::SyncLog enum — add jodgig_users_employers to origin_table.
Acceptance
- Service, job, and scheduler entry exist; follow
.ai/instructions.mdpatterns. - Per-record
begin / rescueis wired in the batch loop (not per-batch). - Stubs raise
UnimplementedErrorwith the issue reference in the message. - The sync runs in QA against an empty scope and writes a clean
Gig::SyncLogrow (is_successful: true).
Depends on
- 2.1, 2.2 (schema must exist before the sync touches it)