Project: Sales Report with Totals

COBOL GnuCOBOL 3.2 · ✓ verified by execution on 2026-08-16

Welcome to the Sales Report capstone project! In this lesson, we will integrate everything you have learned about COBOL—from basic DATA DIVISION structures and PICTURE clauses, to PERFORM loops and sequential file handling—into a single, robust batch program.

Historically, COBOL was designed exactly for this kind of work: reading massive sequential files, performing control break logic on groups of data, and formatting the output into a clean, human-readable printed report. Records in sequential files are processed in a strict sequence, one after another. Because of this, it is crucial that your data is pre-sorted before it hits your report program.

Let’s visualize how our report program processes data in batches:

INPUT.DAT NORTH0150 NORTH0200 SOUTH0500 PROGRAM READ ACCUMULATE REPORT Total NORTH 000350 Total SOUTH 000500

The Capstone Project

Here is the complete program. Notice how the AT END imperative statement is used on the READ command to detect when the file is empty, triggering the final control break output.

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. SALES-REPORT.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT INPUT-FILE ASSIGN TO 'input.dat'
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  INPUT-FILE.
       01  IN-RECORD.
           05  IN-REGION        PIC X(5).
           05  IN-AMOUNT        PIC 9(4).
       WORKING-STORAGE SECTION.
       01  WS-EOF-FLAG          PIC X VALUE 'N'.
           88  EOF              VALUE 'Y'.
       01  WS-PREV-REGION       PIC X(5) VALUE SPACES.
       01  WS-REGION-TOTAL      PIC 9(6) VALUE ZEROS.
       01  WS-FIRST-RECORD      PIC X VALUE 'Y'.
       PROCEDURE DIVISION.
       MAIN-LOGIC.
           OPEN OUTPUT INPUT-FILE
           WRITE IN-RECORD FROM 'NORTH0150'
           WRITE IN-RECORD FROM 'NORTH0200'
           WRITE IN-RECORD FROM 'SOUTH0500'
           CLOSE INPUT-FILE

           OPEN INPUT INPUT-FILE
           PERFORM READ-RECORD
           PERFORM PROCESS-RECORD UNTIL EOF
           PERFORM PRINT-TOTAL
           CLOSE INPUT-FILE
           STOP RUN.
       PROCESS-RECORD.
           IF WS-FIRST-RECORD = 'Y'
               MOVE IN-REGION TO WS-PREV-REGION
               MOVE 'N' TO WS-FIRST-RECORD
           END-IF
           IF IN-REGION NOT = WS-PREV-REGION
               PERFORM PRINT-TOTAL
               MOVE IN-REGION TO WS-PREV-REGION
               MOVE ZEROS TO WS-REGION-TOTAL
           END-IF
           ADD IN-AMOUNT TO WS-REGION-TOTAL
           PERFORM READ-RECORD.
       PRINT-TOTAL.
           IF WS-PREV-REGION NOT = SPACES
               DISPLAY 'Total for ' WS-PREV-REGION ': ' WS-REGION-TOTAL
           END-IF.
       READ-RECORD.
           READ INPUT-FILE AT END MOVE 'Y' TO WS-EOF-FLAG END-READ.
Output
Total for NORTH: 000350
Total for SOUTH: 000500

Report Formatting

A report isn’t useful if it’s hard to read. You can add headers to your reports to make the context immediately obvious:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. HDR.
       PROCEDURE DIVISION.
           DISPLAY '*** DAILY SALES REPORT ***'.
           STOP RUN.
Output
*** DAILY SALES REPORT ***

Additionally, numeric variables with leading zeros are visually cluttered. You can format report output cleanly using zero suppression (the Z character) in your PICTURE clauses:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ZSUP.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-NUM PIC 9(4) VALUE 0050.
       01 WS-FMT PIC ZZZ9.
       PROCEDURE DIVISION.
           MOVE WS-NUM TO WS-FMT.
           DISPLAY WS-FMT.
           STOP RUN.
