Skip to main content

Employer migration: the new plan

This is the implementation guide for giving existing JodGig employers access to JodApp while both systems remain live.

The plan starts with what an employer must be able to do. It then works backwards into application changes, migration rules, database writes, and rollout checks. The implementation is split into complete phases so each pull request proves a useful part of the journey.

The plan in one picture

TL;DR
DecisionChoice
Source of truthJodGig owns migrated employer identity and legacy access data.
DirectionChanges flow from JodGig to JodApp only.
MethodCalculate the complete intended result, then make JodApp agree with it.
FrequencyRun daily after the company and outlet syncs. Allow the same job to be started manually.
SizeRead all employer-type users. The population is small enough that a checkpoint adds risk without useful savings.

The calculation step is deliberately separate from the writing step. The dry run and the live run must use the same calculation. Otherwise the report cannot prove what the live job will do.

Start with the business outcome

TL;DR

The migration is complete when correct employer access works and incorrect access does not. Creating rows alone is not success.

An eligible JodGig employer must be able to:

  1. log in to JodApp with the email and password currently managed in JodGig;
  2. enter every valid company they are allowed to manage;
  3. see only the outlets allowed by their role;
  4. choose which of their allowed companies they are currently working in when they have several memberships;
  5. lose legacy access after it is removed in JodGig; and
  6. keep any membership created directly in JodApp, even if JodGig does not know about it.

How each business outcome is delivered

This table is organized by business outcome, not by implementation order. Each row starts with something an employer or reviewer must be able to observe, then shows the work that makes it true. Phase links within a row are listed in implementation order. The complete ordered plan is in Delivery phases.

Business outcomeWork that makes the outcome trueMain implementation phases, in order
Same login worksPrevent migrated users from editing JodGig-owned login fields in JodApp; match the correct identity; then copy and maintain the JodGig password representation safely.Phase 1Phase 2Phase 3
Correct companiesCalculate the complete membership set from current JodGig company relationships, then create, update or revoke those JodGig-provisioned memberships.Phase 2Phase 3
Correct outlet accessEstablish the outlet_manager role and membership-based authorization; calculate the required outlet assignments; then make JodApp agree with that calculation.Phase 1Phase 2Phase 3
Choosing a company to work inLet the employer select one active membership; calculate a valid initial default; then save that default without changing which companies they may access.Phase 1Phase 2Phase 3
Removed access stops workingRequire an active membership on every employer request; detect company access that disappeared from JodGig; then revoke the corresponding JodGig-provisioned membership.Phase 1Phase 2Phase 3
Native JodApp access survivesRecord whether JodGig or JodApp created each membership; include native memberships when deciding what to preserve; then update or revoke only JodGig-provisioned memberships.Phase 1Phase 2Phase 3
People can verify correctnessProduce the intended result without writing, review named examples and counts, prove that a second identical run makes no changes, and expose failures after launch.Phase 2Phase 4

The records and what they mean

TL;DR

A person is an identity. Employer access is a membership. Outlet limits belong to that membership.

JodApp recordQuestion it answersExample
Identities::UserWho is logging in?“This is Aisyah.”
Org::MembershipWhich company may this person represent, and in what role?“Aisyah is an HQ manager at Company A.”
Org::OutletAssignmentWhich outlets may this membership use?“At Company A, Aisyah manages Orchard and Bugis.”

One identity may have several memberships. This is required for external super-HQ users and for enterprise customers whose divisions are represented as separate JodApp companies.

There is no Identities::Employer. Employer is how a person acts for a company, so it is represented by Org::Membership.

Source ownership while both systems are live

TL;DR

JodGig owns migrated employer identity and legacy access. JodApp owns its native memberships and new product data.

InformationOwnerReconciliation rule
Migrated email, name, mobile, address and passwordJodGigCopy later JodGig changes to JodApp. Do not offer JodApp edit paths for migrated users.
Whether a legacy employer is activeJodGigRevoke their JodGig-provisioned memberships when the source becomes inactive.
Legacy company and outlet accessJodGigMake JodGig-provisioned access agree with the current source relationships.
Notification choices on a JodGig-provisioned membershipJodGigCopy all three channels one way on every reconciliation.
Membership created directly in JodAppJodAppNever update or revoke it because of missing JodGig access.
Careers jobs and other new-product dataJodAppNever send it back to JodGig through this reconciliation.

Memberships therefore need explicit provenance, for example provisioning_source = jodgig | jodapp. The legacy person ID remains on Identities::User; it should not be copied to every membership.

Password compatibility exception

Source passwordJodApp passwordAction
BCryptAny valueCopy the source BCrypt hash, adjusting its prefix for Ruby if needed.
Legacy formatSame legacy valueKeep it until the person logs in.
Unchanged legacy formatJodApp upgraded it to BCrypt after a successful loginPreserve the JodApp BCrypt value so the daily run does not undo the upgrade.
Source later changes to BCryptLocally upgraded or old valueCopy the new source hash.

