PAYCHEK/i — Payroll & Human Resources

SteelFrame X application operation manual  ·  ← back to Operation Manuals  ·  Sign On

PAYCHEK/i is an employee payroll and HR application: an employee master with a graduated-tax withholding table, weekly time capture with an edit/validate pass, a full gross-to-net payroll run (regular + overtime earnings, pre-tax deductions, graduated tax, post-tax deductions), payslip and payroll-register printing, employer-contribution and leave accrual, a double-entry GL distribution into a SQL feed, and quarterly / year-end tax reporting with an accumulator reset. The business logic is coded in fixed- and free-form ILE RPG, with an ILE COBOL trial balance and an embedded-SQL GL feed; the periodic work runs as four CL job cycles. This manual is the reference for the operator who runs the cycles and the online screens, and for the developer maintaining the application. It is grounded entirely in the committed source (payroll-app/src/sources.mjs, src/seed.mjs, and the test/py_*.mjs battles). Everything runs in library PAYCHEK.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

PAYCHEK/i runs the full weekly-to-annual payroll cycle:

A.2 The gross-to-net model (every figure is hand-derivable)

The pay calculation is deliberately closed-form and derivable to the cent — the tests assert the exact arithmetic. For one employee for one period (from the source header of sources.mjs):

  regular pay = REGHRS * RATE
  overtime    = OTHRS  * RATE * OTFACT           OTFACT is 7P4, seeded 1.5000
  gross       = regular + overtime
  pre-tax     = gross * DEDRATE                  for every ACTIVE pre-tax ('P') deduction
  taxable     = gross - pre-tax
  tax         = graduated over PYTAXB: each band taxes the slice of taxable
                that falls inside (BLOW, BHIGH] at BRATE
  post-tax    = flat-amount deductions ('F')     DEDAMT
  net         = gross - pre-tax - tax - post-tax

Money multiplications are half-adjusted to the cent (RPG eval(h)): e.g. FICA 2750.00×0.0765 = 210.375 rounds to 210.38, where a truncating calc would give 210.37. Rates are packed 7P4 (four decimal places) because an hourly rate or a 5.25% election rarely lands on cents; money is packed 11P2 (or wider for YTD/GL totals).

A.3 Component & flow

  ONLINE                 DAILY                  WEEKLY                 MONTHLY / ANNUAL
  ------                 -----                  ------                 ----------------
  PYMENU (5250)          PYDAILY  --calls-->    PYWEEK  --calls-->     PYMONTH --calls-->
   1 -> PYEMPIQ            PYTIMLD (raw load)     PYCALC (gross-to-net)  PYERACC (ER FICA/pension)
   2 -> PYSLPIQ (SFL)      PYTIMED (edit/split)   PYSLPRT (payslips)     PYLVACR (leave accrual)
                           PYTIMRP (reject rpt)   PYGLRPT (COBOL bal.)   PYGLDST (SQL GL feed)
                                                                         PYGLRPT (COBOL bal.)
                                                                        PYYEAR  --calls-->
                                                                         PYQTAX (tax summary)
                                                                         PYW2   (W2 + YTD reset)
                                                                         PYQTAX (nil re-check)
     data files (library PAYCHEK)
     -------------------------------------------------------------------------------------
     PYEMP  employee master      PYTIME  time cards (+PYTIMLF by status)
     PYDEDM deduction elections  PYTAXB  graduated tax bands
     PYPAYH payroll headers      PYPAYD  payslip lines (+PYPAYDLF by code)
     PYYTD  YTD accumulators     PYLEAVE leave balances
     PYERCON employer/leave ledger (durable audit trail)
     PYGLFEED SQL GL feed (+PYGLFACC index over ACCT,DRCR)

A single payroll event flows: PYDAILY loads and validates the week's cards (setting TSTAT='V') → PYWEEK runs PYCALC, which reads each 'V' card, computes gross-to-net from PYEMP/PYDEDM/PYTAXB, writes PYPAYH+PYPAYD, rolls PYYTD, and flips the card to 'P'PYMONTH accrues the employer side and distributes to the GL → PYYEAR reports and resets. There is no separate business-rule layer: each RPG program owns its own calculation directly over the keyed files.

A.4 Object inventory

