> "The best way to learn COBOL is to build something real. Not a textbook exercise — something you could walk into a data center and explain to a production support team."
In This Chapter
- Introduction: From Exercises to Engineering
- Phase 1: Requirements Analysis
- Phase 2: Data Design
- Phase 3: Program Design
- Phase 4: Implementation
- Phase 5: Testing Strategy
- Phase 6: Deployment JCL
- Phase 7: Bringing It All Together
- The Architecture of Real-World COBOL Systems
- Common Mistakes and How to Avoid Them
- Deep Dive: The BMS Map for ACCT-INQ
- Understanding VSAM Internals for Banking Applications
- Data Integrity Across Programs
- Working with the Student Mainframe Lab
- Extending the System
- Understanding the Complete Data Flow
- Advanced Topics: What Production Systems Add
- Summary
- Chapter Summary
Chapter 43: Capstone 1 — Building a Complete Banking Transaction System
"The best way to learn COBOL is to build something real. Not a textbook exercise — something you could walk into a data center and explain to a production support team." — Maria Chen, to Derek Washington, on his first day
Introduction: From Exercises to Engineering
For forty-two chapters, you have studied COBOL in pieces. You have learned how to define data structures, process sequential files, work with VSAM, build CICS transactions, generate reports, handle errors, and design modular programs. Each chapter gave you a tool. This capstone asks you to use them all at once.
This is not a toy project. The system you will build in this chapter — a complete banking transaction processing system — mirrors the architecture of real-world COBOL applications that process millions of transactions every day. The programs are smaller than production code, certainly, but the patterns are identical. The design decisions you make here are the same decisions that Maria Chen made when she helped redesign GlobalBank's core system fifteen years ago, and the same decisions that Derek Washington will face in his career.
💡 Why a Banking System? Banking is COBOL's ancestral home. The first commercial COBOL programs processed financial transactions, and today, COBOL still handles an estimated 95% of ATM transactions, 80% of in-person transactions, and $3 trillion in daily commerce. When you build a banking system in COBOL, you are not practicing an academic exercise — you are working in the language's native domain. The patterns you learn here — VSAM keyed access, batch sequential processing, CICS online inquiry, audit trail management, JCL job orchestration — are the same patterns that power the financial systems you interact with every day, from ATM withdrawals to direct deposits to credit card authorizations.
What You Will Build
By the end of this capstone, you will have designed, coded, tested, and deployed a complete banking transaction system consisting of five integrated programs:
| Program | Function | Type | Key Skills |
|---|---|---|---|
| ACCT-LOAD | Initial account master file load | Batch | VSAM KSDS, sequential input, validation |
| TXN-PROC | Batch transaction processor | Batch | File matching, business logic, audit trail |
| ACCT-INQ | Online account inquiry | CICS | BMS maps, VSAM browsing, screen design |
| RPT-STMT | Statement generator | Batch | Report writing, control breaks, formatting |
| UTIL-DATE | Date utility subprogram | Called | Subprogram design, date arithmetic, reuse |
These programs share data through a common VSAM KSDS file (the account master), use shared copybooks for data definitions, and follow consistent error handling and logging conventions. Together, they form a cohesive system — not five unrelated programs. Building a cohesive system from multiple programs is fundamentally different from writing a single standalone program. It requires design discipline, interface agreements, and testing strategies that go beyond individual program correctness.
The Assignment: Derek's First Real Project
At GlobalBank, Maria Chen has decided that Derek Washington is ready for his first integration assignment. After six months of maintenance work and small enhancements, Derek needs to prove he can design a system from scratch.
"Here's what I want you to build," Maria says, sliding a requirements document across her desk. "A simplified version of our account processing system. Not because we need it — we already have one. But because building it will teach you things that fixing bugs never will."
Derek picks up the document. It is three pages long.
"Three pages? Our production system has a requirements binder that fills a shelf."
Maria smiles. "Every production system started with three pages. The shelf came later."
"How long do I have?" Derek asks.
"Four weeks. That's not enough time if you start coding immediately. It's plenty if you spend the first week designing. The biggest mistake new developers make is writing code before they understand the problem."
Derek will learn that Maria is right — the design time is not wasted time. The hours he spends on copybooks, data dictionaries, and structure charts will save days of debugging later. This lesson, more than any specific COBOL technique, is what separates a programmer from a software engineer.
⚖️ The Capstone Philosophy. This chapter follows a complete software development lifecycle. We begin with requirements analysis, move through data design and program design, implement each program, build a testing strategy, and create deployment JCL. This mirrors how professional COBOL systems are actually built — not all at once, but in methodical phases. Each phase builds on the previous one, and each produces artifacts that the team can review, discuss, and verify before moving forward.
Phase 1: Requirements Analysis
Understanding the Business Problem
Before writing a single line of code, Derek must understand what the system needs to do. Maria has given him a simplified version of GlobalBank's requirements, suitable for a training project but structurally identical to the real system.
Business Context: GlobalBank maintains checking and savings accounts for approximately 50,000 customers. Each business day, the bank processes transactions (deposits, withdrawals, transfers, fees) against these accounts. At month-end, the bank generates statements for every active account. At any time during the business day, customer service representatives need to look up account information.
Functional Requirements:
-
Account Master File Creation: The system must be able to load account records from a sorted sequential file into a VSAM KSDS. Each account record contains: account number, customer name, account type (checking/savings), current balance, available balance, status (active/closed/frozen), date opened, date of last activity, and YTD interest.
-
Transaction Processing: The system must process a daily sequential transaction file against the account master. Supported transaction types are deposits (CR), withdrawals (DR), transfers (XF), and fee assessments (FE). Each transaction must be validated, applied, and logged to an audit trail file. Insufficient funds must be handled gracefully. The system must produce a transaction summary report.
-
Online Inquiry: Customer service representatives must be able to look up any account by account number and see current balance, available balance, status, and recent activity. The inquiry must be fast (sub-second response) and must not lock the account record.
-
Statement Generation: At month-end, the system must generate a formatted statement for every active account, showing opening balance, all transactions for the period, fees, interest, and closing balance.
-
Date Utilities: All date processing must handle leap years, validate date ranges, and compute days-between for interest calculations. Date logic must be centralized in a reusable subprogram.
📊 Non-Functional Requirements. Production systems always have non-functional requirements: performance targets, availability windows, recovery procedures, security controls. Derek's training system focuses on functional correctness, but Maria reminds him: "In production, the code that works but runs for six hours instead of thirty minutes is a bug, not a feature."
Creating the Data Dictionary
Before designing files or programs, Derek creates a data dictionary — a single source of truth for every data element in the system.
| Field Name | PIC Clause | Description | Valid Values |
|---|---|---|---|
| ACCT-NUMBER | X(10) | Unique account identifier | Alpha-numeric, not spaces |
| CUST-LAST-NAME | X(25) | Customer surname | Non-blank |
| CUST-FIRST-NAME | X(20) | Customer given name | Non-blank |
| CUST-MIDDLE-INIT | X(01) | Middle initial | A-Z or space |
| ACCT-TYPE | X(01) | Account classification | C=Checking, S=Savings |
| ACCT-STATUS | X(01) | Current account state | A=Active, C=Closed, F=Frozen |
| CURRENT-BALANCE | S9(9)V99 | Ledger balance | Signed, can be negative |
| AVAIL-BALANCE | S9(9)V99 | Available balance | Signed, can be negative |
| DATE-OPENED | 9(08) | Account opening date | YYYYMMDD format |
| DATE-LAST-ACTIVITY | 9(08) | Last transaction date | YYYYMMDD format |
| YTD-INTEREST | S9(7)V99 | Year-to-date interest | Signed |
| TXN-TYPE | X(02) | Transaction classification | CR, DR, XF, FE |
| TXN-AMOUNT | S9(9)V99 | Transaction amount | Positive for CR, FE; positive for DR (sign applied by logic) |
| TXN-DATE | 9(08) | Transaction date | YYYYMMDD format |
| TXN-TIME | 9(06) | Transaction time | HHMMSS format |
| TXN-STATUS | X(01) | Processing result | S=Success, F=Failed, P=Pending |
⚠️ A Note on PIC Clauses. Notice that CURRENT-BALANCE uses S9(9)V99, not 9(9)V99. The S is critical. Bank accounts can have negative balances (overdrafts), and if you omit the sign, COBOL will silently treat negative numbers as positive. Maria once spent three days tracking a balancing error caused by a missing S in a PIC clause. "The compiler didn't complain," she tells Derek. "It just did exactly what we told it to do — which wasn't what we meant."
Phase 2: Data Design
The Account Master Copybook
The first design artifact is the copybook that defines the account master record. Every program in the system will use this copybook, ensuring consistent data definitions across the entire application.
*================================================================*
* COPYBOOK: ACCTMSTR *
* Account Master Record Layout *
* Used by: ACCT-LOAD, TXN-PROC, ACCT-INQ, RPT-STMT *
* VSAM KSDS - Primary Key: ACCT-NUMBER (positions 1-10) *
*================================================================*
01 ACCOUNT-MASTER-RECORD.
05 ACCT-NUMBER PIC X(10).
05 ACCT-CUSTOMER-INFO.
10 CUST-LAST-NAME PIC X(25).
10 CUST-FIRST-NAME PIC X(20).
10 CUST-MIDDLE-INIT PIC X(01).
05 ACCT-DETAILS.
10 ACCT-TYPE PIC X(01).
88 ACCT-CHECKING VALUE 'C'.
88 ACCT-SAVINGS VALUE 'S'.
10 ACCT-STATUS PIC X(01).
88 ACCT-ACTIVE VALUE 'A'.
88 ACCT-CLOSED VALUE 'C'.
88 ACCT-FROZEN VALUE 'F'.
88 ACCT-VALID-STATUS VALUE 'A' 'C' 'F'.
10 CURRENT-BALANCE PIC S9(9)V99 COMP-3.
10 AVAIL-BALANCE PIC S9(9)V99 COMP-3.
10 DATE-OPENED PIC 9(08).
10 DATE-LAST-ACTIVITY PIC 9(08).
10 YTD-INTEREST PIC S9(7)V99 COMP-3.
05 ACCT-COUNTERS.
10 MTD-TXN-COUNT PIC S9(5) COMP-3.
10 MTD-DEBIT-TOTAL PIC S9(9)V99 COMP-3.
10 MTD-CREDIT-TOTAL PIC S9(9)V99 COMP-3.
05 FILLER PIC X(20).
Several design decisions deserve explanation:
COMP-3 for monetary fields. Packed decimal (COMP-3) is the standard encoding for monetary values in mainframe COBOL. It saves storage space (a S9(9)V99 field takes 6 bytes in COMP-3 versus 12 bytes in display format) and avoids the floating-point precision issues that plague binary representations. When you are dealing with money, COMP-3 is not optional — it is required.
88-level condition names. Using condition names like ACCT-CHECKING and ACCT-ACTIVE makes the code self-documenting. Instead of writing IF ACCT-TYPE = 'C', programs can write IF ACCT-CHECKING, which reads naturally and reduces the chance of mistyping a literal value.
FILLER at the end. The 20-byte FILLER provides expansion room. When production systems need to add fields — and they always do — the FILLER can be redefined without changing the record length. Maria insists on FILLER in every record layout. "It's cheap insurance," she says. "Changing a record length in production requires reloading every file and recompiling every program. Twenty bytes of FILLER costs you nothing and saves you a weekend."
Group-level organization. Notice how related fields are grouped under ACCT-CUSTOMER-INFO, ACCT-DETAILS, and ACCT-COUNTERS. This is not just for readability — it allows programs to MOVE entire groups at once and makes the copybook self-documenting.
The Transaction Record Copybook
*================================================================*
* COPYBOOK: TXNREC *
* Transaction Record Layout *
* Used by: TXN-PROC, RPT-STMT *
*================================================================*
01 TRANSACTION-RECORD.
05 TXN-ACCT-NUMBER PIC X(10).
05 TXN-TYPE PIC X(02).
88 TXN-CREDIT VALUE 'CR'.
88 TXN-DEBIT VALUE 'DR'.
88 TXN-TRANSFER VALUE 'XF'.
88 TXN-FEE VALUE 'FE'.
88 TXN-VALID-TYPE VALUE 'CR' 'DR' 'XF' 'FE'.
05 TXN-AMOUNT PIC S9(9)V99 COMP-3.
05 TXN-DATE PIC 9(08).
05 TXN-TIME PIC 9(06).
05 TXN-DESCRIPTION PIC X(30).
05 TXN-REF-NUMBER PIC X(12).
05 TXN-TARGET-ACCT PIC X(10).
05 TXN-STATUS PIC X(01).
88 TXN-SUCCESS VALUE 'S'.
88 TXN-FAILED VALUE 'F'.
88 TXN-PENDING VALUE 'P'.
05 TXN-REASON-CODE PIC X(04).
05 FILLER PIC X(10).
The TXN-TARGET-ACCT field supports transfer transactions. When TXN-TYPE is 'XF', the amount is debited from TXN-ACCT-NUMBER and credited to TXN-TARGET-ACCT. For all other transaction types, this field is spaces.
The Audit Trail Copybook
*================================================================*
* COPYBOOK: AUDITREC *
* Audit Trail Record Layout *
* Used by: TXN-PROC *
*================================================================*
01 AUDIT-TRAIL-RECORD.
05 AUD-TIMESTAMP.
10 AUD-DATE PIC 9(08).
10 AUD-TIME PIC 9(06).
05 AUD-PROGRAM-ID PIC X(08).
05 AUD-ACCT-NUMBER PIC X(10).
05 AUD-ACTION PIC X(03).
88 AUD-READ VALUE 'RD '.
88 AUD-UPDATE VALUE 'UPD'.
88 AUD-INSERT VALUE 'INS'.
88 AUD-DELETE VALUE 'DEL'.
05 AUD-TXN-TYPE PIC X(02).
05 AUD-TXN-AMOUNT PIC S9(9)V99 COMP-3.
05 AUD-BEFORE-BALANCE PIC S9(9)V99 COMP-3.
05 AUD-AFTER-BALANCE PIC S9(9)V99 COMP-3.
05 AUD-STATUS PIC X(01).
05 AUD-REASON-CODE PIC X(04).
05 AUD-OPERATOR-ID PIC X(08).
05 FILLER PIC X(20).
🔴 Why Audit Trails Matter. In financial systems, every change to an account must be traceable. Auditors, regulators, and fraud investigators need to reconstruct exactly what happened, when, and why. The audit trail is not a nice-to-have — in banking, it is a regulatory requirement. Derek's system is a training exercise, but the audit trail pattern is something he will use in every production system he works on.
VSAM File Design
The account master is a VSAM KSDS (Key-Sequenced Data Set). Derek must define it before any program can use it.
IDCAMS Definition:
//DEFCLUST EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE GLOBALBANK.ACCT.MASTER -
CLUSTER -
PURGE
SET MAXCC = 0
DEFINE CLUSTER -
(NAME(GLOBALBANK.ACCT.MASTER) -
INDEXED -
RECSZ(200 200) -
KEYS(10 0) -
FREESPACE(20 10) -
SHAREOPTIONS(2 3)) -
DATA -
(NAME(GLOBALBANK.ACCT.MASTER.DATA) -
CISZ(4096) -
CYLINDERS(5 1)) -
INDEX -
(NAME(GLOBALBANK.ACCT.MASTER.INDEX) -
CYLINDERS(1 1))
/*
Key design decisions in the VSAM definition:
KEYS(10 0): The primary key is the first 10 bytes (ACCT-NUMBER), starting at offset 0. This matches the copybook layout where ACCT-NUMBER is the first field.
FREESPACE(20 10): 20% free space in each CI (Control Interval) and 10% free CAs (Control Areas) are left empty. This accommodates insertions without excessive CI/CA splits. For a file that receives regular inserts (new accounts), free space is essential for performance.
SHAREOPTIONS(2 3): Share option 2 for cross-region allows multiple readers and one writer. Share option 3 for cross-system allows full sharing with the application responsible for integrity. These are appropriate for a file accessed by both batch and CICS.
CISZ(4096): A 4K CI size is a good default for random access patterns. Larger CI sizes improve sequential performance but waste buffer space for random reads. Since the online inquiry program (ACCT-INQ) uses random access, 4K is appropriate.
📊 Capacity Planning. With 200-byte records and 4096-byte CIs, each CI holds approximately 18 records (after accounting for CI control information). With 20% free space, each CI starts with about 14 records. For 50,000 accounts, the data component needs approximately 2,778 CIs, or about 46 tracks on a 3390 device. The 5-cylinder primary allocation (750 tracks) provides ample room for growth.
Phase 3: Program Design
System Architecture
Before coding individual programs, Derek creates a system architecture overview. Maria insists on this step: "If you can't draw the system on a whiteboard, you don't understand it well enough to code it."
The system flows as follows:
Sequential Input ──→ [ACCT-LOAD] ──→ VSAM KSDS (Account Master)
│
├──→ [ACCT-INQ] ──→ CICS Terminal
│
Transaction File ──→ [TXN-PROC] ──→ Updated VSAM KSDS
│ │
├──→ Audit Trail File
└──→ Transaction Summary Report
│
[RPT-STMT] ──→ Account Statements
│
[UTIL-DATE] ←── Called by TXN-PROC, RPT-STMT
Each program has a single, well-defined responsibility. ACCT-LOAD only loads accounts. TXN-PROC only processes transactions. ACCT-INQ only handles inquiries. RPT-STMT only generates statements. UTIL-DATE only performs date calculations. This is the separation of concerns principle, and it is just as important in COBOL as in any other language.
Structure Charts
For each program, Derek creates a structure chart — a hierarchical diagram showing the program's paragraph structure. Structure charts are the COBOL equivalent of a UML class diagram: they show the program's organization at a glance.
ACCT-LOAD Structure Chart:
0000-MAIN
│
┌────────────┼────────────┐
│ │ │
1000-INIT 2000-PROCESS 3000-TERM
│
┌────────┼────────┐
│ │ │
2100-READ 2200-VALIDATE 2300-WRITE
-INPUT -RECORD -VSAM
│
┌────────┼────────┐
│ │ │
2210-VAL 2220-VAL 2230-VAL
-ACCT-NUM -BALANCE -DATES
TXN-PROC Structure Chart:
0000-MAIN
│
┌──────────────┼──────────────┐
│ │ │
1000-INIT 2000-PROCESS 3000-TERM
│
┌───────────┼───────────┐
│ │ │
2100-READ 2200-PROCESS 2300-WRITE
-TRANS -TRANS -AUDIT
│
┌─────────────┼─────────────┐
│ │ │
2210-PROCESS 2220-PROCESS 2230-PROCESS
-CREDIT -DEBIT -TRANSFER
│
┌────────┼────────┐
│ │ │
2221-CHECK 2222-APPLY 2223-LOG
-FUNDS -DEBIT -RESULT
✅ Design Before Code. Notice that Derek has not written a single line of COBOL yet. He has defined requirements, created a data dictionary, designed copybooks, defined VSAM files, and drawn structure charts. Only now — with a clear understanding of what he is building and how the pieces fit together — will he start coding. This is not wasted time. This is how professional systems are built. As Maria says: "An hour of design saves a day of debugging."
Phase 4: Implementation
Program 1: ACCT-LOAD (Initial Account Master Load)
ACCT-LOAD reads a sorted sequential input file and loads records into the VSAM KSDS. It validates every record before writing it and produces a load statistics report.
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCT-LOAD.
*================================================================*
* Program: ACCT-LOAD *
* Purpose: Load account master VSAM KSDS from sequential input *
* Author: Derek Washington *
* Date: 2024-01-15 *
* System: GlobalBank Core Banking - Training *
*================================================================*
* Modification Log: *
* Date Author Description *
* ---------- --------- ----------------------------------------- *
* 2024-01-15 DW Initial creation *
*================================================================*
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
DECIMAL-POINT IS COMMA.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUT-FILE
ASSIGN TO ACCTIN
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-INPUT-STATUS.
SELECT ACCOUNT-MASTER
ASSIGN TO ACCTMSTR
ORGANIZATION IS INDEXED
ACCESS MODE IS SEQUENTIAL
RECORD KEY IS ACCT-NUMBER
FILE STATUS IS WS-VSAM-STATUS.
SELECT REPORT-FILE
ASSIGN TO RPTOUT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-REPORT-STATUS.
DATA DIVISION.
FILE SECTION.
FD INPUT-FILE
RECORDING MODE IS F
RECORD CONTAINS 200 CHARACTERS.
COPY ACCTMSTR REPLACING ==:PREFIX:== BY ==INP==.
FD ACCOUNT-MASTER
RECORD CONTAINS 200 CHARACTERS.
COPY ACCTMSTR.
FD REPORT-FILE
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 REPORT-LINE PIC X(132).
WORKING-STORAGE SECTION.
01 WS-FILE-STATUSES.
05 WS-INPUT-STATUS PIC X(02).
88 WS-INPUT-OK VALUE '00'.
88 WS-INPUT-EOF VALUE '10'.
05 WS-VSAM-STATUS PIC X(02).
88 WS-VSAM-OK VALUE '00'.
88 WS-VSAM-DUP-KEY VALUE '22'.
05 WS-REPORT-STATUS PIC X(02).
88 WS-REPORT-OK VALUE '00'.
01 WS-COUNTERS.
05 WS-RECORDS-READ PIC 9(07) VALUE ZERO.
05 WS-RECORDS-LOADED PIC 9(07) VALUE ZERO.
05 WS-RECORDS-REJECTED PIC 9(07) VALUE ZERO.
05 WS-DUPLICATE-KEYS PIC 9(07) VALUE ZERO.
01 WS-FLAGS.
05 WS-EOF-FLAG PIC X(01) VALUE 'N'.
88 WS-END-OF-FILE VALUE 'Y'.
88 WS-MORE-RECORDS VALUE 'N'.
05 WS-VALID-RECORD PIC X(01) VALUE 'Y'.
88 WS-RECORD-IS-VALID VALUE 'Y'.
88 WS-RECORD-INVALID VALUE 'N'.
01 WS-ERROR-MESSAGE PIC X(50).
01 WS-DATE-WORK.
05 WS-CURRENT-DATE PIC 9(08).
05 WS-CURRENT-TIME PIC 9(06).
*--- Report formatting work areas ---
01 WS-RPT-HEADER-1.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(40)
VALUE 'GLOBALBANK - ACCOUNT MASTER LOAD REPORT'.
05 FILLER PIC X(50) VALUE SPACES.
05 FILLER PIC X(06) VALUE 'DATE: '.
05 WH1-DATE PIC X(10).
05 FILLER PIC X(25) VALUE SPACES.
01 WS-RPT-DETAIL.
05 FILLER PIC X(01) VALUE SPACES.
05 WD-ACCT PIC X(10).
05 FILLER PIC X(02) VALUE SPACES.
05 WD-NAME PIC X(30).
05 FILLER PIC X(02) VALUE SPACES.
05 WD-TYPE PIC X(08).
05 FILLER PIC X(02) VALUE SPACES.
05 WD-STATUS-MSG PIC X(10).
05 FILLER PIC X(02) VALUE SPACES.
05 WD-BALANCE PIC -(9)9.99.
05 FILLER PIC X(02) VALUE SPACES.
05 WD-MESSAGE PIC X(40).
01 WS-RPT-SUMMARY.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(25) VALUE 'RECORDS READ: '.
05 WS-DISP-READ PIC Z,ZZZ,ZZ9.
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(25) VALUE 'RECORDS LOADED: '.
05 WS-DISP-LOAD PIC Z,ZZZ,ZZ9.
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(25) VALUE 'RECORDS REJECTED: '.
05 WS-DISP-REJ PIC Z,ZZZ,ZZ9.
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-RECORDS
UNTIL WS-END-OF-FILE
PERFORM 3000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT INPUT-FILE
OPEN OUTPUT ACCOUNT-MASTER
OPEN OUTPUT REPORT-FILE
IF NOT WS-INPUT-OK
DISPLAY 'ERROR OPENING INPUT FILE: ' WS-INPUT-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
IF NOT WS-VSAM-OK
DISPLAY 'ERROR OPENING VSAM FILE: ' WS-VSAM-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
ACCEPT WS-CURRENT-DATE FROM DATE YYYYMMDD
MOVE WS-CURRENT-DATE TO WH1-DATE
WRITE REPORT-LINE FROM WS-RPT-HEADER-1
AFTER ADVANCING PAGE
PERFORM 2100-READ-INPUT
.
2000-PROCESS-RECORDS.
ADD 1 TO WS-RECORDS-READ
PERFORM 2200-VALIDATE-RECORD
IF WS-RECORD-IS-VALID
PERFORM 2300-WRITE-VSAM
ELSE
ADD 1 TO WS-RECORDS-REJECTED
PERFORM 2400-WRITE-ERROR
END-IF
PERFORM 2100-READ-INPUT
.
2100-READ-INPUT.
READ INPUT-FILE INTO ACCOUNT-MASTER-RECORD
EVALUATE TRUE
WHEN WS-INPUT-OK
CONTINUE
WHEN WS-INPUT-EOF
SET WS-END-OF-FILE TO TRUE
WHEN OTHER
DISPLAY 'READ ERROR ON INPUT: ' WS-INPUT-STATUS
SET WS-END-OF-FILE TO TRUE
END-EVALUATE
.
2200-VALIDATE-RECORD.
SET WS-RECORD-IS-VALID TO TRUE
MOVE SPACES TO WS-ERROR-MESSAGE
PERFORM 2210-VALIDATE-ACCT-NUMBER
IF WS-RECORD-IS-VALID
PERFORM 2220-VALIDATE-BALANCES
END-IF
IF WS-RECORD-IS-VALID
PERFORM 2230-VALIDATE-DATES
END-IF
IF WS-RECORD-IS-VALID
PERFORM 2240-VALIDATE-STATUS
END-IF
.
2210-VALIDATE-ACCT-NUMBER.
IF ACCT-NUMBER = SPACES OR LOW-VALUES
SET WS-RECORD-INVALID TO TRUE
MOVE 'BLANK ACCOUNT NUMBER' TO WS-ERROR-MESSAGE
END-IF
.
2220-VALIDATE-BALANCES.
IF CURRENT-BALANCE NOT NUMERIC
SET WS-RECORD-INVALID TO TRUE
MOVE 'INVALID CURRENT BALANCE' TO WS-ERROR-MESSAGE
END-IF
.
2230-VALIDATE-DATES.
IF DATE-OPENED = ZEROES OR
DATE-OPENED < 19000101 OR
DATE-OPENED > WS-CURRENT-DATE
SET WS-RECORD-INVALID TO TRUE
MOVE 'INVALID DATE OPENED' TO WS-ERROR-MESSAGE
END-IF
.
2240-VALIDATE-STATUS.
IF NOT ACCT-VALID-STATUS
SET WS-RECORD-INVALID TO TRUE
MOVE 'INVALID ACCOUNT STATUS' TO WS-ERROR-MESSAGE
END-IF
.
2300-WRITE-VSAM.
WRITE ACCOUNT-MASTER-RECORD
EVALUATE TRUE
WHEN WS-VSAM-OK
ADD 1 TO WS-RECORDS-LOADED
MOVE 'LOADED' TO WD-STATUS-MSG
PERFORM 2500-WRITE-DETAIL
WHEN WS-VSAM-DUP-KEY
ADD 1 TO WS-DUPLICATE-KEYS
ADD 1 TO WS-RECORDS-REJECTED
MOVE 'DUPLICATE KEY' TO WS-ERROR-MESSAGE
PERFORM 2400-WRITE-ERROR
WHEN OTHER
DISPLAY 'VSAM WRITE ERROR: ' WS-VSAM-STATUS
' ACCT: ' ACCT-NUMBER
ADD 1 TO WS-RECORDS-REJECTED
MOVE 'VSAM WRITE ERROR' TO WS-ERROR-MESSAGE
PERFORM 2400-WRITE-ERROR
END-EVALUATE
.
2400-WRITE-ERROR.
MOVE ACCT-NUMBER TO WD-ACCT
STRING CUST-FIRST-NAME DELIMITED BY ' '
' ' DELIMITED BY SIZE
CUST-LAST-NAME DELIMITED BY ' '
INTO WD-NAME
END-STRING
MOVE 'REJECTED' TO WD-STATUS-MSG
MOVE WS-ERROR-MESSAGE TO WD-MESSAGE
WRITE REPORT-LINE FROM WS-RPT-DETAIL
AFTER ADVANCING 1 LINE
.
2500-WRITE-DETAIL.
MOVE ACCT-NUMBER TO WD-ACCT
STRING CUST-FIRST-NAME DELIMITED BY ' '
' ' DELIMITED BY SIZE
CUST-LAST-NAME DELIMITED BY ' '
INTO WD-NAME
END-STRING
MOVE CURRENT-BALANCE TO WD-BALANCE
MOVE SPACES TO WD-MESSAGE
WRITE REPORT-LINE FROM WS-RPT-DETAIL
AFTER ADVANCING 1 LINE
.
3000-TERMINATE.
MOVE WS-RECORDS-READ TO WS-DISP-READ
MOVE WS-RECORDS-LOADED TO WS-DISP-LOAD
MOVE WS-RECORDS-REJECTED TO WS-DISP-REJ
WRITE REPORT-LINE FROM WS-RPT-SUMMARY
AFTER ADVANCING 3 LINES
CLOSE INPUT-FILE
ACCOUNT-MASTER
REPORT-FILE
DISPLAY 'ACCT-LOAD COMPLETE: '
WS-RECORDS-LOADED ' LOADED, '
WS-RECORDS-REJECTED ' REJECTED'
IF WS-RECORDS-REJECTED > ZERO
MOVE 4 TO RETURN-CODE
ELSE
MOVE 0 TO RETURN-CODE
END-IF
.
Let us walk through the key design decisions in ACCT-LOAD:
Return codes communicate program status. The program sets RETURN-CODE to 0 for success, 4 for warnings (some records rejected), and 16 for fatal errors (file open failures). JCL can test these return codes to decide whether subsequent job steps should execute.
Validation cascades. Each validation paragraph checks one aspect of the record. If any validation fails, subsequent validations are skipped. This is more efficient than checking everything and also produces clearer error messages — the first error found is the one reported.
The EOF pattern. The classic COBOL pattern of reading ahead (performing the first READ in initialization, then reading at the end of the processing loop) ensures that end-of-file is detected before the record is processed. This is the standard idiom and any COBOL programmer will recognize it immediately.
🔗 Cross-Reference: File Status Codes. The file status handling here builds on the patterns introduced in Chapter 14 (File Status and Error Handling). Notice how every file operation checks its status code. This is defensive programming — never assume a file operation succeeded.
Program 2: TXN-PROC (Batch Transaction Processor)
TXN-PROC is the heart of the system. It reads a sequential transaction file, validates each transaction, applies it to the account master, and writes an audit trail record for every action.
IDENTIFICATION DIVISION.
PROGRAM-ID. TXN-PROC.
*================================================================*
* Program: TXN-PROC *
* Purpose: Process daily transactions against account master *
* Author: Derek Washington *
* Date: 2024-01-20 *
* System: GlobalBank Core Banking - Training *
*================================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANSACTION-FILE
ASSIGN TO TXNIN
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-TXN-STATUS.
SELECT ACCOUNT-MASTER
ASSIGN TO ACCTMSTR
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS ACCT-NUMBER
FILE STATUS IS WS-VSAM-STATUS.
SELECT AUDIT-FILE
ASSIGN TO AUDITOUT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-AUDIT-STATUS.
SELECT SUMMARY-REPORT
ASSIGN TO RPTOUT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
DATA DIVISION.
FILE SECTION.
FD TRANSACTION-FILE
RECORDING MODE IS F
RECORD CONTAINS 100 CHARACTERS.
COPY TXNREC.
FD ACCOUNT-MASTER
RECORD CONTAINS 200 CHARACTERS.
COPY ACCTMSTR.
FD AUDIT-FILE
RECORDING MODE IS F
RECORD CONTAINS 100 CHARACTERS.
COPY AUDITREC.
FD SUMMARY-REPORT
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 SUMMARY-LINE PIC X(132).
WORKING-STORAGE SECTION.
01 WS-FILE-STATUSES.
05 WS-TXN-STATUS PIC X(02).
88 WS-TXN-OK VALUE '00'.
88 WS-TXN-EOF VALUE '10'.
05 WS-VSAM-STATUS PIC X(02).
88 WS-VSAM-OK VALUE '00'.
88 WS-VSAM-NOT-FOUND VALUE '23'.
05 WS-AUDIT-STATUS PIC X(02).
05 WS-RPT-STATUS PIC X(02).
01 WS-COUNTERS.
05 WS-TXN-READ PIC 9(07) VALUE ZERO.
05 WS-TXN-APPLIED PIC 9(07) VALUE ZERO.
05 WS-TXN-REJECTED PIC 9(07) VALUE ZERO.
05 WS-CREDITS-APPLIED PIC 9(07) VALUE ZERO.
05 WS-DEBITS-APPLIED PIC 9(07) VALUE ZERO.
05 WS-TRANSFERS-APPLIED PIC 9(07) VALUE ZERO.
05 WS-FEES-APPLIED PIC 9(07) VALUE ZERO.
05 WS-TOTAL-CREDITS PIC S9(11)V99 COMP-3
VALUE ZERO.
05 WS-TOTAL-DEBITS PIC S9(11)V99 COMP-3
VALUE ZERO.
01 WS-WORK-FIELDS.
05 WS-SAVE-BALANCE PIC S9(9)V99 COMP-3.
05 WS-NEW-BALANCE PIC S9(9)V99 COMP-3.
05 WS-REJECT-REASON PIC X(04).
05 WS-REJECT-MSG PIC X(40).
01 WS-FLAGS.
05 WS-EOF-FLAG PIC X(01) VALUE 'N'.
88 WS-END-OF-FILE VALUE 'Y'.
88 WS-MORE-RECORDS VALUE 'N'.
05 WS-TXN-VALID PIC X(01) VALUE 'Y'.
88 WS-TRANSACTION-OK VALUE 'Y'.
88 WS-TRANSACTION-BAD VALUE 'N'.
01 WS-DATE-WORK.
05 WS-CURRENT-DATE PIC 9(08).
05 WS-CURRENT-TIME PIC 9(06).
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-TRANSACTIONS
UNTIL WS-END-OF-FILE
PERFORM 3000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT TRANSACTION-FILE
OPEN I-O ACCOUNT-MASTER
OPEN OUTPUT AUDIT-FILE
OPEN OUTPUT SUMMARY-REPORT
IF NOT WS-TXN-OK
DISPLAY 'FATAL: CANNOT OPEN TRANSACTION FILE: '
WS-TXN-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
IF NOT WS-VSAM-OK
DISPLAY 'FATAL: CANNOT OPEN ACCOUNT MASTER: '
WS-VSAM-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
ACCEPT WS-CURRENT-DATE FROM DATE YYYYMMDD
ACCEPT WS-CURRENT-TIME FROM TIME
DISPLAY 'TXN-PROC STARTED AT ' WS-CURRENT-DATE
' ' WS-CURRENT-TIME
PERFORM 2100-READ-TRANSACTION
.
2000-PROCESS-TRANSACTIONS.
ADD 1 TO WS-TXN-READ
PERFORM 2200-VALIDATE-TRANSACTION
IF WS-TRANSACTION-OK
PERFORM 2300-APPLY-TRANSACTION
ELSE
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
END-IF
PERFORM 2100-READ-TRANSACTION
.
2100-READ-TRANSACTION.
READ TRANSACTION-FILE
EVALUATE TRUE
WHEN WS-TXN-OK
CONTINUE
WHEN WS-TXN-EOF
SET WS-END-OF-FILE TO TRUE
WHEN OTHER
DISPLAY 'READ ERROR ON TXN FILE: ' WS-TXN-STATUS
SET WS-END-OF-FILE TO TRUE
END-EVALUATE
.
2200-VALIDATE-TRANSACTION.
SET WS-TRANSACTION-OK TO TRUE
MOVE SPACES TO WS-REJECT-REASON
MOVE SPACES TO WS-REJECT-MSG
IF NOT TXN-VALID-TYPE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'ITYP' TO WS-REJECT-REASON
MOVE 'INVALID TRANSACTION TYPE' TO WS-REJECT-MSG
END-IF
IF WS-TRANSACTION-OK AND TXN-AMOUNT NOT > ZERO
AND NOT TXN-FEE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'IAMT' TO WS-REJECT-REASON
MOVE 'INVALID TRANSACTION AMOUNT' TO WS-REJECT-MSG
END-IF
IF WS-TRANSACTION-OK AND TXN-ACCT-NUMBER = SPACES
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'IACN' TO WS-REJECT-REASON
MOVE 'BLANK ACCOUNT NUMBER' TO WS-REJECT-MSG
END-IF
IF WS-TRANSACTION-OK AND TXN-TRANSFER
AND TXN-TARGET-ACCT = SPACES
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'IXFR' TO WS-REJECT-REASON
MOVE 'TRANSFER MISSING TARGET ACCOUNT'
TO WS-REJECT-MSG
END-IF
.
2300-APPLY-TRANSACTION.
MOVE TXN-ACCT-NUMBER TO ACCT-NUMBER
READ ACCOUNT-MASTER
EVALUATE TRUE
WHEN WS-VSAM-OK
CONTINUE
WHEN WS-VSAM-NOT-FOUND
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'ANFD' TO WS-REJECT-REASON
MOVE 'ACCOUNT NOT FOUND' TO WS-REJECT-MSG
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
GO TO 2300-EXIT
WHEN OTHER
DISPLAY 'VSAM READ ERROR: ' WS-VSAM-STATUS
' ACCT: ' ACCT-NUMBER
ADD 1 TO WS-TXN-REJECTED
GO TO 2300-EXIT
END-EVALUATE
IF NOT ACCT-ACTIVE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'INAC' TO WS-REJECT-REASON
MOVE 'ACCOUNT NOT ACTIVE' TO WS-REJECT-MSG
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
GO TO 2300-EXIT
END-IF
MOVE CURRENT-BALANCE TO WS-SAVE-BALANCE
EVALUATE TRUE
WHEN TXN-CREDIT
PERFORM 2310-PROCESS-CREDIT
WHEN TXN-DEBIT
PERFORM 2320-PROCESS-DEBIT
WHEN TXN-TRANSFER
PERFORM 2330-PROCESS-TRANSFER
WHEN TXN-FEE
PERFORM 2340-PROCESS-FEE
END-EVALUATE
.
2300-EXIT.
EXIT
.
2310-PROCESS-CREDIT.
ADD TXN-AMOUNT TO CURRENT-BALANCE
ADD TXN-AMOUNT TO AVAIL-BALANCE
ADD TXN-AMOUNT TO MTD-CREDIT-TOTAL
ADD 1 TO MTD-TXN-COUNT
MOVE TXN-DATE TO DATE-LAST-ACTIVITY
REWRITE ACCOUNT-MASTER-RECORD
IF WS-VSAM-OK
ADD 1 TO WS-TXN-APPLIED
ADD 1 TO WS-CREDITS-APPLIED
ADD TXN-AMOUNT TO WS-TOTAL-CREDITS
PERFORM 2700-WRITE-SUCCESS-AUDIT
ELSE
DISPLAY 'REWRITE ERROR: ' WS-VSAM-STATUS
' ACCT: ' ACCT-NUMBER
ADD 1 TO WS-TXN-REJECTED
END-IF
.
2320-PROCESS-DEBIT.
IF TXN-AMOUNT > AVAIL-BALANCE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'INSF' TO WS-REJECT-REASON
MOVE 'INSUFFICIENT FUNDS' TO WS-REJECT-MSG
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
ELSE
SUBTRACT TXN-AMOUNT FROM CURRENT-BALANCE
SUBTRACT TXN-AMOUNT FROM AVAIL-BALANCE
ADD TXN-AMOUNT TO MTD-DEBIT-TOTAL
ADD 1 TO MTD-TXN-COUNT
MOVE TXN-DATE TO DATE-LAST-ACTIVITY
REWRITE ACCOUNT-MASTER-RECORD
IF WS-VSAM-OK
ADD 1 TO WS-TXN-APPLIED
ADD 1 TO WS-DEBITS-APPLIED
ADD TXN-AMOUNT TO WS-TOTAL-DEBITS
PERFORM 2700-WRITE-SUCCESS-AUDIT
ELSE
DISPLAY 'REWRITE ERROR: ' WS-VSAM-STATUS
' ACCT: ' ACCT-NUMBER
ADD 1 TO WS-TXN-REJECTED
END-IF
END-IF
.
2330-PROCESS-TRANSFER.
IF TXN-AMOUNT > AVAIL-BALANCE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'INSF' TO WS-REJECT-REASON
MOVE 'INSUFFICIENT FUNDS FOR TRANSFER'
TO WS-REJECT-MSG
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
ELSE
SUBTRACT TXN-AMOUNT FROM CURRENT-BALANCE
SUBTRACT TXN-AMOUNT FROM AVAIL-BALANCE
ADD TXN-AMOUNT TO MTD-DEBIT-TOTAL
ADD 1 TO MTD-TXN-COUNT
MOVE TXN-DATE TO DATE-LAST-ACTIVITY
REWRITE ACCOUNT-MASTER-RECORD
IF WS-VSAM-OK
PERFORM 2335-CREDIT-TARGET
ELSE
DISPLAY 'REWRITE ERROR ON SOURCE: '
WS-VSAM-STATUS
ADD 1 TO WS-TXN-REJECTED
END-IF
END-IF
.
2335-CREDIT-TARGET.
MOVE TXN-TARGET-ACCT TO ACCT-NUMBER
READ ACCOUNT-MASTER
IF WS-VSAM-OK AND ACCT-ACTIVE
ADD TXN-AMOUNT TO CURRENT-BALANCE
ADD TXN-AMOUNT TO AVAIL-BALANCE
ADD TXN-AMOUNT TO MTD-CREDIT-TOTAL
ADD 1 TO MTD-TXN-COUNT
MOVE TXN-DATE TO DATE-LAST-ACTIVITY
REWRITE ACCOUNT-MASTER-RECORD
IF WS-VSAM-OK
ADD 1 TO WS-TXN-APPLIED
ADD 1 TO WS-TRANSFERS-APPLIED
PERFORM 2700-WRITE-SUCCESS-AUDIT
ELSE
DISPLAY 'REWRITE ERROR ON TARGET: '
WS-VSAM-STATUS
ADD 1 TO WS-TXN-REJECTED
END-IF
ELSE
SET WS-TRANSACTION-BAD TO TRUE
MOVE 'TNFD' TO WS-REJECT-REASON
MOVE 'TARGET ACCOUNT NOT FOUND/ACTIVE'
TO WS-REJECT-MSG
ADD 1 TO WS-TXN-REJECTED
PERFORM 2800-WRITE-REJECT-AUDIT
END-IF
.
2340-PROCESS-FEE.
SUBTRACT TXN-AMOUNT FROM CURRENT-BALANCE
SUBTRACT TXN-AMOUNT FROM AVAIL-BALANCE
ADD TXN-AMOUNT TO MTD-DEBIT-TOTAL
ADD 1 TO MTD-TXN-COUNT
MOVE TXN-DATE TO DATE-LAST-ACTIVITY
REWRITE ACCOUNT-MASTER-RECORD
IF WS-VSAM-OK
ADD 1 TO WS-TXN-APPLIED
ADD 1 TO WS-FEES-APPLIED
PERFORM 2700-WRITE-SUCCESS-AUDIT
ELSE
DISPLAY 'REWRITE ERROR: ' WS-VSAM-STATUS
' ACCT: ' ACCT-NUMBER
ADD 1 TO WS-TXN-REJECTED
END-IF
.
2700-WRITE-SUCCESS-AUDIT.
MOVE WS-CURRENT-DATE TO AUD-DATE
ACCEPT WS-CURRENT-TIME FROM TIME
MOVE WS-CURRENT-TIME TO AUD-TIME
MOVE 'TXN-PROC' TO AUD-PROGRAM-ID
MOVE TXN-ACCT-NUMBER TO AUD-ACCT-NUMBER
MOVE 'UPD' TO AUD-ACTION
MOVE TXN-TYPE TO AUD-TXN-TYPE
MOVE TXN-AMOUNT TO AUD-TXN-AMOUNT
MOVE WS-SAVE-BALANCE TO AUD-BEFORE-BALANCE
MOVE CURRENT-BALANCE TO AUD-AFTER-BALANCE
MOVE 'S' TO AUD-STATUS
MOVE SPACES TO AUD-REASON-CODE
MOVE 'BATCH' TO AUD-OPERATOR-ID
WRITE AUDIT-TRAIL-RECORD
.
2800-WRITE-REJECT-AUDIT.
MOVE WS-CURRENT-DATE TO AUD-DATE
ACCEPT WS-CURRENT-TIME FROM TIME
MOVE WS-CURRENT-TIME TO AUD-TIME
MOVE 'TXN-PROC' TO AUD-PROGRAM-ID
MOVE TXN-ACCT-NUMBER TO AUD-ACCT-NUMBER
MOVE 'RD ' TO AUD-ACTION
MOVE TXN-TYPE TO AUD-TXN-TYPE
MOVE TXN-AMOUNT TO AUD-TXN-AMOUNT
MOVE ZERO TO AUD-BEFORE-BALANCE
MOVE ZERO TO AUD-AFTER-BALANCE
MOVE 'F' TO AUD-STATUS
MOVE WS-REJECT-REASON TO AUD-REASON-CODE
MOVE 'BATCH' TO AUD-OPERATOR-ID
WRITE AUDIT-TRAIL-RECORD
.
3000-TERMINATE.
DISPLAY '=================================='
DISPLAY 'TXN-PROC PROCESSING SUMMARY'
DISPLAY '=================================='
DISPLAY 'TRANSACTIONS READ: ' WS-TXN-READ
DISPLAY 'TRANSACTIONS APPLIED: ' WS-TXN-APPLIED
DISPLAY 'TRANSACTIONS REJECTED: ' WS-TXN-REJECTED
DISPLAY '----------------------------------'
DISPLAY 'CREDITS APPLIED: ' WS-CREDITS-APPLIED
DISPLAY 'DEBITS APPLIED: ' WS-DEBITS-APPLIED
DISPLAY 'TRANSFERS APPLIED: ' WS-TRANSFERS-APPLIED
DISPLAY 'FEES APPLIED: ' WS-FEES-APPLIED
DISPLAY '=================================='
CLOSE TRANSACTION-FILE
ACCOUNT-MASTER
AUDIT-FILE
SUMMARY-REPORT
IF WS-TXN-REJECTED > ZERO
MOVE 4 TO RETURN-CODE
ELSE
MOVE 0 TO RETURN-CODE
END-IF
.
Key Design Patterns in TXN-PROC:
Random access for updates. Unlike ACCT-LOAD (which writes sequentially), TXN-PROC opens the VSAM file for I-O with RANDOM access. This is because transactions arrive in arbitrary order — a withdrawal for account 0000050000 might be followed by a deposit for account 0000010000. Random access lets us read any record directly by key.
Before-image capture. Before modifying any balance, the program saves the current balance in WS-SAVE-BALANCE. This before-image is written to the audit trail, allowing auditors to verify that the after-image equals the before-image plus or minus the transaction amount.
Transfer as a two-phase operation. A transfer debits the source account and credits the target account. If the source debit succeeds but the target credit fails, we have a data integrity problem. In a production system, this would be wrapped in a unit of work (using CICS or DB2 commit/rollback). In our batch program, we note the limitation and log the failure.
⚠️ The Transfer Problem. Derek notices that if the target account credit fails after the source account debit succeeds, the money has vanished. Maria nods. "That's why production systems use two-phase commit. In batch, you'd either process transfers in a separate step with restart logic, or use DB2 with explicit COMMIT/ROLLBACK. Your training system handles this by logging the failure — which is what a real system would do as the first step toward recovery."
Program 3: ACCT-INQ (CICS Online Inquiry)
ACCT-INQ is a CICS transaction that allows customer service representatives to look up account information. It demonstrates the fundamental CICS programming model: receive a screen, process the request, send a response.
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCT-INQ.
*================================================================*
* Program: ACCT-INQ *
* Purpose: CICS online account inquiry *
* Trans ID: AINQ *
* Map: AINQMAP / AINQSET *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COMMAREA.
05 WS-COMM-STATE PIC X(01).
88 WS-FIRST-TIME VALUE SPACES.
88 WS-PROCESS-INPUT VALUE '1'.
05 WS-COMM-ACCT-NUM PIC X(10).
COPY ACCTMSTR.
01 WS-DISPLAY-FIELDS.
05 WS-DISP-BALANCE PIC $$$,$$$, MATH1 $,$$$,$$9.99-.
05 WS-DISP-INTEREST PIC $$,$$$,$$9.99-.
05 WS-DISP-DATE PIC XXXX/XX/XX.
05 WS-DISP-STATUS PIC X(08).
05 WS-DISP-TYPE PIC X(10).
01 WS-RESP-CODE PIC S9(08) COMP.
01 WS-VSAM-STATUS PIC X(02).
COPY AINQMAP.
PROCEDURE DIVISION.
0000-MAIN.
EVALUATE TRUE
WHEN WS-FIRST-TIME
PERFORM 1000-SEND-EMPTY-MAP
WHEN WS-PROCESS-INPUT
PERFORM 2000-PROCESS-INQUIRY
WHEN OTHER
PERFORM 1000-SEND-EMPTY-MAP
END-EVALUATE
EXEC CICS RETURN
TRANSID('AINQ')
COMMAREA(WS-COMMAREA)
LENGTH(LENGTH OF WS-COMMAREA)
END-EXEC
.
1000-SEND-EMPTY-MAP.
MOVE LOW-VALUES TO AINQMAPO
MOVE 'ENTER ACCOUNT NUMBER AND PRESS ENTER'
TO MSGO
MOVE -1 TO ACCTNOL
EXEC CICS SEND
MAP('AINQMAP')
MAPSET('AINQSET')
FROM(AINQMAPO)
ERASE
CURSOR
END-EXEC
SET WS-PROCESS-INPUT TO TRUE
.
2000-PROCESS-INQUIRY.
EXEC CICS RECEIVE
MAP('AINQMAP')
MAPSET('AINQSET')
INTO(AINQMAPI)
RESP(WS-RESP-CODE)
END-EXEC
IF WS-RESP-CODE NOT = DFHRESP(NORMAL)
PERFORM 1000-SEND-EMPTY-MAP
GO TO 2000-EXIT
END-IF
IF ACCTNOI = SPACES OR LOW-VALUES
MOVE 'PLEASE ENTER AN ACCOUNT NUMBER'
TO MSGO
MOVE -1 TO ACCTNOL
EXEC CICS SEND
MAP('AINQMAP')
MAPSET('AINQSET')
FROM(AINQMAPO)
DATAONLY
CURSOR
END-EXEC
GO TO 2000-EXIT
END-IF
PERFORM 2100-READ-ACCOUNT
.
2000-EXIT.
EXIT
.
2100-READ-ACCOUNT.
MOVE ACCTNOI TO ACCT-NUMBER
EXEC CICS READ
DATASET('ACCTMSTR')
INTO(ACCOUNT-MASTER-RECORD)
RIDFLD(ACCT-NUMBER)
LENGTH(LENGTH OF ACCOUNT-MASTER-RECORD)
RESP(WS-RESP-CODE)
END-EXEC
EVALUATE WS-RESP-CODE
WHEN DFHRESP(NORMAL)
PERFORM 2200-FORMAT-DISPLAY
WHEN DFHRESP(NOTFND)
MOVE LOW-VALUES TO AINQMAPO
MOVE ACCTNOI TO ACCTNOO
MOVE 'ACCOUNT NOT FOUND' TO MSGO
MOVE -1 TO ACCTNOL
EXEC CICS SEND
MAP('AINQMAP')
MAPSET('AINQSET')
FROM(AINQMAPO)
DATAONLY
CURSOR
END-EXEC
WHEN OTHER
MOVE LOW-VALUES TO AINQMAPO
STRING 'FILE ERROR - RESP: '
DELIMITED BY SIZE
WS-RESP-CODE DELIMITED BY SIZE
INTO MSGO
END-STRING
EXEC CICS SEND
MAP('AINQMAP')
MAPSET('AINQSET')
FROM(AINQMAPO)
DATAONLY
END-EXEC
END-EVALUATE
.
2200-FORMAT-DISPLAY.
MOVE LOW-VALUES TO AINQMAPO
MOVE ACCT-NUMBER TO ACCTNOO
STRING CUST-FIRST-NAME DELIMITED BY ' '
' ' DELIMITED BY SIZE
CUST-MIDDLE-INIT DELIMITED BY SIZE
' ' DELIMITED BY SIZE
CUST-LAST-NAME DELIMITED BY ' '
INTO CNAMEO
END-STRING
EVALUATE TRUE
WHEN ACCT-CHECKING
MOVE 'CHECKING' TO ATYPEO
WHEN ACCT-SAVINGS
MOVE 'SAVINGS ' TO ATYPEO
END-EVALUATE
EVALUATE TRUE
WHEN ACCT-ACTIVE
MOVE 'ACTIVE ' TO ASTATO
WHEN ACCT-CLOSED
MOVE 'CLOSED ' TO ASTATO
WHEN ACCT-FROZEN
MOVE 'FROZEN ' TO ASTATO
END-EVALUATE
MOVE CURRENT-BALANCE TO WS-DISP-BALANCE
MOVE WS-DISP-BALANCE TO CBALO
MOVE AVAIL-BALANCE TO WS-DISP-AVAIL
MOVE WS-DISP-AVAIL TO ABALO
MOVE DATE-OPENED TO WS-DISP-DATE
MOVE WS-DISP-DATE TO DTOPNO
MOVE DATE-LAST-ACTIVITY TO WS-DISP-DATE
MOVE WS-DISP-DATE TO DTLSTO
MOVE YTD-INTEREST TO WS-DISP-INTEREST
MOVE WS-DISP-INTEREST TO YINTO
MOVE 'ACCOUNT FOUND - DATA DISPLAYED' TO MSGO
EXEC CICS SEND
MAP('AINQMAP')
MAPSET('AINQSET')
FROM(AINQMAPO)
DATAONLY
END-EXEC
.
The CICS Programming Model:
CICS programs operate differently from batch programs. Instead of running from start to finish, they are pseudo-conversational: the program sends a screen, ends, and is reinvoked when the user presses Enter. The COMMAREA preserves state between invocations.
In ACCT-INQ, the flow is:
- First invocation (WS-FIRST-TIME): Send an empty map with a prompt.
- Second invocation (WS-PROCESS-INPUT): Receive the user's input, read the VSAM file, format and display the results.
- The user can enter a new account number and press Enter to repeat.
No UPDATE on READ. Notice that the READ command does not specify UPDATE. This is deliberate. An inquiry should not lock the record, because customer service representatives may leave a screen displayed for minutes while they talk to a customer. If the record were locked, it would block batch processing and other online transactions.
🔵 BMS Map Definition. The BMS (Basic Mapping Support) map for ACCT-INQ would be defined separately and assembled. The map defines the screen layout — field positions, attributes, lengths, and colors. While we do not include the full BMS macro source here, the field names (ACCTNOI/ACCTNOO, CNAMEO, CBALO, etc.) correspond to the input and output fields on the screen.
Program 4: RPT-STMT (Statement Generator)
RPT-STMT generates monthly account statements. It reads the account master sequentially and, for each active account, produces a formatted statement.
IDENTIFICATION DIVISION.
PROGRAM-ID. RPT-STMT.
*================================================================*
* Program: RPT-STMT *
* Purpose: Generate monthly account statements *
* Author: Derek Washington *
* Date: 2024-02-01 *
*================================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCOUNT-MASTER
ASSIGN TO ACCTMSTR
ORGANIZATION IS INDEXED
ACCESS MODE IS SEQUENTIAL
RECORD KEY IS ACCT-NUMBER
FILE STATUS IS WS-VSAM-STATUS.
SELECT TRANSACTION-HISTORY
ASSIGN TO TXNHIST
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-TXN-STATUS.
SELECT STATEMENT-FILE
ASSIGN TO STMTOUT
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-STMT-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCOUNT-MASTER
RECORD CONTAINS 200 CHARACTERS.
COPY ACCTMSTR.
FD TRANSACTION-HISTORY
RECORDING MODE IS F
RECORD CONTAINS 100 CHARACTERS.
COPY TXNREC.
FD STATEMENT-FILE
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 STMT-LINE PIC X(132).
WORKING-STORAGE SECTION.
01 WS-FILE-STATUSES.
05 WS-VSAM-STATUS PIC X(02).
88 WS-VSAM-OK VALUE '00'.
88 WS-VSAM-EOF VALUE '10'.
05 WS-TXN-STATUS PIC X(02).
88 WS-TXN-OK VALUE '00'.
88 WS-TXN-EOF VALUE '10'.
05 WS-STMT-STATUS PIC X(02).
01 WS-COUNTERS.
05 WS-ACCTS-PROCESSED PIC 9(07) VALUE ZERO.
05 WS-STMTS-GENERATED PIC 9(07) VALUE ZERO.
05 WS-PAGE-COUNT PIC 9(03) VALUE ZERO.
05 WS-LINE-COUNT PIC 9(02) VALUE 60.
05 WS-TXN-COUNT-ACCT PIC 9(05) VALUE ZERO.
01 WS-TOTALS.
05 WS-OPENING-BALANCE PIC S9(9)V99 COMP-3.
05 WS-TOTAL-CREDITS-ACCT PIC S9(9)V99 COMP-3.
05 WS-TOTAL-DEBITS-ACCT PIC S9(9)V99 COMP-3.
05 WS-CLOSING-BALANCE PIC S9(9)V99 COMP-3.
01 WS-FLAGS.
05 WS-EOF-ACCT PIC X(01) VALUE 'N'.
88 WS-END-OF-ACCOUNTS VALUE 'Y'.
88 WS-MORE-ACCOUNTS VALUE 'N'.
05 WS-EOF-TXN PIC X(01) VALUE 'N'.
88 WS-END-OF-TXNS VALUE 'Y'.
88 WS-MORE-TXNS VALUE 'N'.
01 WS-DATE-WORK.
05 WS-CURRENT-DATE PIC 9(08).
05 WS-STMT-MONTH PIC 9(02).
05 WS-STMT-YEAR PIC 9(04).
05 WS-STMT-PERIOD PIC X(20).
*--- Statement header lines ---
01 WS-STMT-HEADER-1.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(50)
VALUE '================================================'.
05 FILLER PIC X(81) VALUE SPACES.
01 WS-STMT-HEADER-2.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(35)
VALUE ' GLOBALBANK - ACCOUNT STATEMENT '.
05 FILLER PIC X(10) VALUE SPACES.
05 FILLER PIC X(08) VALUE 'PERIOD: '.
05 SH2-PERIOD PIC X(20).
05 FILLER PIC X(58) VALUE SPACES.
01 WS-STMT-ACCT-LINE.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(09) VALUE 'ACCOUNT: '.
05 SA-ACCT PIC X(10).
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(06) VALUE 'NAME: '.
05 SA-NAME PIC X(46).
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(06) VALUE 'TYPE: '.
05 SA-TYPE PIC X(10).
05 FILLER PIC X(34) VALUE SPACES.
01 WS-STMT-TXN-HDR.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(10) VALUE 'DATE '.
05 FILLER PIC X(02) VALUE SPACES.
05 FILLER PIC X(08) VALUE 'TYPE '.
05 FILLER PIC X(02) VALUE SPACES.
05 FILLER PIC X(30) VALUE 'DESCRIPTION '.
05 FILLER PIC X(02) VALUE SPACES.
05 FILLER PIC X(15) VALUE ' AMOUNT'.
05 FILLER PIC X(02) VALUE SPACES.
05 FILLER PIC X(15) VALUE ' BALANCE'.
05 FILLER PIC X(45) VALUE SPACES.
01 WS-STMT-TXN-LINE.
05 FILLER PIC X(01) VALUE SPACES.
05 ST-DATE PIC X(10).
05 FILLER PIC X(02) VALUE SPACES.
05 ST-TYPE PIC X(08).
05 FILLER PIC X(02) VALUE SPACES.
05 ST-DESC PIC X(30).
05 FILLER PIC X(02) VALUE SPACES.
05 ST-AMT PIC -(9)9.99.
05 FILLER PIC X(02) VALUE SPACES.
05 ST-BAL PIC -(9)9.99.
05 FILLER PIC X(43) VALUE SPACES.
01 WS-STMT-SUMMARY.
05 FILLER PIC X(01) VALUE SPACES.
05 FILLER PIC X(50)
VALUE '------------------------------------------------'.
05 FILLER PIC X(81) VALUE SPACES.
01 WS-RUNNING-BALANCE PIC S9(9)V99 COMP-3.
01 WS-DATE-UTIL-PARAMS.
05 WS-DU-FUNCTION PIC X(02).
05 WS-DU-DATE-1 PIC 9(08).
05 WS-DU-DATE-2 PIC 9(08).
05 WS-DU-DAYS-RESULT PIC S9(05) COMP-3.
05 WS-DU-RETURN-CODE PIC 9(02).
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-ACCOUNTS
UNTIL WS-END-OF-ACCOUNTS
PERFORM 3000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT ACCOUNT-MASTER
OPEN INPUT TRANSACTION-HISTORY
OPEN OUTPUT STATEMENT-FILE
ACCEPT WS-CURRENT-DATE FROM DATE YYYYMMDD
MOVE WS-CURRENT-DATE(5:2) TO WS-STMT-MONTH
MOVE WS-CURRENT-DATE(1:4) TO WS-STMT-YEAR
STRING WS-STMT-MONTH DELIMITED BY SIZE
'/' DELIMITED BY SIZE
WS-STMT-YEAR DELIMITED BY SIZE
INTO WS-STMT-PERIOD
END-STRING
PERFORM 2100-READ-ACCOUNT
.
2000-PROCESS-ACCOUNTS.
ADD 1 TO WS-ACCTS-PROCESSED
IF ACCT-ACTIVE
PERFORM 2200-GENERATE-STATEMENT
ADD 1 TO WS-STMTS-GENERATED
END-IF
PERFORM 2100-READ-ACCOUNT
.
2100-READ-ACCOUNT.
READ ACCOUNT-MASTER NEXT
EVALUATE TRUE
WHEN WS-VSAM-OK
CONTINUE
WHEN WS-VSAM-EOF
SET WS-END-OF-ACCOUNTS TO TRUE
WHEN OTHER
DISPLAY 'VSAM READ ERROR: ' WS-VSAM-STATUS
SET WS-END-OF-ACCOUNTS TO TRUE
END-EVALUATE
.
2200-GENERATE-STATEMENT.
PERFORM 2210-WRITE-STMT-HEADER
PERFORM 2220-COMPUTE-OPENING-BAL
MOVE WS-OPENING-BALANCE TO WS-RUNNING-BALANCE
MOVE ZERO TO WS-TXN-COUNT-ACCT
MOVE ZERO TO WS-TOTAL-CREDITS-ACCT
MOVE ZERO TO WS-TOTAL-DEBITS-ACCT
PERFORM 2230-WRITE-TXN-HEADER
PERFORM 2240-WRITE-STMT-FOOTER
.
2210-WRITE-STMT-HEADER.
MOVE WS-STMT-PERIOD TO SH2-PERIOD
WRITE STMT-LINE FROM WS-STMT-HEADER-1
AFTER ADVANCING PAGE
WRITE STMT-LINE FROM WS-STMT-HEADER-2
AFTER ADVANCING 1 LINE
MOVE ACCT-NUMBER TO SA-ACCT
STRING CUST-FIRST-NAME DELIMITED BY ' '
' ' DELIMITED BY SIZE
CUST-LAST-NAME DELIMITED BY ' '
INTO SA-NAME
END-STRING
EVALUATE TRUE
WHEN ACCT-CHECKING
MOVE 'CHECKING' TO SA-TYPE
WHEN ACCT-SAVINGS
MOVE 'SAVINGS' TO SA-TYPE
END-EVALUATE
WRITE STMT-LINE FROM WS-STMT-ACCT-LINE
AFTER ADVANCING 2 LINES
.
2220-COMPUTE-OPENING-BAL.
COMPUTE WS-OPENING-BALANCE =
CURRENT-BALANCE
- MTD-CREDIT-TOTAL
+ MTD-DEBIT-TOTAL
END-COMPUTE
.
2230-WRITE-TXN-HEADER.
WRITE STMT-LINE FROM WS-STMT-TXN-HDR
AFTER ADVANCING 2 LINES
WRITE STMT-LINE FROM WS-STMT-SUMMARY
AFTER ADVANCING 1 LINE
.
2240-WRITE-STMT-FOOTER.
WRITE STMT-LINE FROM WS-STMT-SUMMARY
AFTER ADVANCING 2 LINES
INITIALIZE WS-STMT-TXN-LINE
MOVE 'OPENING BALANCE:' TO ST-DESC
MOVE WS-OPENING-BALANCE TO ST-BAL
WRITE STMT-LINE FROM WS-STMT-TXN-LINE
AFTER ADVANCING 1 LINE
MOVE 'TOTAL CREDITS:' TO ST-DESC
MOVE MTD-CREDIT-TOTAL TO ST-AMT
WRITE STMT-LINE FROM WS-STMT-TXN-LINE
AFTER ADVANCING 1 LINE
MOVE 'TOTAL DEBITS:' TO ST-DESC
MOVE MTD-DEBIT-TOTAL TO ST-AMT
WRITE STMT-LINE FROM WS-STMT-TXN-LINE
AFTER ADVANCING 1 LINE
MOVE 'CLOSING BALANCE:' TO ST-DESC
MOVE CURRENT-BALANCE TO ST-BAL
MOVE ZERO TO ST-AMT
WRITE STMT-LINE FROM WS-STMT-TXN-LINE
AFTER ADVANCING 1 LINE
WRITE STMT-LINE FROM WS-STMT-SUMMARY
AFTER ADVANCING 1 LINE
.
3000-TERMINATE.
CLOSE ACCOUNT-MASTER
TRANSACTION-HISTORY
STATEMENT-FILE
DISPLAY 'RPT-STMT COMPLETE: '
WS-STMTS-GENERATED ' STATEMENTS GENERATED'
' FROM ' WS-ACCTS-PROCESSED ' ACCOUNTS'
MOVE 0 TO RETURN-CODE
.
Program 5: UTIL-DATE (Date Utility Subprogram)
UTIL-DATE is a called subprogram that provides date validation, date arithmetic, and date formatting services. Every program in the system that needs to work with dates calls UTIL-DATE rather than implementing its own date logic.
IDENTIFICATION DIVISION.
PROGRAM-ID. UTIL-DATE.
*================================================================*
* Program: UTIL-DATE *
* Purpose: Date utility subprogram *
* Called by: TXN-PROC, RPT-STMT, ACCT-LOAD *
*================================================================*
* Functions: *
* VD - Validate Date (YYYYMMDD) *
* DB - Days Between two dates *
* DA - Date Add (add days to a date) *
* FD - Format Date (YYYYMMDD to MM/DD/YYYY) *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-WORK-FIELDS.
05 WS-YEAR PIC 9(04).
05 WS-MONTH PIC 9(02).
05 WS-DAY PIC 9(02).
05 WS-LEAP-YEAR PIC X(01).
88 WS-IS-LEAP VALUE 'Y'.
88 WS-NOT-LEAP VALUE 'N'.
05 WS-MAX-DAY PIC 9(02).
05 WS-JULIAN-1 PIC 9(07).
05 WS-JULIAN-2 PIC 9(07).
05 WS-TEMP-DAYS PIC S9(05) COMP-3.
01 WS-DAYS-IN-MONTH.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 28.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 30.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 30.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 30.
05 FILLER PIC 9(02) VALUE 31.
05 FILLER PIC 9(02) VALUE 30.
05 FILLER PIC 9(02) VALUE 31.
01 WS-MONTH-TABLE REDEFINES WS-DAYS-IN-MONTH.
05 WS-MONTH-DAYS PIC 9(02)
OCCURS 12 TIMES.
LINKAGE SECTION.
01 LS-PARAMETERS.
05 LS-FUNCTION PIC X(02).
88 LS-VALIDATE-DATE VALUE 'VD'.
88 LS-DAYS-BETWEEN VALUE 'DB'.
88 LS-DATE-ADD VALUE 'DA'.
88 LS-FORMAT-DATE VALUE 'FD'.
05 LS-DATE-1 PIC 9(08).
05 LS-DATE-2 PIC 9(08).
05 LS-DAYS-RESULT PIC S9(05) COMP-3.
05 LS-FORMATTED-DATE PIC X(10).
05 LS-RETURN-CODE PIC 9(02).
88 LS-SUCCESS VALUE 00.
88 LS-INVALID-FUNCTION VALUE 01.
88 LS-INVALID-DATE VALUE 02.
88 LS-OVERFLOW VALUE 03.
PROCEDURE DIVISION USING LS-PARAMETERS.
0000-MAIN.
MOVE 00 TO LS-RETURN-CODE
EVALUATE TRUE
WHEN LS-VALIDATE-DATE
PERFORM 1000-VALIDATE-DATE
WHEN LS-DAYS-BETWEEN
PERFORM 2000-DAYS-BETWEEN
WHEN LS-DATE-ADD
PERFORM 3000-DATE-ADD
WHEN LS-FORMAT-DATE
PERFORM 4000-FORMAT-DATE
WHEN OTHER
MOVE 01 TO LS-RETURN-CODE
END-EVALUATE
GOBACK
.
1000-VALIDATE-DATE.
MOVE LS-DATE-1(1:4) TO WS-YEAR
MOVE LS-DATE-1(5:2) TO WS-MONTH
MOVE LS-DATE-1(7:2) TO WS-DAY
IF WS-YEAR < 1900 OR WS-YEAR > 2099
MOVE 02 TO LS-RETURN-CODE
GO TO 1000-EXIT
END-IF
IF WS-MONTH < 01 OR WS-MONTH > 12
MOVE 02 TO LS-RETURN-CODE
GO TO 1000-EXIT
END-IF
PERFORM 5000-CHECK-LEAP-YEAR
MOVE WS-MONTH-DAYS(WS-MONTH) TO WS-MAX-DAY
IF WS-MONTH = 2 AND WS-IS-LEAP
MOVE 29 TO WS-MAX-DAY
END-IF
IF WS-DAY < 01 OR WS-DAY > WS-MAX-DAY
MOVE 02 TO LS-RETURN-CODE
END-IF
.
1000-EXIT.
EXIT
.
2000-DAYS-BETWEEN.
PERFORM 1000-VALIDATE-DATE
IF LS-RETURN-CODE NOT = 00
GO TO 2000-EXIT
END-IF
MOVE LS-DATE-1 TO WS-JULIAN-1
PERFORM 6000-DATE-TO-JULIAN
MOVE LS-DATE-2(1:4) TO WS-YEAR
MOVE LS-DATE-2(5:2) TO WS-MONTH
MOVE LS-DATE-2(7:2) TO WS-DAY
PERFORM 5000-CHECK-LEAP-YEAR
IF WS-MONTH < 01 OR WS-MONTH > 12
MOVE 02 TO LS-RETURN-CODE
GO TO 2000-EXIT
END-IF
MOVE WS-JULIAN-1 TO WS-TEMP-DAYS
MOVE LS-DATE-2 TO WS-JULIAN-1
PERFORM 6000-DATE-TO-JULIAN
COMPUTE LS-DAYS-RESULT =
WS-JULIAN-1 - WS-TEMP-DAYS
END-COMPUTE
.
2000-EXIT.
EXIT
.
3000-DATE-ADD.
PERFORM 1000-VALIDATE-DATE
IF LS-RETURN-CODE NOT = 00
GO TO 3000-EXIT
END-IF
MOVE LS-DATE-1(1:4) TO WS-YEAR
MOVE LS-DATE-1(5:2) TO WS-MONTH
MOVE LS-DATE-1(7:2) TO WS-DAY
ADD LS-DAYS-RESULT TO WS-DAY
PERFORM UNTIL WS-DAY > 0 AND
WS-DAY <= WS-MAX-DAY
PERFORM 5000-CHECK-LEAP-YEAR
MOVE WS-MONTH-DAYS(WS-MONTH) TO WS-MAX-DAY
IF WS-MONTH = 2 AND WS-IS-LEAP
MOVE 29 TO WS-MAX-DAY
END-IF
IF WS-DAY > WS-MAX-DAY
SUBTRACT WS-MAX-DAY FROM WS-DAY
ADD 1 TO WS-MONTH
IF WS-MONTH > 12
MOVE 1 TO WS-MONTH
ADD 1 TO WS-YEAR
END-IF
ELSE
IF WS-DAY < 1
SUBTRACT 1 FROM WS-MONTH
IF WS-MONTH < 1
MOVE 12 TO WS-MONTH
SUBTRACT 1 FROM WS-YEAR
END-IF
PERFORM 5000-CHECK-LEAP-YEAR
MOVE WS-MONTH-DAYS(WS-MONTH) TO WS-MAX-DAY
IF WS-MONTH = 2 AND WS-IS-LEAP
MOVE 29 TO WS-MAX-DAY
END-IF
ADD WS-MAX-DAY TO WS-DAY
END-IF
END-IF
END-PERFORM
COMPUTE LS-DATE-2 =
(WS-YEAR * 10000) +
(WS-MONTH * 100) +
WS-DAY
END-COMPUTE
.
3000-EXIT.
EXIT
.
4000-FORMAT-DATE.
PERFORM 1000-VALIDATE-DATE
IF LS-RETURN-CODE NOT = 00
MOVE '**/**/****' TO LS-FORMATTED-DATE
GO TO 4000-EXIT
END-IF
STRING LS-DATE-1(5:2) DELIMITED BY SIZE
'/' DELIMITED BY SIZE
LS-DATE-1(7:2) DELIMITED BY SIZE
'/' DELIMITED BY SIZE
LS-DATE-1(1:4) DELIMITED BY SIZE
INTO LS-FORMATTED-DATE
END-STRING
.
4000-EXIT.
EXIT
.
5000-CHECK-LEAP-YEAR.
SET WS-NOT-LEAP TO TRUE
IF FUNCTION MOD(WS-YEAR, 4) = 0
IF FUNCTION MOD(WS-YEAR, 100) NOT = 0
SET WS-IS-LEAP TO TRUE
ELSE
IF FUNCTION MOD(WS-YEAR, 400) = 0
SET WS-IS-LEAP TO TRUE
END-IF
END-IF
END-IF
.
6000-DATE-TO-JULIAN.
MOVE LS-DATE-1(1:4) TO WS-YEAR
MOVE LS-DATE-1(5:2) TO WS-MONTH
MOVE LS-DATE-1(7:2) TO WS-DAY
COMPUTE WS-JULIAN-1 =
(WS-YEAR * 365) +
(WS-YEAR / 4) -
(WS-YEAR / 100) +
(WS-YEAR / 400) +
WS-DAY
END-COMPUTE
PERFORM VARYING WS-TEMP-DAYS FROM 1 BY 1
UNTIL WS-TEMP-DAYS >= WS-MONTH
ADD WS-MONTH-DAYS(WS-TEMP-DAYS)
TO WS-JULIAN-1
IF WS-TEMP-DAYS = 2
PERFORM 5000-CHECK-LEAP-YEAR
IF WS-IS-LEAP
ADD 1 TO WS-JULIAN-1
END-IF
END-IF
END-PERFORM
.
The Subprogram Design Pattern:
UTIL-DATE illustrates several important subprogram design principles:
Single LINKAGE SECTION interface. All parameters are grouped under one 01-level item (LS-PARAMETERS). The caller passes this structure via CALL ... USING. This is cleaner than passing multiple individual parameters.
Function code dispatching. The LS-FUNCTION field determines which operation to perform. This is a common pattern in COBOL subprograms — one program provides multiple related services, selected by a function code.
Return codes, not ABENDs. When UTIL-DATE encounters an error (invalid date, unsupported function), it sets LS-RETURN-CODE and returns normally. It never ABENDs. The calling program decides how to handle the error. This is defensive programming: a utility should report problems, not crash the caller.
GOBACK, not STOP RUN. Subprograms use GOBACK to return control to the caller. STOP RUN would terminate the entire run unit, including the calling program. This is a beginner mistake that Maria has seen more than once: "Using STOP RUN in a subprogram is like hanging up on someone mid-sentence."
Phase 5: Testing Strategy
Unit Testing
Derek creates test data for each program. For ACCT-LOAD, the test data includes valid records, records with invalid account numbers, records with invalid dates, and duplicate records. For TXN-PROC, the test data includes each transaction type, insufficient funds scenarios, account-not-found cases, and transfer edge cases.
Test Case Matrix for TXN-PROC:
| Test ID | Description | Input | Expected Result |
|---|---|---|---|
| TC-001 | Valid credit | CR $500.00, active checking | Balance increased, audit logged |
| TC-002 | Valid debit | DR $200.00, sufficient funds | Balance decreased, audit logged |
| TC-003 | NSF debit | DR $10,000.00, balance $500 | Rejected, reason INSF |
| TC-004 | Valid transfer | XF $300.00, both accounts active | Source debited, target credited |
| TC-005 | Transfer to closed | XF $300.00, target closed | Rejected, reason TNFD |
| TC-006 | Fee assessment | FE $25.00, active account | Balance decreased, no NSF check |
| TC-007 | Invalid type | XX $100.00 | Rejected, reason ITYP |
| TC-008 | Account not found | CR $500.00, non-existent account | Rejected, reason ANFD |
| TC-009 | Frozen account | DR $100.00, frozen account | Rejected, reason INAC |
| TC-010 | Zero amount debit | DR $0.00 | Rejected, reason IAMT |
💡 Test-Driven Thinking. Notice that Derek defined test cases before writing the code. This is good practice in any language, but it is especially valuable in COBOL, where the compile-test cycle on a mainframe can be slow. Thinking through edge cases in advance reduces the number of test iterations needed.
Integration Testing
After unit-testing each program individually, Derek runs the full job stream:
- Run ACCT-LOAD to create the master file
- Run TXN-PROC to process transactions
- Run RPT-STMT to generate statements
- Verify that the audit trail balances
The integration test verifies that programs share data correctly through the VSAM file and that copybook definitions are consistent across all programs.
Regression Testing
Maria explains the concept of regression testing: "Every time you change one program, you run the entire test suite. Not just the tests for the program you changed — all the tests. Because changing TXN-PROC might break RPT-STMT if you changed the way balances are stored."
Derek creates a JCL procedure that runs all test cases and compares output to expected results. This becomes the system's regression test suite.
Test Data Design Principles
Creating effective test data is a skill unto itself. Derek learns several principles from Maria:
Boundary Testing. Always test at the exact boundaries of conditions. If the maximum account balance is $999,999,999.99 (PIC S9(9)V99 COMP-3), create a test record at that exact amount. If the minimum transaction amount is $0.01, test with exactly $0.01. If accounts are valid from date opened through today, test with a transaction on the exact open date and on today's date.
Combinatorial Testing. Some bugs only appear when specific combinations of conditions occur. For TXN-PROC, the combination of "transfer" + "source account frozen" + "target account active" exercises a specific code path. Derek creates a combinatorial matrix:
| Account Status | Transaction Type | Expected Behavior |
|---|---|---|
| Active, checking | Credit | Accept, increase balance |
| Active, checking | Debit | Accept if sufficient funds |
| Active, savings | Credit | Accept, increase balance |
| Active, savings | Transfer out | Accept if sufficient funds |
| Frozen | Any | Reject with INAC |
| Closed | Any | Reject with INAC |
| Active | Fee | Accept (no NSF check) |
Negative Testing. For every "should succeed" test, create a corresponding "should fail" test. If TC-001 tests a valid credit, TC-003 tests an NSF debit. If TC-004 tests a valid transfer, TC-005 tests a transfer to a closed account. The "should fail" tests verify that error handling works correctly.
Volume Testing. Derek creates a large test file (10,000 transactions against 1,000 accounts) to verify that the system handles volume correctly. Common volume-related bugs include: counter overflow (a PIC 9(05) counter reaching 100,000), accumulated rounding errors in monetary calculations, and VSAM file space issues.
Maria reviews Derek's test plan and adds one more requirement: "Your test data should include at least one account that receives every type of transaction in a single batch run — credit, debit, transfer in, transfer out, and fee. This exercises the 'same account, multiple transactions' path, which is where most race condition and sequencing bugs hide."
End-to-End Verification
The final testing step is end-to-end verification: running the complete system from ACCT-LOAD through RPT-STMT and verifying that the closing balances on the statement match the expected results calculated by hand.
Derek creates a spreadsheet with 10 test accounts and 50 test transactions. He calculates the expected ending balance for each account manually. After running the full system, he compares the statement report balances against his spreadsheet. All 10 accounts match.
"This is the most important test," Maria tells him. "If the closing balances match what you calculated by hand, you know that every program in the system — ACCT-LOAD, TXN-PROC, and RPT-STMT — is processing data consistently. No amount of unit testing can substitute for this end-to-end check."
Priya Kapoor, who stops by to review Derek's work, adds perspective: "In production, the auditors do exactly this test every quarter. They pick a sample of accounts, calculate expected balances from the transaction history, and compare them against the system. If they don't match, we have a very bad day."
Phase 6: Deployment JCL
The Complete Job Stream
In production, COBOL programs do not run individually — they run as steps within JCL job streams. Derek's system uses two job streams: one for daily processing and one for month-end.
Daily Processing Job Stream:
//GBDAILY JOB (ACCT,001),'DAILY PROCESSING',
// CLASS=A,MSGCLASS=X,MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*================================================================*
//* GLOBALBANK DAILY TRANSACTION PROCESSING *
//* RUN DAILY AFTER CLOSE OF BUSINESS *
//*================================================================*
//*
//*--- STEP 1: PROCESS DAILY TRANSACTIONS ---
//*
//STEP010 EXEC PGM=TXNPROC
//STEPLIB DD DSN=GLOBALBANK.LOAD.LIBRARY,DISP=SHR
//TXNIN DD DSN=GLOBALBANK.DAILY.TRANSACTIONS,DISP=OLD
//ACCTMSTR DD DSN=GLOBALBANK.ACCT.MASTER,DISP=SHR
//AUDITOUT DD DSN=GLOBALBANK.AUDIT.TRAIL(+1),
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1)),
// DCB=(RECFM=FB,LRECL=100,BLKSIZE=0)
//RPTOUT DD DSN=GLOBALBANK.TXN.SUMMARY,DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,(5,1)),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//*
//*--- STEP 2: BACKUP ACCOUNT MASTER (ONLY IF STEP 1 RC <= 4) ---
//*
//STEP020 EXEC PGM=IDCAMS,COND=(4,LT,STEP010)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
REPRO INFILE(MASTERIN) OUTFILE(BACKUP)
/*
//MASTERIN DD DSN=GLOBALBANK.ACCT.MASTER,DISP=SHR
//BACKUP DD DSN=GLOBALBANK.ACCT.MASTER.BACKUP,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,1)),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//*
//*--- STEP 3: ARCHIVE PROCESSED TRANSACTIONS ---
//*
//STEP030 EXEC PGM=IEBGENER,COND=(4,LT,STEP010)
//SYSPRINT DD SYSOUT=*
//SYSIN DD DUMMY
//SYSUT1 DD DSN=GLOBALBANK.DAILY.TRANSACTIONS,DISP=SHR
//SYSUT2 DD DSN=GLOBALBANK.TXN.ARCHIVE(+1),
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1)),
// DCB=(RECFM=FB,LRECL=100,BLKSIZE=0)
Key JCL Design Decisions:
COND parameter for conditional execution. STEP020 (backup) and STEP030 (archive) only run if STEP010 completes with a return code of 4 or less. If transaction processing fails (RC=16), there is no point backing up a potentially corrupted master file.
GDG (Generation Data Group) for audit trail. The (+1) notation on GLOBALBANK.AUDIT.TRAIL creates a new generation each day. This provides automatic rotation — old generations can be expired based on a retention policy, while recent generations remain available for research.
DISP=(NEW,CATLG,DELETE) for output files. If the step completes normally, the file is cataloged. If it ABENDs, the file is deleted. This prevents partial output files from being mistaken for complete ones.
Month-End Job Stream:
//GBMONTH JOB (ACCT,002),'MONTH END',
// CLASS=A,MSGCLASS=X,MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*================================================================*
//* GLOBALBANK MONTH-END PROCESSING *
//* RUN ON LAST BUSINESS DAY OF MONTH AFTER DAILY PROCESSING *
//*================================================================*
//*
//*--- STEP 1: GENERATE STATEMENTS ---
//*
//STEP010 EXEC PGM=RPTSTMT
//STEPLIB DD DSN=GLOBALBANK.LOAD.LIBRARY,DISP=SHR
//ACCTMSTR DD DSN=GLOBALBANK.ACCT.MASTER,DISP=SHR
//TXNHIST DD DSN=GLOBALBANK.TXN.ARCHIVE(0),DISP=SHR
//STMTOUT DD DSN=GLOBALBANK.STATEMENTS,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//*
//*--- STEP 2: RESET MONTH-TO-DATE COUNTERS ---
//*
//STEP020 EXEC PGM=ACCTMTDR,COND=(4,LT,STEP010)
//STEPLIB DD DSN=GLOBALBANK.LOAD.LIBRARY,DISP=SHR
//ACCTMSTR DD DSN=GLOBALBANK.ACCT.MASTER,DISP=SHR
//SYSOUT DD SYSOUT=*
📊 Production vs. Training. Derek's JCL is simplified compared to production. A real GlobalBank job stream would include automated scheduling (via CA-7 or TWS), notification steps (sending emails on failure), GDG management, checkpoint/restart logic, and security controls (RACF profiles on datasets). But the structure — sequential steps with conditional execution — is identical.
Phase 7: Bringing It All Together
The Code Review
When Derek presents his completed system to Maria, she reviews it with the thoroughness that only a twenty-year COBOL veteran can bring.
"Your copybooks are clean," she begins. "Consistent naming, good use of 88-levels, FILLER at the end. That's solid." She marks a check on her notepad.
"Your ACCT-LOAD validation is good but incomplete. What happens if someone loads the same file twice?"
Derek thinks. "The duplicate key check would catch it."
"True, but is that what you want? A load program that rejects 50,000 records because they already exist? Maybe you want an option for initial load versus reload."
She continues through TXN-PROC. "Your transfer logic has a gap. If the source debit succeeds but the target credit fails, the money is lost. In production, we'd use a two-phase approach. For this exercise, your audit trail logging is the right first step. But document the limitation."
"Your CICS program is clean. I like that you used RESP instead of HANDLE CONDITION. Modern CICS, modern thinking."
"The date utility is well-designed. One suggestion: add a function to return the day of the week. We use that everywhere — for determining business days, for scheduling, for reporting."
Maria's final assessment: "This is junior developer work that shows senior developer thinking. The architecture is right. The patterns are right. The edge cases need more attention. That comes with experience."
⚖️ The Code Review Culture. In mainframe shops, code reviews are not optional. They are how knowledge transfers from senior to junior developers. Maria's review of Derek's code is not adversarial — it is mentorship. She is teaching him not just what to fix, but how to think. This is The Human Factor in action: the best COBOL systems are built by teams where experienced developers invest in the next generation.
Lessons Learned
Derek documents what he learned from the capstone:
-
Design time is not wasted time. The hours spent on copybooks, data dictionaries, and structure charts saved days of debugging. Starting to code before understanding the problem leads to costly rework.
-
Copybooks are contracts. When multiple programs share a copybook, they share an understanding of the data. Changing a copybook changes the contract. This is why copybook changes require recompiling every program that uses them.
-
Error handling is not an afterthought. Every file operation, every VSAM access, every data validation must be explicitly coded. COBOL does not throw exceptions — it sets status codes. If you do not check them, errors are silent.
-
The audit trail is the story of what happened. Without it, you cannot debug production problems, you cannot satisfy auditors, and you cannot prove your system is correct. Build the audit trail first, not last.
-
Batch and online programs are different worlds. Batch programs control their own flow — they run from start to finish. CICS programs are guests in the CICS region — they must play by CICS rules (pseudo-conversational, no file I/O through native COBOL, RESP codes instead of file status).
🔴 Theme: Legacy != Obsolete. The system Derek built uses patterns that have been in production for decades. VSAM, batch processing, CICS, copybooks, JCL — none of these technologies are new. But they are not obsolete either. They process trillions of dollars in transactions every day. Understanding these patterns is not studying history — it is learning the foundation that modern financial systems are built on.
✅ Theme: Readability is a Feature. Look back at Derek's code. The paragraph names describe what they do (2210-VALIDATE-ACCT-NUMBER, 2320-PROCESS-DEBIT). The 88-level conditions read like English (IF ACCT-ACTIVE, IF TXN-CREDIT). The copybooks are self-documenting with comments. A new developer joining the team could read this code and understand the system. That readability has economic value — it reduces the cost of maintenance, which is where most of a system's budget goes.
🔗 Theme: Defensive Programming. Every file operation checks its status code. Every transaction is validated before processing. Every potential error is handled explicitly. The program never assumes success — it verifies it. This defensive approach means that when something goes wrong (and in production, something always goes wrong), the system fails gracefully with useful diagnostic information instead of crashing with a SOC7 ABEND.
What Derek Would Do Differently
After the code review, Derek reflects on what he would change if he started over:
-
Build the test data before writing any code. Derek wrote the programs first and then created test data. This meant he was testing against data he designed to work, not data that would challenge his assumptions. Next time, he would design the test cases first — especially the edge cases and error cases — and let those cases drive his validation logic.
-
Create the audit trail copybook earlier. Derek added the audit trail late in the development process, which required going back and modifying both ACCT-LOAD and TXN-PROC. If he had designed the audit record first, the integration would have been smoother.
-
Think about operational concerns from the start. The JCL job stream, the GDG for output files, the COND parameters for conditional execution — these were afterthoughts. In a production system, the operational design is as important as the program design. Maria confirms this: "I've seen technically excellent programs that were nightmares to deploy because nobody thought about the JCL until the last minute."
-
Use EVALUATE more aggressively. Several places where Derek used nested IF statements would be clearer as EVALUATE blocks. The transaction type processing in TXN-PROC, for example, uses a series of IF/ELSE IF blocks that would read better as EVALUATE WS-TXN-TYPE.
-
Add more comments about why, not what. Derek's comments tend to describe what the code does ("Check if account is active"). Maria suggests commenting on why the code does it ("Active check must precede balance check because frozen accounts should not process any transaction, regardless of balance sufficiency"). The "why" is what future developers need most — the "what" is already in the code.
These reflections are not signs of failure — they are signs of professional growth. Every experienced developer looks at their past code and sees improvements they would make. The important thing is to recognize the patterns and apply them to the next project.
The Architecture of Real-World COBOL Systems
Derek's capstone system, while simplified, demonstrates the fundamental architecture that powers most large-scale COBOL applications. Let us examine how this architecture scales to production systems.
The Batch Window
In production, batch processing runs in a defined window — typically overnight, when online systems are quiescent. GlobalBank's batch window runs from 6 PM to 6 AM. During this window, the daily job stream processes transactions, generates reports, performs backups, and prepares files for the next business day.
The batch window is a constraint that shapes system design. If transaction volume grows faster than processing speed, the batch window closes — meaning batch jobs cannot complete before the next business day begins. This is why performance matters in batch COBOL: a program that processes 10,000 records per second is not fast enough if you have 100 million records and only 12 hours.
Strategies for managing the batch window include:
- Sort optimization: Sorting input files to match VSAM key sequence reduces random I/O
- Buffer allocation: Proper BUFND and BUFNI parameters for VSAM reduce physical I/O
- Program efficiency: Avoiding unnecessary I/O, minimizing PERFORM nesting, using COMP-3 for arithmetic
- Parallel processing: Splitting input files and running multiple instances concurrently
The Online-Batch Integration Challenge
ACCT-INQ (online) and TXN-PROC (batch) both access the same VSAM file. This creates a contention problem: if batch has the file open for I-O, can online programs read it?
The answer depends on VSAM share options: - SHAREOPTIONS(1): One user at a time. Batch locks out online. - SHAREOPTIONS(2): Multiple readers, one writer. Online can read while batch writes, but data might be stale. - SHAREOPTIONS(3): Multiple readers and writers. Application must manage integrity. - SHAREOPTIONS(4): Multiple readers and writers with VSAM-managed buffering.
Derek's system uses SHAREOPTIONS(2,3), which is a common production configuration. In practice, most shops schedule batch to run when CICS regions are down, or use a staging approach where batch writes to a shadow file that is swapped in after processing.
Recovery and Restart
Production systems must handle failures gracefully. If TXN-PROC ABENDs after processing 40,000 of 100,000 transactions, what happens?
Without checkpoint/restart logic, the entire job must be rerun. But the first 40,000 transactions have already been applied to the account master. Rerunning them would apply them again, doubling every deposit and withdrawal.
The solution is checkpoint/restart: 1. The program writes a checkpoint record every N transactions (e.g., every 1,000) 2. The checkpoint records the position in the input file and a count of records processed 3. If the program ABENDs, it can be restarted from the last checkpoint 4. Restart logic skips already-processed records and resumes from the checkpoint position
Derek's training system does not implement checkpoint/restart, but Maria notes it on his list of "things to learn next."
Security Considerations
In production, COBOL programs run under security controls:
- RACF (or ACF2/Top Secret) controls who can run programs and access datasets
- CICS security controls who can execute transactions and access resources
- DB2 security controls who can execute SQL statements against tables
- Audit trails record who did what, when, for regulatory compliance
Derek's audit trail is the first step toward a security-aware system. In production, the AUD-OPERATOR-ID field would contain the actual TSO userid or CICS operator ID, not a hardcoded value.
Common Mistakes and How to Avoid Them
Maria shares her catalog of common mistakes that junior developers make when building their first COBOL system:
Mistake 1: Not Checking File Status After Every I/O
* WRONG: Assumes READ always succeeds
READ ACCOUNT-MASTER
MOVE CURRENT-BALANCE TO WS-DISPLAY-BAL
* RIGHT: Always check file status
READ ACCOUNT-MASTER
IF WS-VSAM-OK
MOVE CURRENT-BALANCE TO WS-DISPLAY-BAL
ELSE
PERFORM 9000-HANDLE-VSAM-ERROR
END-IF
Mistake 2: Using STOP RUN in a Subprogram
* WRONG: Kills the entire run unit
IF LS-RETURN-CODE NOT = 00
DISPLAY 'ERROR IN DATE UTILITY'
STOP RUN
END-IF
* RIGHT: Return to caller with error code
IF LS-RETURN-CODE NOT = 00
GOBACK
END-IF
Mistake 3: Hardcoding Literals Instead of Using 88-Levels
* WRONG: Magic values scattered through code
IF ACCT-TYPE = 'C'
PERFORM 2210-PROCESS-CHECKING
* RIGHT: Self-documenting condition names
IF ACCT-CHECKING
PERFORM 2210-PROCESS-CHECKING
Mistake 4: Not Initializing Accumulators
* WRONG: Relies on VALUE clauses (which only set initial values)
ADD TXN-AMOUNT TO WS-TOTAL-CREDITS
* After a restart, WS-TOTAL-CREDITS may contain
* leftover values from the previous run
* RIGHT: Explicitly initialize in 1000-INITIALIZE
1000-INITIALIZE.
INITIALIZE WS-COUNTERS
INITIALIZE WS-TOTALS
Mistake 5: Forgetting the Sign on Monetary Fields
* WRONG: Balance cannot go negative (overdraft)
05 CURRENT-BALANCE PIC 9(9)V99 COMP-3.
* RIGHT: Signed field handles overdrafts
05 CURRENT-BALANCE PIC S9(9)V99 COMP-3.
Mistake 6: Not Handling the Transfer Two-Phase Problem
* WRONG: If target credit fails, source was already debited
SUBTRACT TXN-AMOUNT FROM SOURCE-BALANCE
REWRITE SOURCE-RECORD
ADD TXN-AMOUNT TO TARGET-BALANCE
REWRITE TARGET-RECORD *> What if this fails?
* BETTER: Log both phases and verify
SUBTRACT TXN-AMOUNT FROM SOURCE-BALANCE
REWRITE SOURCE-RECORD
IF REWRITE-OK
ADD TXN-AMOUNT TO TARGET-BALANCE
REWRITE TARGET-RECORD
IF NOT REWRITE-OK
PERFORM 2339-LOG-TRANSFER-FAILURE
*> Recovery procedure needed
END-IF
END-IF
Deep Dive: The BMS Map for ACCT-INQ
While we showed the COBOL code for ACCT-INQ earlier, the screen itself is defined through BMS (Basic Mapping Support) macros. Understanding BMS is essential for any COBOL developer working with CICS online transactions.
Screen Layout Design
Before writing BMS macros, Derek designs the screen on paper. The standard 3270 terminal is 24 rows by 80 columns. The screen layout for the account inquiry is:
Row 01: Title line — "GLOBALBANK ACCOUNT INQUIRY"
Row 02: Blank separator
Row 03: Account Number input field with label
Row 04: Blank separator
Row 05: Customer Name (display only)
Row 06: Account Type (display only)
Row 07: Account Status (display only)
Row 08: Blank separator
Row 09: Current Balance (display only)
Row 10: Available Balance (display only)
Row 11: Blank separator
Row 12: Date Opened (display only)
Row 13: Last Activity Date (display only)
Row 14: YTD Interest (display only)
Row 15: Blank separator
Row 16-22: Reserved for future expansion
Row 23: Message line (status messages, errors)
Row 24: PF key legend — "PF3=Exit PF12=Cancel"
This layout follows established 3270 design conventions: the input field is near the top, display fields fill the middle, and the message line and PF key legend are at the bottom. Users can enter an account number and press Enter without scrolling.
BMS Macro Source
The BMS macros define the physical and symbolic maps. The physical map controls the screen layout; the symbolic map generates the COBOL copybook that the program uses:
AINQSET DFHMSD TYPE=&SYSPARM, X
LANG=COBOL, X
MODE=INOUT, X
TERM=3270-2, X
CTRL=FREEKB, X
STORAGE=AUTO, X
TIOAPFX=YES
*
AINQMAP DFHMDI SIZE=(24,80), X
LINE=1, X
COLUMN=1
*
DFHMDF POS=(01,01),LENGTH=26, X
ATTRB=(ASKIP,BRT), X
INITIAL='GLOBALBANK ACCOUNT INQUIRY'
*
DFHMDF POS=(03,01),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='ACCOUNT NUMBER:'
ACCTNO DFHMDF POS=(03,18),LENGTH=10, X
ATTRB=(UNPROT,BRT,IC), X
INITIAL=' '
DFHMDF POS=(03,29),LENGTH=01, X
ATTRB=ASKIP
*
DFHMDF POS=(05,01),LENGTH=14, X
ATTRB=(ASKIP,NORM), X
INITIAL='CUSTOMER NAME:'
CNAME DFHMDF POS=(05,18),LENGTH=46, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(06,01),LENGTH=13, X
ATTRB=(ASKIP,NORM), X
INITIAL='ACCOUNT TYPE:'
ATYPE DFHMDF POS=(06,18),LENGTH=10, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(07,01),LENGTH=07, X
ATTRB=(ASKIP,NORM), X
INITIAL='STATUS:'
ASTAT DFHMDF POS=(07,18),LENGTH=08, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(09,01),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='CURRENT BALANCE:'
CBAL DFHMDF POS=(09,18),LENGTH=15, X
ATTRB=(ASKIP,BRT)
*
DFHMDF POS=(10,01),LENGTH=18, X
ATTRB=(ASKIP,NORM), X
INITIAL='AVAILABLE BALANCE:'
ABAL DFHMDF POS=(10,18),LENGTH=15, X
ATTRB=(ASKIP,BRT)
*
DFHMDF POS=(12,01),LENGTH=12, X
ATTRB=(ASKIP,NORM), X
INITIAL='DATE OPENED:'
DTOPN DFHMDF POS=(12,18),LENGTH=10, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(13,01),LENGTH=14, X
ATTRB=(ASKIP,NORM), X
INITIAL='LAST ACTIVITY:'
DTLST DFHMDF POS=(13,18),LENGTH=10, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(14,01),LENGTH=13, X
ATTRB=(ASKIP,NORM), X
INITIAL='YTD INTEREST:'
YINT DFHMDF POS=(14,18),LENGTH=13, X
ATTRB=(ASKIP,NORM)
*
MSG DFHMDF POS=(23,01),LENGTH=50, X
ATTRB=(ASKIP,BRT)
*
DFHMDF POS=(24,01),LENGTH=25, X
ATTRB=(ASKIP,NORM), X
INITIAL='PF3=EXIT PF12=CANCEL'
*
DFHMSD TYPE=FINAL
END
Understanding BMS Attributes:
- ASKIP: Auto-skip — the cursor cannot be positioned in this field. Used for labels and display-only fields.
- UNPROT: Unprotected — the user can type in this field. Used for input fields.
- BRT: Bright — displayed in high intensity. Used for emphasis.
- NORM: Normal — displayed in standard intensity.
- IC: Insert Cursor — the cursor is initially positioned in this field when the screen is displayed.
The field names (ACCTNO, CNAME, CBAL, etc.) generate COBOL data names in the symbolic map. Each field gets both an input suffix (I) and output suffix (O), which is why the COBOL program references ACCTNOI (for input) and ACCTNOO (for output).
How BMS Maps Work at Runtime
When CICS sends a map, it merges the physical map (stored in the CICS load library) with the data in the symbolic map (provided by the program). The merged result is transmitted to the terminal as a 3270 data stream. When the user presses Enter, CICS receives the 3270 data stream, extracts the modified fields, and places them in the symbolic map for the program to read.
This separation of presentation (BMS maps) from logic (COBOL program) is an early example of the MVC (Model-View-Controller) pattern. The map is the view, the COBOL program is the controller, and the VSAM file is the model.
Understanding VSAM Internals for Banking Applications
The VSAM KSDS that stores the account master is not a black box. Understanding how VSAM organizes data helps Derek make better design decisions and diagnose performance problems.
Control Intervals and Control Areas
VSAM stores data in Control Intervals (CIs) — fixed-size blocks that are the unit of I/O. A CI contains records, free space, and control information. Multiple CIs are grouped into Control Areas (CAs) — the unit of space allocation.
For Derek's account master with CISZ(4096):
- Each CI is 4,096 bytes
- Each CI has 10 bytes of CI control information (CIDF and RDFs)
- Available space per CI: 4,086 bytes
- With 200-byte records: up to 20 records per CI
- With 20% free space (FREESPACE first parameter): about 16 records initially
When a new account is inserted into a CI that is full, VSAM performs a CI split: it creates a new CI, moves approximately half the records from the full CI to the new CI, and inserts the new record in the appropriate CI. CI splits are expensive — they require multiple I/O operations and can temporarily degrade performance.
Buffer Pool Management
When a COBOL program reads a VSAM record, the record might already be in a buffer pool (memory) from a previous access. If it is, no physical I/O is needed — this is a buffer hit. If it is not, VSAM must read the CI from disk — a buffer miss.
The JCL AMP parameter controls how many buffers are allocated:
//ACCTMSTR DD DSN=GLOBALBANK.ACCT.MASTER,DISP=SHR,
// AMP=('BUFND=10,BUFNI=3')
- BUFND: Data buffers. Each buffer holds one data CI. More buffers = more records in memory = fewer physical I/Os.
- BUFNI: Index buffers. Each buffer holds one index CI. Keeping the index in memory eliminates index I/O for random reads.
For sequential access (ACCT-LOAD, RPT-STMT), larger BUFND values improve performance because VSAM can read ahead. For random access (TXN-PROC), BUFND helps if there is locality of reference (consecutive transactions for the same or nearby accounts).
Maria advises Derek: "Start with the defaults. Profile the program. If I/O is the bottleneck, increase BUFND. Don't guess — measure."
Alternate Index Design
In production, the account master might need an alternate index on customer name — so customer service can look up accounts by name, not just by number. VSAM Alternate Indexes (AIX) provide this capability:
DEFINE AIX -
(NAME(GLOBALBANK.ACCT.MASTER.NAMEIX) -
RELATE(GLOBALBANK.ACCT.MASTER) -
KEYS(25 10) -
NONUNIQUEKEY -
UPGRADE) -
DATA -
(NAME(GLOBALBANK.ACCT.MASTER.NAMEIX.DATA))
The NONUNIQUEKEY specification is important: multiple accounts can have the same customer name. The UPGRADE keyword tells VSAM to automatically update the AIX when the base cluster is modified.
Data Integrity Across Programs
One of the most important architectural concerns in Derek's system is data integrity across programs. When five programs share a single VSAM file, any program can potentially corrupt the data for all others.
The Consistency Contract
All programs must agree on:
- Record format: The copybook defines the record layout. If one program writes a record with a different layout, all other programs will misinterpret the data.
- Validation rules: If ACCT-LOAD validates that ACCT-STATUS is 'A', 'C', or 'F', then no other program should write a different value. The 88-level conditions in the copybook enforce this — but only if every program uses them.
- Business rules: If a debit should never make the balance negative (for savings accounts), then both TXN-PROC (batch debits) and ACCT-INQ (which might be extended to support online debits) must enforce this rule.
- Sequence of operations: If RPT-STMT reads the account master while TXN-PROC is updating it, RPT-STMT might see partially updated data. The SHAREOPTIONS and batch scheduling must prevent this.
Preventing Data Corruption
Maria shares three rules for preventing data corruption in multi-program systems:
Rule 1: One writer at a time. Never have two programs with I-O access to the same VSAM file simultaneously. Schedule batch programs to run sequentially, not in parallel.
Rule 2: Always check the REWRITE. After updating a record, verify that the REWRITE succeeded before proceeding to the next transaction. A failed REWRITE means the record was not updated — but the program might have already logged the transaction as successful.
Rule 3: Validate on read, not just on write. When TXN-PROC reads an account record, it should validate the data before operating on it. If the record is corrupted (perhaps from a previous ABEND), the program should report the error instead of applying a transaction to bad data.
* Defensive read - validate data before using it
READ ACCOUNT-MASTER
IF WS-VSAM-OK
IF NOT ACCT-VALID-STATUS
DISPLAY 'DATA INTEGRITY ERROR: ACCT '
ACCT-NUMBER
' INVALID STATUS: '
ACCT-STATUS
ADD 1 TO WS-TXN-REJECTED
GO TO 2300-EXIT
END-IF
IF CURRENT-BALANCE NOT NUMERIC
DISPLAY 'DATA INTEGRITY ERROR: ACCT '
ACCT-NUMBER
' INVALID BALANCE'
ADD 1 TO WS-TXN-REJECTED
GO TO 2300-EXIT
END-IF
END-IF
This defensive validation adds a few microseconds per transaction but has saved GlobalBank from propagating corrupted data through an entire batch run.
Working with the Student Mainframe Lab
If you are running these programs on the Student Mainframe Lab (Hercules emulator or IBM Z Xplore), there are several practical considerations:
GnuCOBOL Compatibility
GnuCOBOL does not support VSAM or CICS. To run the batch programs (ACCT-LOAD, TXN-PROC, RPT-STMT, UTIL-DATE) on GnuCOBOL, you will need to substitute indexed files for VSAM:
* GnuCOBOL version: use ORGANIZATION IS INDEXED
SELECT ACCOUNT-MASTER
ASSIGN TO 'acctmstr.dat'
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS ACCT-NUMBER
FILE STATUS IS WS-VSAM-STATUS.
GnuCOBOL's indexed file support uses Berkeley DB or similar backends. The behavior is similar to VSAM KSDS for the purposes of these exercises, though performance characteristics differ.
For ACCT-INQ (the CICS program), you would need to create a batch equivalent that reads an account number from ACCEPT and displays the result via DISPLAY. This demonstrates the same lookup logic without the CICS infrastructure.
Compiling and Running
On GnuCOBOL:
# Compile each program
cobc -x -o acctload ACCTLOAD.cbl
cobc -x -o txnproc TXNPROC.cbl
cobc -x -o rptstmt RPTSTMT.cbl
cobc -m -o utildate UTILDATE.cbl # -m for subprogram (shared object)
# Run the load
export COB_FILE_PATH=.
./acctload < test_accounts.dat
# Run transaction processing
./txnproc < test_transactions.dat
# Run statements
./rptstmt
On the mainframe (z/OS), the JCL provided earlier handles compilation and execution. The STEPLIB DD statement points to the load library where the compiled programs reside.
Creating Test Data
For testing, Derek creates two test data files. Here is a sample of the account load data (formatted as fixed-length records matching the ACCTMSTR copybook):
| Account | Last Name | First Name | MI | Type | Status | Balance | Date Opened |
|---|---|---|---|---|---|---|---|
| 0000010001 | WASHINGTON | DEREK | D | C | A | 5000.00 | 20230115 |
| 0000010002 | CHEN | MARIA | M | S | A | 25000.00 | 20100301 |
| 0000010003 | KAPOOR | PRIYA | P | C | A | 12500.00 | 20150620 |
| 0000010004 | CLOSED-ACCT | TEST | T | C | C | 0.00 | 20200101 |
| 0000010005 | FROZEN-ACCT | TEST | T | S | F | 8000.00 | 20190715 |
And sample transaction data:
| Account | Type | Amount | Description |
|---|---|---|---|
| 0000010001 | CR | 1500.00 | PAYROLL DEPOSIT |
| 0000010001 | DR | 200.00 | ATM WITHDRAWAL |
| 0000010002 | CR | 500.00 | SAVINGS TRANSFER |
| 0000010003 | XF | 1000.00 | TRANSFER TO 0000010001 |
| 0000010001 | FE | 25.00 | MONTHLY FEE |
| 0000099999 | CR | 100.00 | ACCOUNT NOT FOUND TEST |
| 0000010004 | DR | 50.00 | CLOSED ACCOUNT TEST |
| 0000010001 | DR | 99999.00 | NSF TEST |
This test data exercises every code path: valid credits, debits, transfers, fees, account-not-found, closed-account rejection, and insufficient-funds rejection.
Extending the System
Derek's capstone system is complete, but it is deliberately simplified. Here are the extensions that would be needed for a production system:
Interest Calculation
A production system would calculate and apply interest. For savings accounts, interest accrues daily and is credited monthly. The UTIL-DATE subprogram's days-between function provides the foundation for this calculation.
* Interest calculation sketch
CALL 'UTIL-DATE' USING WS-DATE-UTIL-PARAMS
COMPUTE WS-DAILY-INTEREST =
CURRENT-BALANCE *
(WS-ANNUAL-RATE / 365) *
WS-DU-DAYS-RESULT
END-COMPUTE
Multi-Currency Support
GlobalBank's production system handles multiple currencies. This requires exchange rate tables, currency conversion routines, and rounding rules that vary by currency. The copybook would need additional fields:
05 ACCT-CURRENCY PIC X(03).
88 ACCT-USD VALUE 'USD'.
88 ACCT-EUR VALUE 'EUR'.
88 ACCT-GBP VALUE 'GBP'.
Online Transaction Entry
The current system processes transactions in batch. A production system would also support online transaction entry through CICS. This requires CICS programs for deposits, withdrawals, and transfers, with real-time updates to the account master and immediate audit trail writing.
Database Migration
As the system grows, the VSAM files may be migrated to DB2 tables. This is the subject of Chapter 44's capstone — legacy system modernization.
Understanding the Complete Data Flow
To truly grasp how the banking system works, let us trace a single transaction through the entire system — from initial account creation to statement generation. This end-to-end walkthrough ties together every program, every file, and every design decision.
The Life of Account 0000010001
Step 1: Account Creation (ACCT-LOAD)
The journey begins with a flat file containing Derek Washington's new checking account. The record looks like this (in display format for readability):
0000010001WASHINGTON DEREK DC A 00000500000 00000500000 20230115 20230115 00000000000 00000 00000000000 00000000000 [20 bytes FILLER]
ACCT-LOAD reads this record, validates the account number (not blank), validates the balance (numeric), validates the date opened (between 19000101 and today), and validates the status (A is in the valid set). All checks pass. The program writes the record to the VSAM KSDS and reports "LOADED" on the load report.
At this point, the account exists in the VSAM file with a balance of $5,000.00. It has zero transactions for the month, zero debit total, and zero credit total.
Step 2: Payroll Deposit (TXN-PROC, Credit)
The next morning, GlobalBank receives a transaction file from the ACH network. Derek's payroll deposit is in the file:
0000010001CR 00000150000 20240301 143022 PAYROLL DEPOSIT REF000001234 [spaces] P [spaces]
TXN-PROC reads this transaction. It validates the type (CR is valid), the amount ($1,500.00, greater than zero), and the account number (not blank). All checks pass.
TXN-PROC reads account 0000010001 from the VSAM file (random access by key). The account is active. The program saves the current balance ($5,000.00) in WS-SAVE-BALANCE.
The program applies the credit: adds $1,500.00 to CURRENT-BALANCE (now $6,500.00), adds $1,500.00 to AVAIL-BALANCE (now $6,500.00), adds $1,500.00 to MTD-CREDIT-TOTAL, increments MTD-TXN-COUNT by 1, and sets DATE-LAST-ACTIVITY to 20240301.
The program rewrites the account record and writes an audit trail record:
Audit: 20240301 143522 TXN-PROC 0000010001 UPD CR 00000150000 00000500000 00000650000 S [spaces] BATCH
The audit record shows: before-balance $5,000.00, after-balance $6,500.00, status Success. This record provides complete traceability — an auditor can verify that $5,000 + $1,500 = $6,500.
Step 3: ATM Withdrawal (TXN-PROC, Debit)
Later the same day, Derek withdraws $200.00 from an ATM:
TXN-PROC processes this as a DR (debit) transaction. The program reads the account (balance now $6,500.00), checks that $200.00 is not greater than the available balance ($6,500.00 — it is not), subtracts $200.00 from both CURRENT-BALANCE and AVAIL-BALANCE (now $6,300.00), adds $200.00 to MTD-DEBIT-TOTAL, and increments MTD-TXN-COUNT.
Another audit trail record is written with before-balance $6,500.00 and after-balance $6,300.00.
Step 4: Insufficient Funds Attempt (TXN-PROC, Rejected Debit)
Derek's roommate tries to cash a check for $10,000.00 against Derek's account. TXN-PROC reads the account (balance $6,300.00), checks whether $10,000.00 exceeds the available balance. It does. The program sets WS-TRANSACTION-BAD to TRUE, assigns reject reason INSF (Insufficient Funds), and writes a rejection audit record.
The account is not modified. The audit record shows status F (Failed) with reason code INSF. Derek's balance remains $6,300.00.
Step 5: Transfer to Savings (TXN-PROC, Transfer)
Derek transfers $1,000.00 to his savings account (0000010002). TXN-PROC processes this as an XF (transfer) transaction.
First, it debits the source account (0000010001): subtracts $1,000.00 from both balances (now $5,300.00), increments MTD-DEBIT-TOTAL and MTD-TXN-COUNT, rewrites the record.
Then, it credits the target account (0000010002): reads account 0000010002, verifies it is active, adds $1,000.00 to both balances, increments the target's MTD-CREDIT-TOTAL and MTD-TXN-COUNT, rewrites the target record.
The audit trail records both the source debit and the target credit, providing complete traceability for the transfer.
Step 6: Monthly Fee (TXN-PROC, Fee)
At month-end, the system assesses a $25.00 monthly maintenance fee:
TXN-PROC processes this as an FE (fee) transaction. Note that fees do NOT check for insufficient funds — the bank always applies its fees, even if the account goes negative. The program subtracts $25.00 from both balances (now $5,275.00), adds to MTD-DEBIT-TOTAL, and rewrites.
Step 7: Statement Generation (RPT-STMT)
At month-end, RPT-STMT reads account 0000010001 sequentially. The program computes the opening balance:
Opening Balance = Current Balance - MTD Credits + MTD Debits
= $5,275.00 - $1,500.00 + ($200.00 + $1,000.00 + $25.00)
= $5,275.00 - $1,500.00 + $1,225.00
= $5,000.00
This matches the original account balance — confirming that all transactions are accounted for.
The statement shows:
OPENING BALANCE: $5,000.00
TOTAL CREDITS: $1,500.00
TOTAL DEBITS: $1,225.00
CLOSING BALANCE: $5,275.00
Step 8: Online Inquiry (ACCT-INQ)
At any point during this process, a customer service representative can use ACCT-INQ to look up Derek's account. The CICS transaction reads the VSAM record (without locking it) and displays the current balance, available balance, status, and dates. The inquiry does not modify the account and does not interfere with batch processing.
The Audit Trail as the System of Record
Notice that the audit trail records every modification to every account. If the VSAM file were corrupted, the system could be reconstructed from the initial load file plus the complete audit trail. This is not hypothetical — Maria has done it twice in her career. "The audit trail is not just for auditors," she tells Derek. "It's your recovery mechanism. If you lose the master file, you can rebuild it from the trail. If you lose the trail, you've lost the history of the system."
Advanced Topics: What Production Systems Add
Derek's capstone system demonstrates the fundamental patterns. Production systems at GlobalBank add several layers of sophistication:
Checkpoint/Restart
Production TXN-PROC processes 2.3 million transactions per night. If the program ABENDs after processing 1.5 million transactions, restarting from the beginning would reapply 1.5 million transactions (causing duplicate postings) and waste 3 hours of batch window.
The checkpoint/restart mechanism works as follows:
- Every 10,000 transactions, the program writes a checkpoint record to a checkpoint dataset. The record contains the current position in the input file and a summary of processing totals.
- If the program ABENDs, the operator initiates a restart. The restart logic reads the checkpoint record, repositions the input file to the last checkpoint, and resumes processing from there.
- The account master must also be restored to the checkpoint state. In production, this is done using DB2's point-in-time recovery or VSAM's backup from the last checkpoint.
* Checkpoint logic (simplified)
2900-CHECKPOINT.
IF WS-TXN-COUNT - WS-LAST-CHECKPOINT >= 10000
EXEC CICS SYNCPOINT END-EXEC
MOVE WS-TXN-COUNT TO WS-LAST-CHECKPOINT
PERFORM 2910-WRITE-CHECKPOINT-RECORD
END-IF
.
Currency Conversion
GlobalBank processes transactions in multiple currencies. The production TXN-PROC includes a currency conversion subprogram that:
- Reads the exchange rate from a daily rate table
- Converts the transaction amount to the account's base currency
- Applies rounding rules specific to each currency (JPY rounds to whole numbers; USD/EUR round to cents)
- Logs both the original and converted amounts in the audit trail
Real-Time Fraud Detection
Before applying any debit transaction, the production system calls a fraud detection service that checks for suspicious patterns:
- Multiple large withdrawals in a short time
- Transactions from geographically distant locations
- Amounts that exactly match common fraud patterns
- Transactions on newly opened accounts
If the fraud check returns a high-risk score, the transaction is flagged for manual review instead of being applied immediately.
Regulatory Reporting
Financial regulators require banks to report certain transactions. The production system generates:
- CTR (Currency Transaction Report): For cash transactions over $10,000
- SAR (Suspicious Activity Report): For transactions flagged by fraud detection
- 1099-INT: Year-end interest reporting for tax purposes
These reports are generated as part of the batch job stream, using data from both the account master and the audit trail.
Summary
In this capstone, you built a complete banking transaction system from scratch. You followed a professional software development lifecycle:
- Requirements Analysis: Understanding the business problem before writing code
- Data Design: Creating copybooks, data dictionaries, and VSAM definitions
- Program Design: Drawing structure charts and defining interfaces
- Implementation: Writing five integrated COBOL programs
- Testing: Designing test cases for unit, integration, and regression testing
- Deployment: Creating JCL job streams with conditional execution and GDGs
The system demonstrates all five themes of this textbook:
- Legacy != Obsolete: The patterns you used are decades old and still process trillions of dollars daily
- Readability is a Feature: Self-documenting code with meaningful names and consistent structure
- The Modernization Spectrum: The system is designed for incremental improvement, not wholesale replacement
- Defensive Programming: Every operation checks for errors and handles them explicitly
- The Human Factor: Maria's mentorship of Derek illustrates how knowledge transfers in mainframe teams
You are now ready to move beyond building new systems to the equally important challenge of modernizing existing ones — which is the subject of Capstone 2.
Chapter Summary
Building a complete COBOL system requires more than programming skill — it requires systems thinking. Understanding how programs interact through shared files, how batch processing flows through JCL job streams, how online and batch programs coexist, and how errors propagate through a system are all essential skills for a COBOL developer.
The five programs in this capstone — ACCT-LOAD, TXN-PROC, ACCT-INQ, RPT-STMT, and UTIL-DATE — form a cohesive system because they share data definitions (copybooks), follow consistent error handling patterns, and are designed to work together. This is not accidental. It is the result of deliberate design decisions made before the first line of code was written.
Derek Washington's capstone project is a milestone in his career at GlobalBank. He has proven that he can design and implement a system from scratch, think through edge cases, handle errors defensively, and accept feedback from a more experienced developer. These are the skills that separate a programmer from a software engineer.
As Priya Kapoor tells him at the end of his code review: "Building new systems is important. But most of your career will be spent working with systems that already exist. That's where the real skill is — understanding code you didn't write, improving it without breaking it, and modernizing it without losing what makes it work."
That is the challenge of Capstone 2.