[Employer Sync 4.2] Map JodGig employer → Identities::User
TL;DR: Employer-specific mapper (do not touch the shared talent one). Upsert keyed on remote_gig_user_id; lowercase email; unique placeholder mobile via JodGig::ContactNumberService.generate_invalid_format; force is_email_verified / is_phone_verified; carry the password digest as-is for the 4.5 login-side rehash.
Context
For each in-scope employer, the sync produces an Identities::User row JodApp can authenticate. The match key for the upsert is remote_gig_user_id.
Problem
The shared JodGig::Users::AttributeMapperService is built for talent. It does not lowercase email — 214 employers in production have uppercase characters in their email, and the JodApp login lowercases the typed identifier before lookup, so those users could not be found. It also copies users.contact_number straight into Identities::User.mobile — but 1,047 employers (32%) share an office number with another employer, and mobile is UNIQUE in JodApp. The shared mapper also hardcodes is_email_verified: false, which the employer sync needs flipped.
The shared mapper is also called by the talent batch sync and the talent JIT. We cannot change it.
Direction
Build a separate, employer-specific mapping path. Do not modify the shared mapper.
For each JodGig::User, produce the upsert hash:
email:jodgig_user.email.downcase.stripmobile:JodGig::ContactNumberService.generate_invalid_format(contact_number: jodgig_user.contact_number)— a unique placeholder. Drop the in-batch mobile-dedup logic from PR #1634; placeholders are unique by construction.phone_code:jodgig_user.country_codepassword_digest:jodgig_user.password.sub('$2y$', '$2a$')if it is bcrypt; otherwise carry the legacy hash as-is. The login-side rehash (issue 4.5) handles the legacy cases.is_email_verified: true,email_verified_at: Time.current(kept consistent — PR #1634 set the flag without the timestamp).is_phone_verified: true,phone_verified_at: Time.current— JodGig already trusted the account.remote_gig_user_id:jodgig_user.idgov_identity_number,gender,date_of_birth,deactivated_at,deactivation_reason— map as the shared mapper does for talent, but inline here. Do not call the shared mapper.uuid:SecureRandom.uuid(on insert only)address_geo_area_id: the migrated employer's company'saddress_geo_area_id. NOT NULL onidentities_users— every row must have one, and a jodgig employer has no personal address of their own. For super-HQ users mapped to multiple companies, use the company matchingusers.company_idif present, else the earliest bycompanies.created_at(same precedence used foris_default).created_at,updated_at: handled byupsert_all(record_timestamps: true)
Upsert:
Identities::User.upsert_all(
rows,
unique_by: :remote_gig_user_id,
update_only: [
:remote_gig_user_id,
:gender,
:date_of_birth,
:phone_verified_at,
:is_phone_verified,
:gov_identity_number,
:identity_verified,
:deactivated_at,
:deactivation_reason
],
record_timestamps: true,
returning: [:id]
)
The update_only list matches the talent sync's Phase B. Email, mobile, password, and name are set on insert and never touched again.
Acceptance
- Tests: uppercase email → stored lowercase; shared office number → unique placeholder mobile; bcrypt
$2y$→ rewritten to$2a$; non-bcrypt hash → carried as-is (no crash). - Tests: dual-role-by-coincidence (employer + APP user with different emails) → two clean
Identities::Userrows, no overwrite. - Tests: migrated employer's
address_geo_area_idequals their company'saddress_geo_area_id; a super-HQ user across 3 companies gets the company chosen by the precedence rule. - Tests:
is_email_verifiedandemail_verified_atare both set; same for the phone pair. - The shared
JodGig::Users::AttributeMapperServiceis unchanged. - No mobile-dedup logic in the employer path.
Depends on
- 3.1