ObjectTypeRole
PYEMPPFEmployee master (the spine).
PYTIMEPFWeekly time cards.
PYTIMLFLFTime cards keyed by TSTAT then EMPNO.
PYPAYHPFPayroll header (gross-to-net result).
PYPAYDPFItemised payslip lines.
PYPAYDLFLFPayslip lines keyed by LCODE then EMPNO.
PYDEDMPFDeduction master / elections.
PYTAXBPFGraduated withholding bands.
PYLEAVEPFLeave balances.
PYYTDPFYear-to-date accumulators.
PYERCONPFEmployer-contribution / leave ledger (audit trail).
PYGLFEEDSQL tableDouble-entry GL feed.
PYGLFACCSQL indexAccess path over (ACCT, DRCR).
PYEMPD / PYSLIPD / PYMENUDDSPFInquiry, subfile payslip, menu screens.
PYSLIPPPRTFPayslip / payroll-register printer file.
PYREFLD / PYTIMLDRPGLEReference-data and raw time-card loaders.
PYTIMED / PYTIMRPRPGLEDaily edit/validate and reject report.
PYCALC / PYSLPRTRPGLEWeekly gross-to-net run and payslip print.
PYERACC / PYLVACRRPGLEMonthly employer & leave accrual.
PYGLDSTSQLRPGLEMonthly GL distribution (embedded SQL).
PYQTAX / PYW2RPGLEAnnual tax summary and W2 + reset.
PYEMPIQ / PYSLPIQ / PYMENURPGLEOnline inquiry, subfile, menu programs.
PYGLRPTCBLLEILE COBOL payroll trial balance.
PYSETUPCLPCreate every object and compile every program.
PYDAILY / PYWEEK / PYMONTH / PYYEARCLPThe four job cycles.

The catalogue is 9 PFs + 2 LFs + 1 SQL table + 1 SQL index, 3 DSPFs and 1 PRTF, driven by 14 RPG programs, 1 COBOL program and 5 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

PAYCHEK/i has no CICS transaction identifiers and no transid switch. On IBM i, each program is reached by name from a 5250 command-entry line (or a JOBQ/scheduler for the batch cycles). The operator equivalent of "type a transid and Enter" is "type a CALL command and Enter". Before invoking anything, the job's library list must include PAYCHEK — the tested jobs run with LIBL = QSYS QGPL PAYCHEK QTEMP and CURLIB = PAYCHEK.

To do thisType on the command line
Open the operator menu (both screens)CALL PAYCHEK/PYMENU
Open the employee inquiry directlyCALL PAYCHEK/PYEMPIQ
Open the payslip subfile inquiry directlyCALL PAYCHEK/PYSLPIQ
Run the daily time-capture cycleCALL PAYCHEK/PYDAILY (or SBMJOB it)
Run the weekly payroll runCALL PAYCHEK/PYWEEK
Run the monthly accrual / GL cycleCALL PAYCHEK/PYMONTH
Run the annual tax / year-end cycleCALL PAYCHEK/PYYEAR
(one-off) build the whole libraryCALL PAYCHEK/PYSETUP then CALL PAYCHEK/PYREFLD

The batch cycles take no CALL parameters: PAYCHEK/i carries no control-row table. Instead each program hard-scopes its period in its own source — the seeded work week is 202631, the tax year is 2026, and the GL batch is 202608 — and re-runs are made safe by per-row idempotency guards rather than a control date (section F.4). Only PYMENU, PYEMPIQ and PYSLPIQ are interactive; every other program runs to completion and DSPLYs a one-line result banner.

B.2 The menu & the two screens (PYMENU / PYEMPIQ / PYSLPIQ)

PYMENU is the driver: it EXFMTs the menu, then CALLs the chosen program (option 1PYEMPIQ, option 2PYSLPIQ), reports "Invalid option." for anything else, and exits on F3. Control returns to the menu when the called program ends.

PAYCHEK/i Main Menu 1. Employee Inquiry 2. Payslip Inquiry Option . . . . : _ F3=Exit Enter=Select

PYEMPIQ — employee inquiry (plain DSPF, format EMPINQ)

Key an employee number; the program CHAINs PYEMP and renders the master record plus the derived standard-week gross (STDHRS × RATE, half-adjusted). An unknown employee clears the detail and shows "Employee not found."; a fresh key re-CHAINs so the previous employee's values never linger.

Employee Inquiry - PAYCHEK/i Employee number: E00001 Name . . . . . : AVERY LINDQVIST Department . . : D100 Job code . . . : MACH Hourly rate . : 25.0000 Standard hours : 40.00 Std week gross : 1000.00 Tax code . . . : S1 Status . . . . : A Employee found. F3=Exit Enter=Inquire

PYSLPIQ — payslip inquiry with a subfile (PYSLIPD: SSFL / SCTL)

