Skip to main content
Technical ManualLooking for step-by-step instructions? In the User Manual: Employees and Pay RunsPayroll Filings and Tax Payments

Payroll

The Payroll module pays W-2 employees: gross to net with the federal taxes, the income tax and contributions of the state the employee works in (Michigan, Massachusetts with paid family and medical leave, and Virginia come loaded) and city taxes, pay stubs with the year to date, the posting, the payroll tax payments, and the figures of Forms 941, 940, W-2 and W-3 (PAY-01 to PAY-16). The e-file of the returns (MeF, EFW2) and EFTPS payments come next in Phase 6.

ProjectsCrestApps.Almohaseb.Payroll (module), CrestApps.Almohaseb.Payroll.Core (models, tax engines, service, posting)
FeatureCrestApps.Almohaseb.Payroll (Payroll); depends on Accounting and Customers & vendors (the address field and the tax ID rules); enabled by both business recipes
CollectionPayroll

Content types​

TypePartHolds
AlmohasebEmployeeAlmohasebEmployeePart, plus the type's own part with the Address CrestApps AddressFieldFirstName, LastName, ProtectedSsn (ISensitiveDataProtector, purpose CrestApps.Almohaseb.Payroll.EmployeeSsn) and SsnLastFour, HireDate, IsActive, PayType (Hourly, Salary), PayRate (hourly or yearly), PayFrequency (52/26/24/12/4/1 periods), OvertimeMultiplier, WorkState, WorkCity; W-4 FilingStatus, MultipleJobs, DependentsAmount, OtherIncome, Deductions, ExtraWithholding, FederalExempt, NonresidentAlien; the state certificate (MI-W4, M-4, VA-4) StateExemptions, StateSpecialExemptions (age or blindness), StateHeadOfHousehold, StateExtraWithholding, StateExempt
AlmohasebCityIncomeTaxAlmohasebCityIncomeTaxPartCity, ResidentRate, NonresidentRate, Exemption (yearly); the recipes seed Detroit (2.4 / 1.2 / 600)

The editor is four cards: Employee (name, SSN and the home address), Pay, Federal Form W-4 and State withholding certificate. EmployeePartDisplayDriver.Edit returns four shapes built from one EmployeePartViewModel (AlmohasebEmployeePart_Edit, _Pay_Edit, _FederalW4_Edit, _StateW4_Edit), placed Parts:0%Employee;0 to Parts:3%State withholding certificate;3; they share the part's prefix, so one UpdateAsync binds them all. The module's placement.json puts the type's own part and its Address field (ContentPart_Edit differentiators AlmohasebEmployee-AlmohasebEmployeePart and AlmohasebEmployee-AlmohasebEmployee, AddressField_Edit AlmohasebEmployee-Address) in the Employee card; without them Orchard Core adds an empty card named Content.

The legacy stored the SSN in clear (PAY-02), ignored the pay-rate option and W-4 Step 4c, and had a "Miscellaneous" cycle with no periods.

Tax content​

Each year's figures are documents, not code: FederalTaxContent (tax-us-{year}: Social Security rate and wage base, Medicare and Additional Medicare rate and threshold, FUTA rate and base, the nonresident alien addition, and per FederalFilingStatus the standard deduction, the W-4 line 1g adjustment and the brackets) and StateTaxContent (tax-us-{state}-{year}, jurisdiction US-{state}: State, Name, MinimumWages (the no-tax status), StandardDeduction, HeadOfHouseholdDeduction, FirstExemption, AdditionalExemption, SpecialExemption (age or blindness), FicaDeductionLimit, Brackets (none for a state without income tax), and Contributions: StateContribution Code, Name, EmployeeRate, EmployerRate, SmallEmployerRate, SmallEmployerSize, WageBase), each with Verified, VerifiedBy and Source. ITaxContentStore / TaxContentStore (GetFederalAsync, GetStateAsync(state, year), ListFederalAsync, ListStatesAsync(year?), SaveFederalAsync, SaveStateAsync) saves a year (replacing it, unverified) and VerifyAsync(jurisdiction, year, user) marks it verified; TaxContentIndex (Jurisdiction, Year).

