Case Study 1: Daily Transaction Processing at Pioneer National Bank
Background
Pioneer National Bank processes all customer transactions through a nightly batch cycle. During business hours, transactions from ATMs, teller stations, online banking, and mobile apps are collected into a sequential transaction file. Each night, the Daily Transaction Processing program (DLYTRXN) reads this file, validates each transaction, accumulates summary totals by transaction type, and produces a Daily Transaction Summary Report for the operations team.
This batch job is the first step in Pioneer National's end-of-day processing chain. If DLYTRXN fails or produces incorrect totals, every downstream job -- account posting, interest calculation, statement generation, and regulatory reporting -- is affected. The program must therefore be production-hardened with comprehensive file status checking, error counting, and control total verification.
Marcus Williams, a COBOL developer with 15 years of mainframe experience, designed DLYTRXN to demonstrate best practices in sequential file processing: rigorous file status checking on every I/O operation, clean EOF handling, error accumulation with threshold-based abort logic, and control total reconciliation.
The Problem
The daily transaction file (TRANFILE) contains between 50,000 and 500,000 records depending on business volume. Each record is an 80-byte fixed-length record containing:
- Transaction ID (12 digits)
- Account Number (10 digits)
- Transaction Type (2-character code: DP=deposit, WD=withdrawal, TR=transfer, FE=fee, IN=interest, AD=adjustment)
- Transaction Amount (signed, up to $9,999,999.99)
- Transaction Date (YYYYMMDD)
- Transaction Time (HHMMSS)
- Branch Code (4 characters)
- Teller ID (6 characters)
- Filler (18 characters)
The program must produce:
- Summary Report -- Total count and total amount for each transaction type
- Error Report -- All records that fail validation, with reason codes
- Control Totals -- Grand total count, debit total, and credit total for reconciliation
- An exit with return code 0 if processing completes normally, return code 4 if errors were found but within threshold (under 1% of records), and return code 8 if errors exceed the threshold.
The Solution
The COBOL Program
IDENTIFICATION DIVISION.
PROGRAM-ID. DLYTRXN.
AUTHOR. MARCUS WILLIAMS.
DATE-WRITTEN. 2024-11-15.
*================================================================
* PROGRAM: DLYTRXN - DAILY TRANSACTION PROCESSING
* PURPOSE: Read the daily transaction file, validate
* each record, accumulate summary totals by
* transaction type, and produce a summary report
* and error report. Demonstrates sequential file
* processing with rigorous file status checking,
* EOF handling, and control total reconciliation.
*================================================================
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANSACTION-FILE
ASSIGN TO TRANFILE
ORGANIZATION IS SEQUENTIAL
ACCESS MODE IS SEQUENTIAL
FILE STATUS IS WS-TRAN-STATUS.
SELECT SUMMARY-REPORT
ASSIGN TO SUMRPT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-SUMR-STATUS.
SELECT ERROR-REPORT
ASSIGN TO ERRRPT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-ERRR-STATUS.
DATA DIVISION.
FILE SECTION.
FD TRANSACTION-FILE
RECORDING MODE IS F
RECORD CONTAINS 80 CHARACTERS
BLOCK CONTAINS 0 RECORDS.
01 FS-TRAN-RECORD.
05 FS-TR-TRANS-ID PIC 9(12).
05 FS-TR-ACCOUNT-NO PIC 9(10).
05 FS-TR-TYPE PIC X(2).
05 FS-TR-AMOUNT PIC S9(7)V99.
05 FS-TR-DATE.
10 FS-TR-YEAR PIC 9(4).
10 FS-TR-MONTH PIC 9(2).
10 FS-TR-DAY PIC 9(2).
05 FS-TR-TIME.
10 FS-TR-HOUR PIC 9(2).
10 FS-TR-MINUTE PIC 9(2).
10 FS-TR-SECOND PIC 9(2).
05 FS-TR-BRANCH PIC X(4).
05 FS-TR-TELLER PIC X(6).
05 FILLER PIC X(18).
FD SUMMARY-REPORT
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 FS-SUM-LINE PIC X(132).
FD ERROR-REPORT
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 FS-ERR-LINE PIC X(132).
WORKING-STORAGE SECTION.
*----------------------------------------------------------------
* FILE STATUS FIELDS
* Every file gets its own status field. Every I/O
* operation checks its status field immediately after.
*----------------------------------------------------------------
01 WS-TRAN-STATUS PIC X(2).
88 TRAN-OK VALUE "00".
88 TRAN-EOF VALUE "10".
88 TRAN-ERROR VALUE "30" THRU
"99".
01 WS-SUMR-STATUS PIC X(2).
88 SUMR-OK VALUE "00".
01 WS-ERRR-STATUS PIC X(2).
88 ERRR-OK VALUE "00".
*----------------------------------------------------------------
* PROCESSING FLAGS
*----------------------------------------------------------------
01 WS-EOF-FLAG PIC X(1) VALUE 'N'.
88 END-OF-FILE VALUE 'Y'.
88 NOT-END-OF-FILE VALUE 'N'.
01 WS-ABORT-FLAG PIC X(1) VALUE 'N'.
88 ABORT-PROCESSING VALUE 'Y'.
88 CONTINUE-PROCESSING VALUE 'N'.
*----------------------------------------------------------------
* VALIDATION FLAGS
*----------------------------------------------------------------
01 WS-VALID-RECORD PIC X(1).
88 RECORD-VALID VALUE 'Y'.
88 RECORD-INVALID VALUE 'N'.
01 WS-ERROR-REASON PIC X(30).
*----------------------------------------------------------------
* VALID TRANSACTION TYPES
*----------------------------------------------------------------
01 WS-VALID-TYPES.
05 FILLER PIC X(2) VALUE "DP".
05 FILLER PIC X(2) VALUE "WD".
05 FILLER PIC X(2) VALUE "TR".
05 FILLER PIC X(2) VALUE "FE".
05 FILLER PIC X(2) VALUE "IN".
05 FILLER PIC X(2) VALUE "AD".
01 WS-VALID-TYPE-TABLE REDEFINES WS-VALID-TYPES.
05 WS-VALID-TYPE PIC X(2)
OCCURS 6 TIMES.
01 WS-TYPE-IDX PIC 9(1).
01 WS-TYPE-FOUND PIC X(1).
*----------------------------------------------------------------
* SUMMARY ACCUMULATORS (by transaction type)
* Index: 1=DP, 2=WD, 3=TR, 4=FE, 5=IN, 6=AD
*----------------------------------------------------------------
01 WS-SUMMARY-TABLE.
05 WS-SUM-ENTRY OCCURS 6 TIMES.
10 WS-SUM-TYPE-CODE PIC X(2).
10 WS-SUM-TYPE-DESC PIC X(15).
10 WS-SUM-COUNT PIC S9(7) COMP-3.
10 WS-SUM-DEBIT-TOT PIC S9(11)V99 COMP-3.
10 WS-SUM-CREDIT-TOT PIC S9(11)V99 COMP-3.
*----------------------------------------------------------------
* GRAND TOTALS
*----------------------------------------------------------------
01 WS-GRAND-TOTALS.
05 WS-TOTAL-RECORDS PIC S9(7) COMP-3 VALUE 0.
05 WS-TOTAL-VALID PIC S9(7) COMP-3 VALUE 0.
05 WS-TOTAL-ERRORS PIC S9(7) COMP-3 VALUE 0.
05 WS-GRAND-DEBITS PIC S9(13)V99 COMP-3
VALUE ZERO.
05 WS-GRAND-CREDITS PIC S9(13)V99 COMP-3
VALUE ZERO.
*----------------------------------------------------------------
* ERROR THRESHOLD
*----------------------------------------------------------------
01 WS-ERROR-THRESHOLD PIC 9V99 VALUE 0.01.
01 WS-ERROR-RATE PIC 9V9(4) COMP-3.
*----------------------------------------------------------------
* RETURN CODE
*----------------------------------------------------------------
01 WS-RETURN-CODE PIC S9(4) COMP VALUE 0.
*----------------------------------------------------------------
* REPORT LINES
*----------------------------------------------------------------
01 WS-RPT-HEADER-1.
05 FILLER PIC X(50) VALUE
"PIONEER NATIONAL BANK - DAILY TRANSACTION SUMMARY".
05 FILLER PIC X(20) VALUE SPACES.
05 FILLER PIC X(6) VALUE "DATE: ".
05 WS-HDR-DATE PIC X(10).
05 FILLER PIC X(46) VALUE SPACES.
01 WS-RPT-COL-HDR.
05 FILLER PIC X(6) VALUE "TYPE ".
05 FILLER PIC X(17) VALUE "DESCRIPTION ".
05 FILLER PIC X(12) VALUE " COUNT".
05 FILLER PIC X(20) VALUE " DEBIT TOTAL ".
05 FILLER PIC X(20) VALUE " CREDIT TOTAL ".
05 FILLER PIC X(57) VALUE SPACES.
01 WS-RPT-DETAIL.
05 WS-DTL-TYPE PIC X(4).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-DESC PIC X(15).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-COUNT PIC ZZ,ZZZ,ZZ9.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-DEBITS PIC $Z,ZZZ,ZZZ,ZZ9.99-.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-CREDITS PIC $Z,ZZZ,ZZZ,ZZ9.99-.
05 FILLER PIC X(57) VALUE SPACES.
01 WS-RPT-TOTAL-LINE.
05 FILLER PIC X(6) VALUE "TOTALS".
05 FILLER PIC X(17) VALUE SPACES.
05 WS-TOT-COUNT PIC ZZ,ZZZ,ZZ9.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-TOT-DEBITS PIC $Z,ZZZ,ZZZ,ZZ9.99-.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-TOT-CREDITS PIC $Z,ZZZ,ZZZ,ZZ9.99-.
05 FILLER PIC X(57) VALUE SPACES.
01 WS-ERR-DETAIL.
05 WS-ERR-TRANS-ID PIC 9(12).
05 FILLER PIC X(1) VALUE SPACE.
05 WS-ERR-ACCOUNT PIC 9(10).
05 FILLER PIC X(1) VALUE SPACE.
05 WS-ERR-TYPE PIC X(2).
05 FILLER PIC X(1) VALUE SPACE.
05 WS-ERR-AMOUNT PIC -(7)9.99.
05 FILLER PIC X(1) VALUE SPACE.
05 WS-ERR-REASON PIC X(30).
05 FILLER PIC X(55) VALUE SPACES.
01 WS-DISP-COUNT PIC Z,ZZZ,ZZ9.
PROCEDURE DIVISION.
0000-MAIN-CONTROL.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-TRANSACTIONS
UNTIL END-OF-FILE OR ABORT-PROCESSING
PERFORM 3000-WRITE-SUMMARY-REPORT
PERFORM 4000-RECONCILE-TOTALS
PERFORM 9000-FINALIZE
MOVE WS-RETURN-CODE TO RETURN-CODE
STOP RUN
.
1000-INITIALIZE.
DISPLAY "DLYTRXN: Beginning daily transaction "
"processing"
* Initialize summary table
MOVE "DP" TO WS-SUM-TYPE-CODE(1)
MOVE "DEPOSITS" TO WS-SUM-TYPE-DESC(1)
MOVE "WD" TO WS-SUM-TYPE-CODE(2)
MOVE "WITHDRAWALS" TO WS-SUM-TYPE-DESC(2)
MOVE "TR" TO WS-SUM-TYPE-CODE(3)
MOVE "TRANSFERS" TO WS-SUM-TYPE-DESC(3)
MOVE "FE" TO WS-SUM-TYPE-CODE(4)
MOVE "FEES" TO WS-SUM-TYPE-DESC(4)
MOVE "IN" TO WS-SUM-TYPE-CODE(5)
MOVE "INTEREST" TO WS-SUM-TYPE-DESC(5)
MOVE "AD" TO WS-SUM-TYPE-CODE(6)
MOVE "ADJUSTMENTS" TO WS-SUM-TYPE-DESC(6)
PERFORM VARYING WS-TYPE-IDX FROM 1 BY 1
UNTIL WS-TYPE-IDX > 6
MOVE ZERO TO WS-SUM-COUNT(WS-TYPE-IDX)
MOVE ZERO TO WS-SUM-DEBIT-TOT(WS-TYPE-IDX)
MOVE ZERO TO WS-SUM-CREDIT-TOT(WS-TYPE-IDX)
END-PERFORM
* Open all files with status checking
OPEN INPUT TRANSACTION-FILE
IF NOT TRAN-OK
DISPLAY "DLYTRXN: FATAL - Cannot open "
"TRANFILE. Status: " WS-TRAN-STATUS
MOVE 16 TO WS-RETURN-CODE
SET ABORT-PROCESSING TO TRUE
STOP RUN
END-IF
OPEN OUTPUT SUMMARY-REPORT
IF NOT SUMR-OK
DISPLAY "DLYTRXN: FATAL - Cannot open "
"SUMRPT. Status: " WS-SUMR-STATUS
MOVE 16 TO WS-RETURN-CODE
SET ABORT-PROCESSING TO TRUE
STOP RUN
END-IF
OPEN OUTPUT ERROR-REPORT
IF NOT ERRR-OK
DISPLAY "DLYTRXN: FATAL - Cannot open "
"ERRRPT. Status: " WS-ERRR-STATUS
MOVE 16 TO WS-RETURN-CODE
SET ABORT-PROCESSING TO TRUE
STOP RUN
END-IF
* Prime the read
PERFORM 2100-READ-TRANSACTION
.
2000-PROCESS-TRANSACTIONS.
ADD 1 TO WS-TOTAL-RECORDS
* Validate the transaction
PERFORM 2200-VALIDATE-TRANSACTION
IF RECORD-VALID
PERFORM 2300-ACCUMULATE-TOTALS
ADD 1 TO WS-TOTAL-VALID
ELSE
PERFORM 2400-WRITE-ERROR
ADD 1 TO WS-TOTAL-ERRORS
* Check error threshold
IF WS-TOTAL-RECORDS > 100
COMPUTE WS-ERROR-RATE =
WS-TOTAL-ERRORS / WS-TOTAL-RECORDS
IF WS-ERROR-RATE > WS-ERROR-THRESHOLD
DISPLAY "DLYTRXN: ERROR threshold "
"exceeded. Aborting."
DISPLAY " Error rate: " WS-ERROR-RATE
MOVE 12 TO WS-RETURN-CODE
SET ABORT-PROCESSING TO TRUE
END-IF
END-IF
END-IF
* Read next record
PERFORM 2100-READ-TRANSACTION
.
2100-READ-TRANSACTION.
READ TRANSACTION-FILE
AT END
SET END-OF-FILE TO TRUE
NOT AT END
IF NOT TRAN-OK
DISPLAY "DLYTRXN: READ error on "
"TRANFILE. Status: "
WS-TRAN-STATUS
DISPLAY " Record: " WS-TOTAL-RECORDS
ADD 1 TO WS-TOTAL-ERRORS
END-IF
END-READ
.
2200-VALIDATE-TRANSACTION.
SET RECORD-VALID TO TRUE
* Check 1: Transaction type must be valid
MOVE 'N' TO WS-TYPE-FOUND
PERFORM VARYING WS-TYPE-IDX FROM 1 BY 1
UNTIL WS-TYPE-IDX > 6
IF FS-TR-TYPE = WS-VALID-TYPE(WS-TYPE-IDX)
MOVE 'Y' TO WS-TYPE-FOUND
END-IF
END-PERFORM
IF WS-TYPE-FOUND = 'N'
SET RECORD-INVALID TO TRUE
MOVE "INVALID TRANSACTION TYPE"
TO WS-ERROR-REASON
END-IF
* Check 2: Amount must not be zero
IF RECORD-VALID
IF FS-TR-AMOUNT = ZERO
SET RECORD-INVALID TO TRUE
MOVE "ZERO AMOUNT NOT ALLOWED"
TO WS-ERROR-REASON
END-IF
END-IF
* Check 3: Date must be valid
IF RECORD-VALID
IF FS-TR-MONTH < 01 OR FS-TR-MONTH > 12
SET RECORD-INVALID TO TRUE
MOVE "INVALID MONTH IN DATE"
TO WS-ERROR-REASON
END-IF
END-IF
IF RECORD-VALID
IF FS-TR-DAY < 01 OR FS-TR-DAY > 31
SET RECORD-INVALID TO TRUE
MOVE "INVALID DAY IN DATE"
TO WS-ERROR-REASON
END-IF
END-IF
* Check 4: Account number must not be all zeros
IF RECORD-VALID
IF FS-TR-ACCOUNT-NO = ZERO
SET RECORD-INVALID TO TRUE
MOVE "INVALID ACCOUNT NUMBER"
TO WS-ERROR-REASON
END-IF
END-IF
.
2300-ACCUMULATE-TOTALS.
* -------------------------------------------------------
* Find the matching summary entry and add the amount
* to the appropriate total (debit or credit).
* -------------------------------------------------------
PERFORM VARYING WS-TYPE-IDX FROM 1 BY 1
UNTIL WS-TYPE-IDX > 6
IF FS-TR-TYPE = WS-SUM-TYPE-CODE(WS-TYPE-IDX)
ADD 1 TO WS-SUM-COUNT(WS-TYPE-IDX)
IF FS-TR-AMOUNT < ZERO
ADD FS-TR-AMOUNT TO
WS-SUM-DEBIT-TOT(WS-TYPE-IDX)
ADD FS-TR-AMOUNT TO WS-GRAND-DEBITS
ELSE
ADD FS-TR-AMOUNT TO
WS-SUM-CREDIT-TOT(WS-TYPE-IDX)
ADD FS-TR-AMOUNT TO WS-GRAND-CREDITS
END-IF
END-IF
END-PERFORM
.
2400-WRITE-ERROR.
INITIALIZE WS-ERR-DETAIL
MOVE FS-TR-TRANS-ID TO WS-ERR-TRANS-ID
MOVE FS-TR-ACCOUNT-NO TO WS-ERR-ACCOUNT
MOVE FS-TR-TYPE TO WS-ERR-TYPE
MOVE FS-TR-AMOUNT TO WS-ERR-AMOUNT
MOVE WS-ERROR-REASON TO WS-ERR-REASON
MOVE WS-ERR-DETAIL TO FS-ERR-LINE
WRITE FS-ERR-LINE
IF NOT ERRR-OK
DISPLAY "DLYTRXN: WRITE error on ERRRPT. "
"Status: " WS-ERRR-STATUS
END-IF
.
3000-WRITE-SUMMARY-REPORT.
* -------------------------------------------------------
* Write the summary report with headers, detail lines
* for each transaction type, and grand totals.
* -------------------------------------------------------
* Header
MOVE WS-RPT-HEADER-1 TO FS-SUM-LINE
WRITE FS-SUM-LINE
MOVE SPACES TO FS-SUM-LINE
WRITE FS-SUM-LINE
MOVE WS-RPT-COL-HDR TO FS-SUM-LINE
WRITE FS-SUM-LINE
MOVE ALL "-" TO FS-SUM-LINE
WRITE FS-SUM-LINE
* Detail lines for each type
PERFORM VARYING WS-TYPE-IDX FROM 1 BY 1
UNTIL WS-TYPE-IDX > 6
INITIALIZE WS-RPT-DETAIL
MOVE WS-SUM-TYPE-CODE(WS-TYPE-IDX)
TO WS-DTL-TYPE
MOVE WS-SUM-TYPE-DESC(WS-TYPE-IDX)
TO WS-DTL-DESC
MOVE WS-SUM-COUNT(WS-TYPE-IDX)
TO WS-DTL-COUNT
MOVE WS-SUM-DEBIT-TOT(WS-TYPE-IDX)
TO WS-DTL-DEBITS
MOVE WS-SUM-CREDIT-TOT(WS-TYPE-IDX)
TO WS-DTL-CREDITS
MOVE WS-RPT-DETAIL TO FS-SUM-LINE
WRITE FS-SUM-LINE
IF NOT SUMR-OK
DISPLAY "DLYTRXN: WRITE error on SUMRPT."
" Status: " WS-SUMR-STATUS
END-IF
END-PERFORM
* Grand total line
MOVE ALL "-" TO FS-SUM-LINE
WRITE FS-SUM-LINE
MOVE WS-TOTAL-VALID TO WS-TOT-COUNT
MOVE WS-GRAND-DEBITS TO WS-TOT-DEBITS
MOVE WS-GRAND-CREDITS TO WS-TOT-CREDITS
MOVE WS-RPT-TOTAL-LINE TO FS-SUM-LINE
WRITE FS-SUM-LINE
.
4000-RECONCILE-TOTALS.
* -------------------------------------------------------
* Display control totals for reconciliation and set
* the return code based on processing results.
* -------------------------------------------------------
DISPLAY " "
DISPLAY "DLYTRXN: ===== CONTROL TOTALS ====="
MOVE WS-TOTAL-RECORDS TO WS-DISP-COUNT
DISPLAY " Total records read: " WS-DISP-COUNT
MOVE WS-TOTAL-VALID TO WS-DISP-COUNT
DISPLAY " Valid records: " WS-DISP-COUNT
MOVE WS-TOTAL-ERRORS TO WS-DISP-COUNT
DISPLAY " Error records: " WS-DISP-COUNT
DISPLAY " Grand debit total: " WS-GRAND-DEBITS
DISPLAY " Grand credit total: " WS-GRAND-CREDITS
DISPLAY "DLYTRXN: =========================="
* Set return code
IF ABORT-PROCESSING
DISPLAY "DLYTRXN: Processing aborted. RC=12"
MOVE 12 TO WS-RETURN-CODE
ELSE IF WS-TOTAL-ERRORS > ZERO
DISPLAY "DLYTRXN: Completed with errors. RC=4"
MOVE 4 TO WS-RETURN-CODE
ELSE
DISPLAY "DLYTRXN: Completed successfully. RC=0"
MOVE 0 TO WS-RETURN-CODE
END-IF
.
9000-FINALIZE.
CLOSE TRANSACTION-FILE
IF NOT TRAN-OK AND NOT TRAN-EOF
DISPLAY "DLYTRXN: CLOSE error on TRANFILE. "
"Status: " WS-TRAN-STATUS
END-IF
CLOSE SUMMARY-REPORT
CLOSE ERROR-REPORT
.
The Companion JCL
//DLYTRXNJ JOB (ACCT),'DAILY TRANS PROC',
// CLASS=A,MSGCLASS=X,
// MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*================================================================
//* JOB: DLYTRXNJ
//* PURPOSE: EXECUTE THE DAILY TRANSACTION PROCESSING PROGRAM
//* READS DAILY TRANSACTION FILE AND PRODUCES
//* SUMMARY AND ERROR REPORTS
//* SCHEDULE: DAILY AT 22:00 CST
//*================================================================
//*
//*-------- STEP 1: EXECUTE THE TRANSACTION PROCESSOR -------------
//*
//STEP01 EXEC PGM=DLYTRXN
//STEPLIB DD DSN=PIONEER.PROD.LOADLIB,DISP=SHR
//*
//* INPUT: DAILY TRANSACTION FILE
//*
//TRANFILE DD DSN=PIONEER.DAILY.TRANS.D&LYYMMDD,
// DISP=SHR,
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920)
//*
//* OUTPUT: SUMMARY REPORT
//*
//SUMRPT DD DSN=PIONEER.DAILY.SUMMARY.D&LYYMMDD,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=27984)
//*
//* OUTPUT: ERROR REPORT
//*
//ERRRPT DD DSN=PIONEER.DAILY.ERRORS.D&LYYMMDD,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=27984)
//*
//* SYSOUT FOR DISPLAY MESSAGES
//*
//SYSOUT DD SYSOUT=*
//*
//*-------- STEP 2: PRINT SUMMARY REPORT TO SPOOL -----------------
//*
//STEP02 EXEC PGM=IEBGENER,
// COND=(8,LT,STEP01)
//SYSUT1 DD DSN=PIONEER.DAILY.SUMMARY.D&LYYMMDD,
// DISP=SHR
//SYSUT2 DD SYSOUT=A,
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=27984)
//SYSPRINT DD SYSOUT=*
//SYSIN DD DUMMY
//*
//*-------- STEP 3: PRINT ERROR REPORT IF ERRORS FOUND ------------
//*
//STEP03 EXEC PGM=IEBGENER,
// COND=(0,EQ,STEP01)
//SYSUT1 DD DSN=PIONEER.DAILY.ERRORS.D&LYYMMDD,
// DISP=SHR
//SYSUT2 DD SYSOUT=A,
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=27984)
//SYSPRINT DD SYSOUT=*
//SYSIN DD DUMMY
Solution Walkthrough
File Status Checking: The Non-Negotiable Practice
Every file in the program has a dedicated FILE STATUS variable, and every I/O operation checks it immediately. The pattern is consistent throughout:
OPEN INPUT TRANSACTION-FILE
IF NOT TRAN-OK
DISPLAY "DLYTRXN: FATAL - Cannot open "
"TRANFILE. Status: " WS-TRAN-STATUS
MOVE 16 TO WS-RETURN-CODE
STOP RUN
END-IF
The file status "00" means success. "10" means end-of-file. Any value from "30" through "99" indicates an error. The 88-level conditions make these checks readable and self-documenting.
The Priming Read Pattern
The program uses the classic "priming read" pattern for sequential file processing:
- Read the first record before entering the processing loop
- Process the current record
- Read the next record at the end of the processing loop
- The loop terminates when EOF is detected
This pattern ensures that the EOF condition is tested before attempting to process a record, and that the record in the buffer is always a valid, unprocessed record when the loop body executes.
Error Threshold Abort
The program tracks the error rate and aborts if errors exceed 1% of records processed. This prevents a corrupted input file from producing a misleading summary report:
IF WS-TOTAL-RECORDS > 100
COMPUTE WS-ERROR-RATE =
WS-TOTAL-ERRORS / WS-TOTAL-RECORDS
IF WS-ERROR-RATE > WS-ERROR-THRESHOLD
SET ABORT-PROCESSING TO TRUE
END-IF
END-IF
The check is only performed after 100 records to avoid premature abort on a small number of early errors.
Return Code Communication via JCL
The COBOL program sets RETURN-CODE (a special register) to communicate results to JCL:
- RC=0: Clean completion, all records valid
- RC=4: Completed with some errors (within threshold)
- RC=8: Error threshold exceeded
- RC=12: Aborted due to excessive errors
- RC=16: Fatal error (file open failure)
The JCL uses COND parameters to control subsequent steps based on this return code. Step 2 runs only if Step 1 returned less than 8 (success or minor errors). Step 3 (error report printing) runs only if Step 1 returned a non-zero code.
Lessons Learned
1. File Status Checking Is Not Optional
Every COBOL program that processes files in production must check FILE STATUS after every I/O operation. Without it, a file open failure causes unpredictable behavior rather than a clean abort. Marcus's rule: "If it touches a file, check the status."
2. The Priming Read Pattern Eliminates Off-by-One Errors
The priming read ensures that end-of-file is detected before processing, not during. Without it, the program might attempt to process the EOF indicator as if it were a data record.
3. Return Codes Bridge COBOL and JCL
The RETURN-CODE special register is the primary mechanism for a COBOL program to communicate its completion status to the JCL job stream. Well-defined return code conventions enable automated job scheduling and error handling.
4. Error Thresholds Prevent Garbage-In, Garbage-Out
A batch program that blindly processes a corrupted file and produces totals based on partial data is worse than one that aborts early and clearly. The threshold-based abort protects downstream systems from bad data.
5. Control Totals Enable Reconciliation
The grand debit and credit totals displayed at the end of the run enable the operations team to verify that the program processed the expected volume. If the input file was supposed to contain 250,000 records and the program reports 200,000, something went wrong upstream.
Discussion Questions
-
The program uses
BLOCK CONTAINS 0 RECORDSon the transaction file FD. What does this mean, and why is it preferable to specifying a fixed block size in the COBOL program? -
The validation logic uses a sequential check pattern where each check is only performed if the previous check passed. What are the trade-offs of this approach versus performing all checks and accumulating multiple error reasons per record?
-
The JCL uses
&LYYMMDDas a symbolic parameter in data set names. What is this, and how does it help manage daily processing? What happens if the job is rerun on the same day? -
The error report is written to a sequential file and then printed in a subsequent JCL step. Why not write directly to SYSOUT from the COBOL program? What advantage does the intermediate file provide?
-
The return code 16 is used for fatal errors (file open failure). Why is this value chosen rather than, say, 99 or -1? What is the significance of return code values in the z/OS environment?
-
The program accumulates debits and credits separately rather than computing a net total. Why is this important for reconciliation? What would a net-only approach miss?
-
If the transaction file contained 500,000 records and the program needed to process them within a 30-minute batch window, what performance considerations would be important? How do BLOCK CONTAINS, BUFFERING, and I/O techniques affect throughput?