This is the app's subfile screen. It CHAINs a payroll header (PYPAYH) for the keyed employee (fixed work week 202631), renders gross and net, then loads the itemised payslip lines from PYPAYD on a partial (PYWEEK, EMPNO) key so exactly one payslip loads in line order. The subfile is SFLPAG(5) / SFLSIZ(20) with ROLLUP/ROLLDOWN paging and SFLEND(*MORE).

Payslip Inquiry - PAYCHEK/i Employee: E00002 Name . . . . . : BRIONY OKONKWO Gross . . . . : 1425.00 Net pay . . . : 1109.10 Seq T Code Description Amount 1 E REG REGULAR HOURS 1200.00 2 E OT OVERTIME HOURS 225.00 3 P 401K 401K RETIREMENT 85.50 4 T FWH FED TAX WITHHELD 217.90 5 F UNIN UNION DUES 12.50 Payslip found. F3=Exit Roll=Page Enter=Inquire

Subfile columns (SSFL) & actions

FieldType (DDS)Shows
SSEQ2Y,0 outputLine sequence (from PYPAYD.LSEQ).
STYP1A outputLine type: E earning, P pre-tax, T tax, F post-tax.
SCODE4A outputLine code (REG/OT/401K/FWH/UNIN…).
SDESC20A outputLine description.
SAMT13A outputLine amount (edited).
IEMP6A input/outputEmployee number keyed on the header line.
Honest statement (a known ENGINE defect, not an application fault): on a second inquiry in the same session, PYSLPIQ correctly re-renders the header and writes the new employee's subfile records, but the SteelFrame X render surface leaves the previous inquiry's subfile detail rows on the display. The interactive battle proves this is a render-side fault, not a program fault, by reproducing it on the unmodified mrp-app template and by inquiring an employee with no payslip at all (zero subfile records written, yet the stale rows remain). The application's own clear/reload discipline is correct; the header and the "no payslip" message always render from the live enquiry.

B.3 Controls & audit workflow (no four-eyes maker–checker)

Honest statement: PAYCHEK/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user enters, a second user approves" step: a validated time card is picked up and paid by PYCALC in the same run, and the online screens are inquiry-only (no maintenance option posts a payroll figure). The manual documents the control model the application does have:

In sum, the control posture is staged state-gating on time cards + a durable employer ledger + reconciling control totals + unique-key idempotency, all enforced in the RPG/data layer, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

PAYCHEK/i's work runs as four CL job cycles rather than one monolithic job: a daily cycle (time capture + edit), a weekly cycle (the payroll run + print + trial balance), a monthly cycle (employer & leave accrual + GL distribution), and an annual cycle (tax summary + W2 + reset). Each CL ADDLIBLE LIB(PAYCHEK), then CALLs its member programs in order and sends a completion banner. There are no CALL parameters and no control-row table; the period is fixed in each program's source (week 202631, year 2026).

-- a scheduled submission is a bare CALL of the cycle CL:
SBMJOB CMD(CALL PGM(PAYCHEK/PYDAILY)) JOB(PYDAILY)
SBMJOB CMD(CALL PGM(PAYCHEK/PYWEEK))  JOB(PYWEEK)

C.1 Full batch program set

ProgramCyclePurposeReadsWrites / result banner
PYREFLDsetupSeed reference data. PYEMP(5), PYDEDM(6), PYTAXB(3), PYLEAVE(6); PYREFLD EMP=5 DED=6 BAND=3 LEAVE=6.
PYTIMLDdailyLoad one week of raw time cards. PYTIME ('E' rows); PYTIMLD CARDS=6 SKIP=1.
PYTIMEDdailyEdit/validate; split reg/OT; reason-code rejects. PYTIME, PYEMP.PYTIME → 'V'/'R'; PYTIMED OK=4 REJ=2.
PYTIMRPdailyReject exception report over the status LF. PYTIMLF (TSTAT='R').PYTIMRP REJ=2 E1=1 E2=1 E3=0.
PYCALCweeklyGross-to-net for every validated card. PYTIME('V'), PYEMP, PYDEDM, PYTAXB. PYPAYH, PYPAYD, PYYTD; cards → 'P'; PYCALC PAID=4 SKIP=2.
PYSLPRTweeklyPrint payslips + register total. PYPAYH, PYPAYD, PYEMP.PYSLIPP; PYSLPRT SLIPS=4 NET=4449.10.
PYGLRPTweekly/monthlyCOBOL payroll trial balance. PYPAYH.DSPLY GROSS/NET/TAX/… and PYGLRPT BALDIFF 0.00.
PYERACCmonthlyEmployer FICA 7.65% + pension 3.00% accrual. PYYTD.PYERCON, PYYTD; PYERACC ACCRUED=4 … FICA=449.44 PENS=176.25.
PYLVACRmonthlyMonthly leave accrual. PYLEAVE.PYERCON, PYLEAVE; PYLVACR ACCRUED=6 … HRS=48.00.
PYGLDSTmonthlyDouble-entry GL distribution (embedded SQL). PYPAYH, PYERCON.PYGLFEED; PYGLDST BATCH=202608 ROWS=… GROSS=5875.00.
PYQTAXannualQuarterly / annual tax summary. PYYTD.DSPLY EMPS/GROSS/TAXBL/WH and ERFICA/ERPENS/LIAB.
PYW2annualW2-style statements + YTD reset. PYYTD, PYEMP.PYSLIPP; PYYTD zeroed; PYW2 STMTS=4 SKIP=0 GROSS=5875.00.

