Repository navigation
feat(SDK-1313): add support for assignment by compensation type - #2881
Conversation
Widens the assignment type selector to Single | HourlySalaried and plumbs hourly/salaried schedule uuids through the flow context and the schedule-selected event. The hourly/salaried step variant and submit come next; selecting the new option currently lands on the single-schedule step. Switching type clears every schedule already picked under the old type so a Single -> HourlySalaried -> Back -> Single round trip can't submit a stale uuid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…loyees Adds the compensation-type variant of the assignment step: one pay schedule dropdown per compensation type, both required. The review step now builds the request body per assignment type and collapses hourly == salaried down to a single-schedule assignment before previewing, so the changes shown are exactly the ones submitted. The submitted body doubles as the `assigned` event payload, which removes the pre-existing bug where submit unconditionally asserted defaultPayScheduleUuid and would have thrown on a true hourly != salaried assignment. The public `@events` entry widens to a two-arm union accordingly. Drops the schedule-selected context reset added in the previous commit — the per-type body builder makes it unreachable, and the type-selected reset already covers the one path a user can observe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e overview PayScheduleOverview now reads the company's current assignment and, when it's hourly_salaried, renders a table with a row per compensation type instead of the single-schedule description list. Each row carries its own Edit schedule and AutoPilot actions, so AutoPilot is managed per schedule rather than per company. Both row actions are always offered — whether AutoPilot can actually be enabled is a per-schedule runtime fact the dialog already surfaces from autoPayrollEnablementBlockers, not something to predict from compensation type. A row whose assigned schedule isn't in the company's schedule list is dropped rather than rendered without a name or an AutoPilot target. Every other assignment type (single, by_employee, by_department, null) keeps the existing single-schedule overview. Adds the missing MSW handler for GET /pay_schedules/assignments. Its path is also matched by the looser /pay_schedules/:pay_schedule_id handler, so the management tests order the two explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Picks up the new i18n keys, the GET /pay_schedules/assignments endpoint now called by PayScheduleOverview, and the resulting APIModels re-export changes. PayScheduleAssignmentType is added to APIModels. Departments, PayScheduleAssignmentBody, and PayScheduleAssignmentBodyEmployees drop out: the assignment flow now builds its request body from its own two-arm AssignmentSelection union instead of annotating it as PayScheduleAssignmentBody, so the SDK's public surface no longer references those models. The request body is still type-checked against the API's own request type at both mutation call sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three presentation fixes from reviewing the rendered table against the design
prototype:
- AutoPilot status is a Badge (success/info) rather than plain text, matching
the prototype's AutoPilotBadge.
- Compensation type and schedule name collapse into one Schedule column, with
the name as a supporting subline. Two columns for one logical value left the
rows sparse and gave the header an awkward "Compensation type" label.
- The Box body is flush (withPadding={false}), so the table meets the box
edges instead of sitting inside 20px of padding — the same pairing with
isWithinBox that PaymentMethodCard uses for its table.
Row accessible names now include the schedule name, since react-aria labels a
row by its first cell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssignment The flow seeded an empty context, so every step defaulted to the first active schedule rather than what the company already had configured. For a company already split across hourly and salaried schedules, Manage opened with both dropdowns on the same schedule — and because the review step collapses hourly == salaried to a single assignment, submitting without touching anything silently flattened the split. Root now reads paySchedulesGetAssignments and seeds assignmentType together with the uuids. They have to travel together: without the type, the type step's first Continue registers as a type change and the reducer clears the uuids. The seed is frozen with useState rather than recomputed. A successful submit invalidates the whole SDK namespace, so this query refetches mid-submit, and a recomputed seed re-seats the machine back to the first step — mirroring the same guard PaySchedule.tsx documents for its own routing decision. A seeded uuid that no longer resolves in the company's schedule list falls back to the active schedule instead of leaving the dropdown blank. Unsupported assignment types (by_employee, by_department) seed nothing and start on the type step, as before. Also reorders the shared MSW handlers so getPayScheduleAssignments out-ranks getPaySchedule, whose `:pay_schedule_id` pattern matches /assignments and would otherwise answer with a payload that silently parses as an empty assignment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| name="hourlyPayScheduleUuid" | ||
| label={t('compensationStep.hourlyPayScheduleLabel')} | ||
| description={t('compensationStep.hourlyPayScheduleDescription')} | ||
| placeholder="" |
There was a problem hiding this comment.
Nit: do we always have to supply a placeholder? wondering if we can just update to not set this as required?
There was a problem hiding this comment.
oh shoot, I missed that I was passing the "" empty string intentionally.
There was a problem hiding this comment.
according to our own tsdoc:
/**
* Placeholder text displayed when no option is selected.
* Required so empty dropdowns always communicate the action — pass an empty string only when a default value is guaranteed.
*/in this case, the default options are guaranteed since we always pick a schedule -- you can't manage unless you have at least one valid schedule
| */ | ||
| const [seed] = useState(() => toAssignmentSeed(assignments.payScheduleAssignment)) | ||
|
|
||
| const machine = useMemo( |
There was a problem hiding this comment.
Nit: should we just update this machine to use state instead? Feels like that's tech debt we may want to ticket out since the memoization approach was causing issues. (outside of scope for here)
There was a problem hiding this comment.
possibly -- I'm a little bit annoyed at robot3 at the moment because there's not a good way to update the context without either re-creating the entire thing or emitting an event that shows up in onEvent for the consumer. ran into this with the change to let the employee list flow pass props to the onboarding flow -- if the props to give to the onboarding flow ever change, we can't really change that without destroying the state machine or doing a manual transition. which makes sense, I suppose, but is really annoying.
Summary
Extends
PayScheduleAssignmentto support assigning pay schedules by compensation type (hourly/salaried), and adds a compensation-table variant toPayScheduleOverview. Gated behind the existingmanagePaySchedulesalpha flag — no new flag, and no API version bump (hourly_salariedis already modeled in@gusto/embedded-api).Changes
Single | HourlySalaried. Flow context carrieshourlyPayScheduleUuid/salariedPayScheduleUuid, and changing type clears schedules picked under the old one.type: 'single'. This happens upstream of both the preview and the submit, so the review screen shows exactly what gets sent.DataView, one row per compensation type) when the company's assignment ishourly_salaried, with per-row Edit schedule / AutoPilot actions. Every other assignment type keeps the existing single-schedule overview.Two behavior decisions worth calling out:
Both row actions are always offered, with no per-compensation-type suppression. Don't know whether or not to prevent auto pilot until we fetch the pay schedule and see the blockers; already have support in the modal for any blockers.
No employee-count column, unlike the prototype. Didn't see anything in the API for the schedules to indicate how many employees are currently assigned to which schedule.
Related
PayScheduleAssignmentsingle-schedule), already onmainTesting
npm run test -- --run src/components/Company/PaySchedule/Full suite green (4297 passed). Every new branch is mutation-verified — the auto-collapse, both directions of the type-change reset, the row-drop filter, the assignment seeding, and the machine re-seat guard were each broken deliberately to confirm the matching test fails.
Storybook:
Domain/Company/PaySchedule/Management/PayScheduleCompensationOverviewcovers separate schedules, same-schedule-both-rows, AutoPilot off, and a dropped row.Manual, needs
managePaySchedulesenabled in the dev app's Settings panel (it resets on reload):CompanyManagement/PayScheduleon a company with anhourly_salariedassignment → compensation table, one row per type.CompanyManagement/PayScheduleAssignment→ the type step should preselect the company's current type, and the next step should preselect its current schedules.type: 'single'.🤖 Generated with Claude Code