*> ============================================================ *> 21-csv-fb-lf : CDR CSV取込(有LF) (CDR Import with LF) *> Input : FILE-IN (file-in.csv: 外部CDR CSV有LF) *> Output: FILE-OUT (file-out.dat: LF展开后固定长记录) *> Error : ERROR-REPORT (csv-lf-errors.dat: CSV错误明细) *> Audit : AUDIT-FILE (csv-lf-audit.dat: 审计跟踪) *> Coverage: CF-N002~N006, CF-A001, CF-E001~E005 *> SECTION structure: *> 1000-INIT / 2000-OPEN-FILES / 3000-READ-INPUT *> 3100-VALIDATE-REC / 3200-PROCESS-REC / 3300-WRITE-OUTPUT *> 4000-REPORT / 5000-AUDIT / 6000-ERROR-HANDLE / 9000-EXIT *> ============================================================ IDENTIFICATION DIVISION. PROGRAM-ID. CsvFbLf. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT FILE-IN ASSIGN TO 'file-in.csv' ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-IN-STATUS. SELECT FILE-OUT ASSIGN TO 'file-out.dat' ORGANIZATION IS SEQUENTIAL FILE STATUS IS WS-FILE-OUT-STATUS. SELECT ERROR-REPORT ASSIGN TO 'csv-lf-errors.dat' ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-ERR-STATUS. SELECT AUDIT-FILE ASSIGN TO 'csv-lf-audit.dat' ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-AUDIT-STATUS. DATA DIVISION. FILE SECTION. FD FILE-IN. 01 IN-LINE PIC X(200). FD FILE-OUT. 01 OUT-REC PIC X(100). FD ERROR-REPORT. 01 ERR-REC PIC X(200). FD AUDIT-FILE. 01 AUD-REC PIC X(200). WORKING-STORAGE SECTION. *> Telecom CDR record layout 01 WS-TELECOM-REC. COPY "telecom/TEL-CDR.cpy". *> File status fields 01 WS-FILE-IN-STATUS PIC X(02). 01 WS-FILE-OUT-STATUS PIC X(02). 01 WS-ERR-STATUS PIC X(02). 01 WS-AUDIT-STATUS PIC X(02). *> File open indicators 01 WS-FILE-IN-OPEN PIC X(01) VALUE 'N'. 88 WS-FILE-IN-IS-OPEN VALUE 'Y' FALSE 'N'. 01 WS-FILE-OUT-OPEN PIC X(01) VALUE 'N'. 88 WS-FILE-OUT-IS-OPEN VALUE 'Y' FALSE 'N'. 01 WS-ERR-REP-OPEN PIC X(01) VALUE 'N'. 88 WS-ERR-REP-IS-OPEN VALUE 'Y' FALSE 'N'. 01 WS-AUDIT-OPEN PIC X(01) VALUE 'N'. 88 WS-AUDIT-IS-OPEN VALUE 'Y' FALSE 'N'. *> EOF flag (original) 01 WS-EOF PIC X(01) VALUE 'N'. 88 WS-EOF-YES VALUE 'Y' FALSE 'N'. *> Original counters and fields 01 WS-REC-COUNT PIC 9(05) VALUE ZERO. 01 WS-LINE-COUNT PIC 9(05) VALUE ZERO. 01 WS-SEG-TOTAL PIC 9(05) VALUE ZERO. 01 WS-FIELD1 PIC X(30). 01 WS-FIELD2 PIC X(30). 01 WS-FIELD3 PIC X(100). *> INSPECT and segment fields (original) 01 WS-PIPE-COUNT PIC 9(02). 01 WS-SEGMENTS. 05 WS-SEGMENT OCCURS 20 TIMES PIC X(50). 01 WS-SEG-COUNT PIC 9(02). 01 WS-SEG-IDX PIC 9(02). 01 WS-POS PIC 9(03). 01 WS-END-POS PIC 9(03). 01 WS-SEG-LEN PIC 9(03). *> Output assembly (original) 01 WS-OUT-LINE PIC X(100). *> Timestamp fields 01 WS-TIMESTAMP PIC X(20). 01 WS-CUR-DATE PIC X(08). 01 WS-CUR-TIME PIC X(08). 01 WS-DISP-TS. 05 WS-DISP-DATE PIC X(08). 05 WS-DISP-DASH PIC X(01) VALUE '-'. 05 WS-DISP-TIME PIC X(06). *> Error severity constants 01 WS-SEV-I PIC X(01) VALUE 'I'. 01 WS-SEV-W PIC X(01) VALUE 'W'. 01 WS-SEV-E PIC X(01) VALUE 'E'. 01 WS-SEV-C PIC X(01) VALUE 'C'. *> Extended counters 01 WS-TOTAL-INPUT PIC 9(09) VALUE 0. 01 WS-TOTAL-OUTPUT PIC 9(09) VALUE 0. 01 WS-ERR-TOTAL PIC 9(09) VALUE 0. 01 WS-MALFORMED-CNT PIC 9(09) VALUE 0. 01 WS-TRUNC-CNT PIC 9(09) VALUE 0. 01 WS-ERR-REP-WRITTEN PIC 9(09) VALUE 0. 01 WS-FIELD-COUNT-ERR PIC 9(09) VALUE 0. 01 WS-INFO-COUNT PIC 9(09) VALUE 0. 01 WS-WARN-COUNT PIC 9(09) VALUE 0. 01 WS-ERR-SEV-COUNT PIC 9(09) VALUE 0. 01 WS-CRIT-COUNT PIC 9(09) VALUE 0. *> Hash total (batch control) 01 WS-HASH-TOTAL PIC 9(15) VALUE 0. 01 WS-HASH-FIELD-LEN PIC 9(05) VALUE 0. *> Validation control 01 WS-VALID-FLAG PIC X(01) VALUE 'Y'. 88 WS-VALID-RECORD VALUE 'Y' FALSE 'N'. 88 WS-INVALID-RECORD VALUE 'N'. 01 WS-COMMA-COUNT PIC 9(02). 01 WS-FIELD-COUNT PIC 9(02). 01 WS-EXPECTED-FIELDS PIC 9(02) VALUE 3. *> Quote pairing validation 01 WS-QUOTE-OPEN-COUNT PIC 9(03). 01 WS-QUOTE-BALANCE PIC S9(03). 01 WS-QUOTE-STATUS PIC X(01) VALUE 'B'. 88 WS-QUOTES-BALANCED VALUE 'B'. 88 WS-QUOTES-UNBALANCED VALUE 'U'. *> Field length truncation 01 WS-FIELD1-MAX-LEN PIC 9(03) VALUE 030. 01 WS-FIELD2-MAX-LEN PIC 9(03) VALUE 030. 01 WS-FIELD3-MAX-LEN PIC 9(03) VALUE 100. 01 WS-TRUNC-FLAG1 PIC X(01) VALUE 'N'. 88 WS-TRUNCATED-F1 VALUE 'Y' FALSE 'N'. 01 WS-TRUNC-FLAG2 PIC X(01) VALUE 'N'. 88 WS-TRUNCATED-F2 VALUE 'Y' FALSE 'N'. 01 WS-TRUNC-FLAG3 PIC X(01) VALUE 'N'. 88 WS-TRUNCATED-F3 VALUE 'Y' FALSE 'N'. *> Numeric check for segment 1 01 WS-SEGMENT-BUFFER PIC X(50). *> Record reconciliation 01 WS-EXPECTED-OUT PIC 9(09). 01 WS-RECON-DIFF PIC S9(09). 01 WS-RECON-STATUS PIC X(01). 88 WS-RECON-MATCH VALUE 'M'. 88 WS-RECON-MISMATCH VALUE 'X'. *> Error context for 6000 01 WS-ERR-LINE-NUM PIC 9(09). 01 WS-ERR-SEVERITY PIC X(01). 01 WS-ERR-DESC PIC X(80). *> Scratch fields 01 WS-RPT-COUNT PIC Z(9)9. 01 WS-ALL-NUMERIC PIC X(01). 01 WS-IDX PIC 9(02). 01 WS-CHAR-IDX PIC 9(04). 01 WS-COMMA-POS1 PIC 9(03). 01 WS-COMMA-POS2 PIC 9(03). 01 WS-SUB PIC 9(03). 01 WS-CHAR-CHK PIC X(01). PROCEDURE DIVISION. *> ============================================================ *> MAIN SECTION — orchestration *> ============================================================ MAIN SECTION. MB-PROCESS. PERFORM 1000-INIT. PERFORM 2000-OPEN-FILES. PERFORM 3000-READ-INPUT. PERFORM 4000-REPORT. PERFORM 5000-AUDIT. PERFORM 9000-EXIT. STOP RUN. *> ============================================================ *> 1000-INIT SECTION *> ============================================================ 1000-INIT SECTION. 1000-START. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'CsvFbLf STARTED — CDR CSV Import with LF'. DISPLAY '[' WS-TIMESTAMP '] ' 'Input: file-in.csv Output: file-out.dat'. DISPLAY '[' WS-TIMESTAMP '] ' 'Error: csv-lf-errors.dat Audit: csv-lf-audit.dat'. MOVE ZERO TO WS-TOTAL-INPUT WS-TOTAL-OUTPUT WS-ERR-TOTAL WS-MALFORMED-CNT WS-TRUNC-CNT WS-ERR-REP-WRITTEN WS-INFO-COUNT WS-WARN-COUNT WS-ERR-SEV-COUNT WS-CRIT-COUNT WS-FIELD-COUNT-ERR WS-HASH-TOTAL WS-SEG-TOTAL. MOVE 'N' TO WS-FILE-IN-OPEN WS-FILE-OUT-OPEN WS-ERR-REP-OPEN WS-AUDIT-OPEN. 1000-EXIT. EXIT. *> ============================================================ *> 2000-OPEN-FILES SECTION *> ============================================================ 2000-OPEN-FILES SECTION. 2000-START. OPEN INPUT FILE-IN. IF WS-FILE-IN-STATUS NOT = '00' DISPLAY 'CRITICAL: Cannot open FILE-IN status: ' WS-FILE-IN-STATUS MOVE 1 TO RETURN-CODE STOP RUN END-IF. MOVE 'Y' TO WS-FILE-IN-OPEN. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'FILE-IN opened, status: ' WS-FILE-IN-STATUS. OPEN OUTPUT FILE-OUT. IF WS-FILE-OUT-STATUS NOT = '00' DISPLAY 'CRITICAL: Cannot open FILE-OUT status: ' WS-FILE-OUT-STATUS MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT END-IF. MOVE 'Y' TO WS-FILE-OUT-OPEN. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'FILE-OUT opened, status: ' WS-FILE-OUT-STATUS. OPEN OUTPUT ERROR-REPORT. IF WS-ERR-STATUS NOT = '00' DISPLAY 'CRITICAL: Cannot open ERROR-REPORT status: ' WS-ERR-STATUS MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT END-IF. MOVE 'Y' TO WS-ERR-REP-OPEN. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'ERROR-REPORT opened, status: ' WS-ERR-STATUS. *> Write error report header MOVE SPACES TO ERR-REC. STRING 'CSV LF Error Detail - CsvFbLf ' WS-CUR-DATE INTO ERR-REC. WRITE ERR-REC. MOVE 'LINE SEV DESCRIPTION' TO ERR-REC. WRITE ERR-REC. MOVE SPACES TO ERR-REC. WRITE ERR-REC. OPEN OUTPUT AUDIT-FILE. IF WS-AUDIT-STATUS NOT = '00' DISPLAY 'CRITICAL: Cannot open AUDIT-FILE status: ' WS-AUDIT-STATUS MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT END-IF. MOVE 'Y' TO WS-AUDIT-OPEN. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'AUDIT-FILE opened, status: ' WS-AUDIT-STATUS. *> Write audit header MOVE SPACES TO AUD-REC. STRING 'CSV LF Audit - CsvFbLf Run: ' WS-CUR-DATE INTO AUD-REC. WRITE AUD-REC. MOVE SPACES TO AUD-REC. WRITE AUD-REC. 2000-EXIT. EXIT. *> ============================================================ *> 3000-READ-INPUT SECTION — main read-loop *> ============================================================ 3000-READ-INPUT SECTION. 3000-LOOP. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' 'Processing CSV records'. PERFORM UNTIL WS-EOF-YES READ FILE-IN AT END SET WS-EOF-YES TO TRUE NOT AT END ADD 1 TO WS-LINE-COUNT ADD 1 TO WS-TOTAL-INPUT MOVE 'Y' TO WS-VALID-FLAG MOVE 'B' TO WS-QUOTE-STATUS PERFORM 3100-VALIDATE-RECORD IF WS-INVALID-RECORD MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-E TO WS-ERR-SEVERITY PERFORM 6000-ERROR-HANDLE ELSE PERFORM 3200-PROCESS-RECORD END-IF END-READ IF WS-FILE-IN-STATUS NOT = '00' AND WS-FILE-IN-STATUS NOT = '10' AND WS-EOF NOT = 'Y' PERFORM 9900-TIMESTAMP DISPLAY '[' WS-TIMESTAMP '] ' 'ERROR: READ FILE-IN status: ' WS-FILE-IN-STATUS MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-E TO WS-ERR-SEVERITY STRING 'READ failure status=' WS-FILE-IN-STATUS INTO WS-ERR-DESC PERFORM 6000-ERROR-HANDLE END-IF END-PERFORM. 3000-EXIT. EXIT. *> ============================================================ *> 3100-VALIDATE-RECORD SECTION *> ============================================================ 3100-VALIDATE-RECORD SECTION. 3100-START. *> Field count (commas + 1, expect 3) MOVE ZERO TO WS-COMMA-COUNT. INSPECT IN-LINE TALLYING WS-COMMA-COUNT FOR ALL ','. COMPUTE WS-FIELD-COUNT = WS-COMMA-COUNT + 1. IF WS-FIELD-COUNT NOT = WS-EXPECTED-FIELDS MOVE 'N' TO WS-VALID-FLAG ADD 1 TO WS-FIELD-COUNT-ERR WS-MALFORMED-CNT WS-ERR-TOTAL WS-ERR-SEV-COUNT MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-E TO WS-ERR-SEVERITY STRING 'Field count mismatch: expect ' WS-EXPECTED-FIELDS ' got ' WS-FIELD-COUNT INTO WS-ERR-DESC DISPLAY '[' WS-TIMESTAMP '] ' 'ERROR: line ' WS-LINE-COUNT ' field-count ' WS-FIELD-COUNT EXIT SECTION END-IF. *> Quote pairing validation MOVE ZERO TO WS-QUOTE-OPEN-COUNT. INSPECT IN-LINE TALLYING WS-QUOTE-OPEN-COUNT FOR ALL '"'. DIVIDE WS-QUOTE-OPEN-COUNT BY 2 GIVING WS-QUOTE-BALANCE REMAINDER WS-QUOTE-BALANCE. IF WS-QUOTE-BALANCE NOT = 0 MOVE 'U' TO WS-QUOTE-STATUS ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Unbalanced quotes: count=' WS-QUOTE-OPEN-COUNT INTO WS-ERR-DESC PERFORM 6000-ERROR-HANDLE END-IF. *> Estimate raw field lengths from CSV for truncation MOVE ZERO TO WS-COMMA-POS1 WS-COMMA-POS2. MOVE 0 TO WS-SUB. PERFORM VARYING WS-CHAR-IDX FROM 1 BY 1 UNTIL WS-CHAR-IDX > 200 IF IN-LINE(WS-CHAR-IDX:1) = ',' ADD 1 TO WS-SUB IF WS-SUB = 1 MOVE WS-CHAR-IDX TO WS-COMMA-POS1 ELSE IF WS-SUB = 2 MOVE WS-CHAR-IDX TO WS-COMMA-POS2 EXIT PERFORM END-IF END-IF END-IF END-PERFORM. *> Field1 truncation check IF WS-COMMA-POS1 > 0 COMPUTE WS-SUB = WS-COMMA-POS1 - 1 IF WS-SUB > WS-FIELD1-MAX-LEN MOVE 'Y' TO WS-TRUNC-FLAG1 ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Field1 trunc: raw len ' WS-SUB INTO WS-ERR-DESC PERFORM 6000-ERROR-HANDLE END-IF END-IF. *> Field2 truncation check IF WS-COMMA-POS2 > 0 AND WS-COMMA-POS1 > 0 COMPUTE WS-SUB = WS-COMMA-POS2 - WS-COMMA-POS1 - 1 IF WS-SUB > WS-FIELD2-MAX-LEN MOVE 'Y' TO WS-TRUNC-FLAG2 ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Field2 trunc: raw len ' WS-SUB INTO WS-ERR-DESC PERFORM 6000-ERROR-HANDLE END-IF END-IF. *> Field3 truncation check (rest of line after 2nd comma) IF WS-COMMA-POS2 > 0 COMPUTE WS-SUB = 200 - WS-COMMA-POS2 IF WS-SUB > WS-FIELD3-MAX-LEN MOVE 'Y' TO WS-TRUNC-FLAG3 ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Field3 trunc: raw len ' WS-SUB INTO WS-ERR-DESC PERFORM 6000-ERROR-HANDLE END-IF END-IF. 3100-EXIT. EXIT. *> ============================================================ *> 3200-PROCESS-RECORD SECTION *> Original PROCESS-CSV-LINE logic preserved intact: *> UNSTRING -> pipe-count -> replace -> segment -> output *> ============================================================ 3200-PROCESS-RECORD SECTION. 3200-START. *> Unstring CSV into 3 fields MOVE SPACES TO WS-FIELD1 WS-FIELD2 WS-FIELD3. UNSTRING IN-LINE DELIMITED BY ',' INTO WS-FIELD1 WS-FIELD2 WS-FIELD3 END-UNSTRING. *> Char-check Field1 MOVE 'Y' TO WS-ALL-NUMERIC. PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 30 OR WS-ALL-NUMERIC = 'N' MOVE WS-FIELD1(WS-IDX:1) TO WS-CHAR-CHK IF WS-CHAR-CHK NOT = SPACE AND WS-CHAR-CHK < 'A' AND WS-CHAR-CHK > 'Z' AND WS-CHAR-CHK < '0' AND WS-CHAR-CHK > '9' AND WS-CHAR-CHK NOT = '-' MOVE 'N' TO WS-ALL-NUMERIC END-IF END-PERFORM. IF WS-ALL-NUMERIC = 'N' MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Field1 special chars: "' WS-FIELD1(1:15) '"' INTO WS-ERR-DESC ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL PERFORM 6000-ERROR-HANDLE END-IF. *> Post-UNSTRING truncation detection IF FUNCTION LENGTH(FUNCTION TRIM(WS-FIELD1)) > 30 ADD 1 TO WS-TRUNC-CNT MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY MOVE 'Field1 truncated by UNSTRING' TO WS-ERR-DESC ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL PERFORM 6000-ERROR-HANDLE END-IF. IF FUNCTION LENGTH(FUNCTION TRIM(WS-FIELD2)) > 30 ADD 1 TO WS-TRUNC-CNT MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY MOVE 'Field2 truncated by UNSTRING' TO WS-ERR-DESC ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL PERFORM 6000-ERROR-HANDLE END-IF. *> Count pipe markers in field3 using INSPECT TALLYING MOVE ZERO TO WS-PIPE-COUNT. INSPECT WS-FIELD3 TALLYING WS-PIPE-COUNT FOR ALL '|'. *> Replace pipes with spaces for clean segment extraction INSPECT WS-FIELD3 REPLACING ALL '|' BY SPACE. *> Split field3 at original pipe positions into segments MOVE ZERO TO WS-SEG-COUNT. MOVE SPACES TO WS-SEGMENTS. UNSTRING WS-FIELD3 DELIMITED BY ALL SPACES INTO WS-SEGMENT(1) WS-SEGMENT(2) WS-SEGMENT(3) WS-SEGMENT(4) WS-SEGMENT(5) WS-SEGMENT(6) WS-SEGMENT(7) WS-SEGMENT(8) WS-SEGMENT(9) WS-SEGMENT(10) COUNT IN WS-SEG-COUNT END-UNSTRING. *> If no pipes, write one record IF WS-PIPE-COUNT = 0 MOVE 1 TO WS-SEG-COUNT MOVE WS-FIELD3 TO WS-SEGMENT(1) END-IF. *> Write one output record per segment IF WS-SEG-COUNT = 0 MOVE 1 TO WS-SEG-COUNT END-IF. *> Validate first segment numeric IF WS-SEG-COUNT >= 1 MOVE SPACES TO WS-SEGMENT-BUFFER MOVE WS-SEGMENT(1) TO WS-SEGMENT-BUFFER IF WS-SEGMENT-BUFFER NOT IS NUMERIC AND WS-SEGMENT-BUFFER > SPACES MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-W TO WS-ERR-SEVERITY STRING 'Non-numeric segment 1: "' WS-SEGMENT(1)(1:15) '"' INTO WS-ERR-DESC ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL PERFORM 6000-ERROR-HANDLE END-IF END-IF. PERFORM VARYING WS-SEG-IDX FROM 1 BY 1 UNTIL WS-SEG-IDX > WS-SEG-COUNT OR WS-SEG-IDX > 10 IF WS-SEGMENT(WS-SEG-IDX) > SPACES PERFORM 3300-WRITE-OUTPUT END-IF END-PERFORM. 3200-EXIT. EXIT. *> ============================================================ *> 3300-WRITE-OUTPUT SECTION *> ============================================================ 3300-WRITE-OUTPUT SECTION. 3300-START. MOVE SPACES TO WS-OUT-LINE. STRING WS-FIELD1 DELIMITED BY SPACES '|' WS-FIELD2 DELIMITED BY SPACES '|' WS-SEGMENT(WS-SEG-IDX) DELIMITED BY SPACES INTO WS-OUT-LINE END-STRING. MOVE WS-OUT-LINE TO OUT-REC. WRITE OUT-REC. IF WS-FILE-OUT-STATUS NOT = '00' PERFORM 9900-TIMESTAMP DISPLAY '[' WS-TIMESTAMP '] ' 'ERROR: WRITE FILE-OUT status: ' WS-FILE-OUT-STATUS MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM MOVE WS-SEV-E TO WS-ERR-SEVERITY STRING 'WRITE FAIL status=' WS-FILE-OUT-STATUS INTO WS-ERR-DESC ADD 1 TO WS-ERR-SEV-COUNT WS-ERR-TOTAL PERFORM 6000-ERROR-HANDLE END-IF. ADD 1 TO WS-REC-COUNT WS-TOTAL-OUTPUT WS-SEG-TOTAL. COMPUTE WS-HASH-FIELD-LEN = FUNCTION LENGTH(FUNCTION TRIM(WS-SEGMENT(WS-SEG-IDX))). ADD WS-HASH-FIELD-LEN TO WS-HASH-TOTAL. 3300-EXIT. EXIT. *> ============================================================ *> 4000-REPORT SECTION — batch totals & reconciliation *> ============================================================ 4000-REPORT SECTION. 4000-START. PERFORM 9900-TIMESTAMP. DISPLAY '[' WS-TIMESTAMP '] ' '============================================'. DISPLAY '[' WS-TIMESTAMP '] ' 'CsvFbLf BATCH REPORT'. DISPLAY '[' WS-TIMESTAMP '] ' '============================================'. DISPLAY '[' WS-TIMESTAMP '] ' 'Input lines : ' WS-TOTAL-INPUT. DISPLAY '[' WS-TIMESTAMP '] ' 'Output records : ' WS-TOTAL-OUTPUT. DISPLAY '[' WS-TIMESTAMP '] ' 'Segments expanded : ' WS-SEG-TOTAL. DISPLAY '[' WS-TIMESTAMP '] ' 'Error rep entries : ' WS-ERR-REP-WRITTEN. DISPLAY '[' WS-TIMESTAMP '] ' '--------------------------------------------'. DISPLAY '[' WS-TIMESTAMP '] ' 'Error total : ' WS-ERR-TOTAL. DISPLAY '[' WS-TIMESTAMP '] ' ' I=' WS-INFO-COUNT ' W=' WS-WARN-COUNT ' E=' WS-ERR-SEV-COUNT ' C=' WS-CRIT-COUNT. DISPLAY '[' WS-TIMESTAMP '] ' '--------------------------------------------'. DISPLAY '[' WS-TIMESTAMP '] ' 'Malformed : ' WS-MALFORMED-CNT. DISPLAY '[' WS-TIMESTAMP '] ' 'Truncations: ' WS-TRUNC-CNT. DISPLAY '[' WS-TIMESTAMP '] ' 'Field errs : ' WS-FIELD-COUNT-ERR. DISPLAY '[' WS-TIMESTAMP '] ' 'Hash total : ' WS-HASH-TOTAL. *> Final file status check IF WS-FILE-IN-STATUS NOT = '00' AND NOT = '10' DISPLAY ' FILE-IN final status: ' WS-FILE-IN-STATUS. IF WS-FILE-OUT-STATUS NOT = '00' AND NOT = '10' DISPLAY ' FILE-OUT final status: ' WS-FILE-OUT-STATUS. IF WS-ERR-STATUS NOT = '00' AND NOT = '10' DISPLAY ' ERROR-REPORT final status: ' WS-ERR-STATUS. IF WS-AUDIT-STATUS NOT = '00' AND NOT = '10' DISPLAY ' AUDIT-FILE final status: ' WS-AUDIT-STATUS. *> Record count reconciliation DISPLAY '--------------------------------------------'. DISPLAY 'RECONCILIATION'. IF WS-TOTAL-INPUT >= WS-MALFORMED-CNT COMPUTE WS-EXPECTED-OUT = WS-TOTAL-INPUT - WS-MALFORMED-CNT ELSE MOVE ZERO TO WS-EXPECTED-OUT. IF WS-TOTAL-OUTPUT = WS-EXPECTED-OUT MOVE 'M' TO WS-RECON-STATUS DISPLAY 'MATCH: input=' WS-TOTAL-INPUT ' output=' WS-TOTAL-OUTPUT ELSE COMPUTE WS-RECON-DIFF = WS-TOTAL-OUTPUT - WS-EXPECTED-OUT MOVE 'X' TO WS-RECON-STATUS DISPLAY 'MISMATCH: input=' WS-TOTAL-INPUT ' output=' WS-TOTAL-OUTPUT ' diff=' WS-RECON-DIFF END-IF. DISPLAY '============================================'. 4000-EXIT. EXIT. *> ============================================================ *> 5000-AUDIT SECTION *> ============================================================ 5000-AUDIT SECTION. 5000-START. PERFORM 9900-TIMESTAMP. MOVE SPACES TO AUD-REC. STRING '=== CSV LF Import Summary === ' WS-CUR-DATE ' ' WS-CUR-TIME INTO AUD-REC. WRITE AUD-REC. IF WS-AUDIT-STATUS NOT = '00' DISPLAY 'AUDIT header WRITE status: ' WS-AUDIT-STATUS. MOVE SPACES TO AUD-REC. WRITE AUD-REC. MOVE WS-TOTAL-INPUT TO WS-RPT-COUNT. STRING 'Input lines read: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-TOTAL-OUTPUT TO WS-RPT-COUNT. STRING 'Output records written: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-SEG-TOTAL TO WS-RPT-COUNT. STRING 'Segments expanded: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. *> Error summary MOVE SPACES TO AUD-REC. WRITE AUD-REC. STRING 'Error summary —' INTO AUD-REC. WRITE AUD-REC. MOVE WS-ERR-TOTAL TO WS-RPT-COUNT. STRING ' Total: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-INFO-COUNT TO WS-RPT-COUNT. STRING ' INFO (I): ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-WARN-COUNT TO WS-RPT-COUNT. STRING ' WARN (W): ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-ERR-SEV-COUNT TO WS-RPT-COUNT. STRING ' ERROR (E): ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-CRIT-COUNT TO WS-RPT-COUNT. STRING ' CRIT (C): ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. *> Breakdown MOVE SPACES TO AUD-REC. WRITE AUD-REC. STRING 'Malformed records: ' WS-MALFORMED-CNT INTO AUD-REC. WRITE AUD-REC. STRING 'Truncation warnings: ' WS-TRUNC-CNT INTO AUD-REC. WRITE AUD-REC. STRING 'Field count errors: ' WS-FIELD-COUNT-ERR INTO AUD-REC. WRITE AUD-REC. *> Reconciliation MOVE SPACES TO AUD-REC. WRITE AUD-REC. STRING 'Reconciliation: ' WS-RECON-STATUS INTO AUD-REC. WRITE AUD-REC. MOVE WS-EXPECTED-OUT TO WS-RPT-COUNT. STRING ' Expected output: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE WS-TOTAL-OUTPUT TO WS-RPT-COUNT. STRING ' Actual output: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. IF WS-RECON-MISMATCH MOVE WS-RECON-DIFF TO WS-RPT-COUNT STRING ' Difference: ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. *> Batch control totals MOVE SPACES TO AUD-REC. WRITE AUD-REC. STRING '=== Batch Control Totals ===' INTO AUD-REC. WRITE AUD-REC. STRING 'Batch date: ' WS-CUR-DATE INTO AUD-REC. WRITE AUD-REC. STRING 'Batch time: ' WS-CUR-TIME INTO AUD-REC. WRITE AUD-REC. MOVE WS-HASH-TOTAL TO WS-RPT-COUNT. STRING 'Hash total (seg lens): ' WS-RPT-COUNT INTO AUD-REC. WRITE AUD-REC. MOVE SPACES TO AUD-REC. WRITE AUD-REC. STRING '=== End of Audit Report ===' INTO AUD-REC. WRITE AUD-REC. 5000-EXIT. EXIT. *> ============================================================ *> 6000-ERROR-HANDLE SECTION *> ============================================================ 6000-ERROR-HANDLE SECTION. 6000-START. MOVE SPACES TO ERR-REC. STRING WS-ERR-LINE-NUM ' ' WS-ERR-SEVERITY ' ' WS-ERR-DESC INTO ERR-REC. WRITE ERR-REC. IF WS-ERR-STATUS NOT = '00' DISPLAY 'ERROR: WRITE ERROR-REPORT status: ' WS-ERR-STATUS. ADD 1 TO WS-ERR-REP-WRITTEN. DISPLAY '[' WS-TIMESTAMP '] ' 'ERR: line=' WS-ERR-LINE-NUM ' sev=' WS-ERR-SEVERITY ' ' WS-ERR-DESC. 6000-EXIT. EXIT. *> ============================================================ *> 9000-EXIT SECTION *> ============================================================ 9000-EXIT SECTION. 9000-START. PERFORM 9900-TIMESTAMP. *> Close ERROR-REPORT IF WS-ERR-REP-IS-OPEN MOVE SPACES TO ERR-REC WRITE ERR-REC MOVE '--- End of Error Report ---' TO ERR-REC WRITE ERR-REC CLOSE ERROR-REPORT IF WS-ERR-STATUS NOT = '00' DISPLAY 'ERROR: CLOSE ERROR-REPORT status: ' WS-ERR-STATUS ELSE DISPLAY 'ERROR-REPORT closed' END-IF. *> Close FILE-OUT IF WS-FILE-OUT-IS-OPEN CLOSE FILE-OUT IF WS-FILE-OUT-STATUS NOT = '00' DISPLAY 'ERROR: CLOSE FILE-OUT status: ' WS-FILE-OUT-STATUS ELSE DISPLAY 'FILE-OUT closed' END-IF. *> Close AUDIT-FILE IF WS-AUDIT-IS-OPEN CLOSE AUDIT-FILE IF WS-AUDIT-STATUS NOT = '00' DISPLAY 'ERROR: CLOSE AUDIT-FILE status: ' WS-AUDIT-STATUS ELSE DISPLAY 'AUDIT-FILE closed' END-IF. *> Close FILE-IN IF WS-FILE-IN-IS-OPEN CLOSE FILE-IN IF WS-FILE-IN-STATUS NOT = '00' DISPLAY 'ERROR: CLOSE FILE-IN status: ' WS-FILE-IN-STATUS ELSE DISPLAY 'FILE-IN closed' END-IF. *> Final summary DISPLAY '[' WS-TIMESTAMP '] ' 'CsvFbLf COMPLETED.' ' Input=' WS-LINE-COUNT ' Output=' WS-REC-COUNT ' Errors=' WS-ERR-TOTAL. DISPLAY '[' WS-TIMESTAMP '] ' ' Malformed=' WS-MALFORMED-CNT ' Truncated=' WS-TRUNC-CNT ' FieldErr=' WS-FIELD-COUNT-ERR. DISPLAY '[' WS-TIMESTAMP '] ' ' Hash=' WS-HASH-TOTAL ' ErrRep=csv-lf-errors.dat' ' Audit=csv-lf-audit.dat'. STOP RUN. 9000-EXIT-POINT. EXIT. *> ============================================================ *> 9900-TIMESTAMP SECTION *> ============================================================ 9900-TIMESTAMP SECTION. 9900-START. MOVE FUNCTION CURRENT-DATE (1:8) TO WS-CUR-DATE. MOVE FUNCTION CURRENT-DATE (9:6) TO WS-CUR-TIME. MOVE WS-CUR-DATE TO WS-DISP-DATE. MOVE WS-CUR-TIME TO WS-DISP-TIME. MOVE WS-DISP-TS TO WS-TIMESTAMP. 9900-EXIT. EXIT. END PROGRAM CsvFbLf.