C.2 Daily / weekly / monthly / annual detail

Daily — PYDAILY (PYTIMLD → PYTIMED → PYTIMRP)

PYTIMLD writes the week's raw cards, deliberately including exception cards the edit must catch (an unknown employee, a terminated employee, a duplicate key). PYTIMED walks every 'E' card, CHAINs PYEMP, and validates: reason E1 unknown employee, E2 not active, E3 raw hours over the 80.00 weekly ceiling, E4 non-positive hours. A validated card's raw hours are split against STDHRS: hours up to the standard week are regular, the excess is overtime. PYTIMRP reads only 'R' rows over PYTIMLF and reports the reason breakdown.

Expected DSPLY (seeded week 202631):
  PYTIMLD CARDS=6 SKIP=1     6 raw cards; 7th is a duplicate (week,emp) key, refused
  PYTIMED OK=4 REJ=2         E00001/2/3/4 validated; E00009=E1, E00005=E2
  PYTIMRP REJ=2 E1=1 E2=1 E3=0
Reg/OT split: E00001 40/0, E00002 40/5, E00003 35/0, E00004 40/10 (155 reg, 15 OT).

Weekly — PYWEEK (PYCALC → PYSLPRT → PYGLRPT)

PYCALC is the gross-to-net engine (F.3). For each 'V' card it computes the figures, writes one PYPAYH header and the itemised PYPAYD lines, rolls PYYTD forward (creating the row on the first period), and flips the card to 'P'. PYSLPRT prints one payslip block per header plus a register total; PYGLRPT is the COBOL trial balance.

Expected DSPLY:
  PYCALC PAID=4 SKIP=2       4 validated paid, 2 rejects skipped
  PYSLPRT SLIPS=4 NET=4449.10
  PYGLRPT BALDIFF 0.00       gross 5875.00 = net + tax + pre-tax + post-tax

Monthly — PYMONTH (PYERACC → PYLVACR → PYGLDST → PYGLRPT)

PYERACC accrues employer FICA (7.65%) and a pension match (3.00%) on YTD gross, both half-adjusted, into PYERCON and the PYYTD.YERFICA/YERPENS buckets. PYLVACR accrues each active leave entitlement's monthly hours (10.0000 vacation, 4.0000 sick) into LEARNED and recomputes LBAL = LEARNED - LTAKEN. PYGLDST posts a balanced payroll journal into PYGLFEED via embedded exec sql insert.

Expected DSPLY / figures:
  PYERACC ACCRUED=4  FICA=449.44 PENS=176.25
  PYLVACR ACCRUED=6  HRS=48.00
  PYGLDST BATCH=202608 GROSS=5875.00
  GL debits: 6100-WAGE 5875.00, 6200-ERTX 449.44, 6300-ERPN 176.25  (SUM DR = 6500.69)
  GL credits: 2100-NETP 4449.10, 2200-TAXP 990.40, 2300-PRET, 2400-PSTT, 2500-ERTP 449.44, 2600-ERPP

Annual — PYYEAR (PYQTAX → PYW2 → PYQTAX)

PYQTAX sums the YTD accumulators into the filing position: employees paid, total gross, total taxable, total withholding, employer FICA/pension, and the total remittance liability (withholding + employer FICA). PYW2 prints one W2-style statement per employee (BOX1 wages, BOX2 fed tax, BOX3 soc-sec wages, BX12 deferred comp) and then zeroes the YTD accumulator for the new tax year. PYYEAR re-runs PYQTAX after the reset to confirm the year closed to nil.

