MODULE 10 · BATCH REPORTS · 6/7

Multi-level control breaks

15 min30 XPExercise

Real reports rarely stop at one level. Sales by branch within region, payroll by department within division, claims by policy within agent within office. Each level gets its own subtotal, and every level rolls up into the one above it.

The input must be sorted on all the control fields, major to minor: region first, then branch within region.

Rules for more than one level

  1. Test the major key first. A change in region is a break at every level, even if the branch code happens to be the same (N/A followed by S/A is a new branch too). Only if the region is unchanged do you look at the branch.
  2. Break from the bottom up. On a region change, close the branch first (print its total, roll it into the region), then close the region (print, roll into the grand total).
  3. Each level resets only itself and stores its new key.
  4. At end of file, run every level's break, lowest first, then the grand total.

EVALUATE TRUE expresses rule 1 neatly, because it stops at the first WHEN that is true:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TWOLEVEL.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 SALE-DATA.
          05 FILLER       PIC X(6) VALUE "NA0010".
          05 FILLER       PIC X(6) VALUE "NA0020".
          05 FILLER       PIC X(6) VALUE "NB0005".
          05 FILLER       PIC X(6) VALUE "SA0100".
       01 SALE-TABLE REDEFINES SALE-DATA.
          05 SALE-ENTRY OCCURS 4 TIMES.
             10 SALE-REGION PIC X.
             10 SALE-BRANCH PIC X.
             10 SALE-QTY    PIC 9(4).
       01 WS-I            PIC 9.
       01 WS-PREV-REGION  PIC X.
       01 WS-PREV-BRANCH  PIC X.
       01 WS-BRANCH-TOT   PIC 9(5) VALUE 0.
       01 WS-REGION-TOT   PIC 9(5) VALUE 0.
       01 WS-GRAND-TOT    PIC 9(5) VALUE 0.
       PROCEDURE DIVISION.
           MOVE SALE-REGION(1) TO WS-PREV-REGION
           MOVE SALE-BRANCH(1) TO WS-PREV-BRANCH
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
               EVALUATE TRUE
                   WHEN SALE-REGION(WS-I) NOT = WS-PREV-REGION
                       PERFORM BRANCH-BREAK
                       PERFORM REGION-BREAK
                   WHEN SALE-BRANCH(WS-I) NOT = WS-PREV-BRANCH
                       PERFORM BRANCH-BREAK
               END-EVALUATE
               ADD SALE-QTY(WS-I) TO WS-BRANCH-TOT
           END-PERFORM
           PERFORM BRANCH-BREAK
           PERFORM REGION-BREAK
           DISPLAY "GRAND             " WS-GRAND-TOT " ***"
           STOP RUN.

       BRANCH-BREAK.
           DISPLAY "  BRANCH " WS-PREV-REGION WS-PREV-BRANCH
                   "       " WS-BRANCH-TOT " *"
           ADD WS-BRANCH-TOT TO WS-REGION-TOT
           MOVE 0 TO WS-BRANCH-TOT
           IF WS-I <= 4
               MOVE SALE-BRANCH(WS-I) TO WS-PREV-BRANCH
           END-IF.

       REGION-BREAK.
           DISPLAY "REGION " WS-PREV-REGION
                   "          " WS-REGION-TOT " **"
           ADD WS-REGION-TOT TO WS-GRAND-TOT
           MOVE 0 TO WS-REGION-TOT
           IF WS-I <= 4
               MOVE SALE-REGION(WS-I) TO WS-PREV-REGION
           END-IF.

Output:

  BRANCH NA       00030 *
  BRANCH NB       00005 *
REGION N          00035 **
  BRANCH SA       00100 *
REGION S          00100 **
GRAND             00135 ***

Region N's total (35) is the sum of its two branch totals, and the grand total is the sum of the region totals. No detail value is ever added more than once; totals travel upward at the breaks.

Scaling up

With three or four levels the shape stays the same: one WHEN per level from the top down, and each break paragraph performing the one below it first. Many shops write it that way:

       REGION-BREAK.
           PERFORM BRANCH-BREAK
      *    then print the region total, roll it up, reset it

so a single PERFORM REGION-BREAK closes everything beneath it.

Presentation

Common conventions you'll be asked for:

  • Asterisks by level: * branch, ** region, *** grand total.
  • Indentation: lower-level totals indented less, so the eye finds the big numbers.
  • A blank line or a new page after a major break. Starting each region on a new page (reset the line count to force headings) is a frequent requirement, and it is part of this module's challenge.

On the job

When a multi-level report doesn't foot (branch totals don't add up to the region), look for a total that is added at two levels, or a break paragraph that resets the wrong accumulator. Check it by hand with a three-record test file whose sums you can do in your head.

Your task

Finance wants the payroll with subtotals at two levels: department within division. The input is sorted by division, then department.

DD PAYIN — line sequential, 30 bytes: PAY-DIV X(3), PAY-DEPT X(4), PAY-NAME X(15), PAY-SALARY 9(6)V99.

DD RPTOUT — the report. The starter has the layouts, headings and detail printing; PRINT-DETAIL adds each salary to WS-DEPT-TOTAL.

  • When the department changes: print the department total line (*).
  • When the division changes: print the department total, then the division total line (**) and a blank line.
  • At end of file: both final breaks, then the grand total (***).
PAYROLL BY DIVISION AND DEPARTMENT
DIV DEPT NAME                  SALARY
FIN ACCT ALICE MORGAN       52,000.00
FIN ACCT BRIAN KEANE        48,500.00
         DEPT ACCT         100,500.00 *
FIN AUDT CLARE HOGAN        61,000.00
         DEPT AUDT          61,000.00 *
         DIVISION FIN      161,500.00 **

OPS ACCT DAVID NASH         39,999.99
         DEPT ACCT          39,999.99 *
...
GRAND TOTAL                361,000.74 ***

Watch the FIN ACCT → OPS ACCT step: the department code is the same, but a new division always closes the department too.

fixed format
Run your program to see its output here. The first visible test's input and datasets are used.
Submit to grade your program against every test.