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