Expected DSPLY:
  PYQTAX EMPS=4 GROSS=5875.00 TAXBL=5464.50 WH=990.40
  PYQTAX ERFICA=449.44 ERPENS=176.25 LIAB=1439.84
  PYW2 STMTS=4 SKIP=0 GROSS=5875.00
  PYQTAX EMPS=0 GROSS=0.00        post-reset nil re-check

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library PAYCHEK, grounded in the DDS/DDL in sources.mjs. Dates are stored as signed integers in YYYYMMDD form; rates are packed 7P4 (four decimals; 1.5000 = time-and-a-half, 0.0500 = 5%); money is packed 11P2 (or 13P2 for YTD / GL totals). Employee numbers are E00001-style (a letter + 5 digits, so %dec(%subst(empno:2:5)) yields a stable numeric business key).

PYEMP — Employee master (K EMPNO, UNIQUE)

FieldTypeMeaning
EMPNO6AEmployee number (key).
ENAME30AEmployee name.
EDEPT / EJOB4A / 4ADepartment / job code.
RATE7P,4Hourly rate (four decimals).
OTFACT7P,4Overtime factor (seeded 1.5000).
STDHRS5P,2Standard week; the reg/OT split boundary.
HIREDT8S,0Hire date (YYYYMMDD).
PSTAT1AA active, T terminated, L leave of absence.
PFREQ1APay frequency (W weekly).
TAXCD2ATax code into PYTAXB (seeded S1).
EXEMPTS2S,0Withholding exemptions (informational).

PYTIME — Weekly time cards (K TWEEK, EMPNO, UNIQUE)

FieldTypeMeaning
TWEEK / EMPNO6S,0 / 6AWork week (YYYYWW) + employee (key).
REGHRS / OTHRS5P,2Regular / overtime hours (set by the edit split).
RAWHRS5P,2Raw captured hours (before the split).
TDT8S,0Card date.
TSTAT1AE entered, V validated, R rejected, P paid.
TERRCD2AReject reason (E1–E4) or blank.

PYTIMLF is a non-unique LF over the same record keyed K TSTAT, K EMPNO, so the payroll run and the exception report read one state without scanning the week.

PYPAYH — Payroll header (K PYWEEK, EMPNO, UNIQUE)

FieldTypeMeaning
PYWEEK / EMPNO6S,0 / 6APay week + employee (key; the idempotency guard).
REGPAY / OTPAY11P,2Regular / overtime pay.
GROSS11P,2Gross = regular + overtime.
PRETAX11P,2Total active pre-tax deductions.
TAXABLE11P,2Gross less pre-tax.
TAXWH11P,2Graduated tax withheld.
POSTTAX11P,2Total post-tax flat deductions.
NETPAY11P,2Net = gross - pre-tax - tax - post-tax.
PAYDT / HSTAT8S,0 / 1APay date / header status (P paid, Y archived).

PYPAYD — Payslip lines (K PYWEEK, EMPNO, LSEQ, UNIQUE)

FieldTypeMeaning
PYWEEK / EMPNO / LSEQ6S,0 / 6A / 3S,0Pay week + employee + line sequence (key).
LTYPE1AE earning, P pre-tax, T tax, F post-tax.
LCODE / LDESC4A / 20ALine code (REG/OT/401K/FWH/UNIN…) + description.
LHRS / LRATE5P,2 / 7P,4Hours / effective rate (OT line carries RATE×OTFACT).
LAMT11P,2Line amount.

PYPAYDLF is a non-unique LF keyed K LCODE, K EMPNO — a "who is paying into this deduction code, and how much" enquiry across every pay period as a keyed read.

PYDEDM — Deduction elections (K EMPNO, DEDCD, UNIQUE)

FieldTypeMeaning
EMPNO / DEDCD6A / 4AEmployee + deduction code (key).
DEDDESC20ADescription.
DEDTYP1AP pre-tax (rate-based), F post-tax (flat amount).
DEDRATE7P,4Rate for a pre-tax election (e.g. 0.0500).
DEDAMT9P,2Flat amount for a post-tax election (e.g. 12.50).
DEDSTAT1AA active (taken), S suspended (NOT taken).

PYTAXB — Graduated withholding bands (K TAXCD, BSEQ, UNIQUE)

FieldTypeMeaning
TAXCD / BSEQ2A / 3S,0Tax code + band sequence (key; read in ascending order).
BLOW / BHIGH11P,2Band range on taxable pay: (BLOW, BHIGH].
BRATE7P,4Marginal rate for the slice in this band.
BSTAT1AA active band.

Seeded S1 table: band 1 0.01–500.00 @ 10%, band 2 500.01–1500.00 @ 20%, band 3 above 1500.00 @ 30%.

PYLEAVE — Leave balances (K EMPNO, LVTYP, UNIQUE)

