Initial commit: COBOL+JCL credit card billing system with COMP-3, OCCURS, COPY REPLACING, INSPECT, and JCL runner

This commit is contained in:
hsyx3952501
2026-05-25 12:27:00 +08:00
commit 8e551c35d9
17 changed files with 2080 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
bin/*.exe
data/
*.pyc
__pycache__/
.DS_Store
+259
View File
@@ -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.
+187
View File
@@ -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.
+226
View File
@@ -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'.
+482
View File
@@ -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.
+4
View File
@@ -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).
+15
View File
@@ -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).
+6
View File
@@ -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).
+17
View File
@@ -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.
+29
View File
@@ -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
+240
View File
@@ -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
+82
View File
@@ -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()
+190
View File
@@ -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
+31
View File
@@ -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
View File
@@ -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 ""
+113
View File
@@ -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%
+97
View File
@@ -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()