Initial commit: COBOL+JCL credit card billing system with COMP-3, OCCURS, COPY REPLACING, INSPECT, and JCL runner
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
bin/*.exe
|
||||
data/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
@@ -0,0 +1,259 @@
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. CRDCALC.
|
||||
|
||||
* CALCULATE INTEREST AND FEES - CREDIT CARD BATCH SYSTEM
|
||||
* DEMONSTRATES: OCCURS+SEARCH, KEY BREAK, COMPUTE,
|
||||
* EVALUATE, COMP-3, COPY REPLACING
|
||||
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT VALID-IN ASSIGN TO "VALIDIN"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT RATE-FILE ASSIGN TO "RATE"
|
||||
ORGANIZATION IS SEQUENTIAL.
|
||||
SELECT CALC-OUT ASSIGN TO "CALCOUT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD VALID-IN.
|
||||
COPY TXCPY.
|
||||
|
||||
FD RATE-FILE.
|
||||
COPY RATECPY.
|
||||
|
||||
FD CALC-OUT.
|
||||
01 CALC-RECORD PIC X(200).
|
||||
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-SWITCHES.
|
||||
05 WS-EOF-VALID PIC X VALUE 'N'.
|
||||
88 WS-END-OF-VALID VALUE 'Y'.
|
||||
05 WS-EOF-RATE PIC X VALUE 'N'.
|
||||
88 WS-END-OF-RATE VALUE 'Y'.
|
||||
|
||||
01 WS-COUNTERS.
|
||||
05 WS-TOTAL-IN PIC 9(5) VALUE 0.
|
||||
05 WS-TOTAL-OUT PIC 9(5) VALUE 0.
|
||||
05 WS-RATE-COUNT PIC 9(5) VALUE 0.
|
||||
|
||||
* INTERNAL TABLE: RATE TABLE WITH OCCURS + SEARCH
|
||||
01 WS-RATE-TABLE.
|
||||
05 WS-RATE-ENTRY OCCURS 1 TO 5 TIMES
|
||||
DEPENDING ON WS-RATE-COUNT
|
||||
INDEXED BY WS-RT-IDX.
|
||||
10 WS-RT-TYPE PIC X.
|
||||
10 WS-RT-PCT PIC 9(1)V9(8) COMP-3.
|
||||
10 WS-RT-EFF-DATE PIC 9(8).
|
||||
|
||||
* COPY REPLACING DEMO: STATEMENT DATE
|
||||
COPY DATESUB REPLACING ==:TAG:== BY ==WS-STMT==.
|
||||
|
||||
01 WS-CARD-ACCUM.
|
||||
05 WS-CURRENT-CARD PIC 9(16) VALUE 0.
|
||||
05 WS-CARD-PURCHASES PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-CARD-CASH PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-CARD-REFUNDS PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-CARD-INTEREST PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-CARD-FEE PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-CARD-TOTAL PIC S9(9)V99 COMP-3 VALUE 0.
|
||||
05 WS-TX-COUNT PIC 9(4) VALUE 0.
|
||||
|
||||
01 WS-CALC-DETAIL.
|
||||
05 WS-D-CARD PIC 9(16).
|
||||
05 WS-D-SEP1 PIC X VALUE SPACE.
|
||||
05 WS-D-TYPE PIC X.
|
||||
05 WS-D-SEP2 PIC X VALUE SPACE.
|
||||
05 WS-D-AMOUNT PIC -(9)9.99.
|
||||
05 WS-D-SEP3 PIC X VALUE SPACE.
|
||||
05 WS-D-INTEREST PIC -(7)9.99.
|
||||
05 WS-D-SEP4 PIC X VALUE SPACE.
|
||||
05 WS-D-FEE PIC -(7)9.99.
|
||||
05 WS-D-SEP5 PIC X VALUE SPACE.
|
||||
05 WS-D-DESC PIC X(30).
|
||||
|
||||
01 WS-SUMMARY.
|
||||
05 WS-S-CARD PIC 9(16).
|
||||
05 WS-S-SEP1 PIC X VALUE SPACE.
|
||||
05 WS-S-TOTAL-AMT PIC -(9)9.99.
|
||||
05 WS-S-SEP2 PIC X VALUE SPACE.
|
||||
05 WS-S-TOTAL-INT PIC -(9)9.99.
|
||||
05 WS-S-SEP3 PIC X VALUE SPACE.
|
||||
05 WS-S-TOTAL-FEE PIC -(9)9.99.
|
||||
05 WS-S-SEP4 PIC X VALUE SPACE.
|
||||
05 WS-S-GRAND-TOTAL PIC -(9)9.99.
|
||||
05 WS-S-SEP5 PIC X VALUE SPACE.
|
||||
05 WS-S-TX-COUNT PIC Z(4)9.
|
||||
|
||||
01 WS-GRAND-TOTAL PIC S9(12)V99 COMP-3 VALUE 0.
|
||||
01 WS-GRAND-INT PIC S9(12)V99 COMP-3 VALUE 0.
|
||||
01 WS-GRAND-FEE PIC S9(12)V99 COMP-3 VALUE 0.
|
||||
01 WS-GRAND-DISP.
|
||||
05 WS-GD-TOTAL PIC -(12)9.99.
|
||||
05 WS-GD-SP1 PIC X VALUE SPACE.
|
||||
05 WS-GD-INT PIC -(12)9.99.
|
||||
05 WS-GD-SP2 PIC X VALUE SPACE.
|
||||
05 WS-GD-FEE PIC -(12)9.99.
|
||||
|
||||
01 WS-DAYS-DIFF PIC 9(4).
|
||||
01 WS-INT-AMOUNT PIC S9(9)V99 COMP-3.
|
||||
01 WS-FEE-AMOUNT PIC S9(9)V99 COMP-3.
|
||||
01 WS-DAILY-RATE PIC 9(1)V9(8) COMP-3.
|
||||
01 WS-CASH-RATE PIC 9(1)V9(4) COMP-3.
|
||||
01 WS-OVERDUE-RATE PIC 9(1)V9(4) COMP-3.
|
||||
01 WS-STATEMENT-DATE PIC 9(8).
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
0000-MAIN.
|
||||
OPEN INPUT VALID-IN
|
||||
INPUT RATE-FILE
|
||||
OUTPUT CALC-OUT.
|
||||
|
||||
ACCEPT WS-STATEMENT-DATE FROM DATE YYYYMMDD.
|
||||
MOVE WS-STATEMENT-DATE(1:4) TO WS-STMT-YYYY.
|
||||
MOVE WS-STATEMENT-DATE(5:2) TO WS-STMT-MM.
|
||||
MOVE WS-STATEMENT-DATE(7:2) TO WS-STMT-DD.
|
||||
|
||||
* LOAD RATES INTO OCCURS TABLE
|
||||
PERFORM 1000-LOAD-RATES.
|
||||
|
||||
* PROCESS TRANSACTIONS (KEY BREAK: CARD CHANGE)
|
||||
PERFORM 2000-PROCESS-VALID UNTIL WS-END-OF-VALID.
|
||||
|
||||
* WRITE FINAL CARD SUMMARY IF DATA REMAINS
|
||||
IF WS-TX-COUNT > 0
|
||||
PERFORM 3000-WRITE-CARD-SUMMARY.
|
||||
|
||||
* WRITE GRAND TOTAL
|
||||
PERFORM 4000-WRITE-GRAND-TOTAL.
|
||||
|
||||
CLOSE VALID-IN RATE-FILE CALC-OUT.
|
||||
DISPLAY 'CRDCALC: ' WS-TOTAL-IN ' READ, '
|
||||
WS-TOTAL-OUT ' WRITTEN'.
|
||||
GOBACK.
|
||||
|
||||
* LOAD RATES INTO OCCURS TABLE
|
||||
1000-LOAD-RATES.
|
||||
MOVE 0 TO WS-RATE-COUNT.
|
||||
PERFORM UNTIL WS-END-OF-RATE
|
||||
READ RATE-FILE
|
||||
AT END SET WS-END-OF-RATE TO TRUE
|
||||
NOT AT END
|
||||
ADD 1 TO WS-RATE-COUNT
|
||||
MOVE RATE-TYPE
|
||||
TO WS-RT-TYPE(WS-RATE-COUNT)
|
||||
MOVE RATE-PCT
|
||||
TO WS-RT-PCT(WS-RATE-COUNT)
|
||||
MOVE RATE-EFF-DATE
|
||||
TO WS-RT-EFF-DATE(WS-RATE-COUNT)
|
||||
END-READ
|
||||
END-PERFORM.
|
||||
|
||||
* SEARCH TABLE FOR CASH RATE
|
||||
SET WS-RT-IDX TO 1.
|
||||
SEARCH WS-RATE-ENTRY
|
||||
AT END MOVE 0.0005 TO WS-CASH-RATE
|
||||
WHEN WS-RT-TYPE(WS-RT-IDX) = 'C'
|
||||
MOVE WS-RT-PCT(WS-RT-IDX) TO WS-CASH-RATE
|
||||
END-SEARCH.
|
||||
|
||||
* SEARCH TABLE FOR OVERDUE RATE
|
||||
SET WS-RT-IDX TO 1.
|
||||
SEARCH WS-RATE-ENTRY
|
||||
AT END MOVE 0.0500 TO WS-OVERDUE-RATE
|
||||
WHEN WS-RT-TYPE(WS-RT-IDX) = 'O'
|
||||
MOVE WS-RT-PCT(WS-RT-IDX) TO WS-OVERDUE-RATE
|
||||
END-SEARCH.
|
||||
|
||||
IF WS-CASH-RATE = 0 MOVE 0.0005 TO WS-CASH-RATE END-IF.
|
||||
IF WS-OVERDUE-RATE = 0 MOVE 0.0500 TO WS-OVERDUE-RATE
|
||||
END-IF.
|
||||
MOVE 0 TO WS-TOTAL-IN.
|
||||
|
||||
2000-PROCESS-VALID.
|
||||
READ VALID-IN
|
||||
AT END SET WS-END-OF-VALID TO TRUE
|
||||
NOT AT END
|
||||
ADD 1 TO WS-TOTAL-IN
|
||||
IF WS-CURRENT-CARD = 0
|
||||
MOVE TX-CARD-NO TO WS-CURRENT-CARD
|
||||
END-IF
|
||||
* KEY BREAK: WHEN CARD CHANGES, OUTPUT SUMMARY
|
||||
IF TX-CARD-NO NOT = WS-CURRENT-CARD
|
||||
PERFORM 3000-WRITE-CARD-SUMMARY
|
||||
MOVE TX-CARD-NO TO WS-CURRENT-CARD
|
||||
MOVE 0 TO WS-CARD-PURCHASES
|
||||
WS-CARD-CASH
|
||||
WS-CARD-REFUNDS
|
||||
WS-CARD-INTEREST
|
||||
WS-CARD-FEE
|
||||
WS-CARD-TOTAL
|
||||
WS-TX-COUNT
|
||||
END-IF
|
||||
PERFORM 2500-ACCUMULATE-TX
|
||||
END-READ.
|
||||
|
||||
2500-ACCUMULATE-TX.
|
||||
MOVE 0 TO WS-INT-AMOUNT WS-FEE-AMOUNT.
|
||||
ADD 1 TO WS-TX-COUNT.
|
||||
EVALUATE TRUE
|
||||
WHEN TX-PURCHASE
|
||||
ADD TX-AMOUNT TO WS-CARD-PURCHASES
|
||||
MOVE 'PURCHASE' TO WS-D-DESC
|
||||
WHEN TX-CASH
|
||||
ADD TX-AMOUNT TO WS-CARD-CASH
|
||||
MOVE 'CASH ADVANCE' TO WS-D-DESC
|
||||
COMPUTE WS-INT-AMOUNT = TX-AMOUNT *
|
||||
WS-CASH-RATE * 30
|
||||
ADD WS-INT-AMOUNT TO WS-CARD-INTEREST
|
||||
WHEN TX-REFUND
|
||||
ADD TX-AMOUNT TO WS-CARD-PURCHASES
|
||||
MOVE 'REFUND' TO WS-D-DESC
|
||||
END-EVALUATE.
|
||||
|
||||
* FEE CALCULATION: 1% OF CASH ADVANCE (MIN 100)
|
||||
IF TX-CASH
|
||||
COMPUTE WS-FEE-AMOUNT = TX-AMOUNT * 0.01
|
||||
IF WS-FEE-AMOUNT < 100
|
||||
MOVE 100 TO WS-FEE-AMOUNT
|
||||
END-IF
|
||||
ADD WS-FEE-AMOUNT TO WS-CARD-FEE
|
||||
END-IF.
|
||||
|
||||
* WRITE DETAIL LINE
|
||||
MOVE TX-CARD-NO TO WS-D-CARD
|
||||
MOVE TX-TYPE TO WS-D-TYPE
|
||||
MOVE TX-AMOUNT TO WS-D-AMOUNT
|
||||
MOVE WS-INT-AMOUNT TO WS-D-INTEREST
|
||||
MOVE WS-FEE-AMOUNT TO WS-D-FEE
|
||||
WRITE CALC-RECORD FROM WS-CALC-DETAIL.
|
||||
|
||||
3000-WRITE-CARD-SUMMARY.
|
||||
COMPUTE WS-CARD-TOTAL = WS-CARD-PURCHASES + WS-CARD-CASH
|
||||
+ WS-CARD-INTEREST + WS-CARD-FEE - WS-CARD-REFUNDS.
|
||||
ADD WS-CARD-TOTAL TO WS-GRAND-TOTAL.
|
||||
ADD WS-CARD-INTEREST TO WS-GRAND-INT.
|
||||
ADD WS-CARD-FEE TO WS-GRAND-FEE.
|
||||
|
||||
MOVE WS-CURRENT-CARD TO WS-S-CARD
|
||||
MOVE WS-CARD-PURCHASES TO WS-S-TOTAL-AMT
|
||||
MOVE WS-CARD-INTEREST TO WS-S-TOTAL-INT
|
||||
MOVE WS-CARD-FEE TO WS-S-TOTAL-FEE
|
||||
MOVE WS-CARD-TOTAL TO WS-S-GRAND-TOTAL
|
||||
MOVE WS-TX-COUNT TO WS-S-TX-COUNT
|
||||
WRITE CALC-RECORD FROM WS-SUMMARY.
|
||||
ADD 1 TO WS-TOTAL-OUT.
|
||||
|
||||
4000-WRITE-GRAND-TOTAL.
|
||||
MOVE WS-GRAND-TOTAL TO WS-GD-TOTAL.
|
||||
MOVE WS-GRAND-INT TO WS-GD-INT.
|
||||
MOVE WS-GRAND-FEE TO WS-GD-FEE.
|
||||
STRING
|
||||
'GRAND TOTAL CARDS:' WS-TOTAL-OUT
|
||||
' AMOUNT:' WS-GD-TOTAL
|
||||
' INTEREST:' WS-GD-INT
|
||||
' FEE:' WS-GD-FEE
|
||||
INTO CALC-RECORD
|
||||
END-STRING.
|
||||
WRITE CALC-RECORD.
|
||||
@@ -0,0 +1,187 @@
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. CRDRPT.
|
||||
|
||||
* GENERATE MONTHLY STATEMENT AND SUMMARY REPORT
|
||||
* INPUT: BILLING RESULT FROM CRDCALC
|
||||
* OUTPUT: MONTHLY STATEMENT (PER CARD)
|
||||
* SUMMARY REPORT (AGGREGATE)
|
||||
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT CALC-IN ASSIGN TO "BILLING"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT STMT-OUT ASSIGN TO "STMT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT SUMM-OUT ASSIGN TO "SUMMARY"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD CALC-IN.
|
||||
01 CALC-LINE PIC X(200).
|
||||
|
||||
FD STMT-OUT.
|
||||
01 STMT-LINE PIC X(200).
|
||||
|
||||
FD SUMM-OUT.
|
||||
01 SUMM-LINE PIC X(200).
|
||||
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-SWITCHES.
|
||||
05 WS-EOF-CALC PIC X VALUE 'N'.
|
||||
88 WS-END-OF-CALC VALUE 'Y'.
|
||||
|
||||
01 WS-COUNTERS.
|
||||
05 WS-LINE-COUNT PIC 9(5) VALUE 0.
|
||||
05 WS-CARD-COUNT PIC 9(5) VALUE 0.
|
||||
05 WS-DETAIL-COUNT PIC 9(5) VALUE 0.
|
||||
|
||||
01 WS-REPORT-DATE.
|
||||
05 WS-RPT-YYYY PIC 9(4).
|
||||
05 WS-RPT-MM PIC 9(2).
|
||||
05 WS-RPT-DD PIC 9(2).
|
||||
|
||||
01 WS-HEADER1.
|
||||
05 WS-H1-DATE PIC X(8).
|
||||
05 WS-H1-SPACE PIC X(5) VALUE SPACES.
|
||||
05 WS-H1-TITLE PIC X(30)
|
||||
VALUE 'MONTHLY CREDIT CARD STATEMENT'.
|
||||
|
||||
01 WS-HEADER2.
|
||||
05 WS-H2-FILLER PIC X(50) VALUE ALL '-'.
|
||||
|
||||
01 WS-DETAIL-LINE.
|
||||
05 WS-DL-CARD PIC X(16).
|
||||
05 WS-DL-SP1 PIC X(2) VALUE SPACES.
|
||||
05 WS-DL-TYPE PIC X(8).
|
||||
05 WS-DL-SP2 PIC X(2) VALUE SPACES.
|
||||
05 WS-DL-AMOUNT PIC -(9)9.99.
|
||||
05 WS-DL-SP3 PIC X(2) VALUE SPACES.
|
||||
05 WS-DL-INTEREST PIC -(7)9.99.
|
||||
05 WS-DL-SP4 PIC X(2) VALUE SPACES.
|
||||
05 WS-DL-FEE PIC -(7)9.99.
|
||||
05 WS-DL-SP5 PIC X(2) VALUE SPACES.
|
||||
05 WS-DL-DESC PIC X(30).
|
||||
|
||||
01 WS-SUMMARY-LINE.
|
||||
05 WS-SL-CARD PIC X(16).
|
||||
05 WS-SL-SP1 PIC X(2) VALUE SPACES.
|
||||
05 WS-SL-TOTAL PIC -(9)9.99.
|
||||
05 WS-SL-SP2 PIC X(2) VALUE SPACES.
|
||||
05 WS-SL-INT PIC -(9)9.99.
|
||||
05 WS-SL-SP3 PIC X(2) VALUE SPACES.
|
||||
05 WS-SL-FEE PIC -(9)9.99.
|
||||
05 WS-SL-SP4 PIC X(2) VALUE SPACES.
|
||||
05 WS-SL-GRAND PIC -(9)9.99.
|
||||
05 WS-SL-SP5 PIC X(2) VALUE SPACES.
|
||||
05 WS-SL-TXCNT PIC Z(4)9.
|
||||
|
||||
01 WS-TRAILER.
|
||||
05 WS-TR-TOTAL-CARDS PIC Z(5)9.
|
||||
05 WS-TR-SP1 PIC X(5) VALUE SPACES.
|
||||
05 WS-TR-TOTAL-LINES PIC Z(5)9.
|
||||
05 WS-TR-SP2 PIC X(5) VALUE SPACES.
|
||||
05 WS-TR-MSG PIC X(20)
|
||||
VALUE 'END OF REPORT'.
|
||||
|
||||
* PARSE FIELDS FROM INPUT LINE
|
||||
01 WS-PARSE.
|
||||
05 WS-P-CARD PIC 9(16).
|
||||
05 WS-P-TYPE PIC X.
|
||||
05 WS-P-AMOUNT PIC S9(9)V99.
|
||||
05 WS-P-INTEREST PIC S9(7)V99.
|
||||
05 WS-P-FEE PIC S9(7)V99.
|
||||
05 WS-P-DESC PIC X(30).
|
||||
05 WS-P-IS-SUMMARY PIC X VALUE 'N'.
|
||||
88 WS-P-SUMMARY-LINE VALUE 'Y'.
|
||||
|
||||
05 WS-PARSE-REMAIN PIC X(150).
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
0000-MAIN.
|
||||
OPEN INPUT CALC-IN
|
||||
OUTPUT STMT-OUT
|
||||
OUTPUT SUMM-OUT.
|
||||
|
||||
ACCEPT WS-REPORT-DATE FROM DATE YYYYMMDD.
|
||||
MOVE WS-REPORT-DATE(1:4) TO WS-RPT-YYYY.
|
||||
MOVE WS-REPORT-DATE(5:2) TO WS-RPT-MM.
|
||||
MOVE WS-REPORT-DATE(7:2) TO WS-RPT-DD.
|
||||
MOVE WS-REPORT-DATE TO WS-H1-DATE.
|
||||
|
||||
PERFORM 1000-WRITE-HEADER.
|
||||
|
||||
MOVE 0 TO WS-CARD-COUNT WS-DETAIL-COUNT.
|
||||
|
||||
PERFORM 2000-PROCESS-CALC UNTIL WS-END-OF-CALC.
|
||||
|
||||
PERFORM 3000-WRITE-TRAILER.
|
||||
|
||||
CLOSE CALC-IN STMT-OUT SUMM-OUT.
|
||||
DISPLAY 'CRDRPT: ' WS-CARD-COUNT ' CARDS, '
|
||||
WS-DETAIL-COUNT ' LINES'.
|
||||
GOBACK.
|
||||
|
||||
1000-WRITE-HEADER.
|
||||
MOVE SPACES TO STMT-LINE.
|
||||
WRITE STMT-LINE FROM WS-HEADER1.
|
||||
MOVE SPACES TO STMT-LINE.
|
||||
WRITE STMT-LINE FROM WS-HEADER2.
|
||||
MOVE SPACES TO STMT-LINE.
|
||||
|
||||
2000-PROCESS-CALC.
|
||||
READ CALC-IN
|
||||
AT END SET WS-END-OF-CALC TO TRUE
|
||||
NOT AT END
|
||||
ADD 1 TO WS-LINE-COUNT
|
||||
PERFORM 2100-PARSE-LINE
|
||||
END-READ.
|
||||
|
||||
2100-PARSE-LINE.
|
||||
* CHECK IF THIS IS A GRAND TOTAL LINE
|
||||
IF CALC-LINE(1:11) = 'GRAND TOTAL'
|
||||
MOVE CALC-LINE TO SUMM-LINE
|
||||
WRITE SUMM-LINE
|
||||
EXIT PARAGRAPH
|
||||
END-IF.
|
||||
|
||||
* CHECK IF THIS IS A CARD SUMMARY LINE
|
||||
MOVE CALC-LINE TO WS-PARSE-REMAIN.
|
||||
UNSTRING WS-PARSE-REMAIN DELIMITED BY SPACE
|
||||
INTO WS-P-CARD WS-P-AMOUNT WS-P-INTEREST
|
||||
WS-P-FEE WS-P-AMOUNT WS-P-DESC
|
||||
END-UNSTRING.
|
||||
|
||||
* IF FIRST FIELD IS 16-DIGIT NUMBER AND HAS TX-COUNT
|
||||
* AT END, IT'S SUMMARY; OTHERWISE DETAIL
|
||||
IF CALC-LINE(18:1) NOT = ' '
|
||||
PERFORM 2200-WRITE-DETAIL
|
||||
ELSE
|
||||
PERFORM 2300-WRITE-CARD-SUMMARY.
|
||||
|
||||
2200-WRITE-DETAIL.
|
||||
MOVE CALC-LINE TO WS-DETAIL-LINE.
|
||||
MOVE WS-DETAIL-LINE TO STMT-LINE.
|
||||
WRITE STMT-LINE.
|
||||
ADD 1 TO WS-DETAIL-COUNT.
|
||||
|
||||
2300-WRITE-CARD-SUMMARY.
|
||||
MOVE CALC-LINE TO WS-SUMMARY-LINE.
|
||||
MOVE WS-SUMMARY-LINE TO SUMM-LINE.
|
||||
WRITE SUMM-LINE.
|
||||
MOVE WS-SUMMARY-LINE TO STMT-LINE.
|
||||
WRITE STMT-LINE.
|
||||
MOVE SPACES TO STMT-LINE.
|
||||
WRITE STMT-LINE.
|
||||
ADD 1 TO WS-CARD-COUNT.
|
||||
|
||||
3000-WRITE-TRAILER.
|
||||
MOVE SPACES TO STMT-LINE.
|
||||
WRITE STMT-LINE.
|
||||
MOVE WS-CARD-COUNT TO WS-TR-TOTAL-CARDS.
|
||||
MOVE WS-LINE-COUNT TO WS-TR-TOTAL-LINES.
|
||||
WRITE STMT-LINE FROM WS-TRAILER.
|
||||
|
||||
MOVE WS-TRAILER TO SUMM-LINE.
|
||||
WRITE SUMM-LINE.
|
||||
@@ -0,0 +1,226 @@
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. CRDVAL.
|
||||
|
||||
* VALIDATE TRANSACTIONS - CREDIT CARD BATCH SYSTEM
|
||||
* DEMONSTRATES: COPY REPLACING, OCCURS+SEARCH ALL,
|
||||
* INSPECT, STRING, 88-LEVEL, REDEFINES IO
|
||||
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT TX-FILE ASSIGN TO "TRANSIN"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT MEM-FILE ASSIGN TO "MEMBER"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT VALID-OUT ASSIGN TO "VALIDOUT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT REJECT-OUT ASSIGN TO "REJECT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT ERR-OUT ASSIGN TO "REPORTERR"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD TX-FILE.
|
||||
COPY TXCPY.
|
||||
|
||||
FD MEM-FILE.
|
||||
COPY MEMCPY.
|
||||
|
||||
FD VALID-OUT.
|
||||
01 VALID-RECORD PIC X(100).
|
||||
|
||||
FD REJECT-OUT.
|
||||
01 REJECT-RECORD PIC X(100).
|
||||
|
||||
FD ERR-OUT.
|
||||
01 ERR-RECORD PIC X(120).
|
||||
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-SWITCHES.
|
||||
05 WS-EOF-TX PIC X VALUE 'N'.
|
||||
88 WS-END-OF-TX VALUE 'Y'.
|
||||
05 WS-EOF-MEM PIC X VALUE 'N'.
|
||||
88 WS-END-OF-MEM VALUE 'Y'.
|
||||
05 WS-VALID PIC X VALUE 'Y'.
|
||||
88 WS-IS-VALID VALUE 'Y'.
|
||||
05 WS-FOUND PIC X VALUE 'N'.
|
||||
88 WS-IS-FOUND VALUE 'Y'.
|
||||
|
||||
01 WS-COUNTERS.
|
||||
05 WS-TOTAL-READ PIC 9(5) VALUE 0.
|
||||
05 WS-TOTAL-VALID PIC 9(5) VALUE 0.
|
||||
05 WS-TOTAL-REJECT PIC 9(5) VALUE 0.
|
||||
05 WS-TOTAL-MEMBERS PIC 9(5) VALUE 0.
|
||||
|
||||
* DATESUB COPYBOOK WITH COPY REPLACING DEMO
|
||||
* GENERATES: WS-RUN-DATE (WS-RUN-YYYY, WS-RUN-MM, WS-RUN-DD)
|
||||
COPY DATESUB REPLACING ==:TAG:== BY ==WS-RUN==.
|
||||
|
||||
* GENERATES: WS-TX-DATE (WS-TX-YYYY, WS-TX-MM, WS-TX-DD)
|
||||
COPY DATESUB REPLACING ==:TAG:== BY ==WS-TX==.
|
||||
|
||||
* INTERNAL TABLE: MEMBER TABLE WITH OCCURS + SEARCH ALL
|
||||
01 WS-MEMBER-TABLE.
|
||||
05 WS-MEMBER-ENTRY OCCURS 1 TO 100 TIMES
|
||||
DEPENDING ON WS-TOTAL-MEMBERS
|
||||
ASCENDING KEY IS WS-MEM-ID
|
||||
INDEXED BY WS-MEM-IDX.
|
||||
10 WS-MEM-ID PIC 9(16).
|
||||
10 WS-MEM-NAME PIC X(30).
|
||||
10 WS-MEM-LIMIT PIC 9(9)V99.
|
||||
10 WS-MEM-TYPE PIC X.
|
||||
10 WS-MEM-STATUS PIC X.
|
||||
10 WS-MEM-BALANCE PIC S9(9)V99.
|
||||
10 WS-MEM-MINPAY PIC 9(9)V99.
|
||||
10 WS-MEM-ADDR PIC X(60).
|
||||
|
||||
01 WS-ERR-MSG.
|
||||
05 WS-ERR-CARD PIC 9(16).
|
||||
05 WS-ERR-SP1 PIC X(2) VALUE SPACES.
|
||||
05 WS-ERR-CODE PIC X(20).
|
||||
05 WS-ERR-SP2 PIC X(2) VALUE SPACES.
|
||||
05 WS-ERR-DESC PIC X(80).
|
||||
|
||||
01 WS-MERCHANT-CHECK.
|
||||
05 WS-MC-LEN PIC 9(2).
|
||||
05 WS-MC-COUNT PIC 9(2).
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
0000-MAIN.
|
||||
OPEN INPUT TX-FILE
|
||||
INPUT MEM-FILE
|
||||
OUTPUT VALID-OUT
|
||||
OUTPUT REJECT-OUT
|
||||
OUTPUT ERR-OUT.
|
||||
|
||||
ACCEPT WS-RUN-DATE FROM DATE YYYYMMDD.
|
||||
|
||||
PERFORM 1000-LOAD-MEMBERS.
|
||||
PERFORM 2000-PROCESS-TX UNTIL WS-END-OF-TX.
|
||||
PERFORM 3000-WRITE-SUMMARY.
|
||||
|
||||
CLOSE TX-FILE MEM-FILE VALID-OUT REJECT-OUT ERR-OUT.
|
||||
GOBACK.
|
||||
|
||||
* LOAD ALL MEMBERS INTO OCCURS TABLE AT ONCE
|
||||
1000-LOAD-MEMBERS.
|
||||
MOVE 0 TO WS-TOTAL-MEMBERS.
|
||||
PERFORM UNTIL WS-END-OF-MEM
|
||||
READ MEM-FILE
|
||||
AT END SET WS-END-OF-MEM TO TRUE
|
||||
NOT AT END
|
||||
ADD 1 TO WS-TOTAL-MEMBERS
|
||||
MOVE MEM-ID
|
||||
TO WS-MEM-ID(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-NAME
|
||||
TO WS-MEM-NAME(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-CREDIT-LIMIT
|
||||
TO WS-MEM-LIMIT(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-TYPE
|
||||
TO WS-MEM-TYPE(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-STATUS
|
||||
TO WS-MEM-STATUS(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-BALANCE
|
||||
TO WS-MEM-BALANCE(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-MIN-PAYMENT
|
||||
TO WS-MEM-MINPAY(WS-TOTAL-MEMBERS)
|
||||
MOVE MEM-ADDRESS
|
||||
TO WS-MEM-ADDR(WS-TOTAL-MEMBERS)
|
||||
END-READ
|
||||
END-PERFORM.
|
||||
|
||||
2000-PROCESS-TX.
|
||||
READ TX-FILE
|
||||
AT END SET WS-END-OF-TX TO TRUE
|
||||
NOT AT END
|
||||
ADD 1 TO WS-TOTAL-READ
|
||||
PERFORM 2100-VALIDATE-TX
|
||||
END-READ.
|
||||
|
||||
2100-VALIDATE-TX.
|
||||
SET WS-IS-VALID TO TRUE.
|
||||
|
||||
* INSPECT DEMO: CHECK MERCHANT NAME FOR INVALID CHARS
|
||||
MOVE 0 TO WS-MC-COUNT.
|
||||
INSPECT TX-MERCHANT TALLYING WS-MC-COUNT
|
||||
FOR CHARACTERS BEFORE INITIAL SPACE.
|
||||
IF WS-MC-COUNT = 0
|
||||
MOVE 'INVALID-MERCHANT' TO WS-ERR-CODE
|
||||
MOVE 'MERCHANT NAME EMPTY' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH
|
||||
END-IF.
|
||||
CONTINUE.
|
||||
|
||||
IF TX-CARD-NO = 0
|
||||
MOVE 'INVALID-CARD' TO WS-ERR-CODE
|
||||
MOVE 'CARD NUMBER IS ZERO' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
IF TX-AMOUNT <= 0 AND NOT TX-REFUND
|
||||
MOVE 'INVALID-AMOUNT' TO WS-ERR-CODE
|
||||
MOVE 'AMOUNT MUST BE POSITIVE' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
IF TX-REFUND AND TX-AMOUNT >= 0
|
||||
MOVE 'INVALID-REFUND' TO WS-ERR-CODE
|
||||
MOVE 'REFUND AMOUNT MUST BE NEGATIVE' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
MOVE TX-DATE(1:4) TO WS-TX-YYYY
|
||||
MOVE TX-DATE(5:2) TO WS-TX-MM
|
||||
MOVE TX-DATE(7:2) TO WS-TX-DD
|
||||
IF WS-TX-MM NOT = WS-RUN-MM
|
||||
MOVE 'OUT-OF-MONTH' TO WS-ERR-CODE
|
||||
MOVE 'TX DATE NOT IN RUN MONTH' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
* SEARCH ALL DEMO: BINARY SEARCH ON MEMBER TABLE
|
||||
PERFORM 2300-FIND-MEMBER.
|
||||
IF NOT WS-IS-FOUND
|
||||
MOVE 'MEMBER-NOT-FOUND' TO WS-ERR-CODE
|
||||
MOVE 'CARD NOT IN MEMBER FILE' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
IF WS-MEM-STATUS(WS-MEM-IDX) = 'F'
|
||||
MOVE 'FROZEN-CARD' TO WS-ERR-CODE
|
||||
MOVE 'CARD STATUS IS FROZEN' TO WS-ERR-DESC
|
||||
PERFORM 2200-REJECT
|
||||
EXIT PARAGRAPH.
|
||||
|
||||
IF WS-VALID = 'Y'
|
||||
WRITE VALID-RECORD FROM TX-RECORD
|
||||
ADD 1 TO WS-TOTAL-VALID.
|
||||
|
||||
2200-REJECT.
|
||||
WRITE REJECT-RECORD FROM TX-RECORD.
|
||||
MOVE TX-CARD-NO TO WS-ERR-CARD.
|
||||
WRITE ERR-RECORD FROM WS-ERR-MSG.
|
||||
ADD 1 TO WS-TOTAL-REJECT.
|
||||
|
||||
2300-FIND-MEMBER.
|
||||
SET WS-MEM-IDX TO 1.
|
||||
SEARCH ALL WS-MEMBER-ENTRY
|
||||
AT END
|
||||
MOVE 'N' TO WS-FOUND
|
||||
WHEN WS-MEM-ID(WS-MEM-IDX) = TX-CARD-NO
|
||||
MOVE 'Y' TO WS-FOUND.
|
||||
|
||||
3000-WRITE-SUMMARY.
|
||||
STRING
|
||||
'CRDVAL SUMMARY - TOTAL READ:' WS-TOTAL-READ
|
||||
' VALID:' WS-TOTAL-VALID
|
||||
' REJECT:' WS-TOTAL-REJECT
|
||||
' MEMBERS LOADED:' WS-TOTAL-MEMBERS
|
||||
INTO ERR-RECORD
|
||||
END-STRING.
|
||||
WRITE ERR-RECORD.
|
||||
DISPLAY 'CRDVAL: ' WS-TOTAL-READ ' READ, '
|
||||
WS-TOTAL-VALID ' VALID, '
|
||||
WS-TOTAL-REJECT ' REJECTS'.
|
||||
@@ -0,0 +1,482 @@
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. GENDATA.
|
||||
|
||||
* GENERATE COMPREHENSIVE TEST DATA FOR CREDIT CARD BATCH SYSTEM
|
||||
* COVERS: normal, frozen, closed, not-found, empty-merchant,
|
||||
* zero-card, invalid-amount, invalid-refund, out-of-month,
|
||||
* multiple cash advances, refunds, installments
|
||||
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT MEM-OUT ASSIGN TO "MEMOUT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT TX-OUT ASSIGN TO "TXOUT"
|
||||
ORGANIZATION IS LINE SEQUENTIAL.
|
||||
SELECT RATE-OUT ASSIGN TO "RATEOUT"
|
||||
ORGANIZATION IS SEQUENTIAL.
|
||||
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD MEM-OUT.
|
||||
COPY MEMCPY.
|
||||
|
||||
FD TX-OUT.
|
||||
COPY TXCPY.
|
||||
|
||||
FD RATE-OUT.
|
||||
COPY RATECPY.
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
0000-MAIN.
|
||||
OPEN OUTPUT MEM-OUT TX-OUT RATE-OUT.
|
||||
|
||||
PERFORM 1000-GEN-MEMBERS.
|
||||
PERFORM 2000-GEN-TRANSACTIONS.
|
||||
PERFORM 3000-GEN-RATES.
|
||||
|
||||
CLOSE MEM-OUT TX-OUT RATE-OUT.
|
||||
DISPLAY 'GENDATA: TEST DATA CREATED'.
|
||||
GOBACK.
|
||||
|
||||
* 8 MEMBERS
|
||||
1000-GEN-MEMBERS.
|
||||
MOVE 6222021234567800 TO MEM-ID.
|
||||
MOVE 'ZHANG SAN' TO MEM-NAME.
|
||||
MOVE 50000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'G' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 15000.00 TO MEM-BALANCE.
|
||||
MOVE 3000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'BEIJING ROAD NO.1' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
MOVE 6222021234567801 TO MEM-ID.
|
||||
MOVE 'LI SI' TO MEM-NAME.
|
||||
MOVE 100000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'P' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 35000.00 TO MEM-BALANCE.
|
||||
MOVE 7000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'SHANGHAI ROAD NO.2' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
MOVE 6222021234567802 TO MEM-ID.
|
||||
MOVE 'WANG WU' TO MEM-NAME.
|
||||
MOVE 20000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'S' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 8000.00 TO MEM-BALANCE.
|
||||
MOVE 2000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'GUANGZHOU ROAD NO.3' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
MOVE 6222021234567803 TO MEM-ID.
|
||||
MOVE 'ZHAO LIU' TO MEM-NAME.
|
||||
MOVE 80000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'G' TO MEM-TYPE.
|
||||
MOVE 'F' TO MEM-STATUS.
|
||||
MOVE 15000.00 TO MEM-BALANCE.
|
||||
MOVE 8000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'SHENZHEN ROAD NO.4' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
MOVE 6222021234567804 TO MEM-ID.
|
||||
MOVE 'CHEN QI' TO MEM-NAME.
|
||||
MOVE 30000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'S' TO MEM-TYPE.
|
||||
MOVE 'C' TO MEM-STATUS.
|
||||
MOVE 0.00 TO MEM-BALANCE.
|
||||
MOVE 0.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'NANJING ROAD NO.5' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
* NEW: 7805 - Active Gold, edge case transaction target
|
||||
MOVE 6222021234567805 TO MEM-ID.
|
||||
MOVE 'SUN BA' TO MEM-NAME.
|
||||
MOVE 60000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'G' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 5000.00 TO MEM-BALANCE.
|
||||
MOVE 1000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'HANGZHOU ROAD NO.6' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
* NEW: 7806 - Active Platinum, very high limit
|
||||
MOVE 6222021234567806 TO MEM-ID.
|
||||
MOVE 'ZHOU JIU' TO MEM-NAME.
|
||||
MOVE 200000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'P' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 80000.00 TO MEM-BALANCE.
|
||||
MOVE 16000.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'CHENGDU ROAD NO.7' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
* NEW: 7807 - Active Standard, low limit cash-advance heavy
|
||||
MOVE 6222021234567807 TO MEM-ID.
|
||||
MOVE 'WU SHI' TO MEM-NAME.
|
||||
MOVE 15000.00 TO MEM-CREDIT-LIMIT.
|
||||
MOVE 'S' TO MEM-TYPE.
|
||||
MOVE 'A' TO MEM-STATUS.
|
||||
MOVE 3000.00 TO MEM-BALANCE.
|
||||
MOVE 500.00 TO MEM-MIN-PAYMENT.
|
||||
MOVE 'WUHAN ROAD NO.8' TO MEM-ADDRESS.
|
||||
WRITE MEMBER-RECORD.
|
||||
|
||||
* 28 TRANSACTIONS
|
||||
2000-GEN-TRANSACTIONS.
|
||||
* CARD 7800 - 5 transactions (normal usage mix)
|
||||
MOVE 6222021234567800 TO TX-CARD-NO.
|
||||
MOVE 20260501 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 1280.50 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'SUPERMARKET A' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260505 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 3500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ELECTRONICS B' TO TX-MERCHANT.
|
||||
MOVE 5732 TO TX-MCC.
|
||||
MOVE 06 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260510 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 2000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-001' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000001' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260515 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 850.20 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'RESTAURANT C' TO TX-MERCHANT.
|
||||
MOVE 5812 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260520 TO TX-DATE.
|
||||
MOVE 'R' TO TX-TYPE.
|
||||
MOVE -1280.50 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'SUPERMARKET A' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7801 - 5 transactions (high limit, installment, cash advance, refund)
|
||||
MOVE 6222021234567801 TO TX-CARD-NO.
|
||||
MOVE 20260503 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 15000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'FURNITURE D' TO TX-MERCHANT.
|
||||
MOVE 5712 TO TX-MCC.
|
||||
MOVE 12 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260518 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 2200.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'HOTEL E' TO TX-MERCHANT.
|
||||
MOVE 7011 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260522 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 5000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-003' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000003' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260523 TO TX-DATE.
|
||||
MOVE 'R' TO TX-TYPE.
|
||||
MOVE -500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'FURNITURE D' TO TX-MERCHANT.
|
||||
MOVE 5712 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260525 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 3000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-005' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000005' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7802 - 3 transactions (student: small purchases + cash advance)
|
||||
MOVE 6222021234567802 TO TX-CARD-NO.
|
||||
MOVE 20260508 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'PHARMACY F' TO TX-MERCHANT.
|
||||
MOVE 5912 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260511 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 300.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'BOOKSTORE H' TO TX-MERCHANT.
|
||||
MOVE 5942 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
MOVE 20260514 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 1000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-004' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000004' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7803 - 1 transaction (rejected: frozen)
|
||||
MOVE 6222021234567803 TO TX-CARD-NO.
|
||||
MOVE 20260512 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 10000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-002' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000002' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7805 - 5 transactions (edge case validations)
|
||||
* Tx 1: rejected - INVALID-MERCHANT (empty merchant name)
|
||||
MOVE 6222021234567805 TO TX-CARD-NO.
|
||||
MOVE 20260502 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 1000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE SPACES TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 2: rejected - INVALID-CARD (card number = 0)
|
||||
MOVE 0000000000000000 TO TX-CARD-NO.
|
||||
MOVE 20260504 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 2000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'STORE K' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 3: rejected - INVALID-AMOUNT (purchase with zero amount)
|
||||
MOVE 6222021234567805 TO TX-CARD-NO.
|
||||
MOVE 20260506 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 0.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'STORE L' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 4: rejected - INVALID-AMOUNT (purchase with negative amount)
|
||||
MOVE 6222021234567805 TO TX-CARD-NO.
|
||||
MOVE 20260509 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE -500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'STORE M' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 5: rejected - INVALID-REFUND (refund with positive amount)
|
||||
MOVE 6222021234567805 TO TX-CARD-NO.
|
||||
MOVE 20260513 TO TX-DATE.
|
||||
MOVE 'R' TO TX-TYPE.
|
||||
MOVE 300.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'STORE N' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 6: valid transaction for 7805 (so card appears in billing)
|
||||
MOVE 6222021234567805 TO TX-CARD-NO.
|
||||
MOVE 20260519 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 2000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'DELIVERY N' TO TX-MERCHANT.
|
||||
MOVE 5969 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7806 - 3 transactions (high limit edge cases)
|
||||
* Tx 1: rejected - OUT-OF-MONTH (April date, run month is May)
|
||||
MOVE 6222021234567806 TO TX-CARD-NO.
|
||||
MOVE 20260428 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 3000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'TRAVEL O' TO TX-MERCHANT.
|
||||
MOVE 4722 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 2: valid purchase for 7806
|
||||
MOVE 6222021234567806 TO TX-CARD-NO.
|
||||
MOVE 20260521 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 2500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'JEWELRY P' TO TX-MERCHANT.
|
||||
MOVE 5944 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 3: valid cash advance for 7806
|
||||
MOVE 6222021234567806 TO TX-CARD-NO.
|
||||
MOVE 20260525 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 8000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-006' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000006' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 7807 - 4 transactions (low limit cash-advance heavy)
|
||||
* Tx 1: cash advance 1
|
||||
MOVE 6222021234567807 TO TX-CARD-NO.
|
||||
MOVE 20260502 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 500.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-007' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000007' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 2: cash advance 2 (different ATM)
|
||||
MOVE 20260507 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 300.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-008' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000008' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 3: cash advance 3 (same ATM as tx 1)
|
||||
MOVE 20260511 TO TX-DATE.
|
||||
MOVE 'C' TO TX-TYPE.
|
||||
MOVE 200.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ATM-007' TO TX-MERCHANT.
|
||||
MOVE 0 TO TX-MCC.
|
||||
MOVE 0 TO TX-INSTALL.
|
||||
MOVE 'ATM0000007' TO TX-ATM-ID.
|
||||
MOVE 0.50 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* Tx 4: purchase mixed with cash advances
|
||||
MOVE 20260520 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 800.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'GROCERY Q' TO TX-MERCHANT.
|
||||
MOVE 5411 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
* CARD 9999999999999999 - 1 transaction (rejected: not found)
|
||||
MOVE 9999999999999999 TO TX-CARD-NO.
|
||||
MOVE 20260515 TO TX-DATE.
|
||||
MOVE 'P' TO TX-TYPE.
|
||||
MOVE 1000.00 TO TX-AMOUNT.
|
||||
MOVE 'CNY' TO TX-CURRENCY.
|
||||
MOVE 'ONLINE R' TO TX-MERCHANT.
|
||||
MOVE 5969 TO TX-MCC.
|
||||
MOVE 00 TO TX-INSTALL.
|
||||
MOVE SPACES TO TX-ATM-ID.
|
||||
MOVE 0 TO TX-FEE-RATE.
|
||||
WRITE TX-RECORD.
|
||||
|
||||
3000-GEN-RATES.
|
||||
MOVE 'C' TO RATE-TYPE.
|
||||
MOVE 0.0005 TO RATE-PCT.
|
||||
MOVE 20250101 TO RATE-EFF-DATE.
|
||||
WRITE RATE-RECORD.
|
||||
|
||||
MOVE 'O' TO RATE-TYPE.
|
||||
MOVE 0.0500 TO RATE-PCT.
|
||||
MOVE 20250101 TO RATE-EFF-DATE.
|
||||
WRITE RATE-RECORD.
|
||||
@@ -0,0 +1,4 @@
|
||||
01 :TAG:-DATE.
|
||||
05 :TAG:-YYYY PIC 9(4).
|
||||
05 :TAG:-MM PIC 9(2).
|
||||
05 :TAG:-DD PIC 9(2).
|
||||
@@ -0,0 +1,15 @@
|
||||
01 MEMBER-RECORD.
|
||||
05 MEM-ID PIC 9(16).
|
||||
05 MEM-NAME PIC X(30).
|
||||
05 MEM-CREDIT-LIMIT PIC 9(9)V99.
|
||||
05 MEM-TYPE PIC X.
|
||||
88 MEM-GOLD VALUE 'G'.
|
||||
88 MEM-PLATINUM VALUE 'P'.
|
||||
88 MEM-STANDARD VALUE 'S'.
|
||||
05 MEM-STATUS PIC X.
|
||||
88 MEM-ACTIVE VALUE 'A'.
|
||||
88 MEM-FROZEN VALUE 'F'.
|
||||
88 MEM-CLOSED VALUE 'C'.
|
||||
05 MEM-BALANCE PIC S9(9)V99.
|
||||
05 MEM-MIN-PAYMENT PIC 9(9)V99.
|
||||
05 MEM-ADDRESS PIC X(60).
|
||||
@@ -0,0 +1,6 @@
|
||||
01 RATE-RECORD.
|
||||
05 RATE-TYPE PIC X.
|
||||
88 RATE-CASH VALUE 'C'.
|
||||
88 RATE-OVERDUE VALUE 'O'.
|
||||
05 RATE-PCT PIC 9(1)V9(4) COMP-3.
|
||||
05 RATE-EFF-DATE PIC 9(8).
|
||||
@@ -0,0 +1,17 @@
|
||||
01 TX-RECORD.
|
||||
05 TX-CARD-NO PIC 9(16).
|
||||
05 TX-DATE PIC 9(8).
|
||||
05 TX-TYPE PIC X.
|
||||
88 TX-PURCHASE VALUE 'P'.
|
||||
88 TX-CASH VALUE 'C'.
|
||||
88 TX-REFUND VALUE 'R'.
|
||||
05 TX-AMOUNT PIC S9(9)V99.
|
||||
05 TX-CURRENCY PIC X(3).
|
||||
05 TX-MERCHANT PIC X(20).
|
||||
05 TX-DETAIL.
|
||||
10 TX-DETAIL-PURCHASE.
|
||||
15 TX-MCC PIC 9(4).
|
||||
15 TX-INSTALL PIC 9(2).
|
||||
10 TX-DETAIL-CASH REDEFINES TX-DETAIL-PURCHASE.
|
||||
15 TX-ATM-ID PIC X(10).
|
||||
15 TX-FEE-RATE PIC 9(1)V99.
|
||||
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
REM ==========================================
|
||||
REM Git Push Config - jcl-cobol
|
||||
REM 仓库: https://gittea.dev/hsyx3952501/jcl-cobol-git
|
||||
REM ==========================================
|
||||
setlocal
|
||||
|
||||
set REMOTE_URL=https://gittea.dev/hsyx3952501/jcl-cobol-git.git
|
||||
set TOKEN=1afb8ef3b16901f0e44238fff78fca5c0f1ff570
|
||||
|
||||
REM 设置远程仓库(HTTPS + Token 认证)
|
||||
git remote remove origin 2>nul
|
||||
git remote add origin https://hsyx3952501:%TOKEN%@gittea.dev/hsyx3952501/jcl-cobol-git.git
|
||||
|
||||
REM 推送到 main 分支
|
||||
echo.
|
||||
echo Pushing to %REMOTE_URL% ...
|
||||
git push -u origin main
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo.
|
||||
echo === Push successful ===
|
||||
) else (
|
||||
echo.
|
||||
echo === Push failed (RC=%errorlevel%) ===
|
||||
echo You may need to: git pull --rebase origin main
|
||||
)
|
||||
|
||||
endlocal
|
||||
@@ -0,0 +1,240 @@
|
||||
"""
|
||||
JCL Executor - executes parsed JCL steps.
|
||||
Phase 1: sequential execution, COND on return codes,
|
||||
DD mapping to files, SORT mapped to external sort utility.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from parser import Job, JobStep, DDEntry, CondParam, COND_OPS
|
||||
|
||||
|
||||
COB_MAIN_DIR = r"D:\360安全浏览器下载\GC32-BDB-SP1-rename-7z-to-exe"
|
||||
|
||||
class Executor:
|
||||
def __init__(self, root_dir: str, cobol_dir: str, copybook_dir: str):
|
||||
self.root_dir = Path(root_dir).resolve()
|
||||
self.cobol_dir = Path(cobol_dir).resolve()
|
||||
self.copybook_dir = Path(copybook_dir).resolve()
|
||||
self.bin_dir = self.root_dir / "bin"
|
||||
self.bin_dir.mkdir(exist_ok=True)
|
||||
self.last_rc: int = 0
|
||||
self.step_rcs: dict[str, int] = {}
|
||||
|
||||
def _cobol_env(self) -> dict[str, str]:
|
||||
"""Build environment dict for GnuCOBOL execution."""
|
||||
env = os.environ.copy()
|
||||
env["COB_MAIN_DIR"] = COB_MAIN_DIR
|
||||
env["COB_CONFIG_DIR"] = os.path.join(COB_MAIN_DIR, "config")
|
||||
env["COB_LIBRARY_PATH"] = os.path.join(COB_MAIN_DIR, "lib", "gnucobol")
|
||||
env["COBCPY"] = str(self.copybook_dir)
|
||||
cobbin = os.path.join(COB_MAIN_DIR, "bin")
|
||||
env["PATH"] = cobbin + os.pathsep + env.get("PATH", "")
|
||||
return env
|
||||
|
||||
def run(self, job: Job):
|
||||
print(f"\n{'='*60}")
|
||||
print(f"JOB: {job.job_name}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
for i, step in enumerate(job.steps):
|
||||
self._execute_step(step, i)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"JOB {job.job_name} COMPLETED")
|
||||
print(f"STEPS: {len(job.steps)}, FINAL RC: {self.last_rc}")
|
||||
print(f"{'='*60}")
|
||||
return self.last_rc
|
||||
|
||||
def _execute_step(self, step: JobStep, idx: int):
|
||||
print(f"\n--- STEP {idx+1}: {step.step_name} (PGM={step.program}) ---")
|
||||
|
||||
# COND check
|
||||
if step.cond and not self._check_cond(step.cond):
|
||||
print(f" COND: SKIPPED ({step.cond})")
|
||||
return
|
||||
|
||||
program_upper = step.program.upper()
|
||||
|
||||
if program_upper == "SORT":
|
||||
rc = self._run_sort(step)
|
||||
else:
|
||||
rc = self._run_cobol(step)
|
||||
|
||||
self.last_rc = rc
|
||||
self.step_rcs[step.step_name] = rc
|
||||
print(f" RC: {rc}")
|
||||
|
||||
def _check_cond(self, cond: CondParam) -> bool:
|
||||
"""Return True if step should run, False if skipped."""
|
||||
if cond.step_name:
|
||||
target_rc = self.step_rcs.get(cond.step_name, 0)
|
||||
else:
|
||||
# Check all previous steps
|
||||
for rc in self.step_rcs.values():
|
||||
if COND_OPS.get(cond.operator, lambda x, y: False)(rc, cond.code):
|
||||
return False
|
||||
return True
|
||||
|
||||
if COND_OPS.get(cond.operator, lambda x, y: False)(target_rc, cond.code):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _run_cobol(self, step: JobStep) -> int:
|
||||
"""Compile (if needed) and execute a COBOL program."""
|
||||
cbl_path = self.cobol_dir / f"{step.program}.cbl"
|
||||
exe_path = self.bin_dir / f"{step.program}.exe"
|
||||
|
||||
# Compile if source newer than binary
|
||||
if not exe_path.exists() or (
|
||||
cbl_path.exists()
|
||||
and os.path.getmtime(cbl_path) > os.path.getmtime(exe_path)
|
||||
):
|
||||
print(f" COMPILE: {cbl_path.name}")
|
||||
result = subprocess.run(
|
||||
["cobc", "-std=ibm", "-x", str(cbl_path), "-o", str(exe_path)],
|
||||
capture_output=True, text=True, env=self._cobol_env(),
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" COMPILE ERROR (RC={result.returncode}):")
|
||||
print(result.stderr[:500])
|
||||
return result.returncode
|
||||
|
||||
# Map DD entries to file paths
|
||||
stdin_file = None
|
||||
stdout_file = None
|
||||
env_map = {}
|
||||
|
||||
for dd in step.dd_entries:
|
||||
dd_name_upper = dd.dd_name.upper()
|
||||
|
||||
if dd_name_upper == "SYSOUT":
|
||||
if dd.sysout == "*":
|
||||
stdout_file = (
|
||||
self.root_dir / "data" / "output" /
|
||||
f"{step.step_name.lower()}_sysout.txt"
|
||||
)
|
||||
continue
|
||||
|
||||
if dd_name_upper == "SYSIN" and dd.inline_data:
|
||||
# Write inline data to temp file
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
mode="w", delete=False, suffix=".sysin", dir=self.root_dir / "data" / "work"
|
||||
)
|
||||
for line in dd.inline_data:
|
||||
tmp.write(line + "\n")
|
||||
tmp.close()
|
||||
stdin_file = tmp.name
|
||||
env_map[dd_name_upper] = stdin_file
|
||||
continue
|
||||
|
||||
if dd.dsn:
|
||||
# Map DSN to file path
|
||||
file_path = self._resolve_dsn(dd.dsn, dd.disp)
|
||||
env_map[dd_name_upper] = str(file_path)
|
||||
|
||||
# Execute COBOL program
|
||||
print(f" EXECUTE: {exe_path.name}")
|
||||
env = self._cobol_env()
|
||||
env.update(env_map)
|
||||
|
||||
stdin = None
|
||||
if stdin_file:
|
||||
stdin = open(stdin_file, "r")
|
||||
|
||||
stdout = None
|
||||
if stdout_file:
|
||||
stdout = open(stdout_file, "w")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(exe_path)],
|
||||
stdin=stdin,
|
||||
stdout=stdout,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
env=env,
|
||||
cwd=str(self.root_dir),
|
||||
)
|
||||
if result.stderr:
|
||||
print(f" STDERR: {result.stderr[:200]}")
|
||||
return result.returncode
|
||||
finally:
|
||||
if stdin:
|
||||
stdin.close()
|
||||
if stdout:
|
||||
stdout.close()
|
||||
|
||||
def _run_sort(self, step: JobStep) -> int:
|
||||
"""Execute SORT step (maps to GNU sort or PowerShell Sort-Object)."""
|
||||
sortin = None
|
||||
sortout = None
|
||||
sort_fields = None
|
||||
|
||||
for dd in step.dd_entries:
|
||||
dd_name_upper = dd.dd_name.upper()
|
||||
if dd_name_upper == "SORTIN" and dd.dsn:
|
||||
sortin = self._resolve_dsn(dd.dsn, dd.disp)
|
||||
elif dd_name_upper == "SORTOUT" and dd.dsn:
|
||||
sortout = self._resolve_dsn(dd.dsn, dd.disp)
|
||||
elif dd_name_upper == "SYSIN" and dd.inline_data:
|
||||
sort_text = " ".join(dd.inline_data).upper()
|
||||
# Parse SORT FIELDS=(start,len,order,...)
|
||||
match = re.search(
|
||||
r"FIELDS=\s*\(([^)]+)\)", sort_text
|
||||
)
|
||||
if match:
|
||||
sort_fields = match.group(1)
|
||||
|
||||
if not sortin or not sortout:
|
||||
print(" ERROR: SORT requires SORTIN and SORTOUT DD")
|
||||
return 12
|
||||
|
||||
if sort_fields:
|
||||
# Parse sort fields for PowerShell Sort-Object
|
||||
fields = sort_fields.split(",")
|
||||
# fields: start,len,type,order,start2,len2,type2,order2
|
||||
pscmd = f"Get-Content '{sortin}' | Sort-Object"
|
||||
i = 0
|
||||
first = True
|
||||
while i + 3 < len(fields):
|
||||
start = int(fields[i].strip()) - 1 # 0-based
|
||||
length = int(fields[i + 1].strip())
|
||||
order = fields[i + 3].strip() # skip type field (CH, PD, etc.)
|
||||
ascending = order.upper() != "D"
|
||||
if not first:
|
||||
pscmd += ","
|
||||
pscmd += (
|
||||
f" {{$_.Substring({start},{length})}}"
|
||||
f"{'' if ascending else ' -Descending'}"
|
||||
)
|
||||
first = False
|
||||
i += 4
|
||||
pscmd += f" | Set-Content '{sortout}' -Encoding Ascii"
|
||||
else:
|
||||
pscmd = f"Get-Content '{sortin}' | Sort-Object | Set-Content '{sortout}' -Encoding Ascii"
|
||||
|
||||
print(f" SORT CMD: {pscmd[:100]}...")
|
||||
result = subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command", pscmd],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" SORT ERROR: {result.stderr[:200]}")
|
||||
return result.returncode
|
||||
|
||||
def _resolve_dsn(self, dsn: str, disp: Optional[str] = None) -> Path:
|
||||
"""Map z/OS DSN to Windows file path."""
|
||||
# Handle GDG notation (simplified)
|
||||
dsn = re.sub(r"\(\+?\d+\)", "", dsn).strip(".")
|
||||
# If it's a z/OS DSN (no slashes, has dots as qualifiers), convert dots
|
||||
if "/" not in dsn and "\\" not in dsn:
|
||||
dsn = dsn.replace(".", "/")
|
||||
path = (self.root_dir / dsn.lstrip("/")).resolve()
|
||||
return path
|
||||
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
JCL Runner - Main entry point.
|
||||
Usage: python main.py <jcl_file>
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from parser import parse_jcl
|
||||
from executor import Executor
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="JCL Runner - Execute JCL scripts on Windows"
|
||||
)
|
||||
parser.add_argument("jcl_file", help="Path to JCL script")
|
||||
parser.add_argument(
|
||||
"--root",
|
||||
default=".",
|
||||
help="System root directory (default: current dir)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cobol-dir",
|
||||
default="cobol",
|
||||
help="COBOL source directory (relative to root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--copybook-dir",
|
||||
default="copybooks",
|
||||
help="COPYBOOK directory (relative to root)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
cobol_dir = root / args.cobol_dir
|
||||
copybook_dir = root / args.copybook_dir
|
||||
|
||||
# Validate paths
|
||||
if not root.exists():
|
||||
print(f"ERROR: Root directory not found: {root}")
|
||||
sys.exit(1)
|
||||
if not cobol_dir.exists():
|
||||
print(f"ERROR: COBOL directory not found: {cobol_dir}")
|
||||
sys.exit(1)
|
||||
if not copybook_dir.exists():
|
||||
print(f"ERROR: COPYBOOK directory not found: {copybook_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse JCL
|
||||
jcl_path = Path(args.jcl_file)
|
||||
if not jcl_path.exists():
|
||||
print(f"ERROR: JCL file not found: {jcl_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Parsing JCL: {jcl_path}")
|
||||
job = parse_jcl(str(jcl_path))
|
||||
|
||||
if not job:
|
||||
print("ERROR: Failed to parse JCL (no JOB statement found)")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Job: {job.job_name}, Steps: {len(job.steps)}")
|
||||
for i, step in enumerate(job.steps):
|
||||
cond_str = f" COND={step.cond}" if step.cond else ""
|
||||
print(f" {i+1}. {step.step_name}: EXEC PGM={step.program}{cond_str}")
|
||||
|
||||
# Execute
|
||||
executor = Executor(
|
||||
root_dir=str(root),
|
||||
cobol_dir=str(cobol_dir),
|
||||
copybook_dir=str(copybook_dir),
|
||||
)
|
||||
rc = executor.run(job)
|
||||
print(f"\nExit code: {rc}")
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
JCL Parser - parses JCL scripts into structured JobStep objects.
|
||||
Phase 1: supports JOB, EXEC PGM=, DD, SYSOUT, SYSIN inline data,
|
||||
COND=(code,op), * comments.
|
||||
Phase 2+: PROC, GDG, COND with step names, EVEN/ONLY.
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class DDEntry:
|
||||
dd_name: str
|
||||
dsn: Optional[str] = None
|
||||
disp: Optional[str] = None
|
||||
sysout: Optional[str] = None
|
||||
inline_data: list[str] = field(default_factory=list)
|
||||
unit: Optional[str] = None
|
||||
space: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CondParam:
|
||||
code: int
|
||||
operator: str # EQ, NE, GT, GE, LT, LE
|
||||
step_name: Optional[str] = None # None means "any previous step"
|
||||
|
||||
|
||||
@dataclass
|
||||
class JobStep:
|
||||
step_name: str
|
||||
program: str
|
||||
dd_entries: list[DDEntry] = field(default_factory=list)
|
||||
cond: Optional[CondParam] = None
|
||||
parm: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Job:
|
||||
job_name: str
|
||||
steps: list[JobStep] = field(default_factory=list)
|
||||
|
||||
|
||||
# COND operator mapping
|
||||
COND_OPS = {
|
||||
"EQ": lambda rc, code: rc == code,
|
||||
"NE": lambda rc, code: rc != code,
|
||||
"GT": lambda rc, code: rc > code,
|
||||
"GE": lambda rc, code: rc >= code,
|
||||
"LT": lambda rc, code: rc < code,
|
||||
"LE": lambda rc, code: rc <= code,
|
||||
}
|
||||
|
||||
|
||||
def parse_jcl(filepath: str) -> Job:
|
||||
"""Parse a JCL file into a Job object."""
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
raw_lines = f.readlines()
|
||||
|
||||
# Continuation handling: lines ending with ',' continue on next line
|
||||
lines = _merge_continuations(raw_lines)
|
||||
|
||||
job = None
|
||||
current_step: Optional[JobStep] = None
|
||||
current_dd: Optional[DDEntry] = None
|
||||
in_sysin = False
|
||||
sysin_lines: list[str] = []
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
# Skip comments
|
||||
if stripped.startswith("//*"):
|
||||
continue
|
||||
if not stripped:
|
||||
continue
|
||||
|
||||
# Handle SYSIN inline data (lines after //SYSIN DD * until /*)
|
||||
if in_sysin:
|
||||
if stripped == "/*":
|
||||
if current_dd:
|
||||
current_dd.inline_data = sysin_lines
|
||||
sysin_lines = []
|
||||
in_sysin = False
|
||||
current_dd = None
|
||||
else:
|
||||
sysin_lines.append(stripped)
|
||||
continue
|
||||
|
||||
# Must start with //
|
||||
if not stripped.startswith("//"):
|
||||
continue
|
||||
|
||||
content = stripped[2:].strip()
|
||||
|
||||
# JOB statement: //jobname JOB ...
|
||||
if re.search(r"\bJOB\b", content, re.IGNORECASE):
|
||||
parts = stripped[2:].split(None, 2)
|
||||
job_name = parts[0]
|
||||
job = Job(job_name=job_name)
|
||||
continue
|
||||
|
||||
# EXEC statement
|
||||
match = re.match(r"(\w+)\s+EXEC\s+(?:PGM=)?(\w+)", content, re.IGNORECASE)
|
||||
if match:
|
||||
step_name = match.group(1)
|
||||
program = match.group(2)
|
||||
|
||||
# Parse COND parameter
|
||||
cond = None
|
||||
cond_match = re.search(
|
||||
r"COND=\s*\(\s*(\d+)\s*,\s*(\w+)", content, re.IGNORECASE
|
||||
)
|
||||
if cond_match:
|
||||
code = int(cond_match.group(1))
|
||||
op = cond_match.group(2).upper()
|
||||
cond = CondParam(code=code, operator=op)
|
||||
|
||||
# Parse PARM parameter
|
||||
parm = None
|
||||
parm_match = re.search(r"PARM=\s*'([^']*)'", content, re.IGNORECASE)
|
||||
if parm_match:
|
||||
parm = parm_match.group(1)
|
||||
|
||||
current_step = JobStep(
|
||||
step_name=step_name,
|
||||
program=program,
|
||||
cond=cond,
|
||||
parm=parm,
|
||||
)
|
||||
if job:
|
||||
job.steps.append(current_step)
|
||||
continue
|
||||
|
||||
# DD statement
|
||||
dd_match = re.match(r"(\w+)\s+DD\s*(.*)", content, re.IGNORECASE)
|
||||
if dd_match and current_step is not None:
|
||||
dd_name = dd_match.group(1)
|
||||
dd_params = dd_match.group(2)
|
||||
dd = DDEntry(dd_name=dd_name)
|
||||
|
||||
# Parse DSN
|
||||
dsn_match = re.search(r"DSN=\s*([^\s,]+)", dd_params, re.IGNORECASE)
|
||||
if dsn_match:
|
||||
dd.dsn = dsn_match.group(1)
|
||||
|
||||
# Parse DISP
|
||||
disp_match = re.search(
|
||||
r"DISP=\s*\(?([^,\s)]+)(?:,([^,\s)]+))?(?:,([^,\s)]+))?\)?",
|
||||
dd_params, re.IGNORECASE,
|
||||
)
|
||||
if disp_match:
|
||||
dd.disp = disp_match.group(1)
|
||||
|
||||
# Parse SYSOUT
|
||||
sysout_match = re.search(r"SYSOUT=\s*(\*|\w+)", dd_params, re.IGNORECASE)
|
||||
if sysout_match:
|
||||
dd.sysout = sysout_match.group(1)
|
||||
|
||||
# Check for SYSIN inline data
|
||||
if dd_name.upper() == "SYSIN" and "*" in dd_params:
|
||||
in_sysin = True
|
||||
|
||||
current_step.dd_entries.append(dd)
|
||||
current_dd = dd
|
||||
continue
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def _merge_continuations(lines: list[str]) -> list[str]:
|
||||
"""Merge JCL continuation lines (lines ending with ',')."""
|
||||
merged = []
|
||||
buffer = ""
|
||||
for line in lines:
|
||||
stripped = line.rstrip("\n\r")
|
||||
if buffer:
|
||||
buffer += stripped
|
||||
else:
|
||||
buffer = stripped
|
||||
# Check if line ends with continuation
|
||||
if stripped.rstrip().endswith(",") and not stripped.strip().startswith("//*"):
|
||||
continue
|
||||
merged.append(buffer)
|
||||
buffer = ""
|
||||
if buffer:
|
||||
merged.append(buffer)
|
||||
return merged
|
||||
@@ -0,0 +1,31 @@
|
||||
//CREDIT25 JOB (CRD),'MONTHLY BILLING',CLASS=B,MSGCLASS=X
|
||||
//*
|
||||
//* 信用卡月结批处理 - 每月25日运行
|
||||
//* 系统: COBOL+JCL 学习验证平台
|
||||
//*
|
||||
//STEP1 EXEC PGM=SORT
|
||||
//SORTIN DD DSN=data/input/transactions.dat,DISP=SHR
|
||||
//SORTOUT DD DSN=data/work/sorted_tx.dat,DISP=(NEW,DELETE)
|
||||
//SYSIN DD *
|
||||
SORT FIELDS=(1,16,CH,A,17,8,CH,A)
|
||||
/*
|
||||
//*
|
||||
//STEP2 EXEC PGM=CRDVAL,COND=(0,NE)
|
||||
//TRANSIN DD DSN=data/work/sorted_tx.dat,DISP=SHR
|
||||
//MEMBER DD DSN=data/input/member.dat,DISP=SHR
|
||||
//VALIDOUT DD DSN=data/work/validated_tx.dat,DISP=(NEW,DELETE)
|
||||
//REJECT DD DSN=data/output/rejected_tx.dat,DISP=(NEW,CATLG)
|
||||
//REPORTERR DD DSN=data/output/error_report.dat,DISP=(NEW,CATLG)
|
||||
//SYSOUT DD SYSOUT=*
|
||||
//*
|
||||
//STEP3 EXEC PGM=CRDCALC,COND=(0,NE)
|
||||
//VALIDIN DD DSN=data/work/validated_tx.dat,DISP=SHR
|
||||
//RATE DD DSN=data/input/rate.dat,DISP=SHR
|
||||
//CALCOUT DD DSN=data/work/billing_result.dat,DISP=(NEW,DELETE)
|
||||
//SYSOUT DD SYSOUT=*
|
||||
//*
|
||||
//STEP4 EXEC PGM=CRDRPT,COND=(0,NE)
|
||||
//BILLING DD DSN=data/work/billing_result.dat,DISP=SHR
|
||||
//STMT DD DSN=data/output/monthly_statement.dat,DISP=(NEW,CATLG)
|
||||
//SUMMARY DD DSN=data/output/summary_report.dat,DISP=(NEW,CATLG)
|
||||
//SYSOUT DD SYSOUT=*
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# COBOL+JCL 信用卡月结系统 完整运行脚本
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " 信用卡月结批处理系统 - 运行脚本" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
$ROOT = $PSScriptRoot
|
||||
$COBOL_DIR = Join-Path $ROOT "cobol"
|
||||
$COPYBOOK_DIR = Join-Path $ROOT "copybooks"
|
||||
$DATA_INPUT = Join-Path $ROOT "data\input"
|
||||
$DATA_WORK = Join-Path $ROOT "data\work"
|
||||
$DATA_OUTPUT = Join-Path $ROOT "data\output"
|
||||
$BIN_DIR = Join-Path $ROOT "bin"
|
||||
$JCL_DIR = Join-Path $ROOT "jcl"
|
||||
|
||||
# Clean work/output directories
|
||||
Remove-Item "$DATA_WORK\*" -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$DATA_OUTPUT\*" -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path $BIN_DIR -Force | Out-Null
|
||||
|
||||
$env:COBCPY = $COPYBOOK_DIR
|
||||
|
||||
Write-Host "`n[STEP 0] Compiling COBOL programs..." -ForegroundColor Yellow
|
||||
|
||||
# Compile data generator
|
||||
Write-Host " GENDATA.cbl ..." -NoNewline
|
||||
$r = & cobc -x "$COBOL_DIR\GENDATA.cbl" -o "$BIN_DIR\GENDATA.exe" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host " FAILED" -ForegroundColor Red; $r; exit 1 }
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Compile CRDVAL
|
||||
Write-Host " CRDVAL.cbl ..." -NoNewline
|
||||
$r = & cobc -x "$COBOL_DIR\CRDVAL.cbl" -o "$BIN_DIR\CRDVAL.exe" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host " FAILED" -ForegroundColor Red; $r; exit 1 }
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Compile CRDCALC
|
||||
Write-Host " CRDCALC.cbl ..." -NoNewline
|
||||
$r = & cobc -x "$COBOL_DIR\CRDCALC.cbl" -o "$BIN_DIR\CRDCALC.exe" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host " FAILED" -ForegroundColor Red; $r; exit 1 }
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Compile CRDRPT
|
||||
Write-Host " CRDRPT.cbl ..." -NoNewline
|
||||
$r = & cobc -x "$COBOL_DIR\CRDRPT.cbl" -o "$BIN_DIR\CRDRPT.exe" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host " FAILED" -ForegroundColor Red; $r; exit 1 }
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Generate test data
|
||||
Write-Host "`n[STEP 0.5] Generating test data..." -ForegroundColor Yellow
|
||||
& "$BIN_DIR\GENDATA.exe"
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "GENDATA FAILED" -ForegroundColor Red; exit 1 }
|
||||
|
||||
# Move generated files to data/input
|
||||
Move-Item "$ROOT\MEMOUT" "$DATA_INPUT\member.dat" -Force -ErrorAction SilentlyContinue
|
||||
Move-Item "$ROOT\TXOUT" "$DATA_INPUT\transactions.dat" -Force -ErrorAction SilentlyContinue
|
||||
Move-Item "$ROOT\RATEOUT" "$DATA_INPUT\rate.dat" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " Test data -> data/input/" -ForegroundColor Green
|
||||
|
||||
Write-Host "`n[STEP 1] SORT transactions by card + date..." -ForegroundColor Yellow
|
||||
Get-Content "$DATA_INPUT\transactions.dat" | Sort-Object { $_.Substring(0, 16) }, { $_.Substring(16, 8) } | Set-Content "$DATA_WORK\sorted_tx.dat" -Encoding Ascii
|
||||
Write-Host " SORTED -> data/work/sorted_tx.dat" -ForegroundColor Green
|
||||
|
||||
Write-Host "`n[STEP 2] CRDVAL - Validate transactions..." -ForegroundColor Yellow
|
||||
$env:TRANSIN = "$DATA_WORK\sorted_tx.dat"
|
||||
$env:MEMBER = "$DATA_INPUT\member.dat"
|
||||
$env:VALIDOUT = "$DATA_WORK\validated_tx.dat"
|
||||
$env:REJECT = "$DATA_OUTPUT\rejected_tx.dat"
|
||||
$env:REPORTERR = "$DATA_OUTPUT\error_report.dat"
|
||||
& "$BIN_DIR\CRDVAL.exe"
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "CRDVAL FAILED (RC=$LASTEXITCODE)" -ForegroundColor Red }
|
||||
|
||||
Write-Host "`n[STEP 3] CRDCALC - Calculate interest and fees..." -ForegroundColor Yellow
|
||||
$env:VALIDIN = "$DATA_WORK\validated_tx.dat"
|
||||
$env:RATE = "$DATA_INPUT\rate.dat"
|
||||
$env:CALCOUT = "$DATA_WORK\billing_result.dat"
|
||||
& "$BIN_DIR\CRDCALC.exe"
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "CRDCALC FAILED (RC=$LASTEXITCODE)" -ForegroundColor Red }
|
||||
|
||||
Write-Host "`n[STEP 4] CRDRPT - Generate statements and summary..." -ForegroundColor Yellow
|
||||
$env:BILLING = "$DATA_WORK\billing_result.dat"
|
||||
$env:STMT = "$DATA_OUTPUT\monthly_statement.dat"
|
||||
$env:SUMMARY = "$DATA_OUTPUT\summary_report.dat"
|
||||
& "$BIN_DIR\CRDRPT.exe"
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "CRDRPT FAILED (RC=$LASTEXITCODE)" -ForegroundColor Red }
|
||||
|
||||
Write-Host "`n========================================" -ForegroundColor Cyan
|
||||
Write-Host " ALL STEPS COMPLETED" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Output files:"
|
||||
Write-Host " Statement: $DATA_OUTPUT\monthly_statement.dat"
|
||||
Write-Host " Summary: $DATA_OUTPUT\summary_report.dat"
|
||||
Write-Host " Rejected: $DATA_OUTPUT\rejected_tx.dat"
|
||||
Write-Host " Error Rpt: $DATA_OUTPUT\error_report.dat"
|
||||
Write-Host ""
|
||||
Write-Host "To run via JCL runner instead:"
|
||||
Write-Host " python jcl-runner/main.py jcl\CREDIT25.jcl --root $ROOT"
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,113 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set ROOT=D:\jcl-cobol
|
||||
set COB_MAIN_DIR=D:\360安全浏览器下载\GC32-BDB-SP1-rename-7z-to-exe
|
||||
set COB_CONFIG_DIR=%COB_MAIN_DIR%\config
|
||||
set COB_LIBRARY_PATH=%COB_MAIN_DIR%\lib\gnucobol
|
||||
set COBCPY=%ROOT%\copybooks
|
||||
set PATH=%COB_MAIN_DIR%\bin;%PATH%
|
||||
|
||||
set DI=%ROOT%\data\input
|
||||
set DW=%ROOT%\data\work
|
||||
set DO=%ROOT%\data\output
|
||||
|
||||
set passed=0
|
||||
set failed=0
|
||||
|
||||
:: Clean
|
||||
del /q %DI%\* 2>nul
|
||||
del /q %DW%\* 2>nul
|
||||
del /q %DO%\* 2>nul
|
||||
del /q %ROOT%\MEMOUT 2>nul
|
||||
del /q %ROOT%\TXOUT 2>nul
|
||||
del /q %ROOT%\RATEOUT 2>nul
|
||||
|
||||
echo.
|
||||
echo [STEP 1] GENDATA
|
||||
pushd %ROOT%
|
||||
bin\GENDATA.exe
|
||||
popd
|
||||
|
||||
if not exist %ROOT%\MEMOUT (
|
||||
echo FAIL: GENDATA did not create MEMOUT
|
||||
set /a failed+=1
|
||||
goto :done
|
||||
) else (
|
||||
echo PASS: GENDATA created MEMOUT
|
||||
set /a passed+=1
|
||||
)
|
||||
move %ROOT%\MEMOUT %DI%\member.dat >nul
|
||||
move %ROOT%\TXOUT %DI%\transactions.dat >nul
|
||||
move %ROOT%\RATEOUT %DI%\rate.dat >nul
|
||||
|
||||
:: Count lines
|
||||
for /f %%i in ('find /c /v "" ^< %DI%\member.dat') do set memlines=%%i
|
||||
for /f %%i in ('find /c /v "" ^< %DI%\transactions.dat') do set txlines=%%i
|
||||
for /f %%i in ('find /c /v "" ^< %DI%\rate.dat') do set ratelines=%%i
|
||||
|
||||
if %memlines%==5 (echo PASS: members=5 & set /a passed+=1) else (echo FAIL: members expected=5 actual=%memlines% & set /a failed+=1)
|
||||
if %txlines%==10 (echo PASS: transactions=10 & set /a passed+=1) else (echo FAIL: transactions expected=10 actual=%txlines% & set /a failed+=1)
|
||||
if %ratelines%==2 (echo PASS: rates=2 & set /a passed+=1) else (echo FAIL: rates expected=2 actual=%ratelines% & set /a failed+=1)
|
||||
|
||||
echo.
|
||||
echo [STEP 2] SORT
|
||||
:: Sort by card(16) + date(8)
|
||||
powershell -NoProfile -Command "Get-Content '%DI%\transactions.dat' | Sort-Object { $_.Substring(0,16) }, { $_.Substring(16,8) } | Set-Content '%DW%\sorted_tx.dat' -Encoding Ascii"
|
||||
for /f %%i in ('find /c /v "" ^< %DW%\sorted_tx.dat') do set sortedlines=%%i
|
||||
if %sortedlines%==10 (echo PASS: sorted=10 & set /a passed+=1) else (echo FAIL: sorted expected=10 actual=%sortedlines% & set /a failed+=1)
|
||||
|
||||
:: Check first and last card
|
||||
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-Content '%DW%\sorted_tx.dat')[0]"`) do set first=%%a
|
||||
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-Content '%DW%\sorted_tx.dat')[-1]"`) do set last=%%a
|
||||
echo !first! | findstr "^6222021234567800" >nul && (echo PASS: first card=7800 & set /a passed+=1) || (echo FAIL: first card not 7800 & set /a failed+=1)
|
||||
echo !last! | findstr "^9999999999999999" >nul && (echo PASS: last card=9999 & set /a passed+=1) || (echo FAIL: last card not 9999 & set /a failed+=1)
|
||||
|
||||
echo.
|
||||
echo [STEP 3] CRDVAL
|
||||
set TRANSIN=%DW%\sorted_tx.dat
|
||||
set MEMBER=%DI%\member.dat
|
||||
set VALIDOUT=%DW%\validated_tx.dat
|
||||
set REJECT=%DO%\rejected_tx.dat
|
||||
set REPORTERR=%DO%\error_report.dat
|
||||
%ROOT%\bin\CRDVAL.exe
|
||||
|
||||
for /f %%i in ('find /c /v "" ^< %DW%\validated_tx.dat') do set validlines=%%i
|
||||
for /f %%i in ('find /c /v "" ^< %DO%\rejected_tx.dat') do set rejectlines=%%i
|
||||
if %validlines%==8 (echo PASS: valid=8 & set /a passed+=1) else (echo FAIL: valid expected=8 actual=%validlines% & set /a failed+=1)
|
||||
if %rejectlines%==2 (echo PASS: rejects=2 & set /a passed+=1) else (echo FAIL: rejects expected=2 actual=%rejectlines% & set /a failed+=1)
|
||||
|
||||
findstr "FROZEN" %DO%\error_report.dat >nul && (echo PASS: error1=frozen & set /a passed+=1) || (echo FAIL: error1 not frozen & set /a failed+=1)
|
||||
findstr "NOT-FOUND" %DO%\error_report.dat >nul && (echo PASS: error2=not-found & set /a passed+=1) || (echo FAIL: error2 not not-found & set /a failed+=1)
|
||||
|
||||
echo.
|
||||
echo [STEP 4] CRDCALC
|
||||
set VALIDIN=%DW%\validated_tx.dat
|
||||
set RATE=%DI%\rate.dat
|
||||
set CALCOUT=%DW%\billing_result.dat
|
||||
%ROOT%\bin\CRDCALC.exe
|
||||
|
||||
for /f %%i in ('find /c /v "" ^< %DW%\billing_result.dat') do set billinglines=%%i
|
||||
if %billinglines%==12 (echo PASS: billing=12 & set /a passed+=1) else (echo FAIL: billing expected=12 actual=%billinglines% & set /a failed+=1)
|
||||
|
||||
findstr "6480.20" %DW%\billing_result.dat >nul && (echo PASS: card7800=6480.20 & set /a passed+=1) || (echo FAIL: card7800 amount wrong & set /a failed+=1)
|
||||
findstr "17200.00" %DW%\billing_result.dat >nul && (echo PASS: card7801=17200.00 & set /a passed+=1) || (echo FAIL: card7801 amount wrong & set /a failed+=1)
|
||||
findstr "500.00" %DW%\billing_result.dat >nul && (echo PASS: card7802=500.00 & set /a passed+=1) || (echo FAIL: card7802 amount wrong & set /a failed+=1)
|
||||
findstr "24180.20" %DW%\billing_result.dat >nul && (echo PASS: grand=24180.20 & set /a passed+=1) || (echo FAIL: grand total wrong & set /a failed+=1)
|
||||
|
||||
echo.
|
||||
echo [STEP 5] CRDRPT
|
||||
set BILLING=%DW%\billing_result.dat
|
||||
set STMT=%DO%\monthly_statement.dat
|
||||
set SUMMARY=%DO%\summary_report.dat
|
||||
%ROOT%\bin\CRDRPT.exe
|
||||
|
||||
for /f %%i in ('find /c /v "" ^< %DO%\monthly_statement.dat') do set stmtlines=%%i
|
||||
for /f %%i in ('find /c /v "" ^< %DO%\summary_report.dat') do set sumlines=%%i
|
||||
if %stmtlines%==14 (echo PASS: statement=14 lines & set /a passed+=1) else (echo FAIL: statement expected=14 actual=%stmtlines% & set /a failed+=1)
|
||||
if %sumlines%==5 (echo PASS: summary=5 lines & set /a passed+=1) else (echo FAIL: summary expected=5 actual=%sumlines% & set /a failed+=1)
|
||||
|
||||
:done
|
||||
echo.
|
||||
echo === RESULT: %passed% passed, %failed% failed ===
|
||||
exit /b %failed%
|
||||
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
COMP-3 Packed Decimal Verification for COBOL Migration Platform.
|
||||
Validates that binary COMP-3 fields in RATE.dat are correctly encoded.
|
||||
|
||||
Usage: python verify_comp3.py
|
||||
|
||||
COMP-3 format: each byte holds 2 nibbles (4-bit digits),
|
||||
last nibble = sign (0xC/0xF = positive, 0xD = negative).
|
||||
PIC 9(1)V9(4) = 5 digits + sign = 3 bytes.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def unpack_comp3(data: bytes) -> tuple[int, int, str]:
|
||||
"""Unpack COMP-3 bytes -> (integer_value, decimal_places, sign)."""
|
||||
nibbles = []
|
||||
for byte in data:
|
||||
nibbles.append((byte >> 4) & 0x0F)
|
||||
nibbles.append(byte & 0x0F)
|
||||
|
||||
sign_nibble = nibbles[-1]
|
||||
digit_nibbles = nibbles[:-1]
|
||||
|
||||
value = 0
|
||||
for n in digit_nibbles:
|
||||
value = value * 10 + n
|
||||
|
||||
if sign_nibble in (0xC, 0xF):
|
||||
sign = "positive"
|
||||
elif sign_nibble == 0xD:
|
||||
sign = "negative"
|
||||
else:
|
||||
sign = f"unknown(0x{sign_nibble:X})"
|
||||
|
||||
return value, sign, data.hex()
|
||||
|
||||
|
||||
def main():
|
||||
rate_path = "D:/jcl-cobol/data/input/rate.dat"
|
||||
|
||||
with open(rate_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
rec_size = 12 # PIC X(1) + PIC 9(1)V9(4) COMP-3(3) + PIC 9(8)
|
||||
num_records = len(data) // rec_size
|
||||
|
||||
print(f"File: {rate_path}")
|
||||
print(f"Size: {len(data)} bytes")
|
||||
print(f"Records: {num_records}")
|
||||
print()
|
||||
|
||||
expected = {
|
||||
"C": ("Cash rate", 0.0005),
|
||||
"O": ("Overdue rate", 0.0500),
|
||||
}
|
||||
|
||||
all_ok = True
|
||||
|
||||
for i in range(num_records):
|
||||
offset = i * rec_size
|
||||
rec = data[offset : offset + rec_size]
|
||||
|
||||
rate_type = chr(rec[0])
|
||||
pct_bytes = rec[1:4]
|
||||
eff_date = rec[4:12].decode("ascii")
|
||||
|
||||
value, sign, pct_hex = unpack_comp3(pct_bytes)
|
||||
int_part = value // 10000
|
||||
dec_part = value % 10000
|
||||
pct_float = int_part + dec_part / 10000
|
||||
|
||||
name, expected_val = expected.get(rate_type, ("Unknown", None))
|
||||
|
||||
match = abs(pct_float - expected_val) < 0.0001 if expected_val else False
|
||||
status = "PASS" if match else "FAIL"
|
||||
if not match:
|
||||
all_ok = False
|
||||
|
||||
print(f"[{status}] Record {i+1}: type={rate_type} ({name})")
|
||||
print(f" COMP-3 hex: {pct_hex}")
|
||||
print(f" packed int: {value}")
|
||||
print(f" float val: {pct_float:.4f}")
|
||||
print(f" expected: {expected_val}")
|
||||
print(f" eff date: {eff_date}")
|
||||
print()
|
||||
|
||||
if all_ok:
|
||||
print("=== ALL COMP-3 VALUES VERIFIED ===")
|
||||
else:
|
||||
print("=== COMP-3 MISMATCH DETECTED ===")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user