45 min read

> "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."

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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:

  1. First invocation (WS-FIRST-TIME): Send an empty map with a prompt.
  2. Second invocation (WS-PROCESS-INPUT): Receive the user's input, read the VSAM file, format and display the results.
  3. 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:

  1. Run ACCT-LOAD to create the master file
  2. Run TXN-PROC to process transactions
  3. Run RPT-STMT to generate statements
  4. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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:

  1. 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.

  2. 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.

  3. 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."

  4. 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.

  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. 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.
  2. 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.
  3. 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:

  1. Reads the exchange rate from a daily rate table
  2. Converts the transaction amount to the account's base currency
  3. Applies rounding rules specific to each currency (JPY rounds to whole numbers; USD/EUR round to cents)
  4. 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:

  1. Requirements Analysis: Understanding the business problem before writing code
  2. Data Design: Creating copybooks, data dictionaries, and VSAM definitions
  3. Program Design: Drawing structure charts and defining interfaces
  4. Implementation: Writing five integrated COBOL programs
  5. Testing: Designing test cases for unit, integration, and regression testing
  6. 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.