FieldTypeMeaning
EMPNO / LVTYP6A / 1AEmployee + leave type (V vacation, S sick) (key).
LACCR7P,4Hours accrued per month.
LEARNED / LTAKEN7P,2Cumulative earned / taken.
LBAL7P,2Balance = LEARNED - LTAKEN.
LVSTAT1AA active entitlement.

PYYTD — Year-to-date accumulators (K TAXYR, EMPNO, UNIQUE)

FieldTypeMeaning
TAXYR / EMPNO4S,0 / 6ATax year + employee (key).
YGROSS / YPRETAX / YTAXBL13P,2YTD gross / pre-tax / taxable.
YTAXWH / YPOSTTX / YNET13P,2YTD tax withheld / post-tax / net.
YERFICA / YERPENS13P,2YTD employer FICA / pension.
YPERIODS3S,0Pay periods accumulated (drives the "already reset" guard).

PYERCON — Employer-contribution / leave ledger (K ECSEQ, UNIQUE)

FieldTypeMeaning
ECSEQ8S,0Stable business key (30000000-band FICA/PENS, 50000000-band LEAV).
EMPNO6AEmployee.
ECTYP4AFICA / PENS / LEAV.
ECBASE / ECRATE11P,2 / 7P,4Base amount / rate.
ECAMT11P,2Contribution amount (or accrued hours for LEAV).
ECDT / ECMEMO8S,0 / 25APosting date / memo.

PYGLFEED — SQL GL feed (PK GLSEQ; index PYGLFACC on ACCT, DRCR)

ColumnTypeMeaning
GLSEQDECIMAL(8,0)Sequence (PK).
GLBATCHDECIMAL(6,0)Batch (202608).
ACCTCHAR(9)Account, e.g. 6100-WAGE, 2100-NETP.
DRCRCHAR(1)D debit, C credit.
AMTDECIMAL(13,2)Amount.
GLREF / GLDTCHAR(8) / DECIMAL(8,0)Reference (PAYROLL/EMPLOYR) / date.

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. Confirm the job's library list includes PAYCHEK (LIBL = QSYS QGPL PAYCHEK QTEMP).
  2. One-off, on a fresh library: CALL PAYCHEK/PYSETUP then CALL PAYCHEK/PYREFLD (expect PYREFLD EMP=5 DED=6 BAND=3 LEAVE=6).
  3. Run the daily time-capture cycle: SBMJOB CMD(CALL PGM(PAYCHEK/PYDAILY)). Post-check PYTIMLD CARDS=6 SKIP=1, PYTIMED OK=4 REJ=2, PYTIMRP REJ=2 E1=1 E2=1.
  4. Handle inquiries interactively through CALL PAYCHEK/PYMENU (option 1 employee, option 2 payslip).

Pre-checks: raw cards captured and edited (all cards out of the 'E' state) before the weekly run; the reference data seeded once.

E.2 Payroll run, period & year close

  1. Weekly run: SBMJOB CMD(CALL PGM(PAYCHEK/PYWEEK)). Confirm PYCALC PAID=4 SKIP=2, PYSLPRT SLIPS=4 NET=4449.10, and the COBOL PYGLRPT BALDIFF 0.00.
  2. Monthly close: CALL PAYCHEK/PYMONTH. Confirm the accrual banners and the balanced GL feed.
  3. Annual close: CALL PAYCHEK/PYYEAR. Confirm the summary, the W2 print, and the post-reset nil re-check.
  4. Reconcile the figures below against the payroll headers and the GL feed.

Reconciling figures (the exact cents the cycle battle asserts against a hand-derived oracle, for the seeded 4-employee week):

EmployeeGrossPre-taxTaxableTaxPost-taxNet
E000011000.0050.00950.00140.0012.50797.50
E000021425.0085.501339.50217.9012.501109.10
E00003700.000.00700.0090.000.00610.00
E000042750.00275.002475.00542.500.001932.50
Total5875.00410.505464.50990.4025.004449.10

E.3 Failure & re-run rules

Each program DSPLYs a result banner. PAYCHEK/i has no control-date table; every re-run is made safe by a per-row idempotency guard, so a cycle can simply be resubmitted.

