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.
PAYCHEK/i runs the full weekly-to-annual payroll cycle:
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).
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.
| Object | Type | Role |
|---|---|---|
| PYEMP | PF | Employee master (the spine). |
| PYTIME | PF | Weekly time cards. |
| PYTIMLF | LF | Time cards keyed by TSTAT then EMPNO. |
| PYPAYH | PF | Payroll header (gross-to-net result). |
| PYPAYD | PF | Itemised payslip lines. |
| PYPAYDLF | LF | Payslip lines keyed by LCODE then EMPNO. |
| PYDEDM | PF | Deduction master / elections. |
| PYTAXB | PF | Graduated withholding bands. |
| PYLEAVE | PF | Leave balances. |
| PYYTD | PF | Year-to-date accumulators. |
| PYERCON | PF | Employer-contribution / leave ledger (audit trail). |
| PYGLFEED | SQL table | Double-entry GL feed. |
| PYGLFACC | SQL index | Access path over (ACCT, DRCR). |
| PYEMPD / PYSLIPD / PYMENUD | DSPF | Inquiry, subfile payslip, menu screens. |
| PYSLIPP | PRTF | Payslip / payroll-register printer file. |
| PYREFLD / PYTIMLD | RPGLE | Reference-data and raw time-card loaders. |
| PYTIMED / PYTIMRP | RPGLE | Daily edit/validate and reject report. |
| PYCALC / PYSLPRT | RPGLE | Weekly gross-to-net run and payslip print. |
| PYERACC / PYLVACR | RPGLE | Monthly employer & leave accrual. |
| PYGLDST | SQLRPGLE | Monthly GL distribution (embedded SQL). |
| PYQTAX / PYW2 | RPGLE | Annual tax summary and W2 + reset. |
| PYEMPIQ / PYSLPIQ / PYMENU | RPGLE | Online inquiry, subfile, menu programs. |
| PYGLRPT | CBLLE | ILE COBOL payroll trial balance. |
| PYSETUP | CLP | Create every object and compile every program. |
| PYDAILY / PYWEEK / PYMONTH / PYYEAR | CLP | The 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.
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 this | Type on the command line |
|---|---|
| Open the operator menu (both screens) | CALL PAYCHEK/PYMENU |
| Open the employee inquiry directly | CALL PAYCHEK/PYEMPIQ |
| Open the payslip subfile inquiry directly | CALL PAYCHEK/PYSLPIQ |
| Run the daily time-capture cycle | CALL PAYCHEK/PYDAILY (or SBMJOB it) |
| Run the weekly payroll run | CALL PAYCHEK/PYWEEK |
| Run the monthly accrual / GL cycle | CALL PAYCHEK/PYMONTH |
| Run the annual tax / year-end cycle | CALL PAYCHEK/PYYEAR |
| (one-off) build the whole library | CALL 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.
PYMENU is the driver: it EXFMTs the menu, then CALLs the chosen
program (option 1→PYEMPIQ, option 2→PYSLPIQ),
reports "Invalid option." for anything else, and exits on F3. Control returns to
the menu when the called program ends.
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.
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).
| Field | Type (DDS) | Shows |
|---|---|---|
| SSEQ | 2Y,0 output | Line sequence (from PYPAYD.LSEQ). |
| STYP | 1A output | Line type: E earning, P pre-tax, T tax, F post-tax. |
| SCODE | 4A output | Line code (REG/OT/401K/FWH/UNIN…). |
| SDESC | 20A output | Line description. |
| SAMT | 13A output | Line amount (edited). |
| IEMP | 6A input/output | Employee number keyed on the header line. |
SFLCLR via *IN31) and reloaded for the new key.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.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:
TSTAT='E'), then the
daily edit either validates it ('V', with regular/overtime split) or rejects it
('R', with a reason code). The payroll run pays only 'V' rows and
flips them to 'P' (paid); nothing else is payable.PYTIMRP report enumerates the breakdown over the status logical file.3000… band, leave
in the 5000… band) so a re-run regenerates the same key and is refused
rather than double-posting.gross = net + tax + pre-tax + post-tax. The ILE COBOL PYGLRPT trial
balance recomputes this over the headers and reports BALDIFF; a healthy period is
BALDIFF 0.00. The GL distribution is posted as balanced debit/credit pairs so
SUM(DR) = SUM(CR) in the feed.PYTIME(TWEEK,EMPNO),
PYPAYH(PYWEEK,EMPNO), PYYTD(TAXYR,EMPNO) — are what make the loaders
and the payroll run safe to re-run: an already-present row is refused / skipped.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.
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)
| Program | Cycle | Purpose | Reads | Writes / result banner |
|---|---|---|---|---|
| PYREFLD | setup | Seed reference data. | — | PYEMP(5), PYDEDM(6), PYTAXB(3), PYLEAVE(6); PYREFLD EMP=5 DED=6 BAND=3 LEAVE=6. |
| PYTIMLD | daily | Load one week of raw time cards. | — | PYTIME ('E' rows); PYTIMLD CARDS=6 SKIP=1. |
| PYTIMED | daily | Edit/validate; split reg/OT; reason-code rejects. | PYTIME, PYEMP. | PYTIME → 'V'/'R'; PYTIMED OK=4 REJ=2. |
| PYTIMRP | daily | Reject exception report over the status LF. | PYTIMLF (TSTAT='R'). | PYTIMRP REJ=2 E1=1 E2=1 E3=0. |
| PYCALC | weekly | Gross-to-net for every validated card. | PYTIME('V'), PYEMP, PYDEDM, PYTAXB. | PYPAYH, PYPAYD, PYYTD; cards → 'P'; PYCALC PAID=4 SKIP=2. |
| PYSLPRT | weekly | Print payslips + register total. | PYPAYH, PYPAYD, PYEMP. | PYSLIPP; PYSLPRT SLIPS=4 NET=4449.10. |
| PYGLRPT | weekly/monthly | COBOL payroll trial balance. | PYPAYH. | DSPLY GROSS/NET/TAX/… and PYGLRPT BALDIFF 0.00. |
| PYERACC | monthly | Employer FICA 7.65% + pension 3.00% accrual. | PYYTD. | PYERCON, PYYTD; PYERACC ACCRUED=4 … FICA=449.44 PENS=176.25. |
| PYLVACR | monthly | Monthly leave accrual. | PYLEAVE. | PYERCON, PYLEAVE; PYLVACR ACCRUED=6 … HRS=48.00. |
| PYGLDST | monthly | Double-entry GL distribution (embedded SQL). | PYPAYH, PYERCON. | PYGLFEED; PYGLDST BATCH=202608 ROWS=… GROSS=5875.00. |
| PYQTAX | annual | Quarterly / annual tax summary. | PYYTD. | DSPLY EMPS/GROSS/TAXBL/WH and ERFICA/ERPENS/LIAB. |
| PYW2 | annual | W2-style statements + YTD reset. | PYYTD, PYEMP. | PYSLIPP; PYYTD zeroed; PYW2 STMTS=4 SKIP=0 GROSS=5875.00. |
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).
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
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
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
TSTAT='V' and splits reg/OT;
PYCALC pays only 'V' cards, so unedited or unloaded cards are not paid.PYYTD.YGROSS (built by the
payroll run), and the GL distribution reads PYPAYH + PYERCON, so the pay
run and the accruals must precede the GL feed.PYQTAX's employer-FICA figure and the remittance
liability come from PYYTD.YERFICA, populated by the monthly accrual.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).
| Field | Type | Meaning |
|---|---|---|
| EMPNO | 6A | Employee number (key). |
| ENAME | 30A | Employee name. |
| EDEPT / EJOB | 4A / 4A | Department / job code. |
| RATE | 7P,4 | Hourly rate (four decimals). |
| OTFACT | 7P,4 | Overtime factor (seeded 1.5000). |
| STDHRS | 5P,2 | Standard week; the reg/OT split boundary. |
| HIREDT | 8S,0 | Hire date (YYYYMMDD). |
| PSTAT | 1A | A active, T terminated, L leave of absence. |
| PFREQ | 1A | Pay frequency (W weekly). |
| TAXCD | 2A | Tax code into PYTAXB (seeded S1). |
| EXEMPTS | 2S,0 | Withholding exemptions (informational). |
| Field | Type | Meaning |
|---|---|---|
| TWEEK / EMPNO | 6S,0 / 6A | Work week (YYYYWW) + employee (key). |
| REGHRS / OTHRS | 5P,2 | Regular / overtime hours (set by the edit split). |
| RAWHRS | 5P,2 | Raw captured hours (before the split). |
| TDT | 8S,0 | Card date. |
| TSTAT | 1A | E entered, V validated, R rejected, P paid. |
| TERRCD | 2A | Reject 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.
| Field | Type | Meaning |
|---|---|---|
| PYWEEK / EMPNO | 6S,0 / 6A | Pay week + employee (key; the idempotency guard). |
| REGPAY / OTPAY | 11P,2 | Regular / overtime pay. |
| GROSS | 11P,2 | Gross = regular + overtime. |
| PRETAX | 11P,2 | Total active pre-tax deductions. |
| TAXABLE | 11P,2 | Gross less pre-tax. |
| TAXWH | 11P,2 | Graduated tax withheld. |
| POSTTAX | 11P,2 | Total post-tax flat deductions. |
| NETPAY | 11P,2 | Net = gross - pre-tax - tax - post-tax. |
| PAYDT / HSTAT | 8S,0 / 1A | Pay date / header status (P paid, Y archived). |
| Field | Type | Meaning |
|---|---|---|
| PYWEEK / EMPNO / LSEQ | 6S,0 / 6A / 3S,0 | Pay week + employee + line sequence (key). |
| LTYPE | 1A | E earning, P pre-tax, T tax, F post-tax. |
| LCODE / LDESC | 4A / 20A | Line code (REG/OT/401K/FWH/UNIN…) + description. |
| LHRS / LRATE | 5P,2 / 7P,4 | Hours / effective rate (OT line carries RATE×OTFACT). |
| LAMT | 11P,2 | Line 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.
| Field | Type | Meaning |
|---|---|---|
| EMPNO / DEDCD | 6A / 4A | Employee + deduction code (key). |
| DEDDESC | 20A | Description. |
| DEDTYP | 1A | P pre-tax (rate-based), F post-tax (flat amount). |
| DEDRATE | 7P,4 | Rate for a pre-tax election (e.g. 0.0500). |
| DEDAMT | 9P,2 | Flat amount for a post-tax election (e.g. 12.50). |
| DEDSTAT | 1A | A active (taken), S suspended (NOT taken). |
| Field | Type | Meaning |
|---|---|---|
| TAXCD / BSEQ | 2A / 3S,0 | Tax code + band sequence (key; read in ascending order). |
| BLOW / BHIGH | 11P,2 | Band range on taxable pay: (BLOW, BHIGH]. |
| BRATE | 7P,4 | Marginal rate for the slice in this band. |
| BSTAT | 1A | A 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%.
| Field | Type | Meaning |
|---|---|---|
| EMPNO / LVTYP | 6A / 1A | Employee + leave type (V vacation, S sick) (key). |
| LACCR | 7P,4 | Hours accrued per month. |
| LEARNED / LTAKEN | 7P,2 | Cumulative earned / taken. |
| LBAL | 7P,2 | Balance = LEARNED - LTAKEN. |
| LVSTAT | 1A | A active entitlement. |
| Field | Type | Meaning |
|---|---|---|
| TAXYR / EMPNO | 4S,0 / 6A | Tax year + employee (key). |
| YGROSS / YPRETAX / YTAXBL | 13P,2 | YTD gross / pre-tax / taxable. |
| YTAXWH / YPOSTTX / YNET | 13P,2 | YTD tax withheld / post-tax / net. |
| YERFICA / YERPENS | 13P,2 | YTD employer FICA / pension. |
| YPERIODS | 3S,0 | Pay periods accumulated (drives the "already reset" guard). |
| Field | Type | Meaning |
|---|---|---|
| ECSEQ | 8S,0 | Stable business key (30000000-band FICA/PENS, 50000000-band LEAV). |
| EMPNO | 6A | Employee. |
| ECTYP | 4A | FICA / PENS / LEAV. |
| ECBASE / ECRATE | 11P,2 / 7P,4 | Base amount / rate. |
| ECAMT | 11P,2 | Contribution amount (or accrued hours for LEAV). |
| ECDT / ECMEMO | 8S,0 / 25A | Posting date / memo. |
| Column | Type | Meaning |
|---|---|---|
| GLSEQ | DECIMAL(8,0) | Sequence (PK). |
| GLBATCH | DECIMAL(6,0) | Batch (202608). |
| ACCT | CHAR(9) | Account, e.g. 6100-WAGE, 2100-NETP. |
| DRCR | CHAR(1) | D debit, C credit. |
| AMT | DECIMAL(13,2) | Amount. |
| GLREF / GLDT | CHAR(8) / DECIMAL(8,0) | Reference (PAYROLL/EMPLOYR) / date. |
PAYCHEK (LIBL = QSYS QGPL PAYCHEK QTEMP).CALL PAYCHEK/PYSETUP then CALL PAYCHEK/PYREFLD
(expect PYREFLD EMP=5 DED=6 BAND=3 LEAVE=6).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.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.
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.CALL PAYCHEK/PYMONTH. Confirm the accrual banners and the balanced
GL feed.CALL PAYCHEK/PYYEAR. Confirm the summary, the W2 print, and the
post-reset nil re-check.Reconciling figures (the exact cents the cycle battle asserts against a hand-derived oracle, for the seeded 4-employee week):
| Employee | Gross | Pre-tax | Taxable | Tax | Post-tax | Net |
|---|---|---|---|---|---|---|
| E00001 | 1000.00 | 50.00 | 950.00 | 140.00 | 12.50 | 797.50 |
| E00002 | 1425.00 | 85.50 | 1339.50 | 217.90 | 12.50 | 1109.10 |
| E00003 | 700.00 | 0.00 | 700.00 | 90.00 | 0.00 | 610.00 |
| E00004 | 2750.00 | 275.00 | 2475.00 | 542.50 | 0.00 | 1932.50 |
| Total | 5875.00 | 410.50 | 5464.50 | 990.40 | 25.00 | 4449.10 |
BALDIFF 0.00).DEDSTAT='S', so
pre-tax is 0.00 and taxable equals gross; the payslip has no 401K line.SUM(DR) = SUM(CR); six liability credits under the 2% accounts.PYW2 the YTD accumulators
are zero for the new year.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.
| Situation | Behaviour | Action |
|---|---|---|
| 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 / PYCALC | The (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 / PYLVACR | The 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 / PYW2 | An already-reset accumulator has YGROSS≤0, so it is
skipped. | Safe: PYW2 STMTS=0 SKIP=4; payroll history untouched. |
| Over-ceiling / non-positive card | Rejected 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 card | Rejected E1 / E2; visible on the PYTIMRP report. | Fix the master or the card key; the reject stays out of the payroll run. |
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.
CHAIN(EN)/skip guard.'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).READE over PYTIMLF keyed 'R'; counts the reason-code breakdown.READE of PYPAYD in line order) plus a
register total, to the PYSLIPP printer file with overflow on *IN90.exec sql insert. Gross = net + tax + pre-tax + post-tax by construction, so it balances.DISPLAYs the totals and BALDIFF.EXFMT PYMENU; option 1 CALL 'PYEMPIQ', option 2 CALL 'PYSLPIQ',
else "Invalid option."; F3 (CA03/*IN03) exits.CHAINs PYEMP on IEMP, renders the master and the derived
STDHRS × RATE gross (eval(h)), clears on a miss.*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.The PAY1 subroutine, per validated card, in order:
'V' cards; guard the (PYWEEK, EMPNO) header key (already paid → skip);
CHAIN PYEMP for rate / OT factor / tax code; skip a non-active employee.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).READE PYDEDM by employee: for each DEDSTAT='A' & DEDTYP='P',
eval(h) wamt = gross × DEDRATE; accumulate pre-tax; write the P line.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.READE PYDEDM again: for each DEDSTAT='A' & DEDTYP='F',
add DEDAMT; write the F line. net = gross - pre-tax - tax - post-tax.'P'.KLIST (both key parts), never a
partial CHAIN on the first part alone, which would match the wrong employee —
PYPAYH (PYWEEK, EMPNO), PYTIME (TWEEK, EMPNO), PYYTD (TAXYR, EMPNO).%dec(%subst(empno:2:5))) so a re-run regenerates the same key and is
refused (CHAIN(EN) + LEAVESR). FICA/PENS use the 30000000 band, LEAV the
50000000 band — disjoint ranges.CHAINs a second file re-CHAINs the
driving record before updating it, because the lookup moved that file's cursor (PYTIMED, PYCALC,
PYERACC, PYLVACR, PYW2 all do this).exec sql insert into PAYCHEK.PYGLFEED values (…);
— the only SQL-writing program; everything else is native keyed I/O.| Code | Meaning | Test |
|---|---|---|
| E1 | No such employee on the master. | CHAIN PYEMP not found. |
| E2 | Employee is not active (terminated / leave of absence). | PSTAT ≠ 'A'. |
| E3 | Raw hours exceed the 80.00 weekly ceiling. | RAWHRS > 80.00. |
| E4 | Raw 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.
'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.SBMJOB CMD(CALL PGM(PAYCHEK/PYWEEK))).