94400d50d4
作为子目录纳入系统,与核心测试管道协同 Co-Authored-By: Claude <noreply@anthropic.com>
732 lines
32 KiB
COBOL
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.
|