Files
cobol-java-v3/benchmark-programs/21-csv-fb-lf/main-21-csv-fb-lf.cbl
T
NB-076 94400d50d4 feat: add benchmark-programs — 58 telecom COBOL test programs
作为子目录纳入系统,与核心测试管道协同

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 09:53:21 +08:00

732 lines
32 KiB
COBOL

*> ============================================================
*> 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.