Case Study 2: Teller Transaction Entry Screen
Background
Heartland Community Bank's account inquiry system (the AINQ transaction from Case Study 1) was an immediate success. Tellers could look up account information in under a second. The next project was far more ambitious: build a Teller Transaction Entry system that allows deposits, withdrawals, and transfers directly from the teller terminal.
Unlike the inquiry transaction, the transaction entry system requires multiple screens, input validation, file updates with locking, a confirmation workflow, and robust error recovery. A teller entering a $5,000 withdrawal needs to see a confirmation screen before the posting commits. If the CICS region crashes mid-transaction, the system must not leave an account in an inconsistent state.
This case study covers the design and implementation of the TENT (Teller ENTry) transaction -- a multi-screen, pseudo-conversational CICS application with BMS maps, COMMAREA-driven state management, file control for reading and updating VSAM records, and a confirmation workflow.
Project Requirements
Functional Requirements
- The teller types "TENT" and presses Enter to begin
- Entry Screen: The teller enters: - Account number (10 digits, validated) - Transaction type: D (Deposit), W (Withdrawal), T (Transfer) - Amount (up to $99,999,999.99) - For transfers: destination account number - Optional memo text (up to 30 characters)
- Validation: Before proceeding, the system validates: - Account number exists in the VSAM file - Account is active (not closed or frozen) - For withdrawals: sufficient available balance - For transfers: destination account exists and is active - Amount is positive and within daily teller limits
- Confirmation Screen: Displays a summary of the transaction and asks the teller to confirm (Enter) or cancel (PF12)
- Posting: On confirmation, the system updates account balances and writes a transaction log record
- Result Screen: Displays success with a transaction reference number, or failure with an error explanation
- PF3 exits at any point. PF12 cancels and returns to the entry screen.
Non-Functional Requirements
- Optimistic locking: detect if the account was modified between the initial read and the posting
- Transaction reference numbers must be unique and sequential
- All monetary fields use packed decimal (COMP-3) internally
- The confirmation screen must display the teller ID and terminal ID for audit purposes
- Response time under 1 second for the complete entry-validate-confirm-post cycle
BMS Map Definitions
The TENT transaction uses two maps within one mapset: the entry screen and the confirmation screen.
Entry Screen Map
***********************************************************************
* TENTMS - TELLER TRANSACTION ENTRY MAP SET *
***********************************************************************
TENTMS DFHMSD TYPE=&SYSPARM, X
MODE=INOUT, X
LANG=COBOL, X
TIOAPFX=YES, X
STORAGE=AUTO, X
CTRL=(FREEKB,FRSET)
***********************************************************************
* MAP 1: ENTRY SCREEN *
***********************************************************************
ENTRYMAP DFHMDI SIZE=(24,80),LINE=1,COLUMN=1
*
DFHMDF POS=(01,01),LENGTH=20, X
ATTRB=(ASKIP,BRT), X
INITIAL='HEARTLAND COMMUNITY'
DFHMDF POS=(01,22),LENGTH=35, X
ATTRB=(ASKIP,BRT), X
INITIAL='T E L L E R T R A N S A C T I O N'
EDTEFLD DFHMDF POS=(01,65),LENGTH=10, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(02,01),LENGTH=78, X
ATTRB=(ASKIP,NORM), X
INITIAL='---------------------------------------------X
---------------------------------'
*
DFHMDF POS=(04,02),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='Account Number:'
EACCTNO DFHMDF POS=(04,19),LENGTH=10, X
ATTRB=(UNPROT,BRT,IC), X
PICIN='X(10)',PICOUT='X(10)'
DFHMDF POS=(04,30),LENGTH=01,ATTRB=ASKIP
*
EACNM DFHMDF POS=(04,35),LENGTH=30, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(06,02),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='Trans Type: '
ETTYPE DFHMDF POS=(06,19),LENGTH=01, X
ATTRB=(UNPROT,NORM), X
PICIN='X(01)',PICOUT='X(01)'
DFHMDF POS=(06,21),LENGTH=01,ATTRB=ASKIP
DFHMDF POS=(06,25),LENGTH=40, X
ATTRB=(ASKIP,NORM), X
INITIAL='(D=Deposit W=Withdrawal T=Transfer)'
*
DFHMDF POS=(08,02),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='Amount: '
EAMOUNT DFHMDF POS=(08,19),LENGTH=12, X
ATTRB=(UNPROT,NUM,BRT), X
PICIN='9(10)V99'
DFHMDF POS=(08,32),LENGTH=01,ATTRB=ASKIP
*
DFHMDF POS=(10,02),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='Dest Account: '
EDEST DFHMDF POS=(10,19),LENGTH=10, X
ATTRB=(UNPROT,NORM), X
PICIN='X(10)',PICOUT='X(10)'
DFHMDF POS=(10,30),LENGTH=01,ATTRB=ASKIP
DFHMDF POS=(10,35),LENGTH=25, X
ATTRB=(ASKIP,NORM), X
INITIAL='(Required for transfers)'
*
EDSNM DFHMDF POS=(10,62),LENGTH=15, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(12,02),LENGTH=16, X
ATTRB=(ASKIP,NORM), X
INITIAL='Memo: '
EMEMO DFHMDF POS=(12,19),LENGTH=30, X
ATTRB=(UNPROT,NORM), X
PICIN='X(30)',PICOUT='X(30)'
DFHMDF POS=(12,50),LENGTH=01,ATTRB=ASKIP
*
DFHMDF POS=(14,02),LENGTH=40, X
ATTRB=(ASKIP,NORM), X
INITIAL='---------- Account Balances -----------'
*
DFHMDF POS=(15,02),LENGTH=18, X
ATTRB=(ASKIP,NORM), X
INITIAL='Current Balance: '
ECURBAL DFHMDF POS=(15,22),LENGTH=15, X
ATTRB=(ASKIP,BRT), X
PICOUT='$$$,$$$, MATH1 $,$$$,$$9.99'
*
DFHMDF POS=(22,02),LENGTH=70, X
ATTRB=(ASKIP,NORM), X
INITIAL='Enter=Submit PF3=Exit PF5=LookupX
Acct PF12=Clear'
*
EERRMSG DFHMDF POS=(23,02),LENGTH=70, X
ATTRB=(ASKIP,BRT),COLOR=RED
***********************************************************************
* MAP 2: CONFIRMATION SCREEN *
***********************************************************************
CONFMAP DFHMDI SIZE=(24,80),LINE=1,COLUMN=1
*
DFHMDF POS=(01,20),LENGTH=40, X
ATTRB=(ASKIP,BRT), X
INITIAL='C O N F I R M T R A N S A C T I O N'
*
DFHMDF POS=(02,01),LENGTH=78, X
ATTRB=(ASKIP,NORM), X
INITIAL='---------------------------------------------X
---------------------------------'
*
DFHMDF POS=(04,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Transaction Type: '
CTTYPE DFHMDF POS=(04,32),LENGTH=15, X
ATTRB=(ASKIP,BRT)
*
DFHMDF POS=(06,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Source Account: '
CSACCT DFHMDF POS=(06,32),LENGTH=10, X
ATTRB=(ASKIP,BRT)
CSNAME DFHMDF POS=(06,45),LENGTH=30, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(07,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Destination Account:'
CDACCT DFHMDF POS=(07,32),LENGTH=10, X
ATTRB=(ASKIP,BRT)
CDNAME DFHMDF POS=(07,45),LENGTH=30, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(09,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Amount: '
CAMOUNT DFHMDF POS=(09,32),LENGTH=15, X
ATTRB=(ASKIP,BRT), X
PICOUT='$$$,$$$, MATH4 $,$$$,$$9.99'
*
DFHMDF POS=(13,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='New Balance: '
CNEWBAL DFHMDF POS=(13,32),LENGTH=15, X
ATTRB=(ASKIP,BRT), X
PICOUT='$$$,$$$,$$9.99'
*
DFHMDF POS=(15,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Teller ID: '
CTELLER DFHMDF POS=(15,32),LENGTH=08, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(16,10),LENGTH=20, X
ATTRB=(ASKIP,NORM), X
INITIAL='Terminal: '
CTERMID DFHMDF POS=(16,32),LENGTH=04, X
ATTRB=(ASKIP,NORM)
*
DFHMDF POS=(19,15),LENGTH=50, X
ATTRB=(ASKIP,BRT), X
INITIAL='Press ENTER to confirm or PF12 to canceX
l'
*
CERRMSG DFHMDF POS=(23,02),LENGTH=70, X
ATTRB=(ASKIP,BRT),COLOR=RED
*
DFHMSD TYPE=FINAL
END
COMMAREA Design
The COMMAREA for the multi-screen transaction is more complex than the inquiry COMMAREA. It must carry the validated transaction data from the entry screen to the confirmation screen, and store enough account information to detect concurrent modifications.
*---------------------------------------------------------
* COMMAREA COPYBOOK: TENTCOMM
*---------------------------------------------------------
01 WS-COMM-AREA.
* Navigation state
05 CA-STATE PIC X(02).
88 CA-STATE-ENTRY VALUE 'EN'.
88 CA-STATE-LOOKUP VALUE 'LK'.
88 CA-STATE-CONFIRM VALUE 'CF'.
88 CA-STATE-RESULT VALUE 'RS'.
* Transaction data entered by teller
05 CA-TRANS-DATA.
10 CA-ACCT-NO PIC X(10).
10 CA-TRANS-TYPE PIC X(01).
88 CA-DEPOSIT VALUE 'D'.
88 CA-WITHDRAWAL VALUE 'W'.
88 CA-TRANSFER VALUE 'T'.
10 CA-AMOUNT PIC S9(09)V99 COMP-3.
10 CA-DEST-ACCT PIC X(10).
10 CA-MEMO-TEXT PIC X(30).
* Source account data (from initial read)
05 CA-SRC-ACCT-DATA.
10 CA-SRC-NAME PIC X(30).
10 CA-SRC-TYPE PIC X(02).
10 CA-SRC-BAL PIC S9(11)V99 COMP-3.
10 CA-SRC-HOLD PIC S9(11)V99 COMP-3.
10 CA-SRC-AVAIL PIC S9(11)V99 COMP-3.
10 CA-SRC-STATUS PIC X(01).
10 CA-SRC-TIMESTAMP PIC X(26).
* Destination account data (for transfers)
05 CA-DST-ACCT-DATA.
10 CA-DST-NAME PIC X(30).
10 CA-DST-BAL PIC S9(11)V99 COMP-3.
10 CA-DST-STATUS PIC X(01).
10 CA-DST-TIMESTAMP PIC X(26).
* Result data
05 CA-RESULT-DATA.
10 CA-TRANS-REF PIC X(12).
10 CA-NEW-BALANCE PIC S9(11)V99 COMP-3.
10 CA-STATUS-CODE PIC X(04).
10 CA-ERROR-MSG PIC X(70).
05 FILLER PIC X(30).
* Total COMMAREA: approximately 380 bytes
The timestamp fields enable optimistic locking. When the teller presses Enter on the confirmation screen, the posting logic re-reads the account and compares the current timestamp with the one stored in the COMMAREA. If they differ, another teller has modified the account since it was displayed, and the posting is rejected with a conflict message.
COBOL Program Implementation
Main Processing Logic
IDENTIFICATION DIVISION.
PROGRAM-ID. TENTPROG.
*---------------------------------------------------------
* PROGRAM: TENTPROG
* TRANS: TENT
* PURPOSE: Teller Transaction Entry and Posting
*---------------------------------------------------------
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY DFHAID.
COPY DFHBMSCA.
COPY TENTMS.
*---------------------------------------------------------
* COMMAREA (as defined above)
*---------------------------------------------------------
01 WS-COMM-AREA.
05 CA-STATE PIC X(02).
88 CA-STATE-ENTRY VALUE 'EN'.
88 CA-STATE-LOOKUP VALUE 'LK'.
88 CA-STATE-CONFIRM VALUE 'CF'.
88 CA-STATE-RESULT VALUE 'RS'.
05 CA-TRANS-DATA.
10 CA-ACCT-NO PIC X(10).
10 CA-TRANS-TYPE PIC X(01).
88 CA-DEPOSIT VALUE 'D'.
88 CA-WITHDRAWAL VALUE 'W'.
88 CA-TRANSFER VALUE 'T'.
10 CA-AMOUNT PIC S9(09)V99 COMP-3.
10 CA-DEST-ACCT PIC X(10).
10 CA-MEMO-TEXT PIC X(30).
05 CA-SRC-ACCT-DATA.
10 CA-SRC-NAME PIC X(30).
10 CA-SRC-TYPE PIC X(02).
10 CA-SRC-BAL PIC S9(11)V99 COMP-3.
10 CA-SRC-HOLD PIC S9(11)V99 COMP-3.
10 CA-SRC-AVAIL PIC S9(11)V99 COMP-3.
10 CA-SRC-STATUS PIC X(01).
10 CA-SRC-TIMESTAMP PIC X(26).
05 CA-DST-ACCT-DATA.
10 CA-DST-NAME PIC X(30).
10 CA-DST-BAL PIC S9(11)V99 COMP-3.
10 CA-DST-STATUS PIC X(01).
10 CA-DST-TIMESTAMP PIC X(26).
05 CA-RESULT-DATA.
10 CA-TRANS-REF PIC X(12).
10 CA-NEW-BALANCE PIC S9(11)V99 COMP-3.
10 CA-STATUS-CODE PIC X(04).
10 CA-ERROR-MSG PIC X(70).
05 FILLER PIC X(30).
*---------------------------------------------------------
* VSAM record layouts
*---------------------------------------------------------
01 WS-ACCT-RECORD.
05 WS-ACCT-KEY PIC X(10).
05 WS-ACCT-TYPE PIC X(02).
05 WS-CUST-NAME PIC X(30).
05 WS-MEMBER-DATE PIC X(10).
05 WS-CURRENT-BAL PIC S9(11)V99 COMP-3.
05 WS-HOLD-AMOUNT PIC S9(11)V99 COMP-3.
05 WS-ACCT-STATUS PIC X(01).
05 WS-LAST-ACTIVITY PIC X(10).
05 WS-LAST-TELLER PIC X(08).
05 WS-LAST-TIMESTAMP PIC X(26).
05 FILLER PIC X(24).
01 WS-ACCT-REC-LEN PIC S9(04) COMP VALUE 132.
01 WS-TRANS-LOG-REC.
05 WS-TL-REF-NUM PIC X(12).
05 WS-TL-ACCT-NO PIC X(10).
05 WS-TL-TRANS-TYPE PIC X(01).
05 WS-TL-AMOUNT PIC S9(09)V99 COMP-3.
05 WS-TL-DEST-ACCT PIC X(10).
05 WS-TL-MEMO PIC X(30).
05 WS-TL-TELLER-ID PIC X(08).
05 WS-TL-TERM-ID PIC X(04).
05 WS-TL-TIMESTAMP PIC X(26).
05 WS-TL-PREV-BAL PIC S9(11)V99 COMP-3.
05 WS-TL-NEW-BAL PIC S9(11)V99 COMP-3.
01 WS-TL-REC-LEN PIC S9(04) COMP VALUE 128.
*---------------------------------------------------------
* Work fields
*---------------------------------------------------------
01 WS-RESP PIC S9(08) COMP.
01 WS-RESP2 PIC S9(08) COMP.
01 WS-AVAILABLE-BAL PIC S9(11)V99 COMP-3.
01 WS-NEW-BALANCE PIC S9(11)V99 COMP-3.
01 WS-TELLER-ID PIC X(08).
01 WS-COMM-LENGTH PIC S9(04) COMP VALUE 380.
01 WS-DISPLAY-DATE PIC X(10).
01 WS-TRANS-TYPE-DESC PIC X(15).
01 WS-SEQ-NUM PIC 9(08) VALUE 0.
01 WS-GOODBYE-MSG PIC X(40)
VALUE 'Session ended. Remove documents. '.
01 WS-CURRENT-DATE-DATA.
05 WS-DT-YYYY PIC 9(04).
05 WS-DT-MM PIC 9(02).
05 WS-DT-DD PIC 9(02).
05 WS-DT-HH PIC 9(02).
05 WS-DT-MN PIC 9(02).
05 WS-DT-SS PIC 9(02).
05 FILLER PIC X(07).
01 WS-VALID-INPUT PIC X(01).
88 INPUT-IS-VALID VALUE 'Y'.
88 INPUT-IS-INVALID VALUE 'N'.
LINKAGE SECTION.
01 DFHCOMMAREA PIC X(380).
PROCEDURE DIVISION.
0000-MAIN.
EVALUATE TRUE
WHEN EIBCALEN = 0
PERFORM 1000-FIRST-TIME
WHEN OTHER
MOVE DFHCOMMAREA TO WS-COMM-AREA
PERFORM 2000-PROCESS-INPUT
END-EVALUATE
EXEC CICS RETURN TRANSID('TENT')
COMMAREA(WS-COMM-AREA)
LENGTH(WS-COMM-LENGTH)
END-EXEC
.
First-Time Display and Input Processing
1000-FIRST-TIME.
INITIALIZE WS-COMM-AREA
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
.
2000-PROCESS-INPUT.
EVALUATE EIBAID
WHEN DFHPF3
EXEC CICS SEND TEXT
FROM(WS-GOODBYE-MSG)
LENGTH(40)
ERASE FREEKB
END-EXEC
EXEC CICS RETURN END-EXEC
WHEN DFHPF12
INITIALIZE WS-COMM-AREA
SET CA-STATE-ENTRY TO TRUE
MOVE 'Transaction cancelled'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
WHEN DFHENTER
EVALUATE TRUE
WHEN CA-STATE-ENTRY
PERFORM 3000-PROCESS-ENTRY
WHEN CA-STATE-CONFIRM
PERFORM 5000-POST-TRANSACTION
WHEN CA-STATE-RESULT
INITIALIZE WS-COMM-AREA
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
END-EVALUATE
WHEN DFHPF5
IF CA-STATE-ENTRY AND
CA-ACCT-NO NOT = SPACES
PERFORM 3500-LOOKUP-ACCOUNT
END-IF
WHEN DFHCLEAR
PERFORM 1000-FIRST-TIME
WHEN DFHPA1
CONTINUE
WHEN DFHPA2
CONTINUE
WHEN OTHER
MOVE 'Invalid key pressed' TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
END-EVALUATE
.
Entry Screen Validation
The validation logic checks each input field and builds a specific error message for the first error found:
3000-PROCESS-ENTRY.
SET INPUT-IS-VALID TO TRUE
EXEC CICS RECEIVE MAP('ENTRYMAP')
MAPSET('TENTMS')
INTO(ENTRYMAPI)
RESP(WS-RESP)
END-EXEC
IF WS-RESP = DFHRESP(MAPFAIL)
MOVE 'Please enter transaction details'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* Validate account number
MOVE EACCTNOI TO CA-ACCT-NO
IF CA-ACCT-NO = SPACES OR LOW-VALUES
MOVE 'Account number is required'
TO CA-ERROR-MSG
SET INPUT-IS-INVALID TO TRUE
ELSE
IF CA-ACCT-NO IS NOT NUMERIC
MOVE 'Account number must be numeric'
TO CA-ERROR-MSG
SET INPUT-IS-INVALID TO TRUE
END-IF
END-IF
* Validate transaction type
IF INPUT-IS-VALID
MOVE ETTYPEI TO CA-TRANS-TYPE
IF NOT (CA-DEPOSIT OR CA-WITHDRAWAL
OR CA-TRANSFER)
MOVE 'Transaction type must be D, W,'
TO CA-ERROR-MSG
STRING CA-ERROR-MSG DELIMITED BY ' '
' or T' DELIMITED BY SIZE
INTO CA-ERROR-MSG
END-STRING
SET INPUT-IS-INVALID TO TRUE
END-IF
END-IF
* Validate amount
IF INPUT-IS-VALID
MOVE EAMOUNTI TO CA-AMOUNT
IF CA-AMOUNT <= 0
MOVE 'Amount must be greater than zero'
TO CA-ERROR-MSG
SET INPUT-IS-INVALID TO TRUE
END-IF
IF CA-AMOUNT > 99999999.99
MOVE 'Amount exceeds maximum allowed'
TO CA-ERROR-MSG
SET INPUT-IS-INVALID TO TRUE
END-IF
END-IF
* Validate destination for transfers
IF INPUT-IS-VALID AND CA-TRANSFER
MOVE EDESTI TO CA-DEST-ACCT
IF CA-DEST-ACCT = SPACES OR LOW-VALUES
MOVE 'Destination account required'
TO CA-ERROR-MSG
STRING CA-ERROR-MSG DELIMITED BY ' '
' for transfers'
DELIMITED BY SIZE
INTO CA-ERROR-MSG
END-STRING
SET INPUT-IS-INVALID TO TRUE
END-IF
IF CA-DEST-ACCT = CA-ACCT-NO
MOVE 'Cannot transfer to same account'
TO CA-ERROR-MSG
SET INPUT-IS-INVALID TO TRUE
END-IF
END-IF
MOVE EMEMOI TO CA-MEMO-TEXT
IF INPUT-IS-INVALID
PERFORM 8000-SEND-ENTRY-MAP
ELSE
PERFORM 4000-VALIDATE-ACCOUNTS
END-IF
.
Account Validation Against VSAM
3500-LOOKUP-ACCOUNT.
* PF5 lookup: read account and display balance
EXEC CICS RECEIVE MAP('ENTRYMAP')
MAPSET('TENTMS')
INTO(ENTRYMAPI)
RESP(WS-RESP)
END-EXEC
MOVE EACCTNOI TO CA-ACCT-NO
MOVE CA-ACCT-NO TO WS-ACCT-KEY
EXEC CICS READ FILE('ACCTFIL')
INTO(WS-ACCT-RECORD)
RIDFLD(WS-ACCT-KEY)
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP = DFHRESP(NORMAL)
MOVE WS-CUST-NAME TO EACNMO
MOVE WS-CURRENT-BAL TO ECURBALO
COMPUTE WS-AVAILABLE-BAL =
WS-CURRENT-BAL - WS-HOLD-AMOUNT
MOVE WS-AVAILABLE-BAL TO EAVLBALO
MOVE SPACES TO EERRMSGO
ELSE
IF WS-RESP = DFHRESP(NOTFND)
MOVE 'Account not found' TO EERRMSGO
ELSE
MOVE 'File read error' TO EERRMSGO
END-IF
END-IF
EXEC CICS SEND MAP('ENTRYMAP')
MAPSET('TENTMS')
FROM(ENTRYMAPO)
DATAONLY
FREEKB CURSOR
END-EXEC
.
4000-VALIDATE-ACCOUNTS.
* Read source account
MOVE CA-ACCT-NO TO WS-ACCT-KEY
EXEC CICS READ FILE('ACCTFIL')
INTO(WS-ACCT-RECORD)
RIDFLD(WS-ACCT-KEY)
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
EVALUATE WS-RESP
WHEN DFHRESP(NORMAL)
CONTINUE
WHEN DFHRESP(NOTFND)
MOVE 'Source account not found'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
WHEN OTHER
MOVE 'Error reading account file'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-EVALUATE
* Store source account data in COMMAREA
MOVE WS-CUST-NAME TO CA-SRC-NAME
MOVE WS-ACCT-TYPE TO CA-SRC-TYPE
MOVE WS-CURRENT-BAL TO CA-SRC-BAL
MOVE WS-HOLD-AMOUNT TO CA-SRC-HOLD
COMPUTE CA-SRC-AVAIL =
WS-CURRENT-BAL - WS-HOLD-AMOUNT
MOVE WS-ACCT-STATUS TO CA-SRC-STATUS
MOVE WS-LAST-TIMESTAMP TO CA-SRC-TIMESTAMP
* Check source account is active
IF CA-SRC-STATUS NOT = 'A'
MOVE 'Source account is not active'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* Check sufficient funds for withdrawal/transfer
IF CA-WITHDRAWAL OR CA-TRANSFER
IF CA-AMOUNT > CA-SRC-AVAIL
MOVE 'Insufficient available balance'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
END-IF
* For transfers, validate destination account
IF CA-TRANSFER
MOVE CA-DEST-ACCT TO WS-ACCT-KEY
EXEC CICS READ FILE('ACCTFIL')
INTO(WS-ACCT-RECORD)
RIDFLD(WS-ACCT-KEY)
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP = DFHRESP(NOTFND)
MOVE 'Destination account not found'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
IF WS-RESP NOT = DFHRESP(NORMAL)
MOVE 'Error reading dest account'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
MOVE WS-CUST-NAME TO CA-DST-NAME
MOVE WS-CURRENT-BAL TO CA-DST-BAL
MOVE WS-ACCT-STATUS TO CA-DST-STATUS
MOVE WS-LAST-TIMESTAMP TO CA-DST-TIMESTAMP
IF CA-DST-STATUS NOT = 'A'
MOVE 'Destination account not active'
TO CA-ERROR-MSG
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
END-IF
* All validation passed - show confirmation
SET CA-STATE-CONFIRM TO TRUE
PERFORM 8100-SEND-CONFIRM-MAP
.
Transaction Posting with Optimistic Locking
The posting logic re-reads the account with UPDATE to acquire a lock, then checks the timestamp against the COMMAREA value to detect concurrent modifications:
5000-POST-TRANSACTION.
* Read source account with UPDATE lock
MOVE CA-ACCT-NO TO WS-ACCT-KEY
EXEC CICS READ FILE('ACCTFIL')
INTO(WS-ACCT-RECORD)
RIDFLD(WS-ACCT-KEY)
UPDATE
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP NOT = DFHRESP(NORMAL)
MOVE 'Unable to lock source account'
TO CA-ERROR-MSG
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* Optimistic locking check
IF WS-LAST-TIMESTAMP NOT = CA-SRC-TIMESTAMP
EXEC CICS UNLOCK FILE('ACCTFIL')
RESP(WS-RESP)
END-EXEC
MOVE 'Account was modified by another'
TO CA-ERROR-MSG
STRING CA-ERROR-MSG DELIMITED BY ' '
' teller. Please re-enter.'
DELIMITED BY SIZE
INTO CA-ERROR-MSG
END-STRING
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* Re-verify sufficient funds (balance may have
* decreased between validation and posting due to
* holds being placed)
COMPUTE WS-AVAILABLE-BAL =
WS-CURRENT-BAL - WS-HOLD-AMOUNT
IF (CA-WITHDRAWAL OR CA-TRANSFER)
AND CA-AMOUNT > WS-AVAILABLE-BAL
EXEC CICS UNLOCK FILE('ACCTFIL')
RESP(WS-RESP)
END-EXEC
MOVE 'Insufficient funds after re-check'
TO CA-ERROR-MSG
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* Calculate new balance
EVALUATE TRUE
WHEN CA-DEPOSIT
COMPUTE WS-NEW-BALANCE =
WS-CURRENT-BAL + CA-AMOUNT
WHEN CA-WITHDRAWAL
COMPUTE WS-NEW-BALANCE =
WS-CURRENT-BAL - CA-AMOUNT
WHEN CA-TRANSFER
COMPUTE WS-NEW-BALANCE =
WS-CURRENT-BAL - CA-AMOUNT
END-EVALUATE
* Update account record
MOVE WS-NEW-BALANCE TO WS-CURRENT-BAL
MOVE FUNCTION CURRENT-DATE
TO WS-CURRENT-DATE-DATA
STRING WS-DT-YYYY '-' WS-DT-MM '-' WS-DT-DD
DELIMITED BY SIZE
INTO WS-LAST-ACTIVITY
END-STRING
* Get teller ID from CICS
EXEC CICS ASSIGN USERID(WS-TELLER-ID)
RESP(WS-RESP)
END-EXEC
MOVE WS-TELLER-ID TO WS-LAST-TELLER
* Generate new timestamp for optimistic locking
STRING WS-DT-YYYY WS-DT-MM WS-DT-DD
WS-DT-HH WS-DT-MN WS-DT-SS
DELIMITED BY SIZE
INTO WS-LAST-TIMESTAMP
END-STRING
* REWRITE the source account
EXEC CICS REWRITE FILE('ACCTFIL')
FROM(WS-ACCT-RECORD)
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP NOT = DFHRESP(NORMAL)
MOVE 'Failed to update source account'
TO CA-ERROR-MSG
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
* For transfers, update destination account
IF CA-TRANSFER
PERFORM 5100-POST-DESTINATION
IF CA-STATUS-CODE NOT = '0000'
GOBACK
END-IF
END-IF
* Write transaction log record
PERFORM 5200-WRITE-TRANS-LOG
* Set result data
MOVE '0000' TO CA-STATUS-CODE
MOVE WS-NEW-BALANCE TO CA-NEW-BALANCE
SET CA-STATE-RESULT TO TRUE
MOVE 'Transaction posted successfully'
TO CA-ERROR-MSG
PERFORM 8200-SEND-RESULT-MAP
.
5100-POST-DESTINATION.
MOVE CA-DEST-ACCT TO WS-ACCT-KEY
MOVE '0000' TO CA-STATUS-CODE
EXEC CICS READ FILE('ACCTFIL')
INTO(WS-ACCT-RECORD)
RIDFLD(WS-ACCT-KEY)
UPDATE
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP NOT = DFHRESP(NORMAL)
MOVE 'Cannot lock destination account'
TO CA-ERROR-MSG
MOVE '5001' TO CA-STATUS-CODE
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
GOBACK
END-IF
ADD CA-AMOUNT TO WS-CURRENT-BAL
MOVE WS-TELLER-ID TO WS-LAST-TELLER
EXEC CICS REWRITE FILE('ACCTFIL')
FROM(WS-ACCT-RECORD)
LENGTH(WS-ACCT-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP NOT = DFHRESP(NORMAL)
MOVE 'Failed to update dest account'
TO CA-ERROR-MSG
MOVE '5002' TO CA-STATUS-CODE
SET CA-STATE-ENTRY TO TRUE
PERFORM 8000-SEND-ENTRY-MAP
END-IF
.
5200-WRITE-TRANS-LOG.
* Generate transaction reference number
ADD 1 TO WS-SEQ-NUM
STRING 'TXN'
DELIMITED BY SIZE
WS-DT-YYYY WS-DT-MM WS-DT-DD
DELIMITED BY SIZE
WS-SEQ-NUM
DELIMITED BY SIZE
INTO WS-TL-REF-NUM
END-STRING
MOVE CA-ACCT-NO TO WS-TL-ACCT-NO
MOVE CA-TRANS-TYPE TO WS-TL-TRANS-TYPE
MOVE CA-AMOUNT TO WS-TL-AMOUNT
MOVE CA-DEST-ACCT TO WS-TL-DEST-ACCT
MOVE CA-MEMO-TEXT TO WS-TL-MEMO
MOVE WS-TELLER-ID TO WS-TL-TELLER-ID
MOVE EIBTRMID TO WS-TL-TERM-ID
MOVE WS-LAST-TIMESTAMP TO WS-TL-TIMESTAMP
MOVE CA-SRC-BAL TO WS-TL-PREV-BAL
MOVE WS-NEW-BALANCE TO WS-TL-NEW-BAL
EXEC CICS WRITE FILE('TRANSLOG')
FROM(WS-TRANS-LOG-REC)
RIDFLD(WS-TL-REF-NUM)
LENGTH(WS-TL-REC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP = DFHRESP(NORMAL)
MOVE WS-TL-REF-NUM TO CA-TRANS-REF
ELSE
MOVE 'Warning: log write failed'
TO CA-ERROR-MSG
MOVE 'NOLOG' TO CA-TRANS-REF
END-IF
.
Screen Output Routines
8000-SEND-ENTRY-MAP.
INITIALIZE ENTRYMAPO
PERFORM 7000-GET-DATE
MOVE WS-DISPLAY-DATE TO EDTEFLDO
* Restore previously entered data
IF CA-ACCT-NO NOT = SPACES
MOVE CA-ACCT-NO TO EACCTNOO
END-IF
IF CA-SRC-NAME NOT = SPACES
MOVE CA-SRC-NAME TO EACNMO
MOVE CA-SRC-BAL TO ECURBALO
MOVE CA-SRC-AVAIL TO EAVLBALO
END-IF
IF CA-ERROR-MSG NOT = SPACES
MOVE CA-ERROR-MSG TO EERRMSGO
INITIALIZE CA-ERROR-MSG
END-IF
EXEC CICS SEND MAP('ENTRYMAP')
MAPSET('TENTMS')
FROM(ENTRYMAPO)
ERASE FREEKB CURSOR
END-EXEC
.
8100-SEND-CONFIRM-MAP.
INITIALIZE CONFMAPO
* Decode transaction type for display
EVALUATE TRUE
WHEN CA-DEPOSIT
MOVE 'Deposit' TO CTTYPEO
WHEN CA-WITHDRAWAL
MOVE 'Withdrawal' TO CTTYPEO
WHEN CA-TRANSFER
MOVE 'Transfer' TO CTTYPEO
END-EVALUATE
MOVE CA-ACCT-NO TO CSACCTO
MOVE CA-SRC-NAME TO CSNAMEO
MOVE CA-AMOUNT TO CAMOUNTO
MOVE CA-MEMO-TEXT TO CMEMOO
MOVE CA-SRC-BAL TO CCURBALO
* Calculate projected new balance
EVALUATE TRUE
WHEN CA-DEPOSIT
COMPUTE WS-NEW-BALANCE =
CA-SRC-BAL + CA-AMOUNT
WHEN CA-WITHDRAWAL
COMPUTE WS-NEW-BALANCE =
CA-SRC-BAL - CA-AMOUNT
WHEN CA-TRANSFER
COMPUTE WS-NEW-BALANCE =
CA-SRC-BAL - CA-AMOUNT
END-EVALUATE
MOVE WS-NEW-BALANCE TO CNEWBALO
IF CA-TRANSFER
MOVE CA-DEST-ACCT TO CDACCTO
MOVE CA-DST-NAME TO CDNAMEO
END-IF
* Display teller and terminal info
EXEC CICS ASSIGN USERID(WS-TELLER-ID)
RESP(WS-RESP)
END-EXEC
MOVE WS-TELLER-ID TO CTELLERO
MOVE EIBTRMID TO CTERMIDO
EXEC CICS SEND MAP('CONFMAP')
MAPSET('TENTMS')
FROM(CONFMAPO)
ERASE FREEKB
END-EXEC
.
8200-SEND-RESULT-MAP.
* Reuse confirmation map for result display
INITIALIZE CONFMAPO
MOVE CA-TRANS-REF TO CSACCTO
MOVE 'POSTED' TO CTTYPEO
MOVE CA-NEW-BALANCE TO CNEWBALO
MOVE CA-ERROR-MSG TO CERRMSGO
EXEC CICS SEND MAP('CONFMAP')
MAPSET('TENTMS')
FROM(CONFMAPO)
ERASE FREEKB
END-EXEC
.
7000-GET-DATE.
MOVE FUNCTION CURRENT-DATE
TO WS-CURRENT-DATE-DATA
STRING WS-DT-MM '/' WS-DT-DD '/'
WS-DT-YYYY DELIMITED BY SIZE
INTO WS-DISPLAY-DATE
END-STRING
.
Lessons Learned
The TENT transaction processed an average of 8,500 teller transactions per day in its first month of production. The project revealed several important lessons:
-
Optimistic locking is essential for pseudo-conversational updates. Because the CICS task terminates between the initial account read (for validation) and the posting (after confirmation), no lock is held during the teller's think time. Another teller can modify the same account in that window. The timestamp comparison catches these conflicts and forces a re-entry rather than silently applying a stale update.
-
The COMMAREA must carry enough state for conflict detection. The initial design omitted the timestamp fields, relying solely on the balance comparison. This failed when two tellers each deposited money -- the balance changed, but both deposits were valid. The timestamp is a more reliable indicator of any modification, not just balance changes.
-
Separate validation reads from posting reads. The validation READ (without UPDATE) does not lock the record, allowing other tellers to continue reading. The posting READ (with UPDATE) acquires an exclusive lock but holds it for only milliseconds -- the time to compute the new balance and REWRITE. This minimizes lock contention.
-
DATAONLY versus ERASE matters for user experience. Error messages on the entry screen use DATAONLY to update only the message line without flickering the entire screen. The confirmation screen uses ERASE because it is a completely different layout. This distinction makes the application feel polished and responsive.
-
Write transaction logs even if the main update succeeds. If the TRANSLOG WRITE fails, the transaction still posts (the account update already committed via the REWRITE). The log failure is recorded as a warning, and a batch reconciliation job catches the gap. This design avoids rejecting valid transactions due to audit file problems.