Skip to content

Add employee update command for work-state changes - #152

Open
tmfahey wants to merge 5 commits into
mainfrom
fahey/aint-771-employee-work-state
Open

Add employee update command for work-state changes#152
tmfahey wants to merge 5 commits into
mainfrom
fahey/aint-771-employee-work-state

Conversation

@tmfahey

@tmfahey tmfahey commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the employee work-state write path to the CLI:

  • gusto employee update <employee_uuid> --work-state <ST> resolves <ST> to an existing company location in that state, then PUTs the employee's active work address to point at it (PUT /v1/work_addresses/{uuid}). Create the location first with gusto company locations if none exists yet.
  • On a real state change, the response includes a compliance block naming the new state's outstanding (unanswered) tax requirements from GET /v1/companies/{uuid}/tax_requirements/{state}, and whether any of them block payroll - so a silent state move doesn't turn into months of missed filings.
  • Already working from --work-state is a no-op (changed: false): no location lookup, no write, no nudge fetch.

Follows existing conventions: agent-mode writes are gated behind --confirm, --dry-run previews the resolved request without sending it, --example prints a canned payload.

Why a location lookup instead of a bare state string

The work-address PUT only accepts a location_uuid, not a raw state - Gusto ties work-state to a company location record. Rather than silently creating one, employee update fails with a clear no_company_location_for_state error pointing at gusto company locations when there's no active location in the target state yet. Creating locations felt like a separate concern to bolt on here.

Scope change

Updating a work address needs the employees:manage OAuth scope, and the nudge GET needs company_tax_requirements:read. employees:manage was previously dropped from the partner app grant as unused - this PR moves it back into REQUIRED_SCOPES (with an audit-guard test) and adds company_tax_requirements:read. The partner OAuth app registration itself will need updating to match; flagging that here so it doesn't get missed.

Implementation

  • findLocationForState (src/lib/locations.ts) and buildComplianceNudge (src/commands/employee.ts) are small pure functions, unit-tested independently of the network layer.
  • isValidStateCode (src/lib/parse.ts) is a format-only check (two letters) - like the other parse.ts validators, it doesn't hardcode an enumerable state list; the API is the source of truth for whether it's real.
  • The handler combines two reads (active work address, matching location) with the write and a best-effort third read (the nudge) via withCompanyContext, since the single-request write helpers (writeResource/putCompanyResource) don't fit a multi-call flow. The confirmation gate runs up front against the write's general shape, before any network call - the specific work-address UUID isn't known yet at that point.
  • A failed nudge fetch doesn't undo or fail the already-successful write; compliance carries { fetched: false, error } instead, plus a top-level warning string with a copy-pasteable gusto api request retry command. The warning exists so a caller that only checks the top-level ok doesn't mistake "the nudge never loaded" for "no compliance concerns" - the entire point of this feature is catching that exact silent-move failure mode.
  • The employee UUID is percent-encoded into the work-addresses lookup path, matching the existing UUID-encoding convention on other employee-scoped writes.

Testing

  • Unit tests for isValidStateCode, findLocationForState, and buildComplianceNudge.
  • Network tests for the handler: --example, missing/invalid --work-state, invalid --effective-date, the agent-mode confirm gate, no-active-work-address, a malformed (non-array) work-addresses body, already-in-target-state as a no-op (both exact-case and a lowercase --work-state matching the stored uppercase state, with and without --dry-run), a lowercase --work-state resolving to the right location on a real change, no-matching-location, picking the right location out of several, a location whose own stored state is lowercase, --dry-run, --confirm success with the compliance nudge, a rejected PUT (stale version) failing the command rather than reporting a phantom success, a failed nudge fetch surfacing the warning, and UUID encoding.
  • required-scopes.test.ts audit-guard tests for both new scopes.
  • Full local gate green: typecheck, lint, format:check, unit tests, DCO.

Signed-off-by: tmfahey <taylor.fahey@gusto.com>
@tmfahey
tmfahey requested review from a team and ashieh as code owners July 27, 2026 20:40
tmfahey added 4 commits July 28, 2026 10:33
Signed-off-by: tmfahey <taylor.fahey@gusto.com>
Signed-off-by: tmfahey <taylor.fahey@gusto.com>
Signed-off-by: tmfahey <taylor.fahey@gusto.com>
Signed-off-by: tmfahey <taylor.fahey@gusto.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant