diff --git a/docs/reference/Translations/index.md b/docs/reference/Translations/index.md index 8f5bd8835..d82084735 100644 --- a/docs/reference/Translations/index.md +++ b/docs/reference/Translations/index.md @@ -5074,9 +5074,6 @@ Translation keys for the `Payroll.PayrollOverview` i18n namespace. | Property | Default value | | ------ | ------ | | `alerts` | | -| `alerts.checkPaymentWarning_one` | `"You noted {{count}} employee who should be paid by check."` | -| `alerts.checkPaymentWarning_other` | `"You noted {{count}} employees who should be paid by check."` | -| `alerts.checkPaymentWarningDescription` | `"Employees with this payment method will need their checks delivered to them."` | | `alerts.directDepositDeadline` | `"To pay your employees with direct deposit by {{payDate}}, you'll need to run payroll by {{time}} on {{date}}"` | | `alerts.payrollNotCalculated` | `"Payroll is not calculated"` | | `alerts.payrollProcessedMessage` | `"{{amount}} will be debited on {{date}}. Make sure you have these funds available."` | diff --git a/docs/reference/payroll/blocks.md b/docs/reference/payroll/blocks.md index 7198bae0d..0064b2d98 100644 --- a/docs/reference/payroll/blocks.md +++ b/docs/reference/payroll/blocks.md @@ -672,6 +672,13 @@ _Inherits `children`, `className`, `defaultValues`, `FallbackComponent`, `Loader | `runPayroll/receipt/get` | User requested the payroll receipt | `{ payrollId }` | | `runPayroll/pdfPaystub/viewed` | User opened an employee's paystub PDF | `{ employeeId }` | | `payroll/wire/form/done` | Wire-in details were confirmed via the embedded wire form | Submit wire-in response | +| `payroll/printChecks/start` | User opened the print-checks modal from the embedded print-checks banner | — | +| `payroll/printChecks/generate/start` | User submitted the print-checks form | — | +| `payroll/printChecks/generate/succeeded` | Printable checks finished generating | `{ documentUrl }` | +| `payroll/printChecks/generate/failed` | The print-checks request was rejected or generation failed | `{ errorMessage }` | +| `payroll/printChecks/retry` | User retried after a failed check generation | — | +| `payroll/printChecks/cancel` | User cancelled the print-checks form | — | +| `payroll/printChecks/close` | User closed the print-checks failure or summary screen | — |
diff --git a/src/components/Payroll/PayrollOverview/PayrollOverview.stories.tsx b/src/components/Payroll/PayrollOverview/PayrollOverview.stories.tsx index 20834d568..6391e6eea 100644 --- a/src/components/Payroll/PayrollOverview/PayrollOverview.stories.tsx +++ b/src/components/Payroll/PayrollOverview/PayrollOverview.stories.tsx @@ -1357,3 +1357,112 @@ export const WithSkippedEmployee = () => { /> ) } + +const checkPaymentPayrollData = { + payrollDeadline: new Date('2025-09-24T23:00:00.000Z'), + checkDate: '2025-09-26', + processedDate: null, + calculatedAt: new Date('2025-09-15T16:25:07.000Z'), + uuid: 'payroll-uuid', + payrollUuid: 'payroll-uuid', + companyUuid: 'company-uuid', + offCycle: false, + external: false, + payPeriod: { + startDate: '2025-09-12', + endDate: '2025-09-18', + payScheduleUuid: 'schedule-uuid', + }, + totals: { + companyDebit: '5000.00', + netPayDebit: '4000.00', + taxDebit: '1000.00', + reimbursementDebit: '0.00', + childSupportDebit: '0.00', + reimbursements: '0.00', + netPay: '4000.00', + grossPay: '5000.00', + employeeBonuses: '0.00', + employeeCommissions: '0.00', + employeeCashTips: '0.00', + employeePaycheckTips: '0.00', + additionalEarnings: '0.00', + ownersDraw: '0.00', + checkAmount: '1000.00', + employerTaxes: '500.00', + employeeTaxes: '500.00', + benefits: '0.00', + employeeBenefitsDeductions: '0.00', + imputedPay: '0.00', + deferredPayrollTaxes: '0.00', + otherDeductions: '0.00', + }, + companyTaxes: [], + createdAt: new Date('2025-09-15T16:19:04.000Z'), + submissionBlockers: [], + partnerOwnedDisbursement: false, + employeeCompensations: [ + { + employeeUuid: 'emp-check-1', + firstName: 'Isaiah', + lastName: 'Berlin', + excluded: false, + version: 'v1', + grossPay: '4000', + netPay: '3200', + checkAmount: '3200', + paymentMethod: 'Check' as const, + memo: null, + fixedCompensations: [], + hourlyCompensations: [ + { + name: 'Regular Hours', + hours: '40.000', + amount: '4000.0', + jobUuid: 'job-1', + compensationMultiplier: 1, + flsaStatus: 'Nonexempt', + }, + ], + paidTimeOff: [], + taxes: [ + { name: 'Federal Income Tax', employer: false, amount: '80' }, + { name: 'Federal Income Tax', employer: true, amount: '160' }, + ], + benefits: [], + deductions: [], + }, + ], +} + +export const WithCheckPaymentEmployeeUnprocessed = () => { + return ( + + ) +} + +export const WithCheckPaymentEmployeeProcessed = () => { + return ( + + ) +} diff --git a/src/components/Payroll/PayrollOverview/PayrollOverview.test.tsx b/src/components/Payroll/PayrollOverview/PayrollOverview.test.tsx index 361fd9c7a..872b9a52a 100644 --- a/src/components/Payroll/PayrollOverview/PayrollOverview.test.tsx +++ b/src/components/Payroll/PayrollOverview/PayrollOverview.test.tsx @@ -311,3 +311,61 @@ describe('PayrollOverview calculatedAt guard', () => { expect(screen.queryByText(/Review payroll/i)).toBeNull() }) }) + +describe('PayrollOverview print checks modal', () => { + beforeEach(() => { + vi.clearAllMocks() + mockPayrollData = { + ...basePayrollData, + employeeCompensations: [ + { + employeeUuid: 'emp-check-1', + firstName: 'Isaiah', + lastName: 'Berlin', + excluded: false, + version: 'v1', + grossPay: '4000', + netPay: '3200', + checkAmount: '3200', + paymentMethod: 'Check', + memo: null, + fixedCompensations: [], + hourlyCompensations: [], + paidTimeOff: [], + taxes: [], + benefits: [], + deductions: [], + }, + ], + } + mockIsFetching = false + }) + + it('hides the View and print checks button until the payroll is processed', async () => { + mockPayrollData = { ...mockPayrollData, processed: false } + + renderWithProviders( + , + ) + + expect(await screen.findByText(/noted 1 employee/i)).toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'View and print checks' })).toBeNull() + }) + + it('opens the print checks modal from the alert once the payroll is processed', async () => { + const user = userEvent.setup() + mockPayrollData = { + ...mockPayrollData, + processed: true, + processingRequest: { status: 'submit_success', errors: [] }, + } + + renderWithProviders( + , + ) + + await user.click(await screen.findByRole('button', { name: 'View and print checks' })) + + expect(await screen.findByText('Choose check stock')).toBeInTheDocument() + }) +}) diff --git a/src/components/Payroll/PayrollOverview/PayrollOverview.tsx b/src/components/Payroll/PayrollOverview/PayrollOverview.tsx index bf468c23f..46a5c4bd4 100644 --- a/src/components/Payroll/PayrollOverview/PayrollOverview.tsx +++ b/src/components/Payroll/PayrollOverview/PayrollOverview.tsx @@ -20,6 +20,7 @@ import { type ConfirmWireDetailsComponentType, } from '../ConfirmWireDetails/ConfirmWireDetails' import { canCancelPayroll } from '../helpers' +import { PrintChecks } from '../PrintChecks/PrintChecks' import { PayrollOverviewPresentation } from './PayrollOverviewPresentation' import { PayrollOverviewStatus } from './PayrollOverviewTypes' import { useCompanyPaymentSpeed } from '@/hooks/useCompanyPaymentSpeed' @@ -109,6 +110,13 @@ const findWireInRequestUuid = ( * | `runPayroll/receipt/get` | User requested the payroll receipt | `{ payrollId }` | * | `runPayroll/pdfPaystub/viewed` | User opened an employee's paystub PDF | `{ employeeId }` | * | `payroll/wire/form/done` | Wire-in details were confirmed via the embedded wire form | Submit wire-in response | + * | `payroll/printChecks/start` | User opened the print-checks modal from the embedded print-checks banner | — | + * | `payroll/printChecks/generate/start` | User submitted the print-checks form | — | + * | `payroll/printChecks/generate/succeeded` | Printable checks finished generating | `{ documentUrl }` | + * | `payroll/printChecks/generate/failed` | The print-checks request was rejected or generation failed | `{ errorMessage }` | + * | `payroll/printChecks/retry` | User retried after a failed check generation | — | + * | `payroll/printChecks/cancel` | User cancelled the print-checks form | — | + * | `payroll/printChecks/close` | User closed the print-checks failure or summary screen | — | * * @param props - See {@link PayrollOverviewProps}. * @returns The payroll overview surface. @@ -196,6 +204,10 @@ const Root = ({ /> ) + const printChecksBanner = ( + + ) + useEffect(() => { if (wireInRequest?.status === 'pending_review' && !showWireDetailsConfirmation) { setShowWireDetailsConfirmation(true) @@ -436,6 +448,7 @@ const Root = ({ setSelectedUnblockOptions(prev => ({ ...prev, [blockerType]: value })) }} wireInConfirmationRequest={wireInConfirmationRequest} + printChecksBanner={printChecksBanner} withReimbursements={withReimbursements} paymentSpeed={paymentSpeed} pagination={pagination} diff --git a/src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx b/src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx index 2420743bf..29dfa7c42 100644 --- a/src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx +++ b/src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx @@ -32,7 +32,6 @@ import { compensationTypeLabels, FlsaStatus, PAYROLL_RESOLVABLE_SUBMISSION_BLOCKER_TYPES, - PAYMENT_METHODS, } from '@/shared/constants' import type { PaginationControlProps } from '@/components/Common/PaginationControl/PaginationControlTypes' import DownloadIcon from '@/assets/icons/download-cloud.svg?react' @@ -48,6 +47,7 @@ interface PayrollOverviewProps { submissionBlockers?: PayrollSubmissionBlockerType[] selectedUnblockOptions?: Record wireInConfirmationRequest?: React.ReactNode + printChecksBanner?: React.ReactNode pagination?: PaginationControlProps onEdit: () => void onSubmit: () => void @@ -87,6 +87,7 @@ export const PayrollOverviewPresentation = ({ selectedUnblockOptions = {}, onUnblockOptionChange, wireInConfirmationRequest, + printChecksBanner, withReimbursements = true, paymentSpeed, pagination, @@ -212,12 +213,6 @@ export const PayrollOverviewPresentation = ({ ) } - const checkPaymentsCount = - payrollData.employeeCompensations?.reduce( - (acc, comp) => - !comp.excluded && comp.paymentMethod === PAYMENT_METHODS.check ? acc + 1 : acc, - 0, - ) ?? 0 const companyPaysColumns: Array<{ key: string title: string @@ -780,14 +775,7 @@ export const PayrollOverviewPresentation = ({ data={[{}]} /> )} - {checkPaymentsCount > 0 && ( - - {t('alerts.checkPaymentWarningDescription')} - - )} + {printChecksBanner}