💳 COBOL BILLING — QUICK REFERENCE
v1.1 · Post-walkthrough edition: REEL status, test-before-prod, 4-stage pipeline
🐸 Monday 29 Jun 2026 · 08:45 · Rib IT Ltd
📐 BATCH JOB PATTERN
OPEN I-O billing-file.
PERFORM UNTIL WS-EOF = 'Y'
READ billing-file INTO WS-BILL-REC
AT END
MOVE 'Y' TO WS-EOF
NOT AT END
PERFORM 100-PROCESS-BILL
END-READ
END-PERFORM.
CLOSE billing-file.
🐸 Froggy's Rule: "Every billing run needs THREE outputs: the billing file (success), the error report (failures), and a control total (did the numbers balance?). If you only have two, you're missing reconciliation data."
🔄 READ-PROCESS-WRITE
OPEN INPUT trans-file
OUTPUT bill-file
OUTPUT error-report.
PERFORM 200-READ-TRANS
PERFORM UNTIL WS-EOF = 'Y'
PERFORM 300-CALC-BILL
IF WS-BILL-OK
WRITE WS-BILL-REC FROM WS-CALC
ELSE
WRITE WS-ERROR-LINE FROM WS-BAD-AMOUNT
END-IF
PERFORM 200-READ-TRANS
END-PERFORM.
🧮 ROUNDED ARITHMETIC
* Always ROUNDED for financial calcs
MULTIPLY WS-UNIT-PRICE BY WS-QUANTITY
GIVING WS-LINE-TOTAL ROUNDED.
COMPUTE WS-GROSS-AMOUNT ROUNDED =
WS-LINE-TOTAL * (1 + WS-TAX-RATE).
* PICTURE for money: PIC 9(7)V99
* 9(7) = integer part, V = implied decimal, 99 = 2 decimals
77 WS-AMOUNT PIC 9(7)V99 VALUE ZEROS.
77 WS-TAX-AMOUNT PIC 9(7)V99 VALUE ZEROS.
🐸 Froggy's Rule: "If you don't ROUNDED, the bank will round your money into a different dimension. Always. ROUNDED."
📊 CONTROL TOTALS & RECONCILIATION
* Count what went in, what went out
77 WS-REC-COUNT PIC 9(5) VALUE ZEROS.
77 WS-OK-COUNT PIC 9(5) VALUE ZEROS.
77 WS-ERROR-COUNT PIC 9(5) VALUE ZEROS.
77 WS-CONTROL-TOTAL PIC 9(9)V99 VALUE ZEROS.
PROCEDURE DIVISION.
ADD 1 TO WS-REC-COUNT.
ADD WS-AMOUNT TO WS-CONTROL-TOTAL ROUNDED.
* At end-of-job: compare totals
DISPLAY "Records in: " WS-REC-COUNT.
DISPLAY "Control total: $" WS-CONTROL-TOTAL.
🐸 Froggy's Rule: "Control totals are your safety net. When the billing file says $1,492,367.42 and the control total says $1,492,367.42, you sleep well. When they don't match? That's an investigation before anyone sends a bill."
⚠️ ERROR HANDLING
* FILE STATUS check after every I/O:
SELECT billing-file ASSIGN TO "BILLING.DAT"
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-FILE-STATUS.
READ billing-file INTO WS-RECORD
INVALID KEY
DISPLAY "Error: " WS-FILE-STATUS
PERFORM 900-LOG-ERROR
ADD 1 TO WS-ERROR-COUNT
CONTINUE * Don't ABEND!
END-READ.
🐸 Froggy's Rule: "Never STOP RUN on a bad record. One bad customer shouldn't block 50,000 others. Log it, count it, keep going."
🏷️ FINANCIAL PICTURE CLAUSES
| Pattern | Usage | Range |
PIC 9(7)V99 | Amount | 0.00 – 9,999,999.99 |
PIC 9(5) | Count/Totals | 0 – 99,999 |
PIC S9(7)V99 | Signed amount | ±9,999,999.99 |
PIC 9(3) | Small counts | 0 – 999 |
PIC X(30) | Customer name | 30 chars |
PIC X(8) | Date (YYYYMMDD) | 8 chars |
01 WS-BILL-HEADER.
05 WS-BILL-DATE PIC 9(8).
05 WS-BILL-CUST PIC 9(5).
05 WS-BILL-AMOUNT PIC 9(7)V99.
05 WS-BILL-STATUS PIC X.
88 WS-BILL-PAID VALUE 'P'.
88 WS-BILL-DUE VALUE 'D'.
88 WS-BILL-OVERDUE VALUE 'O'.
🔢 JCL RETURN CODES
* Set return code before STOP RUN:
IF WS-ERROR-COUNT = 0
MOVE 0 TO WS-RETURN-CODE
ELSE IF WS-ERROR-COUNT < 5
MOVE 4 TO WS-RETURN-CODE * Warnings
ELSE IF WS-ERROR-COUNT < 20
MOVE 8 TO WS-RETURN-CODE * Errors
ELSE
MOVE 16 TO WS-RETURN-CODE * ABEND
END-IF.
MOVE WS-RETURN-CODE TO RETURN-CODE.
STOP RUN.
0 = SUCCESS
4 = WARNINGS
8 = ERRORS
16 = ABEND
🔀 BILLING CONDITIONALS
* 88-level condition names for readability
EVALUATE WS-BILL-STATUS
WHEN 'P'
PERFORM 400-RECORD-PAYMENT
WHEN 'D'
PERFORM 410-CALC-LATE-FEE
WHEN 'O'
PERFORM 420-ESCALATE-COLLECTIONS
WHEN 'C'
PERFORM 430-CANCEL-BILL
WHEN OTHER
PERFORM 900-LOG-ERROR
END-EVALUATE.
* 88-level version (cleaner):
IF WS-BILL-PAID
PERFORM 400-RECORD-PAYMENT
ELSE IF WS-BILL-OVERDUE
PERFORM 420-ESCALATE
ELSE
PERFORM 410-CALC-LATE-FEE
END-IF.
❓ ASK FROGGY ON MONDAY
Q1: "How does the billing system handle partial failures — does it write an audit trail for every skipped record? What's the threshold before it ABENDs the whole batch?"
Q2: "Is there a monthly/period cutoff logic — what happens if a transaction crosses the billing period boundary?"
Q3: "How does the system handle zero-amount or negative-amount bills? Are those valid or error conditions?"
Q4: "What does the end-of-month reconciliation process look like — are there control total reports generated for auditors?"
🧪 TEST BEFORE PROD
* Froggy's rule: same program, different files
//TESTRUN JOB
*//
* Test region has its OWN dataset prefix:
* TEST.BILLING.INPUT vs PROD.BILLING.INPUT
* Same program. Different files.
*
* If it works in test, it works in prod.
* If it doesn't work in test,
* FILE STATUS tells us why.
🐸 Froggy's Rule: "We have never changed program logic without testing it first. Ever. The emphasis on 'ever' is not dramatic. It is the kind of flat certainty that only comes from 29 years of watching other people skip testing and getting called at 03:00."
Test region: same program, different dataset prefix. FILE STATUS catches dataset issues before production touches real data.
Key question: What is the dataset prefix for test vs production? It should be the first thing you check when setting up a new batch run.
💡 QUICK TIPS
• Always ROUNDED — financial arithmetic without ROUNDED is a bug
• FILE STATUS after every I/O — catches disk errors immediately
• REEL STATUS check before first READ — tape WAITs, never crashes
• 4-stage pipeline — INIT → READ-PROCESS → RECONCILE → CLEANUP
• Control totals backwards — header knows what body should sum to
• Three outputs — billing file + error report + control total
• 88-levels over literals — WS-BILL-PAID is clearer than ='P'
• INITIALIZE before use — old data in WORKING-STORAGE is classic bug
• PERFORM paragraphs not in-line loops — testable, readable, maintainable
• CONTROL TOTAL = sum of all amounts processed. Match vs billing file
• Return codes tell JCL what happened. Never exit with default (0)
🍺 BONUS RULE from Uncle Spencer: "If you can't explain a COBOL billing routine to a junior in two minutes, it's too complicated. Split it."