The AlmohasebPayrollTaxContent recipe step loads them: { "name": "AlmohasebPayrollTaxContent", "Federal": [ … ], "States": [ … ] }. Both business recipes load 2025 and 2026 federal, and 2025 and 2026 Michigan (4.25%, 5,800 / 5,900), Massachusetts (5%, 9% above the surtax threshold, no-tax status 8,000, exemptions 4,400 then 1,000, head of household 2,400, blindness 2,200, FICA deduction up to 2,000; PFML 0.46% employee and 0.42% employer, 0 under 25 employees, up to the Social Security base) and Virginia (standard deduction 8,750, exemptions 930, age or blindness 800, 2% / 3% / 5% / 5.75% from 0 / 3,000 / 5,000 / 17,000). The 2026 W-4 adjustments and nonresident amount and the 2026 Massachusetts surtax threshold are assumptions until checked. Nothing is loaded verified. Content saved before 1.0 as MichiganTaxContent is no longer read; load the year again.

Engines​

All pure, under CrestApps.Almohaseb.Payroll.Services:

  • FederalPayrollTax.BuildTable(status, multipleJobs) builds the Pub 15-T annual percentage method table: the standard one starts at standard deduction − line 1g, the Step 2 one halves the deduction and the brackets, rounded to whole dollars; bases accumulate from the rounded bounds.
  • FederalPayrollTax.IncomeTax(wages, periods, w4, content): Worksheet 1A — annual wages + other income (+ the nonresident addition) − deductions − line 1g (unless Step 2), the row whose lower bound is at or below the wage (the legacy's strict bounds gave 0 on a boundary), ÷ periods, less the Step 3 credits ÷ periods (not below 0), plus Step 4c; 0 when exempt. The legacy had other income and deductions reversed by an operator precedence bug and ignored Step 4c and the exempt flag (PAY-07).
  • FederalPayrollTax.Fica(wages, before, content): Social Security up to the wage base, Medicare, Additional Medicare on the year's wages above the threshold (the legacy had no base and no Additional Medicare, PAY-08); Futa, Unemployment up to their bases.
  • StatePayrollTax.IncomeTax(wages, periods, w4, content, employeeFica): the annualized method — wages × periods; nothing at or under MinimumWages, when exempt or without brackets; less the standard deduction, the head of household deduction, the exemptions (the first, then each further one), the age or blindness exemptions and the employee's Social Security and Medicare × periods up to FicaDeductionLimit; taxed by the brackets, ÷ periods, plus the extra.
  • StatePayrollTax.Contribution(wages, before, contribution, employees): the wages up to the base, at the employee's rate, and the employer's (the small employer rate under SmallEmployerSize employees).
  • StatePayrollTax.FindCity(residence, work, cities): the resident rate where the employee lives, else the nonresident rate where the employee works (the legacy only looked at where the employee lived, PAY-12); City takes the MI-W4 exemptions.
  • PaycheckCalculator.Calculate(employee, regular, overtime, bonus, yearToDate, federal, michigan, cities, employer): the gross (salary ÷ periods, or hours × rate and overtime × rate × multiplier, plus the bonus), each PaycheckTax (code, name, wages, amount, employee or employer) with the employer's matching Social Security and Medicare, FUTA and SUTA, and the net. The state is the employee's WorkState: its income tax (SIT-{state}) and each contribution ({code}-{state}, both sides); the city tax applies in any state. Without the content of the employee's state the paycheck is refused, StateContentMissing (the legacy dropped every tax of an unsupported state, PAY-03, PAY-13); a state without income tax is content without brackets. EmployerPayrollSettings.Employees is the active employees, for the small employer rates. FederalContentMissing, StateContentMissing, BadAmount.

Pay runs​

PayRun (document): PeriodStart, PeriodEnd, PayDate (sets the tax year), Status (Draft, Posted), Lines (PayRunLine: employee, rate, hours, overtime, bonus, regular and overtime pay, gross, taxes, net, notes), PaymentAccountId, Memo, Revision; PayRunIndex (Status, PayDate).

IPayrollService / PayrollService: CreateAsync(start, end, payDate, frequency) (a paycheck for each active employee, of the frequency when given), SaveAsync(id, payDate, entries, memo), DeleteAsync (drafts), PostAsync(id, accountId) (worked out again first, then posted; an asset account), UnpostAsync, GetYearToDateAsync(year, through, except) (gross, net and each tax from the posted runs). The wage bases count the posted runs of the year on or before the pay date. An employee works in the business profile's city unless the record says otherwise. IPayrollLookup (ContentPayrollLookup) reads the published employees (the residence city from the address), the city taxes and PayrollSettings (Settings → Payroll, group almohaseb-payroll: UnemploymentRate, UnemploymentWageBase 9,500, LiabilityAccountId).

PayRunPostingRule (Payroll.PayRun): Dr Gross Wages (gross), Dr Payroll Taxes (employer taxes), Cr the liability (Payroll Liabilities unless set; every tax withheld and owed), Cr the payment account (net). Unposting builds no entry. The legacy booked only the employee's Social Security and Medicare as an expense and no liability (PAY-15).

Tax payments and filings​

PayrollTaxPayment (document): Date, Kind (PayrollTaxKind: Federal941, Futa, StateUnemployment, City, StateIncomeTax, StateContribution), PeriodKey (2026-Q3 or 2026; always a quarter for Federal941), Amount, PaymentAccountId, Confirmation (the EFTPS acknowledgement), Revision; PayrollTaxPaymentIndex (PeriodYear, Date, Kind), added by migration 2.

IPayrollFilingService / PayrollFilingService: Get941Async(year, quarter) and Get940Async(year) (null without the year's federal content), GetW2sAsync(year), ListPostedRunsAsync(year), ListPaymentsAsync(year) (dated in the year or paying one of its periods), GetPaymentAsync, SavePaymentAsync(id, input) (new when id is empty; BadDate, BadPeriod, BadAmount, PaymentAccountRequired (an asset), PostingFailed), DeletePaymentAsync (unposted first). PayrollTaxPaymentPostingRule (Payroll.TaxPayment): Dr the payroll liability (as the pay runs), Cr the account.

PayrollFilings (pure):

  • Build941(year, quarter, runs, payments, federal): the posted runs paid in the quarter. Line 1 counts the employees with pay in runs whose period covers the 12th of the quarter's last month; line 2 the gross; line 3 FIT; 5a and 5c the employee side's Social Security and Medicare wages × 2 × the rate, 5d the Additional Medicare wages (each paycheck's amount ÷ the rate) × the rate; line 7 what the paychecks withheld and owed less 5e; line 13 the Federal941 payments of {year}-Q{quarter}; line 16 each month's withheld and owed by pay date.
  • Build940(year, runs, payments, federal): line 3 the gross; line 5 each employee's wages above the FUTA base in pay date order; line 8 the taxable wages × the rate; line 13 the year's Futa payments; line 16 each quarter's liability. FutaDeposits(form): a quarter's liability with what was carried, once above $500, is due at the end of the next month.
  • BuildW2s(year, runs): boxes 1–6 (6 with Additional Medicare), 14 (Other: the contributions withheld, such as PFML-MA), 15–17 (States: each SIT-{state}) and 18–20 (each CITY-{city}); the employer's taxes are left out.
  • StateWithholding(state, from, to, runs) and States(runs): each state's wages, income tax withheld and employees, for its withholding returns (Michigan's lines of Forms 5080 and 5081).
  • DepositDue(payDate, schedule) (DepositSchedule: Monthly the 15th of the next month, off a weekend; Semiweekly Wednesday to Friday → the next Wednesday, Saturday to Tuesday → the next Friday), Deposits941(year, quarter, runs, schedule) (grouped by due date), ReturnDue(year, quarter) (the last day of the month after the quarter, off a weekend; the fourth quarter's is also the 940 and W-2 date). Federal holidays and the $100,000 next-day rule are not applied.

The schedule is PayrollSettings.DepositSchedule (and PayrollSettingsInfo.DepositSchedule).

Admin​

Payroll menu (almohasebPayroll, icon fa-money-check-dollar): Pay runs (Almohaseb/Payroll, year; /Create; /{id} GET and POST entries[i].EmployeeId, .RegularHours, .OvertimeHours, .Bonus, payDate, memo; /{id}/Post accountId; /{id}/Unpost; /{id}/Delete; /{id}/Stub/{employeeId}, a page without the admin layout), Employees, City income taxes, Tax payments (PayrollFilingsController.Payments, Almohaseb/Payroll/TaxPayments, year, id to change one; POST id, date, kind, periodKey, amount, accountId, confirmation; /Delete id), Payroll filings (Almohaseb/Payroll/Filings, year, quarter), Tax content (Almohaseb/Payroll/TaxContent, /Verify jurisdiction, year); and Settings > Payroll. Pay run ids are constrained to 26 characters so TaxContent is not taken for one.

PermissionKeyImplied by
Run and post payrollManageAlmohasebPayroll
View pay runs and pay stubsViewAlmohasebPayrollManageAlmohasebPayroll
Verify payroll tax contentVerifyAlmohasebTaxContent

See also​

  • Sales Tax: line 8 of the return is the Michigan withholding.
  • Contacts for contractors, paid as vendors.