Output
  50

Data Truncation and Accumulators

A very common logic bug in COBOL batch programs occurs when an accumulator is defined with a PICTURE clause that is too small for the sum of its inputs. COBOL will silently truncate the high-order digits of the result.

Predict the output cobol

Read the code. What exactly will it print? Commit to an answer before you look.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TRUNC.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-TOT PIC 9(2).
       PROCEDURE DIVISION.
           MOVE 150 TO WS-TOT.
           DISPLAY WS-TOT.
           STOP RUN.
Output
50

This is incredibly dangerous in financial reports because no error is thrown! The WRITE statement writes a new record to an open file, but if your accumulator silently overflowed, your final printed report will contain incorrect math. Variables such as accumulators can be reset to their default values using the INITIALIZE statement, or by moving ZEROS into them (as we did in our capstone project), but if they are too small, resetting them won’t prevent truncation on large batches.

Dealing with File Status

When dealing with batch input and output, missing files are a reality. If a previous job failed to run, your input file might simply not exist. Handling file open errors gracefully in a batch program requires checking the FILE STATUS variable.

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. FSTAT.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT IN-FILE ASSIGN TO 'missing.dat'
           ORGANIZATION IS LINE SEQUENTIAL
           FILE STATUS IS WS-STAT.
       DATA DIVISION.
       FILE SECTION.
       FD IN-FILE.
       01 IN-REC PIC X.
       WORKING-STORAGE SECTION.
       01 WS-STAT PIC XX.
       PROCEDURE DIVISION.
           OPEN INPUT IN-FILE.
           DISPLAY 'STATUS: ' WS-STAT.
           STOP RUN.
Output
STATUS: 35

If the status is not 00, your program should gracefully abort rather than continuing to process garbage data. In production environments, continuing with a missing input file can be disastrous. Let’s fix a silent truncation bug in the challenge below to solidify your understanding of accumulators.

Check yourself

Why is it important to have accumulators sized larger than their input fields?

Reveal answer

To prevent silent overflow/truncation. — Accumulators must be sized larger than their input fields to prevent silent overflow/truncation when adding many records together.

What happens when a program hits EOF while reading a sequential file?

Reveal answer

The AT END condition is triggered, stopping the read loop. — EOF only stops the read loop; you must manually trigger the final control break logic using the AT END phrase.

Why check the FILE STATUS variable when opening a batch input file?

Reveal answer

To prevent processing garbage data if the file is missing or corrupt. — If the file doesn't exist (e.g. status 35), you must handle it gracefully instead of continuing to read from a missing source.

Challenges

🐞 Bug Hunt +25 XP

The total accumulator is too small and is truncating the sum of sales. Fix its PICTURE clause so it can hold the value 1500.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "Total: 1500"
Need a hint? (−25% XP)

Increase the size of WS-TOTAL from PIC 9(3) to PIC 9(4).

Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CHALLENGE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-SALES-1 PIC 9(3) VALUE 800.
       01 WS-SALES-2 PIC 9(3) VALUE 700.
       01 WS-TOTAL   PIC 9(4) VALUE 0.
       PROCEDURE DIVISION.
           ADD WS-SALES-1 TO WS-TOTAL
           ADD WS-SALES-2 TO WS-TOTAL
           DISPLAY 'Total: ' WS-TOTAL
           STOP RUN.

Challenge 2 +25 XP

Use an edited PICTURE clause (ZZZ9) to remove the leading zeros from the final output.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "Amt: 50"
Need a hint? (−25% XP)

Change the PICTURE clause of WS-FORMATTED to use Zs.

Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CHALLENGE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-TOTAL PIC 9(4) VALUE 0050.
       01 WS-FORMATTED PIC ZZZ9.
       PROCEDURE DIVISION.
           MOVE WS-TOTAL TO WS-FORMATTED
           DISPLAY 'Amt: ' WS-FORMATTED
           STOP RUN.