# Payroll finalization scope fix ## Confirmed behavior The finalization endpoint protects payroll integrity by allowing one brand scope per request and requiring every active employee in that scope. A mixed-brand request is rejected before any payroll data is written. ## Defects - The page derives `brand_id` from the first displayed row, even when selected rows belong to more than one brand. - The API wrapper discards the server response fields such as `code`, expected employee count, and missing employee IDs. The finalization dialog cannot explain a 422 correctly. - The payroll filter has no brand control, so selecting a full brand scope is impractical across pages. - Legacy cycle records can reuse a `cycle_id` across different date ranges. Ordering only by that non-unique value can select an unrelated open cycle. - A legacy `payroll_finalize_batches.batch_id` primary key can lack `AUTO_INCREMENT`. Finalization then assigns `0` repeatedly and fails after the first batch. ## Change design 1. Keep backend all-employee and single-brand enforcement unchanged. 2. Preserve the full structured error response in the client API wrapper. 3. Derive the finalization brand from selected active rows. Block the confirmation when selection spans brands. 4. Add a brand filter and use it in the payroll list request. Users select all active employees for one brand, then finalize that brand's cutoff. 5. Prefer an exact matching open cycle before applying the existing cutoff-mismatch validation. 6. Accept an exact matching open cycle as the continuity source after a void-and-reopen workflow. Finalized-cutoff overlap remains blocked. 7. Log failed finalization requests on the server without returning internal database errors to the browser. 8. Repair `payroll_finalize_batches.batch_id` with `AUTO_INCREMENT` and fail early with a named schema error when another environment has the same drift. ## Acceptance criteria - A mixed-brand selection does not send a finalization request and clearly explains how to proceed. - A single-brand partial selection receives the server's detailed count and missing-employee response. - Selecting all active employees for one filtered brand sends that brand ID and can proceed to the existing server validations. - A reopened cutoff that exactly matches the current open cycle can be finalized when an earlier payroll range was intentionally skipped or voided. - No server-side finalization rule or payroll data is weakened or changed.