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

  1. The teller types "TENT" and presses Enter to begin
  2. 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)
  3. 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
  4. Confirmation Screen: Displays a summary of the transaction and asks the teller to confirm (Enter) or cancel (PF12)
  5. Posting: On confirmation, the system updates account balances and writes a transaction log record
  6. Result Screen: Displays success with a transaction reference number, or failure with an error explanation
  7. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.