SituationBehaviourAction
Re-run PYTIMLD (raw load)The UNIQUE (TWEEK, EMPNO) key refuses every card. Safe no-op: PYTIMLD CARDS=0 SKIP=7. Card count unchanged.
Re-run PYTIMED (edit)Only 'E' rows are touched; validated/rejected cards are left as-is.Safe: PYTIMED OK=0 REJ=0. Hours do not move a second time.
Re-run PYWEEK / PYCALCThe (PYWEEK, EMPNO) header guard skips already-paid employees; cards are now 'P'.Never double-pays: PYCALC PAID=0 SKIP=6; still 4 headers, net still 4449.10, YTD still 1 period.
Re-run PYERACC / PYLVACRThe stable ledger key (30000000 / 50000000 band) already exists, so CHAIN(EN) + LEAVESR skips it. Safe: ACCRUED=0; balances / YTD do not double.
Re-run PYYEAR / PYW2An already-reset accumulator has YGROSS≤0, so it is skipped.Safe: PYW2 STMTS=0 SKIP=4; payroll history untouched.
Over-ceiling / non-positive cardRejected E3 (>80.00 hrs) or E4; zeroed payable hours, not paid.Correct the raw hours (re-key a fresh card / week) and re-edit.
Unknown / terminated employee cardRejected E1 / E2; visible on the PYTIMRP report. Fix the master or the card key; the reject stays out of the payroll run.
Because the employer ledger (PYERCON) accumulates every contribution under a unique stable key, the payroll headers preserve every period's gross-to-net, and the COBOL trial balance recomputes the identity, any cycle's effect is fully reconstructable after the fact for reconciliation and recovery.

F. Developer Reference ↑ top

The program surface, from sources.mjs. All objects are in library PAYCHEK; source members live in QDDSSRC / QRPGLESRC / QCBLLESRC / QSQLSRC / QCLSRC, created and compiled by PYSETUP.

F.1 Batch programs

PYREFLD (RPGLE) — reference-data loader
Writes 5 employees (4 active + 1 terminated), 6 deduction elections (3 active pre-tax, 1 suspended, 2 post-tax flat), the 3-band S1 tax table, and 6 leave entitlements.
PYTIMLD (RPGLE) — raw time-card loader
Writes week 202631's raw cards including deliberate exceptions (unknown, terminated, duplicate key). Idempotent on the UNIQUE (TWEEK, EMPNO) key via a CHAIN(EN)/skip guard.
PYTIMED (RPGLE) — daily edit/validate
Reads each 'E' card, CHAINs PYEMP, applies E1–E4, splits reg/OT against STDHRS, and updates the card to 'V'/'R'. Re-CHAINs the card after the master lookup (the lookup moves the file cursor).
PYTIMRP (RPGLE) — reject report
READE over PYTIMLF keyed 'R'; counts the reason-code breakdown.
PYCALC (RPGLE) — the weekly gross-to-net run
See F.3.
PYSLPRT (RPGLE) — payslip / register print
Prints one payslip block per header (partial-key READE of PYPAYD in line order) plus a register total, to the PYSLIPP printer file with overflow on *IN90.
PYERACC (RPGLE) — employer accrual
FICA 0.0765 + pension 0.0300 of YGROSS, half-adjusted, into PYERCON (key 30000000 + digits*10 + n) and PYYTD. Idempotent on the stable key.
PYLVACR (RPGLE) — leave accrual
Each active entitlement earns LACCR hours; LEARNED grows, LBAL = LEARNED - LTAKEN. Ledgered at key 50000000 + digits*10 + type#.
PYGLDST (SQLRPGLE) — GL distribution
Accumulates PYPAYH and PYERCON, then writes balanced DR/CR pairs to PYGLFEED via free-form exec sql insert. Gross = net + tax + pre-tax + post-tax by construction, so it balances.
PYQTAX (RPGLE) — tax summary
Sums PYYTD into EMPS/GROSS/TAXBL/WH and ERFICA/ERPENS; liability = WH + employer FICA.
PYW2 (RPGLE) — W2 print + YTD reset
Prints BOX1/BOX2/BOX3/BX12 per employee, then zeroes every PYYTD field for the new year (skips an already-zero accumulator).
PYGLRPT (ILE COBOL) — trial balance
Reads PYPAYH sequentially; computes W-CHK = net + tax + pre + post and W-DIFF = gross - W-CHK; DISPLAYs the totals and BALDIFF.

F.2 Online programs & DDS

PYMENU (RPGLE) / PYMENUD (DSPF)
EXFMT PYMENU; option 1 CALL 'PYEMPIQ', option 2 CALL 'PYSLPIQ', else "Invalid option."; F3 (CA03/*IN03) exits.
PYEMPIQ (RPGLE) / PYEMPD (DSPF, format EMPINQ)
Plain screen; CHAINs PYEMP on IEMP, renders the master and the derived STDHRS × RATE gross (eval(h)), clears on a miss.
PYSLPIQ (RPGLE) / PYSLIPD (DSPF, SSFL/SCTL subfile)
Subfile screen (SFLPAG 5 / SFLSIZ 20 / ROLLUP/ROLLDOWN / SFLEND(*MORE)); clears the subfile with *IN31 after the EXFMT returns the new key, then reloads PYPAYD on a partial (PYWEEK, EMPNO) key. See the B.2 note on the engine stale-row render defect.

F.3 The gross-to-net engine (PYCALC)

The PAY1 subroutine, per validated card, in order:

  1. Skip non-'V' cards; guard the (PYWEEK, EMPNO) header key (already paid → skip); CHAIN PYEMP for rate / OT factor / tax code; skip a non-active employee.
  2. eval(h) wregp = REGHRS × RATE; eval(h) wotp = OTHRS × RATE × OTFACT; gross = regular + overtime. Write the REG earning line (and the OT line if OTHRS > 0, carrying the effective rate RATE×OTFACT).
  3. READE PYDEDM by employee: for each DEDSTAT='A' & DEDTYP='P', eval(h) wamt = gross × DEDRATE; accumulate pre-tax; write the P line.
  4. taxable = gross - pre-tax. READE PYTAXB by tax code in band order: for each active band, tax the slice of taxable inside (BLOW, BHIGH] at BRATE, half-adjusted; write one T (FWH) line.
  5. READE PYDEDM again: for each DEDSTAT='A' & DEDTYP='F', add DEDAMT; write the F line. net = gross - pre-tax - tax - post-tax.
  6. Write the PYPAYH header; roll PYYTD forward (create on the first period, else update & increment YPERIODS); re-CHAIN the time card and flip it to 'P'.

F.4 Idempotency & access-path patterns

F.5 Reject reason-code table (TERRCD, set by PYTIMED)

CodeMeaningTest
E1No such employee on the master.CHAIN PYEMP not found.
E2Employee is not active (terminated / leave of absence).PSTAT ≠ 'A'.
E3Raw hours exceed the 80.00 weekly ceiling.RAWHRS > 80.00.
E4Raw hours are zero or negative.RAWHRS ≤ 0.

A rejected card carries the reason in TERRCD, is set TSTAT='R', and has REGHRS and OTHRS zeroed, so it can never be paid.

G. Glossary ↑ top

Accrual (employer / leave)
Recognising a cost the employer owes on top of gross (FICA, pension) or an entitlement earned (leave hours), posted to the PYERCON ledger and the YTD / leave buckets.
Basis / packed decimal (nP m)
The DDS packed-decimal declaration: total digits n, m decimals. Rates are 7P4 (four decimals), money 11P2, wide totals 13P2.
Deduction (pre-tax / post-tax)
A pre-tax ('P') deduction is rate-based and reduces taxable pay (e.g. 401(k)); a post-tax ('F') deduction is a flat amount taken after tax (e.g. union dues). A DEDSTAT='S' election is suspended and not taken.
FICA
The employer's payroll-tax contribution, here modelled as a flat 7.65% of gross (a deliberately simple, auditable figure, not a wage-base-capped regulatory model).
Graduated withholding
Tax computed band by band: each PYTAXB band taxes the slice of taxable pay inside (BLOW, BHIGH] at its marginal BRATE, summed. Seeded S1: 10% / 20% / 30%.
Gross-to-net
The payroll calculation from gross earnings down to net pay: gross - pre-tax - tax - post-tax.
Half-adjust (eval(h))
RPG rounding to the last retained digit (round half up), so a money multiplication lands on exact cents rather than truncating (2750.00×0.0765 = 210.375 → 210.38).
Idempotent
Safe to run again with the same result. Here every loader / run / accrual is idempotent via a UNIQUE composite key or a stable ledger key, not via a control date.
Overtime factor (OTFACT)
The multiplier applied to overtime hours (seeded 1.5000, time-and-a-half); the effective OT rate is RATE×OTFACT.
Payroll identity
The control total gross = net + tax + pre-tax + post-tax, verified by the COBOL trial balance (BALDIFF 0.00) and by the balanced GL feed.
SBMJOB
Submit Job — the IBM i command that queues a program to run as a batch job (e.g. SBMJOB CMD(CALL PGM(PAYCHEK/PYWEEK))).
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). PYSLPIQ's payslip-line list is a subfile.
Time-card states (TSTAT)
E entered (raw), V validated (edit passed, reg/OT split), R rejected (with a reason code), P paid (picked up by the payroll run).
W2-style statement
The year-end per-employee summary printed by PYW2 (BOX1 wages, BOX2 fed tax, BOX3 soc-sec wages, BX12 deferred comp), after which the YTD accumulators reset.
YTD accumulator (PYYTD)
The per-employee, per-tax-year running totals the quarterly report sums and the year-end statement prints, reset to zero by the annual roll.