This is still one-way ownership. The exception prevents an unchanged old source representation from reversing a safe local format upgrade.

Who receives access

TL;DR

Ordinary employers use their live JodGig company. External super-HQ users use valid jodgig.user_company grants. Internal super-HQ is staff impersonation and is excluded.

JodGig typeJodApp roleCompany sourceOutlet scope
HQhq_managerjodgig.users.company_idWhole company
AREAarea_managerjodgig.users.company_idEvery live outlet assigned to that area user
LOCATIONoutlet_managerjodgig.users.company_idThe matching live outlet
SUPER_HQ_EXTERNALhq_managerValid live jodgig.user_company rowsWhole company for each membership
SUPER_HQ_INTERNALNoneExcludedJOD staff impersonation is a separate future feature

An ordinary employer is eligible when the user is enabled, not deleted, and their company is live, non-obsolete, and present in JodApp.

An external super-HQ identity is eligible when it is enabled, not deleted, and has at least one distinct valid company grant. The grant must not be deleted, its company must be live and non-obsolete, and that company must exist in JodApp.

For external super-HQ:

  • jodgig.user_company determines access;
  • jodgig.users.company_id may choose the default membership only when it is one of those valid grants;
  • otherwise the valid jodgig.user_company row with the highest pivot id becomes the default;
  • changing the default never changes the set of allowed companies; and
  • every migrated membership has is_owner = false.

The five decisions produced by the planner

TL;DR

Every source person receives one explainable decision before any data is written.

DecisionMeaning
migrateEligible source person has no linked JodApp identity yet.
updateEligible source person already exists and their JodGig-owned data or access may need to change.
revokePreviously migrated access is no longer present or eligible in JodGig. Keep historical identity data but stop legacy access.
skipIneligible source person has never received JodApp access. Do not create them.
needs_reviewThe program cannot safely choose, such as an email belonging to a different linked identity. Write nothing for that person.

The daily job reads ineligible employer-type users too. Otherwise a person disabled after migration would disappear from the input while their old JodApp membership remained active.

Delivery phases

TL;DR

Make JodApp enforce the result first, calculate the complete result second, write it third, and schedule it only after verification.

  1. Application readiness — support many memberships, choosing which company the employer is currently working in, active-membership authorization, provenance, role naming, and source-owned edit restrictions.
  2. Desired-state planner — calculate the intended identity, memberships, outlet assignments, defaults and decisions without writing.
  3. Reconciliation writer — apply that plan safely and make repeated runs converge to the same result.
  4. Verification and rollout — test complete journeys, review a production dry run, run twice in QA, then schedule daily.

The reasons for replacing the existing implementation are documented separately in What went wrong previously.

Deliberately outside the launch plan

Keep outReason
Employer migration during loginImmediate provisioning is not required, and login cannot repair a missing company or outlet.
Hourly incremental checkpointsChanges to JodGig companies, company grants and locations do not reliably update jodgig.users.updated_at.
Owner selectionNo current feature reads it, and legacy access is not evidence of company ownership.
Old issue 5.1: create JodGig::Users::UpsertIdentitiesUserService, then change the scheduled talent sync, talent migration during login, and employer sync to use itThis is not required for employer migration. Keep the working talent writers unchanged while we build and verify the replacement employer reconciliation.
Mobile collection promptUseful later, but not required to establish correct employer access.
Login-page redesignProduct presentation should not block identity and access correctness.

Definition of done

TL;DR

A repeat run changes nothing, and every employer request is authorized through one active selected membership.

The migration is ready for daily use only when:

  • every eligible source person maps to exactly one identity;
  • no new disabled, deleted, orphaned, or closed-company person is created;
  • every active JodGig-provisioned membership corresponds to a current valid source grant;
  • every removed source grant is revoked on the next run;
  • native JodApp memberships are unchanged;
  • every area or outlet manager has exactly the intended outlet assignments;
  • every employer endpoint rejects revoked or suspended memberships;
  • a multi-company user can choose any of their active memberships as the company they are currently working in;
  • email, password and notification updates move only from JodGig to JodApp;
  • all ambiguous identity matches are reported without partial writes; and
  • running the same source snapshot twice produces no material changes on the second run.

Decision log

DateDecision
2026-07-23Ownership: every migrated membership has is_owner = false. The business team collects owner information from clients and updates the column manually. The old HQ-first owner-selection rule is retired.
2026-07-23Default membership for external super-HQ: the valid grant with the highest jodgig.user_company.id. Low risk — the frontend will provide a company picker.
2026-07-23Role name is outlet_manager, consistent with Org::Outlet. The existing location_manager enum value and rows are renamed in Phase 1.
2026-07-23Mobile collection stays out of the migration. Later, at first login, employers will be asked to add and OTP-verify their real mobile so they can receive notifications — optional, with per-channel notification choices.