*> ============================================================ *> 12-divide-100 : CDR数据100分割 (CDR 100-Split) *> Input : FILE-IN.DAT (CDR数据: 100件毎分割) *> Output: FILE-OUT-NN (100件毎の分割出力文件: FILE-OUT-01, 02...) *> Coverage: S-N005, S-N006, S-N007, S-R001, S-R002 *> *> Extended: Phase 2 — error recovery, hash totals, audit trail, *> split statistics, boundary checks, CDR awareness, *> header/trailer records, file naming validation. *> ============================================================ IDENTIFICATION DIVISION. PROGRAM-ID. Divide100. ENVIRONMENT DIVISION. CONFIGURATION SECTION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT FILE-IN ASSIGN TO WS-IN-FILE ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS-IN. SELECT FILE-OUT ASSIGN TO WS-OUT-FILE ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS-OUT. SELECT AUDIT-REPORT ASSIGN TO "AUDIT-RPT.TXT" ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS-AUDIT. SELECT SPLIT-STATS ASSIGN TO "SPLIT-STATS.TXT" ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS-STATS. DATA DIVISION. FILE SECTION. FD FILE-IN. 01 FILE-IN-REC. 05 IN-RECORD PIC X(45). FD FILE-OUT. 01 FILE-OUT-REC. 05 OUT-RECORD PIC X(45). FD AUDIT-REPORT. 01 AUDIT-REC PIC X(120). FD SPLIT-STATS. 01 STATS-REC PIC X(120). WORKING-STORAGE SECTION. 01 WS-TELECOM-REC. COPY "telecom/TEL-INVOICE.cpy". *> CDR record mapping 01 WS-CDR-REC. COPY "telecom/TEL-CDR.cpy". *> File status indicators 01 WS-FILE-STATUS-IN PIC X(02). 01 WS-FILE-STATUS-OUT PIC X(02). 01 WS-FILE-STATUS-AUDIT PIC X(02). 01 WS-FILE-STATUS-STATS PIC X(02). *> EOF and control flags 01 WS-STATUS. 05 WS-EOF-FLAG PIC X VALUE 'N'. 88 WS-EOF VALUE 'Y' FALSE 'N'. 05 WS-READ-OK PIC X VALUE 'N'. 88 WS-READ-SUCCESS VALUE 'Y' FALSE 'N'. 05 WS-FIRST-RECORD PIC X VALUE 'Y'. 88 WS-IS-FIRST-RECORD VALUE 'Y' FALSE 'N'. *> Error severity levels 01 WS-SEVERITY PIC X(07). 88 WS-SEVERITY-WARNING VALUE 'WARNING'. 88 WS-SEVERITY-ERROR VALUE 'ERROR'. 88 WS-SEVERITY-FATAL VALUE 'FATAL'. *> Open retry control 01 WS-OPEN-RETRY. 05 WS-OPEN-RETRY-COUNT PIC 9(02) VALUE 0. 05 WS-OPEN-RETRY-MAX PIC 9(02) VALUE 3. 05 WS-ALT-SUFFIX PIC X(04). 88 WS-ALT-NONE VALUE 'NONE'. 88 WS-ALT-BAK VALUE '.BAK'. 88 WS-ALT-TMP VALUE '.TMP'. *> File naming validation 01 WS-FILE-NAME-CHECK. 05 WS-FILE-NAME-VALID PIC X VALUE 'N'. 88 WS-NAME-IS-VALID VALUE 'Y' FALSE 'N'. 05 WS-FILE-NAME-PATTERN PIC X(30). 05 WS-FILE-NAME-PREFIX PIC X(08) VALUE 'FILE-OUT'. 05 WS-FILE-NAME-SUFFIX PIC X(04) VALUE '.DAT'. 05 WS-FILE-NAME-SEQ-BEG PIC 9(02). 05 WS-FILE-NAME-SEQ-END PIC 9(02). *> Counters and accumulators 01 WS-COUNTERS. 05 WS-REC-COUNT PIC 9(05) VALUE 0. 05 WS-FILE-NUM PIC 9(02) VALUE 0. 05 WS-DIVISOR PIC 9(03) VALUE 100. 05 WS-QUOTIENT PIC 9(05). 05 WS-REMAINDER PIC 9(05). 05 WS-WRITE-COUNT PIC 9(05) VALUE 0. 05 WS-READ-COUNT PIC 9(05) VALUE 0. *> Input file path (dynamic for retry support) 01 WS-IN-FILE PIC X(30). *> Output file path 01 WS-OUT-FILE PIC X(30). 01 WS-FILE-NUM-ED PIC 99. *> Display formatting 01 WS-DISPLAY-COUNT PIC Z(05)9. 01 WS-DISPLAY-HASH PIC Z(09)9. 01 WS-DISPLAY-STR. 05 WS-DISPLAY-STR-TEXT PIC X(50). *> Simulated timestamp for DISPLAY tracing 01 WS-TIMESTAMP PIC X(21). 01 WS-TIMESTAMP-OUT PIC X(26). *> Hash temp for byte counting 01 WS-HASH-TEMP PIC 9(10). *> Hash totals for data integrity 01 WS-HASH-TOTALS. 05 WS-HASH-TOTAL-IN PIC 9(10) VALUE 0. 05 WS-HASH-TOTAL-OUT PIC 9(10) VALUE 0. 05 WS-HASH-TOTAL-VERIFY PIC 9(10) VALUE 0. 05 WS-HASH-VERIFIED PIC X VALUE 'N'. 88 WS-HASH-MATCH VALUE 'Y' FALSE 'N'. 88 WS-HASH-MISMATCH VALUE 'N'. 05 WS-HASH-SPLIT OCCURS 99 TIMES. 10 WS-HASH-SPLIT-VAL PIC 9(10). *> Split statistics 01 WS-SPLIT-STATS. 05 WS-SPLIT-REC-COUNT PIC 9(05) VALUE 0. 05 WS-SPLIT-FILE-TOTAL PIC 9(02) VALUE 0. 05 WS-SPLIT-MIN-RECS PIC 9(05) VALUE 99999. 05 WS-SPLIT-MAX-RECS PIC 9(05) VALUE 0. 05 WS-SPLIT-EMPTY-FLAG PIC X VALUE 'N'. 88 WS-SPLIT-EMPTY VALUE 'Y' FALSE 'N'. 05 WS-SPLIT-SINGLE-FLAG PIC X VALUE 'N'. 88 WS-SPLIT-SINGLE VALUE 'Y' FALSE 'N'. *> Boundary condition checks 01 WS-BOUNDARY-FLAG. 05 WS-BOUNDARY-EMPTY PIC X VALUE 'N'. 88 WS-BOUNDARY-IS-EMPTY VALUE 'Y' FALSE 'N'. 05 WS-BOUNDARY-EXACT-MULT PIC X VALUE 'N'. 88 WS-BOUNDARY-IS-EXACT VALUE 'Y' FALSE 'N'. 05 WS-BOUNDARY-SINGLE-REC PIC X VALUE 'N'. 88 WS-BOUNDARY-IS-SINGLE VALUE 'Y' FALSE 'N'. 05 WS-BOUNDARY-TRAILING PIC X VALUE 'N'. 88 WS-BOUNDARY-HAS-TRAIL VALUE 'Y' FALSE 'N'. 05 WS-BOUNDARY-MULTIPLIER PIC 9(05). *> Trailing record handling 01 WS-TRAILER-REC-COUNT PIC 9(05) VALUE 0. 01 WS-TRAILER-RECORD. 05 WS-TRAILER-TYPE PIC X(08) VALUE 'TRAILER'. 05 WS-TRAILER-FILE PIC 9(02). 05 WS-TRL-REC-COUNT PIC 9(05). 05 FILLER PIC X(30) VALUE SPACES. *> Header record for split files 01 WS-HEADER-RECORD. 05 WS-HDR-TYPE PIC X(08) VALUE 'HEADER'. 05 WS-HDR-FILE PIC 9(02). 05 WS-HDR-TIMESTAMP PIC X(12). 05 WS-HDR-RESERVED PIC X(23). 05 FILLER PIC X VALUE SPACE. *> CDR-aware split header 01 WS-CDR-HEADER. 05 WS-CDR-HDR-TYPE PIC X(08) VALUE 'CDR-HDR'. 05 WS-CDR-HDR-FILE PIC 9(02). 05 WS-CDR-HDR-EXPECTED PIC 9(05). 05 WS-CDR-HDR-RESERVED PIC X(30). *> Audit report variables 01 WS-AUDIT-LINE PIC X(120). 01 WS-AUDIT-COUNT PIC 9(05) VALUE 0. *> Batch control totals 01 WS-BATCH-CTRL. 05 WS-BATCH-START-TIME PIC 9(08). 05 WS-BATCH-END-TIME PIC 9(08). 05 WS-BATCH-TOTAL-FILES PIC 9(02) VALUE 0. 05 WS-BATCH-TOTAL-RECS PIC 9(05) VALUE 0. 05 WS-BATCH-ERROR-COUNT PIC 9(03) VALUE 0. 05 WS-BATCH-WARN-COUNT PIC 9(03) VALUE 0. *> File open retry state 01 WS-RETRY-STATE. 05 WS-RETRY-ATTEMPT PIC 9(02) VALUE 0. 05 WS-RETRY-FILE-NAME PIC X(30). 05 WS-RETRY-SUCCESS PIC X VALUE 'N'. 88 WS-RETRY-OK VALUE 'Y' FALSE 'N'. * ============================================================ * PROCEDURE DIVISION * ============================================================ PROCEDURE DIVISION. MAIN SECTION. MAIN-PROCEDURE. PERFORM 1000-INIT-SECTION PERFORM 2000-OPEN-FILES-SECTION PERFORM 3000-PROCESS-SECTION PERFORM 4000-REPORT-SECTION PERFORM 5000-AUDIT-SECTION PERFORM 6000-ERROR-HANDLE-SECTION PERFORM 9000-EXIT-SECTION STOP RUN. *> ============================================================ *> 1000-INIT-SECTION — Initialize all working variables *> ============================================================ 1000-INIT-SECTION SECTION. 1000-INIT. DISPLAY " " DISPLAY "=== Divide100 — Initialize ===" MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP DISPLAY "[" WS-TIMESTAMP "] Initializing program" *> Zero counters MOVE 0 TO WS-REC-COUNT MOVE 0 TO WS-FILE-NUM MOVE 0 TO WS-READ-COUNT MOVE 0 TO WS-WRITE-COUNT MOVE 0 TO WS-TRAILER-REC-COUNT MOVE 0 TO WS-AUDIT-COUNT MOVE 0 TO WS-BATCH-ERROR-COUNT MOVE 0 TO WS-BATCH-WARN-COUNT MOVE 0 TO WS-OPEN-RETRY-COUNT MOVE 0 TO WS-HASH-TOTAL-IN MOVE 0 TO WS-HASH-TOTAL-OUT *> Reset boundary flags MOVE 'N' TO WS-BOUNDARY-EMPTY MOVE 'N' TO WS-BOUNDARY-EXACT-MULT MOVE 'N' TO WS-BOUNDARY-SINGLE-REC MOVE 'N' TO WS-BOUNDARY-TRAILING MOVE 'Y' TO WS-FIRST-RECORD *> Reset retry state MOVE 'NONE' TO WS-ALT-SUFFIX MOVE 0 TO WS-RETRY-ATTEMPT *> Reset split stats MOVE 0 TO WS-SPLIT-REC-COUNT MOVE 0 TO WS-SPLIT-FILE-TOTAL MOVE 99999 TO WS-SPLIT-MIN-RECS MOVE 0 TO WS-SPLIT-MAX-RECS MOVE 'N' TO WS-SPLIT-EMPTY-FLAG MOVE 'N' TO WS-SPLIT-SINGLE-FLAG DISPLAY "[" WS-TIMESTAMP "] Initialization complete" . 1000-EXIT. EXIT. *> ============================================================ *> 2000-OPEN-FILES-SECTION — Open input and audit files *> ============================================================ 2000-OPEN-FILES-SECTION SECTION. 2000-OPEN-FILES. DISPLAY "[" WS-TIMESTAMP "] Opening files" *> Open input file with retry logic MOVE 0 TO WS-RETRY-ATTEMPT MOVE 'N' TO WS-RETRY-SUCCESS MOVE "FILE-IN.DAT" TO WS-IN-FILE PERFORM WITH TEST AFTER UNTIL WS-RETRY-OK OR WS-RETRY-ATTEMPT >= WS-OPEN-RETRY-MAX ADD 1 TO WS-RETRY-ATTEMPT OPEN INPUT FILE-IN IF WS-FILE-STATUS-IN NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: FILE-IN open failed " "status=" WS-FILE-STATUS-IN " attempt=" WS-RETRY-ATTEMPT *> Try alternate file name IF WS-RETRY-ATTEMPT = 1 MOVE '.BAK' TO WS-ALT-SUFFIX STRING "FILE-IN" WS-ALT-SUFFIX DELIMITED BY SIZE INTO WS-IN-FILE ELSE MOVE '.TMP' TO WS-ALT-SUFFIX STRING "FILE-IN" WS-ALT-SUFFIX DELIMITED BY SIZE INTO WS-IN-FILE END-IF DISPLAY "[" WS-TIMESTAMP "] " "Retrying with: " WS-IN-FILE ELSE MOVE WS-IN-FILE TO WS-RETRY-FILE-NAME MOVE 'Y' TO WS-RETRY-SUCCESS DISPLAY "[" WS-TIMESTAMP "] " "FILE-IN opened: " WS-IN-FILE " (attempt " WS-RETRY-ATTEMPT ")" END-IF END-PERFORM IF NOT WS-RETRY-OK MOVE 'FATAL' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "FATAL: Cannot open FILE-IN after " WS-OPEN-RETRY-MAX " attempts" ADD 1 TO WS-BATCH-ERROR-COUNT PERFORM 9000-EXIT-SECTION END-IF *> Open audit report file OPEN OUTPUT AUDIT-REPORT IF WS-FILE-STATUS-AUDIT NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: AUDIT-REPORT open failed " "status=" WS-FILE-STATUS-AUDIT ADD 1 TO WS-BATCH-ERROR-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "AUDIT-REPORT opened successfully" MOVE SPACES TO AUDIT-REC STRING "*** AUDIT REPORT — Divide100 ***" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT END-IF *> Open split stats file OPEN OUTPUT SPLIT-STATS IF WS-FILE-STATUS-STATS NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: SPLIT-STATS open failed " "status=" WS-FILE-STATUS-STATS ADD 1 TO WS-BATCH-ERROR-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "SPLIT-STATS opened successfully" MOVE SPACES TO STATS-REC STRING "*** SPLIT STATISTICS REPORT ***" DELIMITED BY SIZE INTO STATS-REC WRITE STATS-REC END-IF *> Capture batch start time MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP . 2000-EXIT. EXIT. *> ============================================================ *> 3000-PROCESS-SECTION — Main processing loop *> ============================================================ 3000-PROCESS-SECTION SECTION. 3000-PROCESS. DISPLAY "[" WS-TIMESTAMP "] Starting record processing" DISPLAY "=== 100-DIVISION PROCESSING ===" DISPLAY "Records per file: " WS-DIVISOR *> Write audit header MOVE SPACES TO AUDIT-REC STRING "=== 100-DIVISION PROCESSING ===" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Records per file: " WS-DIVISOR DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT PERFORM VARYING WS-REC-COUNT FROM 1 BY 1 UNTIL WS-EOF READ FILE-IN INTO FILE-IN-REC AT END SET WS-EOF TO TRUE IF WS-IS-FIRST-RECORD MOVE 'Y' TO WS-BOUNDARY-EMPTY MOVE 'WARNING' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "WARNING: Input file is empty" END-IF NOT AT END SET WS-READ-SUCCESS TO TRUE ADD 1 TO WS-READ-COUNT IF WS-IS-FIRST-RECORD MOVE 'N' TO WS-FIRST-RECORD END-IF PERFORM 3100-READ-INPUT-SECTION PERFORM 3200-VALIDATE-SECTION PERFORM 3300-APPLY-RULES-SECTION PERFORM 3400-WRITE-OUTPUT-SECTION END-READ IF WS-FILE-STATUS-IN NOT = '00' AND NOT WS-EOF MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: FILE-IN read failed " "record=" WS-REC-COUNT " status=" WS-FILE-STATUS-IN ADD 1 TO WS-BATCH-ERROR-COUNT END-IF END-PERFORM *> Boundary checks after processing IF WS-BOUNDARY-IS-EMPTY MOVE SPACES TO AUDIT-REC STRING "WARNING: Input file was empty — " "no output files created" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT ELSE DIVIDE WS-READ-COUNT BY WS-DIVISOR GIVING WS-BOUNDARY-MULTIPLIER REMAINDER WS-REMAINDER IF WS-REMAINDER = 0 MOVE 'Y' TO WS-BOUNDARY-EXACT-MULT DISPLAY "[" WS-TIMESTAMP "] " "NOTE: Exact multiple of divisor — " "no trailing records" ELSE MOVE 'Y' TO WS-BOUNDARY-TRAILING MOVE WS-REMAINDER TO WS-TRAILER-REC-COUNT DISPLAY "[" WS-TIMESTAMP "] " "NOTE: " WS-TRAILER-REC-COUNT " trailing record(s) in last file" END-IF IF WS-READ-COUNT = 1 MOVE 'Y' TO WS-BOUNDARY-SINGLE-REC END-IF END-IF *> Close last output file if any were opened IF WS-FILE-NUM > 0 PERFORM WRITE-TRAILER-RECORD CLOSE FILE-OUT IF WS-FILE-STATUS-OUT NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: Close failed for " WS-OUT-FILE " status=" WS-FILE-STATUS-OUT ADD 1 TO WS-BATCH-ERROR-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "Closed final file: " WS-OUT-FILE END-IF END-IF MOVE WS-REC-COUNT TO WS-DISPLAY-COUNT DISPLAY " " DISPLAY "[" WS-TIMESTAMP "] Total records read: " WS-DISPLAY-COUNT DISPLAY "[" WS-TIMESTAMP "] Output files created: " WS-FILE-NUM . 3000-EXIT. EXIT. *> ============================================================ *> 3100-READ-INPUT-SECTION — Input record processing *> ============================================================ 3100-READ-INPUT-SECTION SECTION. 3100-READ-INPUT. *> Accumulate hash total (sum of numeric representation *> of the input record bytes) PERFORM COMPUTE-INPUT-HASH *> Classify record type based on known layouts MOVE IN-RECORD TO WS-TELECOM-REC MOVE IN-RECORD TO WS-CDR-REC *> Check if record resembles a CDR by CDR-ID pattern IF CDR-ID (1:3) = 'CDR' DISPLAY "[" WS-TIMESTAMP "] " "Record " WS-REC-COUNT ": CDR record detected: " CDR-ID ELSE IF INV-ID (1:3) = 'INV' DISPLAY "[" WS-TIMESTAMP "] " "Record " WS-REC-COUNT ": Invoice record detected: " INV-ID ELSE DISPLAY "[" WS-TIMESTAMP "] " "Record " WS-REC-COUNT ": Generic record" END-IF END-IF . 3100-EXIT. EXIT. *> ============================================================ *> 3200-VALIDATE-SECTION — Validate input record *> ============================================================ 3200-VALIDATE-SECTION SECTION. 3200-VALIDATE. *> Validate output file naming convention before writing IF WS-FILE-NUM > 0 MOVE WS-OUT-FILE TO WS-FILE-NAME-PATTERN IF WS-FILE-NAME-PATTERN (1:8) = WS-FILE-NAME-PREFIX MOVE 'Y' TO WS-FILE-NAME-VALID ELSE MOVE 'N' TO WS-FILE-NAME-VALID MOVE 'WARNING' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "WARNING: Unconventional file name: " WS-OUT-FILE ADD 1 TO WS-BATCH-WARN-COUNT END-IF END-IF . 3200-EXIT. EXIT. *> ============================================================ *> 3300-APPLY-RULES-SECTION — Apply division rules *> ============================================================ 3300-APPLY-RULES-SECTION SECTION. 3300-APPLY-RULES. *> Use DIVIDE to determine file number *> === ORIGINAL LOGIC PRESERVED (unaltered) === DIVIDE WS-REC-COUNT BY WS-DIVISOR GIVING WS-QUOTIENT REMAINDER WS-REMAINDER *> If remainder = 1, start a new output file *> === ORIGINAL LOGIC PRESERVED (unaltered) === IF WS-REMAINDER = 1 IF WS-FILE-NUM > 0 PERFORM WRITE-TRAILER-RECORD CLOSE FILE-OUT IF WS-FILE-STATUS-OUT NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: Close failed for " WS-OUT-FILE " status=" WS-FILE-STATUS-OUT ADD 1 TO WS-BATCH-ERROR-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "Closed: " WS-OUT-FILE END-IF END-IF ADD 1 TO WS-FILE-NUM MOVE WS-FILE-NUM TO WS-FILE-NUM-ED STRING "FILE-OUT-" DELIMITED BY SIZE WS-FILE-NUM-ED DELIMITED BY SIZE ".DAT" DELIMITED BY SIZE INTO WS-OUT-FILE OPEN OUTPUT FILE-OUT IF WS-FILE-STATUS-OUT NOT = '00' MOVE 'FATAL' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "FATAL: Cannot open " WS-OUT-FILE " status=" WS-FILE-STATUS-OUT ADD 1 TO WS-BATCH-ERROR-COUNT PERFORM 6000-ERROR-HANDLE-SECTION ELSE DISPLAY "[" WS-TIMESTAMP "] " "Opened: " WS-OUT-FILE PERFORM WRITE-HEADER-RECORD END-IF END-IF . 3300-EXIT. EXIT. *> ============================================================ *> 3400-WRITE-OUTPUT-SECTION — Write record to output *> ============================================================ 3400-WRITE-OUTPUT-SECTION SECTION. 3400-WRITE-OUTPUT. *> Hash the output record before writing PERFORM COMPUTE-OUTPUT-HASH *> Write record to current output file *> === ORIGINAL LOGIC PRESERVED (unaltered) === MOVE IN-RECORD TO OUT-RECORD WRITE FILE-OUT-REC IF WS-FILE-STATUS-OUT NOT = '00' MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "ERROR: Write failed for record " WS-REC-COUNT " status=" WS-FILE-STATUS-OUT ADD 1 TO WS-BATCH-ERROR-COUNT ELSE ADD 1 TO WS-WRITE-COUNT ADD 1 TO WS-SPLIT-REC-COUNT ADD 1 TO WS-HASH-SPLIT-VAL (WS-FILE-NUM) END-IF . 3400-EXIT. EXIT. *> ============================================================ *> 4000-REPORT-SECTION — Generate split statistics *> ============================================================ 4000-REPORT-SECTION SECTION. 4000-REPORT. DISPLAY " " DISPLAY "[" WS-TIMESTAMP "] === Split Statistics ===" DISPLAY "Total input records: " WS-READ-COUNT DISPLAY "Total output records: " WS-WRITE-COUNT DISPLAY "Output files created: " WS-FILE-NUM *> Populate split stats MOVE WS-FILE-NUM TO WS-SPLIT-FILE-TOTAL IF WS-READ-COUNT = 0 MOVE 'Y' TO WS-SPLIT-EMPTY-FLAG END-IF IF WS-READ-COUNT = 1 MOVE 'Y' TO WS-SPLIT-SINGLE-FLAG END-IF *> Build min/max split sizes PERFORM VARYING WS-FILE-NUM-ED FROM 1 BY 1 UNTIL WS-FILE-NUM-ED > WS-FILE-NUM IF WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) < WS-SPLIT-MIN-RECS MOVE WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) TO WS-SPLIT-MIN-RECS END-IF IF WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) > WS-SPLIT-MAX-RECS MOVE WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) TO WS-SPLIT-MAX-RECS END-IF END-PERFORM DISPLAY "Min records per split: " WS-SPLIT-MIN-RECS DISPLAY "Max records per split: " WS-SPLIT-MAX-RECS *> Write detailed stats to SPLIT-STATS file MOVE SPACES TO STATS-REC STRING "SPLIT STATS: files=" WS-FILE-NUM " total-records=" WS-READ-COUNT " min=" WS-SPLIT-MIN-RECS " max=" WS-SPLIT-MAX-RECS DELIMITED BY SIZE INTO STATS-REC WRITE STATS-REC *> Sequential output file inventory MOVE SPACES TO STATS-REC STRING "=== Split File Inventory ===" DELIMITED BY SIZE INTO STATS-REC WRITE STATS-REC PERFORM VARYING WS-FILE-NUM-ED FROM 1 BY 1 UNTIL WS-FILE-NUM-ED > WS-FILE-NUM MOVE SPACES TO STATS-REC STRING "FILE-OUT-" WS-FILE-NUM-ED ".DAT: " WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) " records" DELIMITED BY SIZE INTO STATS-REC WRITE STATS-REC END-PERFORM *> Boundary condition summary MOVE SPACES TO STATS-REC STRING "Boundary: EMPTY=" WS-BOUNDARY-EMPTY " EXACT-MULT=" WS-BOUNDARY-EXACT-MULT " SINGLE=" WS-BOUNDARY-SINGLE-REC " TRAILING=" WS-BOUNDARY-TRAILING " TRAIL-COUNT=" WS-TRAILER-REC-COUNT DELIMITED BY SIZE INTO STATS-REC WRITE STATS-REC . 4000-EXIT. EXIT. *> ============================================================ *> 5000-AUDIT-SECTION — Hash verification and audit trail *> ============================================================ 5000-AUDIT-SECTION SECTION. 5000-AUDIT. DISPLAY " " DISPLAY "[" WS-TIMESTAMP "] === Audit Trail ===" *> Display hash totals MOVE WS-HASH-TOTAL-IN TO WS-DISPLAY-HASH DISPLAY "Hash total (input): " WS-DISPLAY-HASH MOVE WS-HASH-TOTAL-OUT TO WS-DISPLAY-HASH DISPLAY "Hash total (output): " WS-DISPLAY-HASH *> Verify hash totals: input hash must equal sum of *> all split file hashes MOVE 0 TO WS-HASH-TOTAL-VERIFY PERFORM VARYING WS-FILE-NUM-ED FROM 1 BY 1 UNTIL WS-FILE-NUM-ED > WS-FILE-NUM ADD WS-HASH-SPLIT-VAL (WS-FILE-NUM-ED) TO WS-HASH-TOTAL-VERIFY END-PERFORM IF WS-HASH-TOTAL-IN = WS-HASH-TOTAL-VERIFY MOVE 'Y' TO WS-HASH-VERIFIED DISPLAY "[" WS-TIMESTAMP "] " "PASS: Hash total verified OK" ELSE MOVE 'N' TO WS-HASH-VERIFIED MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "FAIL: Hash total mismatch!" DISPLAY " Input hash: " WS-HASH-TOTAL-IN DISPLAY " Sum of splits: " WS-HASH-TOTAL-VERIFY ADD 1 TO WS-BATCH-ERROR-COUNT END-IF *> Verify output hash matches input hash IF WS-HASH-TOTAL-IN NOT = WS-HASH-TOTAL-OUT MOVE 'ERROR' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "FAIL: Output hash != Input hash!" ADD 1 TO WS-BATCH-ERROR-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "PASS: Output hash matches input hash" END-IF *> Write final audit records MOVE SPACES TO AUDIT-REC STRING "=== Audit Summary ===" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Total input records: " WS-READ-COUNT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Total output records: " WS-WRITE-COUNT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Output files created: " WS-FILE-NUM DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Hash total (input): " WS-HASH-TOTAL-IN DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT MOVE SPACES TO AUDIT-REC STRING "Hash total (output): " WS-HASH-TOTAL-OUT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT IF WS-HASH-MATCH MOVE SPACES TO AUDIT-REC STRING "Hash verification: PASS" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT ELSE MOVE SPACES TO AUDIT-REC STRING "Hash verification: FAIL" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT END-IF MOVE SPACES TO AUDIT-REC STRING "Error count: " WS-BATCH-ERROR-COUNT " Warning count: " WS-BATCH-WARN-COUNT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT *> Log batch completion MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP MOVE SPACES TO AUDIT-REC STRING "Batch completed at: " WS-TIMESTAMP DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT DISPLAY "[" WS-TIMESTAMP "] " "Audit report written: " WS-AUDIT-COUNT " lines" . 5000-EXIT. EXIT. *> ============================================================ *> 6000-ERROR-HANDLE-SECTION — Central error handler *> ============================================================ 6000-ERROR-HANDLE-SECTION SECTION. 6000-ERROR-HANDLE. DISPLAY "[" WS-TIMESTAMP "] " "*** ERROR HANDLER INVOKED ***" EVALUATE TRUE WHEN WS-SEVERITY-FATAL DISPLAY "[" WS-TIMESTAMP "] " "FATAL: Aborting program" MOVE SPACES TO AUDIT-REC STRING "FATAL ERROR — Batch aborted" DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT PERFORM 9000-EXIT-SECTION WHEN WS-SEVERITY-ERROR DISPLAY "[" WS-TIMESTAMP "] " "ERROR: Continuing with error" MOVE SPACES TO AUDIT-REC STRING "ERROR: Continuing — " "error count=" WS-BATCH-ERROR-COUNT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT WHEN WS-SEVERITY-WARNING DISPLAY "[" WS-TIMESTAMP "] " "WARNING: Non-critical issue" MOVE SPACES TO AUDIT-REC STRING "WARNING issued — " "warn count=" WS-BATCH-WARN-COUNT DELIMITED BY SIZE INTO AUDIT-REC WRITE AUDIT-REC ADD 1 TO WS-AUDIT-COUNT WHEN OTHER CONTINUE END-EVALUATE . 6000-EXIT. EXIT. *> ============================================================ *> 9000-EXIT-SECTION — Program cleanup and exit *> ============================================================ 9000-EXIT-SECTION SECTION. 9000-EXIT. DISPLAY " " DISPLAY "[" WS-TIMESTAMP "] === Program Termination ===" *> Close any open files safely CLOSE FILE-IN IF WS-FILE-STATUS-IN NOT = '00' DISPLAY "[" WS-TIMESTAMP "] " "NOTE: FILE-IN close status=" WS-FILE-STATUS-IN END-IF CLOSE AUDIT-REPORT IF WS-FILE-STATUS-AUDIT NOT = '00' DISPLAY "[" WS-TIMESTAMP "] " "NOTE: AUDIT-REPORT close status=" WS-FILE-STATUS-AUDIT END-IF CLOSE SPLIT-STATS IF WS-FILE-STATUS-STATS NOT = '00' DISPLAY "[" WS-TIMESTAMP "] " "NOTE: SPLIT-STATS close status=" WS-FILE-STATUS-STATS END-IF *> Final summary display MOVE WS-REC-COUNT TO WS-DISPLAY-COUNT DISPLAY " " DISPLAY "=== FINAL SUMMARY ===" DISPLAY "Total records read: " WS-DISPLAY-COUNT DISPLAY "Output files created: " WS-FILE-NUM DISPLAY "Errors: " WS-BATCH-ERROR-COUNT DISPLAY "Warnings: " WS-BATCH-WARN-COUNT IF WS-HASH-MATCH DISPLAY "Hash integrity: PASS" ELSE DISPLAY "Hash integrity: FAIL" END-IF DISPLAY " " DISPLAY "=== Divide100 ended ===" STOP RUN . *> ============================================================ *> WRITE-HEADER-RECORD — Write header record to split file *> ============================================================ WRITE-HEADER-RECORD SECTION. WRITE-HDR. *> Write standard header MOVE SPACES TO WS-HEADER-RECORD MOVE 'HEADER' TO WS-HDR-TYPE MOVE WS-FILE-NUM TO WS-HDR-FILE MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP MOVE WS-TIMESTAMP (1:12) TO WS-HDR-TIMESTAMP MOVE WS-HEADER-RECORD TO OUT-RECORD WRITE FILE-OUT-REC IF WS-FILE-STATUS-OUT = '00' ADD 1 TO WS-WRITE-COUNT ADD 1 TO WS-HASH-SPLIT-VAL (WS-FILE-NUM) DISPLAY "[" WS-TIMESTAMP "] " "Header written to " WS-OUT-FILE ELSE MOVE 'WARNING' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "WARNING: Header write failed for " WS-OUT-FILE ADD 1 TO WS-BATCH-WARN-COUNT END-IF *> Write CDR-aware header (second header line with metadata) MOVE SPACES TO WS-CDR-HEADER MOVE 'CDR-HDR' TO WS-CDR-HDR-TYPE MOVE WS-FILE-NUM TO WS-CDR-HDR-FILE MOVE 100 TO WS-CDR-HDR-EXPECTED MOVE WS-CDR-HEADER TO OUT-RECORD WRITE FILE-OUT-REC IF WS-FILE-STATUS-OUT = '00' ADD 1 TO WS-WRITE-COUNT ADD 1 TO WS-HASH-SPLIT-VAL (WS-FILE-NUM) ELSE MOVE 'WARNING' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "WARNING: CDR header write failed for " WS-OUT-FILE ADD 1 TO WS-BATCH-WARN-COUNT END-IF . WRITE-HDR-EXIT. EXIT. *> ============================================================ *> WRITE-TRAILER-RECORD — Write trailer to closing split file *> ============================================================ WRITE-TRAILER-RECORD SECTION. WRITE-TRL. MOVE SPACES TO WS-TRAILER-RECORD MOVE 'TRAILER' TO WS-TRAILER-TYPE MOVE WS-FILE-NUM TO WS-TRAILER-FILE *> Count = current split count minus header records *> (we wrote 2 header records per file) IF WS-HASH-SPLIT-VAL (WS-FILE-NUM) > 2 COMPUTE WS-TRL-REC-COUNT = WS-HASH-SPLIT-VAL (WS-FILE-NUM) - 2 ELSE MOVE 0 TO WS-TRL-REC-COUNT END-IF MOVE WS-TRAILER-RECORD TO OUT-RECORD WRITE FILE-OUT-REC IF WS-FILE-STATUS-OUT = '00' ADD 1 TO WS-WRITE-COUNT ADD 1 TO WS-HASH-SPLIT-VAL (WS-FILE-NUM) DISPLAY "[" WS-TIMESTAMP "] " "Trailer written to " WS-OUT-FILE " count=" WS-TRL-REC-COUNT ELSE MOVE 'WARNING' TO WS-SEVERITY DISPLAY "[" WS-TIMESTAMP "] " "WARNING: Trailer write failed for " WS-OUT-FILE ADD 1 TO WS-BATCH-WARN-COUNT END-IF . WRITE-TRL-EXIT. EXIT. *> ============================================================ *> WRITE-AUDIT-LINE — Write one line to audit report *> ============================================================ WRITE-AUDIT-LINE SECTION. WRITE-AUDIT. MOVE WS-AUDIT-LINE TO AUDIT-REC WRITE AUDIT-REC IF WS-FILE-STATUS-AUDIT = '00' ADD 1 TO WS-AUDIT-COUNT ELSE DISPLAY "[" WS-TIMESTAMP "] " "ERROR writing audit record" END-IF . WRITE-AUDIT-EXIT. EXIT. *> ============================================================ *> COMPUTE-INPUT-HASH — Accumulate input hash total *> ============================================================ COMPUTE-INPUT-HASH SECTION. COMP-HASH-IN. *> Simple hash: sum the numeric values of each byte MOVE 0 TO WS-HASH-TEMP INSPECT IN-RECORD TALLYING WS-HASH-TEMP FOR CHARACTERS BEFORE INITIAL SPACE ADD WS-HASH-TEMP TO WS-HASH-TOTAL-IN . COMP-HASH-IN-EXIT. EXIT. *> ============================================================ *> COMPUTE-OUTPUT-HASH — Accumulate output hash total *> ============================================================ COMPUTE-OUTPUT-HASH SECTION. COMP-HASH-OUT. *> Simple hash: sum the numeric values of each byte MOVE 0 TO WS-HASH-TEMP INSPECT OUT-RECORD TALLYING WS-HASH-TEMP FOR CHARACTERS BEFORE INITIAL SPACE ADD WS-HASH-TEMP TO WS-HASH-TOTAL-OUT . COMP-HASH-OUT-EXIT. EXIT.