problemhardoodatm-ood-designatm ood designatmooddesignobject-oriented-design-for-an-atmobject oriented design for an atmobjectorienteddesignforanatm

OOD - ATM

HardUpdated: Jan 1, 2026

Problem

Design a comprehensive object-oriented ATM (Automated Teller Machine) system that handles customer authentication via card and PIN, processes banking transactions (balance inquiry, cash withdrawal, cash/check deposits, fund transfers), manages cash inventory with optimal denomination dispensing, maintains session state with security timeouts, integrates with bank backend systems for account validation and transaction posting, supports operator maintenance operations (cash refill, receipt paper reload), and provides audit logging for all transactions and hardware events. Your design should demonstrate SOLID principles and utilize design patterns to ensure the system is secure, reliable, and maintainable.

Solution

1. Requirements Analysis

Functional Requirements:

  • Card authentication: Read card, validate card number, verify PIN with bank backend
  • Session management: Track active session, implement timeout after inactivity, secure card retention
  • Support multiple transaction types:
    • Balance Inquiry: Check available and total balance
    • Cash Withdrawal: Dispense cash in optimal denominations
    • Cash Deposit: Accept bills and validate amounts
    • Check Deposit: Scan check, capture amount, store for clearing
    • Fund Transfer: Transfer between accounts (checking/savings or to other accounts)
  • Cash dispenser management: Track denominations (20,20, 50, $100 bills), optimize dispensing algorithm
  • Receipt printing: Generate receipts for all transactions
  • Account types: Support Checking and Savings accounts with different rules
  • Operator functions: Refill cash, reload receipt paper, collect deposited items
  • Transaction limits: Daily withdrawal limits, per-transaction limits
  • Error handling: Insufficient funds, insufficient cash in ATM, card retention for invalid PIN
  • Audit logging: Record all transactions, hardware events, errors

Non-Functional Requirements:

  • Security: Encrypt PIN transmission, limit PIN attempts (3 max), secure card retention on failure
  • Reliability: Handle hardware failures gracefully, transaction atomicity (complete or rollback)
  • Performance: Response time < 3 seconds for balance inquiry, < 5 seconds for cash dispensing
  • Availability: 24/7 operation with graceful degradation when low on cash
  • Maintainability: Modular hardware components, easy to swap/upgrade
  • Auditability: Complete transaction trail, tamper-proof logs

2. Use Case Diagram

Actors:

  • Customer: Primary user who performs banking transactions
  • Operator: Maintains ATM (refills cash, paper, clears jams)
  • Bank System: Backend that validates accounts and processes transactions
  • Administrator: Monitors ATM health, generates reports, manages configurations

Primary Use Cases:

  • Authenticate Customer - Validate card and PIN
  • Check Balance - View account balance
  • Withdraw Cash - Dispense cash from account
  • Deposit Cash - Accept cash deposits
  • Deposit Check - Accept check deposits
  • Transfer Funds - Move money between accounts
  • Print Receipt - Generate transaction receipt
  • Refill Cash - Operator replenishes cash cassettes
  • View Transaction Logs - Admin reviews transaction history
  • Monitor ATM Status - Check hardware health and cash levels
graph TD
    subgraph ATMSystem["๐Ÿง ATM System"]
        UC1[๐Ÿ” Authenticate Customer]
        UC2[๐Ÿ’ฐ Check Balance]
        UC3[๐Ÿ’ต Withdraw Cash]
        UC4[๐Ÿ“ฅ Deposit Cash]
        UC5[๐Ÿ“„ Deposit Check]
        UC6[๐Ÿ”„ Transfer Funds]
        UC7[๐Ÿงพ Print Receipt]
        UC8[๐Ÿ”ง Refill Cash]
        UC9[๐Ÿ“‹ View Transaction Logs]
        UC10[๐Ÿ“Š Monitor ATM Status]
        UC11[โš ๏ธ Handle Errors]
        UC12[๐Ÿ”’ Retain Card]
    end

    Customer[๐Ÿ‘ค Customer]
    Operator[๐Ÿ”ง Operator]
    BankSystem[๐Ÿฆ Bank System]
    Admin[๐Ÿ‘จโ€๐Ÿ’ผ Administrator]

    Customer --> UC1
    Customer --> UC2
    Customer --> UC3
    Customer --> UC4
    Customer --> UC5
    Customer --> UC6

    Operator --> UC8

    BankSystem --> UC1
    BankSystem --> UC3
    BankSystem --> UC4
    BankSystem --> UC5
    BankSystem --> UC6

    Admin --> UC9
    Admin --> UC10

    UC1 -.includes.-> UC12
    UC2 -.includes.-> UC7
    UC3 -.includes.-> UC7
    UC4 -.includes.-> UC7
    UC5 -.includes.-> UC7
    UC6 -.includes.-> UC7

    UC1 -.extends.-> UC11
    UC3 -.extends.-> UC11
    UC4 -.extends.-> UC11

    style Customer fill:#E3F2FD,stroke:#1976D2,stroke-width:2px
    style Operator fill:#FFF3E0,stroke:#F57C00,stroke-width:2px
    style BankSystem fill:#E8F5E9,stroke:#388E3C,stroke-width:2px
    style Admin fill:#F3E5F5,stroke:#7B1FA2,stroke-width:2px

    style UC1 fill:#FFCCBC,stroke:#D84315,stroke-width:2px
    style UC2 fill:#B3E5FC,stroke:#0277BD,stroke-width:2px
    style UC3 fill:#FFE082,stroke:#F57F17,stroke-width:2px
    style UC4 fill:#C8E6C9,stroke:#388E3C,stroke-width:2px
    style UC5 fill:#C8E6C9,stroke:#388E3C,stroke-width:2px
    style UC6 fill:#81D4FA,stroke:#0277BD,stroke-width:2px
    style UC7 fill:#E1BEE7,stroke:#7B1FA2,stroke-width:2px
    style UC8 fill:#FFCC80,stroke:#EF6C00,stroke-width:2px
    style UC9 fill:#CE93D8,stroke:#6A1B9A,stroke-width:2px
    style UC10 fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px
    style UC11 fill:#EF9A9A,stroke:#C62828,stroke-width:2px
    style UC12 fill:#FFAB91,stroke:#D84315,stroke-width:2px

3. Class Diagram

Core Classes and Their Responsibilities:

ATM Core:

  • ATM - Main controller coordinating all components and managing sessions
  • ATMState (interface) - State pattern for ATM operational states
  • IdleState, CardInsertedState, AuthenticatedState, TransactionState - Concrete states
  • Session - Tracks active customer session with timeout management

Hardware Components:

  • CardReader - Reads card magnetic stripe or chip
  • PINPad - Secure PIN entry keypad
  • Screen - Display interface for user interaction
  • CashDispenser - Manages cash inventory and dispensing with denominations
  • CashAcceptor - Accepts and validates deposited bills
  • CheckScanner - Scans and reads check details
  • ReceiptPrinter - Prints transaction receipts
  • DepositEnvelope - Stores deposited checks

Banking:

  • BankingService - Interface to bank backend
  • Account (abstract) - Base account with balance operations
  • CheckingAccount, SavingsAccount - Concrete account types
  • Card - ATM card with encrypted PIN
  • Customer - Account holder information

Transactions (Strategy Pattern):

  • Transaction (abstract) - Template method for transaction execution
  • BalanceInquiry - Query account balance
  • Withdrawal - Cash withdrawal with denomination optimization
  • CashDeposit - Deposit cash
  • CheckDeposit - Deposit check
  • Transfer - Fund transfer between accounts

Supporting Classes:

  • Money - Value object for currency amounts
  • CashDenomination - Represents bill denominations
  • TransactionReceipt - Receipt data
  • AuditLog - Transaction and event logging
  • DenominationDispenser (Chain of Responsibility) - Optimal bill dispensing
classDiagram
    %% ATM Core
    class ATM {
        -String atmId
        -String location
        -ATMState currentState
        -Session activeSession
        -CardReader cardReader
        -PINPad pinPad
        -Screen screen
        -CashDispenser cashDispenser
        -ReceiptPrinter receiptPrinter
        -BankingService bankingService
        +insertCard(Card) void
        +enterPIN(String) bool
        +selectTransaction(TransactionType) void
        +executeTransaction(Transaction) TransactionResult
        +ejectCard() void
        +setState(ATMState) void
    }

    class ATMState {
        <<interface>>
        +insertCard(ATM, Card) void
        +enterPIN(ATM, String) void
        +selectTransaction(ATM, TransactionType) void
        +ejectCard(ATM) void
    }

    class IdleState {
        +insertCard(ATM, Card) void
        +enterPIN(ATM, String) void
        +selectTransaction(ATM, TransactionType) void
        +ejectCard(ATM) void
    }

    class CardInsertedState {
        -int pinAttempts
        +insertCard(ATM, Card) void
        +enterPIN(ATM, String) void
        +selectTransaction(ATM, TransactionType) void
        +ejectCard(ATM) void
    }

    class AuthenticatedState {
        +insertCard(ATM, Card) void
        +enterPIN(ATM, String) void
        +selectTransaction(ATM, TransactionType) void
        +ejectCard(ATM) void
    }

    class TransactionState {
        -Transaction currentTransaction
        +insertCard(ATM, Card) void
        +enterPIN(ATM, String) void
        +selectTransaction(ATM, TransactionType) void
        +ejectCard(ATM) void
    }

    ATMState <|.. IdleState
    ATMState <|.. CardInsertedState
    ATMState <|.. AuthenticatedState
    ATMState <|.. TransactionState
    ATM --> ATMState
    ATM --> Session

    class Session {
        -String sessionId
        -Card card
        -Account account
        -LocalDateTime startTime
        -LocalDateTime lastActivityTime
        -int timeoutSeconds
        +isExpired() bool
        +updateActivity() void
        +getAccount() Account
    }

    Session --> Card
    Session --> Account

    %% Hardware Components
    class CardReader {
        -CardReaderStatus status
        +readCard() Card
        +ejectCard() void
        +retainCard() void
        +isCardPresent() bool
    }

    class PINPad {
        -bool isSecure
        +getEncryptedPIN() String
        +clear() void
    }

    class Screen {
        -String currentMessage
        +displayMessage(String) void
        +displayMenu(List~String~) void
        +showBalance(Money) void
        +showError(String) void
        +clear() void
    }

    class CashDispenser {
        -Map~CashDenomination Int~ inventory
        -int maxDispenseAmount
        -DenominationDispenser dispenserChain
        +dispenseCash(Money) DispenseResult
        +canDispense(Money) bool
        +getInventory() Map~CashDenomination Int~
        +refill(Map~CashDenomination Int~) void
        +isLowOnCash() bool
    }

    class CashAcceptor {
        -List~Money~ acceptedBills
        +acceptBill() Money
        +getTotalAccepted() Money
        +clear() void
    }

    class CheckScanner {
        +scanCheck() Check
        +validateCheck(Check) bool
    }

    class ReceiptPrinter {
        -int paperLevel
        +printReceipt(TransactionReceipt) void
        +hasPaper() bool
        +refillPaper() void
    }

    ATM --> CardReader
    ATM --> PINPad
    ATM --> Screen
    ATM --> CashDispenser
    ATM --> CashAcceptor
    ATM --> CheckScanner
    ATM --> ReceiptPrinter

    %% Cash Denomination Management
    class CashDenomination {
        <<enumeration>>
        TWENTY
        FIFTY
        HUNDRED
    }

    class DenominationDispenser {
        <<interface>>
        -DenominationDispenser nextDispenser
        +dispense(Money) DispenseResult
        +setNext(DenominationDispenser) void
    }

    class HundredDollarDispenser {
        -int count
        +dispense(Money) DispenseResult
    }

    class FiftyDollarDispenser {
        -int count
        +dispense(Money) DispenseResult
    }

    class TwentyDollarDispenser {
        -int count
        +dispense(Money) DispenseResult
    }

    DenominationDispenser <|.. HundredDollarDispenser
    DenominationDispenser <|.. FiftyDollarDispenser
    DenominationDispenser <|.. TwentyDollarDispenser
    CashDispenser --> DenominationDispenser
    CashDispenser --> CashDenomination

    %% Banking Domain
    class BankingService {
        <<interface>>
        +validateCard(Card) bool
        +authenticatePIN(Card, String) bool
        +getAccount(String) Account
        +processTransaction(Transaction) TransactionResult
        +postTransaction(Transaction) void
    }

    class Card {
        -String cardNumber
        -String cardHolderName
        -LocalDate expiryDate
        -String encryptedPIN
        -CardType cardType
        -String accountNumber
        +isExpired() bool
        +validatePIN(String) bool
    }

    class Account {
        <<abstract>>
        -String accountNumber
        -String accountHolderName
        -Money availableBalance
        -Money totalBalance
        -Money dailyWithdrawalLimit
        -Money todayWithdrawn
        +canWithdraw(Money) bool
        +withdraw(Money) bool
        +deposit(Money) void
        +getAvailableBalance() Money*
        +getTotalBalance() Money*
    }

    class CheckingAccount {
        -Money overdraftLimit
        -bool hasDebitCard
        +getAvailableBalance() Money
        +getTotalBalance() Money
    }

    class SavingsAccount {
        -double interestRate
        -int withdrawalsThisMonth
        -int maxWithdrawalsPerMonth
        +getAvailableBalance() Money
        +getTotalBalance() Money
        +canWithdraw(Money) bool
    }

    Account <|-- CheckingAccount
    Account <|-- SavingsAccount
    Card --> Account
    BankingService --> Account
    ATM --> BankingService

    %% Transactions (Strategy Pattern)
    class Transaction {
        <<abstract>>
        -String transactionId
        -LocalDateTime timestamp
        -Account account
        -TransactionStatus status
        -Money amount
        +execute() TransactionResult*
        +validate() bool*
        +createReceipt() TransactionReceipt
        -logTransaction() void
    }

    class BalanceInquiry {
        +execute() TransactionResult
        +validate() bool
    }

    class Withdrawal {
        -Money requestedAmount
        -Map~CashDenomination Int~ dispensedBills
        +execute() TransactionResult
        +validate() bool
        -optimizeDenominations() Map~CashDenomination Int~
    }

    class CashDeposit {
        -List~Money~ depositedBills
        -Money totalAmount
        +execute() TransactionResult
        +validate() bool
    }

    class CheckDeposit {
        -Check check
        -Money checkAmount
        +execute() TransactionResult
        +validate() bool
    }

    class Transfer {
        -Account sourceAccount
        -Account destinationAccount
        -Money transferAmount
        -TransferType transferType
        +execute() TransactionResult
        +validate() bool
    }

    Transaction <|-- BalanceInquiry
    Transaction <|-- Withdrawal
    Transaction <|-- CashDeposit
    Transaction <|-- CheckDeposit
    Transaction <|-- Transfer
    Transaction --> Account
    ATM --> Transaction

    class TransactionResult {
        -bool success
        -String message
        -Money balanceAfter
        -TransactionReceipt receipt
    }

    class TransactionReceipt {
        -String transactionId
        -LocalDateTime timestamp
        -TransactionType type
        -Money amount
        -Money balanceAfter
        -String atmLocation
        +format() String
    }

    Transaction --> TransactionResult
    TransactionResult --> TransactionReceipt

    %% Value Objects
    class Money {
        <<value object>>
        -BigDecimal amount
        -String currency
        +add(Money) Money
        +subtract(Money) Money
        +multiply(int) Money
        +compareTo(Money) int
        +isPositive() bool
    }

    %% Supporting Classes
    class Check {
        -String checkNumber
        -String routingNumber
        -String accountNumber
        -Money amount
        -LocalDate date
    }

    class AuditLog {
        -String logId
        -LocalDateTime timestamp
        -String atmId
        -String eventType
        -String details
        +log(String, String) void
    }

    class Customer {
        -String customerId
        -String name
        -String email
        -String phoneNumber
        -List~Account~ accounts
        -CustomerStatus status
    }

    Customer --> Account
    CheckDeposit --> Check

Design Patterns Applied:

  1. State Pattern: ATMState manages ATM operational states (Idle, CardInserted, Authenticated, Transaction)
  2. Strategy Pattern: Transaction hierarchy for different transaction types
  3. Template Method: Transaction.execute() defines transaction processing template
  4. Chain of Responsibility: DenominationDispenser for optimal cash dispensing
  5. Singleton: ATM instance (implied)
  6. Factory Pattern: Transaction creation based on user selection
  7. Value Object: Money for immutable currency representation

SOLID Principles Demonstrated:

  • Single Responsibility: Each hardware component has one clear purpose
  • Open/Closed: New transaction types can be added without modifying ATM core
  • Liskov Substitution: Any Transaction or Account subclass can replace parent
  • Interface Segregation: Focused interfaces for hardware components and banking service
  • Dependency Inversion: ATM depends on BankingService interface, not concrete implementation

4. Activity Diagrams

Activity: Customer Withdrawal Transaction

flowchart TD
    Start([๐Ÿง Customer approaches ATM]) --> InsertCard[๐Ÿ’ณ Insert card]
    InsertCard --> ReadCard[๐Ÿ“– Read card data]
    ReadCard --> ValidCard{โœ“ Valid card?}

    ValidCard -->|No| InvalidCard[โŒ Show invalid card error]
    InvalidCard --> EjectCard1[โ๏ธ Eject card]
    EjectCard1 --> End1([End])

    ValidCard -->|Yes| PromptPIN[๐Ÿ”ข Prompt for PIN]
    PromptPIN --> EnterPIN[๐Ÿ” Enter PIN]
    EnterPIN --> ValidatePIN[๐Ÿ” Validate PIN with bank]
    ValidatePIN --> PINValid{โœ“ Correct PIN?}

    PINValid -->|No| IncrementAttempts[โž• Increment attempts]
    IncrementAttempts --> MaxAttempts{โ“ Max attempts (3)?}
    MaxAttempts -->|Yes| RetainCard[๐Ÿ”’ Retain card]
    RetainCard --> NotifyBank[๐Ÿ“ง Notify bank]
    NotifyBank --> End1
    MaxAttempts -->|No| PromptPIN

    PINValid -->|Yes| CreateSession[โœ… Create session]
    CreateSession --> DisplayMenu[๐Ÿ“‹ Display transaction menu]
    DisplayMenu --> SelectWithdraw[๐Ÿ’ต Select withdrawal]
    SelectWithdraw --> EnterAmount[๐Ÿ’ฐ Enter amount]

    EnterAmount --> ValidateAmount{โœ“ Valid amount?}
    ValidateAmount -->|No| AmountError[โŒ Invalid amount error]
    AmountError --> EnterAmount

    ValidateAmount -->|Yes| CheckBalance{๐Ÿ’ณ Sufficient balance?}
    CheckBalance -->|No| InsufficientFunds[โš ๏ธ Insufficient funds]
    InsufficientFunds --> TryAgain{๐Ÿ”„ Try different amount?}
    TryAgain -->|Yes| EnterAmount
    TryAgain -->|No| CancelTx1

    CheckBalance -->|Yes| CheckDailyLimit{๐Ÿ“Š Within daily limit?}
    CheckDailyLimit -->|No| LimitExceeded[โš ๏ธ Daily limit exceeded]
    LimitExceeded --> CancelTx1

    CheckDailyLimit -->|Yes| CheckCash{๐Ÿ’ต Cash available in ATM?}
    CheckCash -->|No| InsufficientCash[โš ๏ธ Insufficient cash in ATM]
    InsufficientCash --> CancelTx1

    CheckCash -->|Yes| CalculateDenominations[๐Ÿงฎ Calculate bill denominations]
    CalculateDenominations --> DebitAccount[๐Ÿ’ณ Debit account]
    DebitAccount --> DispenseCash[๐Ÿ’ธ Dispense cash]
    DispenseCash --> VerifyDispense{โœ“ Cash dispensed?}

    VerifyDispense -->|No| RollbackTx[โ™ป๏ธ Rollback transaction]
    RollbackTx --> HardwareError[โš ๏ธ Hardware error]
    HardwareError --> CancelTx1

    VerifyDispense -->|Yes| UpdateInventory[๐Ÿ“‰ Update cash inventory]
    UpdateInventory --> PrintReceipt[๐Ÿงพ Print receipt]
    PrintReceipt --> LogTransaction[๐Ÿ“ Log transaction]
    LogTransaction --> MoreTransactions{๐Ÿค” More transactions?}

    MoreTransactions -->|Yes| DisplayMenu
    MoreTransactions -->|No| ThankYou[๐Ÿ‘‹ Thank you message]
    ThankYou --> EjectCard2[โ๏ธ Eject card]
    EjectCard2 --> EndSession[๐Ÿ”š End session]
    EndSession --> End2([โœ… Transaction complete])

    CancelTx1[โŒ Cancel transaction] --> EjectCard2

    style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px
    style End1 fill:#FFCDD2,stroke:#C62828,stroke-width:3px
    style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px

    style InsertCard fill:#B3E5FC,stroke:#0277BD,stroke-width:2px
    style ReadCard fill:#81D4FA,stroke:#0277BD,stroke-width:2px
    style PromptPIN fill:#FFCCBC,stroke:#D84315,stroke-width:2px
    style EnterPIN fill:#FFAB91,stroke:#D84315,stroke-width:2px
    style ValidatePIN fill:#FF8A65,stroke:#D84315,stroke-width:2px

    style CreateSession fill:#A5D6A7,stroke:#388E3C,stroke-width:2px
    style DisplayMenu fill:#81C784,stroke:#2E7D32,stroke-width:2px
    style SelectWithdraw fill:#66BB6A,stroke:#2E7D32,stroke-width:2px

    style EnterAmount fill:#FFE082,stroke:#F57F17,stroke-width:2px
    style CalculateDenominations fill:#FFCC80,stroke:#EF6C00,stroke-width:2px
    style DebitAccount fill:#FFE0B2,stroke:#EF6C00,stroke-width:2px

    style DispenseCash fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px
    style UpdateInventory fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px
    style PrintReceipt fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px
    style LogTransaction fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px

    style ValidCard fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style PINValid fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style MaxAttempts fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ValidateAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style CheckBalance fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style CheckDailyLimit fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style CheckCash fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style VerifyDispense fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style MoreTransactions fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style TryAgain fill:#FFF9C4,stroke:#F9A825,stroke-width:2px

    style InvalidCard fill:#EF9A9A,stroke:#C62828,stroke-width:2px
    style RetainCard fill:#EF5350,stroke:#C62828,stroke-width:2px
    style InsufficientFunds fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style LimitExceeded fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style InsufficientCash fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style AmountError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style HardwareError fill:#EF9A9A,stroke:#C62828,stroke-width:2px

Activity: Cash Deposit Transaction

flowchart TD
    Start([๐Ÿ’ณ Customer authenticated]) --> SelectDeposit[๐Ÿ“ฅ Select deposit]
    SelectDeposit --> ChooseType{๐Ÿ’ต Cash or Check?}

    ChooseType -->|Cash| PromptBills[๐Ÿ“ Prompt to insert bills]
    ChooseType -->|Check| ScanCheck[๐Ÿ“„ Scan check]

    PromptBills --> InsertBill[๐Ÿ’ต Insert bill]
    InsertBill --> ValidateBill{โœ“ Valid bill?}

    ValidateBill -->|No| RejectBill[โŒ Reject bill]
    RejectBill --> MoreBills1{โž• Insert more?}

    ValidateBill -->|Yes| AcceptBill[โœ… Accept bill]
    AcceptBill --> UpdateTotal[๐Ÿ’ฐ Update total]
    UpdateTotal --> MoreBills1

    MoreBills1 -->|Yes| InsertBill
    MoreBills1 -->|No| ConfirmCashAmount{โœ“ Confirm total?}

    ConfirmCashAmount -->|No| ReturnBills[โ†ฉ๏ธ Return all bills]
    ReturnBills --> Cancel1

    ConfirmCashAmount -->|Yes| ProcessCashDeposit[โš™๏ธ Process deposit]
    ProcessCashDeposit --> CreditCashAccount

    ScanCheck --> ValidateCheck{โœ“ Valid check?}
    ValidateCheck -->|No| CheckError[โŒ Invalid check]
    CheckError --> ReturnCheck[โ†ฉ๏ธ Return check]
    ReturnCheck --> Cancel1

    ValidateCheck -->|Yes| CaptureAmount[๐Ÿ’ฐ Capture check amount]
    CaptureAmount --> ConfirmCheckAmount{โœ“ Confirm amount?}

    ConfirmCheckAmount -->|No| ReturnCheck
    ConfirmCheckAmount -->|Yes| StoreCheck[๐Ÿ“ Store check for clearing]
    StoreCheck --> ProcessCheckDeposit[โš™๏ธ Process deposit]
    ProcessCheckDeposit --> PendingCredit[โณ Mark as pending]
    PendingCredit --> CreditCheckAccount

    CreditCashAccount[๐Ÿ’ณ Credit account - Cash]
    CreditCheckAccount[๐Ÿ’ณ Credit account - Check]

    CreditCashAccount --> UpdateBalance1
    CreditCheckAccount --> UpdateBalance1

    UpdateBalance1[๐Ÿ“Š Update balance] --> PrintDepositReceipt[๐Ÿงพ Print receipt]
    PrintDepositReceipt --> LogDepositTx[๐Ÿ“ Log transaction]
    LogDepositTx --> ShowConfirmation[โœ… Show confirmation]
    ShowConfirmation --> MoreTx{๐Ÿค” More transactions?}

    MoreTx -->|Yes| ReturnToMenu[๐Ÿ“‹ Return to menu]
    ReturnToMenu --> End1([Continue])
    MoreTx -->|No| End2([โœ… Complete])

    Cancel1[โŒ Cancel deposit] --> End3([End])

    style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px
    style End1 fill:#81D4FA,stroke:#0277BD,stroke-width:3px
    style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px
    style End3 fill:#FFCDD2,stroke:#C62828,stroke-width:3px

    style SelectDeposit fill:#B3E5FC,stroke:#0277BD,stroke-width:2px
    style PromptBills fill:#81D4FA,stroke:#0277BD,stroke-width:2px
    style InsertBill fill:#4FC3F7,stroke:#01579B,stroke-width:2px

    style ScanCheck fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px
    style ValidateCheck fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px
    style CaptureAmount fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px
    style StoreCheck fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px

    style AcceptBill fill:#A5D6A7,stroke:#388E3C,stroke-width:2px
    style UpdateTotal fill:#81C784,stroke:#2E7D32,stroke-width:2px
    style ProcessCashDeposit fill:#66BB6A,stroke:#2E7D32,stroke-width:2px
    style ProcessCheckDeposit fill:#66BB6A,stroke:#2E7D32,stroke-width:2px

    style CreditCashAccount fill:#FFE082,stroke:#F57F17,stroke-width:2px
    style CreditCheckAccount fill:#FFE082,stroke:#F57F17,stroke-width:2px
    style UpdateBalance1 fill:#FFCC80,stroke:#EF6C00,stroke-width:2px

    style PrintDepositReceipt fill:#E1BEE7,stroke:#7B1FA2,stroke-width:2px
    style LogDepositTx fill:#CE93D8,stroke:#6A1B9A,stroke-width:2px
    style ShowConfirmation fill:#C8E6C9,stroke:#388E3C,stroke-width:2px

    style ChooseType fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ValidateBill fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style MoreBills1 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ConfirmCashAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ConfirmCheckAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style MoreTx fill:#FFF9C4,stroke:#F9A825,stroke-width:2px

    style RejectBill fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style CheckError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style ReturnBills fill:#FFAB91,stroke:#D84315,stroke-width:2px
    style ReturnCheck fill:#FFAB91,stroke:#D84315,stroke-width:2px

Activity: Fund Transfer Transaction

flowchart TD
    Start([๐Ÿ’ณ Customer authenticated]) --> SelectTransfer[๐Ÿ”„ Select transfer]
    SelectTransfer --> ChooseAccounts{๐Ÿ“Š Transfer type?}

    ChooseAccounts -->|Between own accounts| SelectSource1[๐Ÿ“ค Select source account]
    ChooseAccounts -->|To another customer| SelectSource2[๐Ÿ“ค Select source account]

    SelectSource1 --> SelectDest1[๐Ÿ“ฅ Select destination account]
    SelectDest1 --> EnterAmount1

    SelectSource2 --> EnterDestAccount[๐Ÿ”ข Enter destination account]
    EnterDestAccount --> ValidateDestAccount{โœ“ Valid account?}

    ValidateDestAccount -->|No| InvalidAccount[โŒ Invalid account]
    InvalidAccount --> TryAgain1{๐Ÿ”„ Try again?}
    TryAgain1 -->|Yes| EnterDestAccount
    TryAgain1 -->|No| CancelTransfer

    ValidateDestAccount -->|Yes| EnterAmount1

    EnterAmount1[๐Ÿ’ฐ Enter transfer amount] --> ValidateAmount{โœ“ Valid amount?}

    ValidateAmount -->|No| AmountError[โŒ Invalid amount]
    AmountError --> EnterAmount1

    ValidateAmount -->|Yes| CheckSourceBalance{๐Ÿ’ณ Sufficient balance?}

    CheckSourceBalance -->|No| InsufficientFunds[โš ๏ธ Insufficient funds]
    InsufficientFunds --> TryAgain2{๐Ÿ”„ Different amount?}
    TryAgain2 -->|Yes| EnterAmount1
    TryAgain2 -->|No| CancelTransfer

    CheckSourceBalance -->|Yes| CheckTransferLimit{๐Ÿ“Š Within limits?}

    CheckTransferLimit -->|No| LimitExceeded[โš ๏ธ Transfer limit exceeded]
    LimitExceeded --> CancelTransfer

    CheckTransferLimit -->|Yes| ShowSummary[๐Ÿ“‹ Show transfer summary]
    ShowSummary --> ConfirmTransfer{โœ“ Confirm transfer?}

    ConfirmTransfer -->|No| CancelTransfer
    ConfirmTransfer -->|Yes| BeginTransaction[๐Ÿ”’ Begin transaction]

    BeginTransaction --> DebitSource[๐Ÿ“‰ Debit source account]
    DebitSource --> SourceDebited{โœ“ Debit successful?}

    SourceDebited -->|No| TransactionError[โŒ Transaction error]
    TransactionError --> Rollback[โ™ป๏ธ Rollback]
    Rollback --> CancelTransfer

    SourceDebited -->|Yes| CreditDestination[๐Ÿ“ˆ Credit destination]
    CreditDestination --> DestCredited{โœ“ Credit successful?}

    DestCredited -->|No| RollbackDebit[โ™ป๏ธ Rollback debit]
    RollbackDebit --> TransactionError

    DestCredited -->|Yes| CommitTransaction[โœ… Commit transaction]
    CommitTransaction --> RecordTransfer[๐Ÿ“ Record transfer]
    RecordTransfer --> PrintTransferReceipt[๐Ÿงพ Print receipt]
    PrintTransferReceipt --> ShowSuccess[โœ… Transfer successful]
    ShowSuccess --> LogTransferTx[๐Ÿ“ Log transaction]
    LogTransferTx --> MoreTx{๐Ÿค” More transactions?}

    MoreTx -->|Yes| ReturnMenu[๐Ÿ“‹ Return to menu]
    ReturnMenu --> End1([Continue])
    MoreTx -->|No| End2([โœ… Complete])

    CancelTransfer[โŒ Cancel transfer] --> End3([End])

    style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px
    style End1 fill:#81D4FA,stroke:#0277BD,stroke-width:3px
    style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px
    style End3 fill:#FFCDD2,stroke:#C62828,stroke-width:3px

    style SelectTransfer fill:#B3E5FC,stroke:#0277BD,stroke-width:2px
    style SelectSource1 fill:#81D4FA,stroke:#0277BD,stroke-width:2px
    style SelectSource2 fill:#81D4FA,stroke:#0277BD,stroke-width:2px
    style SelectDest1 fill:#4FC3F7,stroke:#01579B,stroke-width:2px
    style EnterDestAccount fill:#4FC3F7,stroke:#01579B,stroke-width:2px

    style EnterAmount1 fill:#FFE082,stroke:#F57F17,stroke-width:2px
    style ShowSummary fill:#FFCC80,stroke:#EF6C00,stroke-width:2px

    style BeginTransaction fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px
    style DebitSource fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px
    style CreditDestination fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px
    style CommitTransaction fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px

    style RecordTransfer fill:#A5D6A7,stroke:#388E3C,stroke-width:2px
    style PrintTransferReceipt fill:#81C784,stroke:#2E7D32,stroke-width:2px
    style ShowSuccess fill:#66BB6A,stroke:#2E7D32,stroke-width:2px
    style LogTransferTx fill:#4CAF50,stroke:#1B5E20,stroke-width:2px

    style ChooseAccounts fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ValidateDestAccount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ValidateAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style CheckSourceBalance fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style CheckTransferLimit fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style ConfirmTransfer fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style SourceDebited fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style DestCredited fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style TryAgain1 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style TryAgain2 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px
    style MoreTx fill:#FFF9C4,stroke:#F9A825,stroke-width:2px

    style InvalidAccount fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style AmountError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style InsufficientFunds fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style LimitExceeded fill:#FFCCBC,stroke:#E64A19,stroke-width:2px
    style TransactionError fill:#EF9A9A,stroke:#C62828,stroke-width:2px
    style Rollback fill:#EF5350,stroke:#C62828,stroke-width:2px
    style RollbackDebit fill:#EF5350,stroke:#C62828,stroke-width:2px

5. High-Level Code Implementation

Java

import java.time.*;
import java.util.*;
import java.math.BigDecimal;

// ============================================================================
// Enums
// ============================================================================

enum TransactionType {
    BALANCE_INQUIRY,
    WITHDRAWAL,
    CASH_DEPOSIT,
    CHECK_DEPOSIT,
    TRANSFER
}

enum TransactionStatus {
    PENDING,
    IN_PROGRESS,
    COMPLETED,
    FAILED,
    CANCELLED,
    ROLLED_BACK
}

enum CardType {
    DEBIT,
    CREDIT,
    ATM
}

enum CustomerStatus {
    ACTIVE,
    BLOCKED,
    SUSPENDED,
    CLOSED
}

enum CardReaderStatus {
    IDLE,
    CARD_INSERTED,
    READING,
    ERROR
}

enum CashDenomination {
    TWENTY(20),
    FIFTY(50),
    HUNDRED(100);

    private final int value;

    CashDenomination(int value) {
        this.value = value;
    }

    public int getValue() { return value; }
}

enum TransferType {
    CHECKING_TO_SAVINGS,
    SAVINGS_TO_CHECKING,
    TO_EXTERNAL_ACCOUNT
}

// ============================================================================
// Value Objects
// ============================================================================

class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
        this.amount = amount;
        this.currency = currency;
    }

    public Money(double amount) {
        this(new BigDecimal(amount), "USD");
    }

    public Money add(Money other) {
        validateCurrency(other);
        return new Money(this.amount.add(other.amount), this.currency);
    }

    public Money subtract(Money other) {
        validateCurrency(other);
        return new Money(this.amount.subtract(other.amount), this.currency);
    }

    public Money multiply(int factor) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
    }

    public boolean isGreaterThan(Money other) {
        validateCurrency(other);
        return this.amount.compareTo(other.amount) > 0;
    }

    public boolean isGreaterThanOrEqual(Money other) {
        validateCurrency(other);
        return this.amount.compareTo(other.amount) >= 0;
    }

    public boolean isPositive() {
        return this.amount.compareTo(BigDecimal.ZERO) > 0;
    }

    private void validateCurrency(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
    }

    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }

    @Override
    public String toString() {
        return String.format("%s %.2f", currency, amount);
    }
}

// ============================================================================
// ATM State Pattern
// ============================================================================

interface ATMState {
    void insertCard(ATM atm, Card card);
    void enterPIN(ATM atm, String pin);
    void selectTransaction(ATM atm, TransactionType type);
    void ejectCard(ATM atm);
}

class IdleState implements ATMState {
    @Override
    public void insertCard(ATM atm, Card card) {
        if (card.isExpired()) {
            atm.getScreen().showError("Card expired");
            atm.getCardReader().ejectCard();
            return;
        }
        atm.setCurrentCard(card);
        atm.setState(new CardInsertedState());
        atm.getScreen().displayMessage("Please enter your PIN");
    }

    @Override
    public void enterPIN(ATM atm, String pin) {
        atm.getScreen().showError("Please insert card first");
    }

    @Override
    public void selectTransaction(ATM atm, TransactionType type) {
        atm.getScreen().showError("Please insert card first");
    }

    @Override
    public void ejectCard(ATM atm) {
        // No card to eject
    }
}

class CardInsertedState implements ATMState {
    private int pinAttempts = 0;
    private static final int MAX_PIN_ATTEMPTS = 3;

    @Override
    public void insertCard(ATM atm, Card card) {
        atm.getScreen().showError("Card already inserted");
    }

    @Override
    public void enterPIN(ATM atm, String pin) {
        Card card = atm.getCurrentCard();
        boolean valid = atm.getBankingService().authenticatePIN(card, pin);

        if (valid) {
            Account account = atm.getBankingService().getAccount(card.getAccountNumber());
            Session session = new Session(card, account);
            atm.setActiveSession(session);
            atm.setState(new AuthenticatedState());
            atm.getScreen().displayMessage("Authentication successful");

            // Display main menu
            List<String> menuOptions = Arrays.asList(
                "1. Balance Inquiry",
                "2. Withdraw Cash",
                "3. Deposit Cash",
                "4. Transfer Funds"
            );
            atm.getScreen().displayMenu(menuOptions);
        } else {
            pinAttempts++;
            if (pinAttempts >= MAX_PIN_ATTEMPTS) {
                atm.getCardReader().retainCard();
                atm.getBankingService().notifyCardRetained(card);
                atm.getScreen().showError("Card retained due to incorrect PIN");
                atm.setState(new IdleState());
            } else {
                atm.getScreen().showError("Incorrect PIN. Attempt " + pinAttempts + " of " + MAX_PIN_ATTEMPTS);
            }
        }
    }

    @Override
    public void selectTransaction(ATM atm, TransactionType type) {
        atm.getScreen().showError("Please enter PIN first");
    }

    @Override
    public void ejectCard(ATM atm) {
        atm.getCardReader().ejectCard();
        atm.setState(new IdleState());
    }
}

class AuthenticatedState implements ATMState {
    @Override
    public void insertCard(ATM atm, Card card) {
        atm.getScreen().showError("Session already active");
    }

    @Override
    public void enterPIN(ATM atm, String pin) {
        atm.getScreen().showError("Already authenticated");
    }

    @Override
    public void selectTransaction(ATM atm, TransactionType type) {
        Session session = atm.getActiveSession();
        if (session.isExpired()) {
            atm.getScreen().showError("Session expired. Please restart.");
            ejectCard(atm);
            return;
        }

        session.updateActivity();
        Transaction transaction = createTransaction(type, session.getAccount(), atm);
        atm.setState(new TransactionState(transaction));
        atm.executeTransaction(transaction);
    }

    @Override
    public void ejectCard(ATM atm) {
        atm.endSession();
        atm.getCardReader().ejectCard();
        atm.setState(new IdleState());
        atm.getScreen().displayMessage("Thank you for using our ATM");
    }

    private Transaction createTransaction(TransactionType type, Account account, ATM atm) {
        switch (type) {
            case BALANCE_INQUIRY:
                return new BalanceInquiry(account);
            case WITHDRAWAL:
                return new Withdrawal(account, atm.getCashDispenser());
            case CASH_DEPOSIT:
                return new CashDeposit(account, atm.getCashAcceptor());
            case TRANSFER:
                return new Transfer(account);
            default:
                throw new IllegalArgumentException("Unknown transaction type");
        }
    }
}

class TransactionState implements ATMState {
    private Transaction currentTransaction;

    public TransactionState(Transaction transaction) {
        this.currentTransaction = transaction;
    }

    @Override
    public void insertCard(ATM atm, Card card) {
        atm.getScreen().showError("Transaction in progress");
    }

    @Override
    public void enterPIN(ATM atm, String pin) {
        // Handle PIN for transaction authorization if needed
    }

    @Override
    public void selectTransaction(ATM atm, TransactionType type) {
        atm.getScreen().showError("Please complete current transaction");
    }

    @Override
    public void ejectCard(ATM atm) {
        // Complete or cancel transaction first
        atm.getScreen().showError("Please complete transaction first");
    }

    public Transaction getCurrentTransaction() {
        return currentTransaction;
    }
}

// ============================================================================
// Session Management
// ============================================================================

class Session {
    private String sessionId;
    private Card card;
    private Account account;
    private LocalDateTime startTime;
    private LocalDateTime lastActivityTime;
    private static final int TIMEOUT_SECONDS = 120; // 2 minutes

    public Session(Card card, Account account) {
        this.sessionId = UUID.randomUUID().toString();
        this.card = card;
        this.account = account;
        this.startTime = LocalDateTime.now();
        this.lastActivityTime = LocalDateTime.now();
    }

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(lastActivityTime.plusSeconds(TIMEOUT_SECONDS));
    }

    public void updateActivity() {
        this.lastActivityTime = LocalDateTime.now();
    }

    public Account getAccount() { return account; }
    public Card getCard() { return card; }
    public String getSessionId() { return sessionId; }
}

// ============================================================================
// ATM Core
// ============================================================================

class ATM {
    private String atmId;
    private String location;
    private ATMState currentState;
    private Session activeSession;
    private Card currentCard;

    // Hardware components
    private CardReader cardReader;
    private PINPad pinPad;
    private Screen screen;
    private CashDispenser cashDispenser;
    private CashAcceptor cashAcceptor;
    private CheckScanner checkScanner;
    private ReceiptPrinter receiptPrinter;

    // Services
    private BankingService bankingService;
    private AuditLog auditLog;

    public ATM(String atmId, String location) {
        this.atmId = atmId;
        this.location = location;
        this.currentState = new IdleState();

        // Initialize hardware
        this.cardReader = new CardReader();
        this.pinPad = new PINPad();
        this.screen = new Screen();
        this.cashDispenser = new CashDispenser();
        this.cashAcceptor = new CashAcceptor();
        this.checkScanner = new CheckScanner();
        this.receiptPrinter = new ReceiptPrinter();

        this.auditLog = new AuditLog(atmId);
    }

    public void insertCard(Card card) {
        currentState.insertCard(this, card);
    }

    public void enterPIN(String pin) {
        currentState.enterPIN(this, pin);
    }

    public void selectTransaction(TransactionType type) {
        currentState.selectTransaction(this, type);
    }

    public void executeTransaction(Transaction transaction) {
        if (!transaction.validate()) {
            screen.showError("Transaction validation failed");
            return;
        }

        TransactionResult result = transaction.execute();

        if (result.isSuccess()) {
            if (receiptPrinter.hasPaper()) {
                receiptPrinter.printReceipt(result.getReceipt());
            }
            screen.displayMessage("Transaction successful");
            auditLog.log("TRANSACTION_SUCCESS", transaction.toString());
        } else {
            screen.showError("Transaction failed: " + result.getMessage());
            auditLog.log("TRANSACTION_FAILED", result.getMessage());
        }

        // Return to authenticated state
        setState(new AuthenticatedState());

        // Display menu again
        List<String> menuOptions = Arrays.asList(
            "1. Balance Inquiry",
            "2. Withdraw Cash",
            "3. Deposit Cash",
            "4. Transfer Funds",
            "5. Exit"
        );
        screen.displayMenu(menuOptions);
    }

    public void ejectCard() {
        currentState.ejectCard(this);
    }

    public void endSession() {
        this.activeSession = null;
        this.currentCard = null;
    }

    // Getters and setters
    public void setState(ATMState state) { this.currentState = state; }
    public void setActiveSession(Session session) { this.activeSession = session; }
    public void setCurrentCard(Card card) { this.currentCard = card; }

    public Session getActiveSession() { return activeSession; }
    public Card getCurrentCard() { return currentCard; }
    public CardReader getCardReader() { return cardReader; }
    public Screen getScreen() { return screen; }
    public CashDispenser getCashDispenser() { return cashDispenser; }
    public CashAcceptor getCashAcceptor() { return cashAcceptor; }
    public BankingService getBankingService() { return bankingService; }
    public String getAtmId() { return atmId; }
    public String getLocation() { return location; }

    public void setBankingService(BankingService service) {
        this.bankingService = service;
    }
}

// ============================================================================
// Hardware Components
// ============================================================================

class CardReader {
    private CardReaderStatus status;
    private Card insertedCard;

    public CardReader() {
        this.status = CardReaderStatus.IDLE;
    }

    public Card readCard() {
        this.status = CardReaderStatus.READING;
        // Simulate card reading
        this.status = CardReaderStatus.CARD_INSERTED;
        return insertedCard;
    }

    public void ejectCard() {
        insertedCard = null;
        status = CardReaderStatus.IDLE;
    }

    public void retainCard() {
        // Card is kept by machine
        insertedCard = null;
        status = CardReaderStatus.IDLE;
    }

    public boolean isCardPresent() {
        return insertedCard != null;
    }
}

class PINPad {
    public String getEncryptedPIN() {
        // Simulate secure PIN entry
        // In real implementation, PIN would be encrypted
        return "encrypted_pin";
    }

    public void clear() {
        // Clear PIN entry
    }
}

class Screen {
    private String currentMessage;

    public void displayMessage(String message) {
        this.currentMessage = message;
        System.out.println("Screen: " + message);
    }

    public void displayMenu(List<String> options) {
        System.out.println("Screen Menu:");
        for (String option : options) {
            System.out.println("  " + option);
        }
    }

    public void showBalance(Money available, Money total) {
        System.out.println("Available Balance: " + available);
        System.out.println("Total Balance: " + total);
    }

    public void showError(String error) {
        System.out.println("ERROR: " + error);
    }

    public void clear() {
        currentMessage = "";
    }
}

class CashDispenser {
    private Map<CashDenomination, Integer> inventory;
    private DenominationDispenser dispenserChain;

    public CashDispenser() {
        this.inventory = new EnumMap<>(CashDenomination.class);
        inventory.put(CashDenomination.TWENTY, 100);
        inventory.put(CashDenomination.FIFTY, 50);
        inventory.put(CashDenomination.HUNDRED, 30);

        // Setup chain of responsibility
        DenominationDispenser hundred = new HundredDollarDispenser();
        DenominationDispenser fifty = new FiftyDollarDispenser();
        DenominationDispenser twenty = new TwentyDollarDispenser();

        hundred.setNext(fifty);
        fifty.setNext(twenty);

        this.dispenserChain = hundred;
    }

    public DispenseResult dispenseCash(Money amount) {
        if (!canDispense(amount)) {
            return new DispenseResult(false, "Insufficient cash in ATM", null);
        }

        DispenseResult result = dispenserChain.dispense(amount, inventory);

        if (result.isSuccess()) {
            // Update inventory
            for (Map.Entry<CashDenomination, Integer> entry : result.getDispensedBills().entrySet()) {
                inventory.merge(entry.getKey(), -entry.getValue(), Integer::sum);
            }
        }

        return result;
    }

    public boolean canDispense(Money amount) {
        int totalAvailable = 0;
        for (Map.Entry<CashDenomination, Integer> entry : inventory.entrySet()) {
            totalAvailable += entry.getKey().getValue() * entry.getValue();
        }
        return totalAvailable >= amount.getAmount().intValue();
    }

    public void refill(Map<CashDenomination, Integer> bills) {
        for (Map.Entry<CashDenomination, Integer> entry : bills.entrySet()) {
            inventory.merge(entry.getKey(), entry.getValue(), Integer::sum);
        }
    }

    public boolean isLowOnCash() {
        int total = inventory.values().stream().mapToInt(Integer::intValue).sum();
        return total < 50;
    }

    public Map<CashDenomination, Integer> getInventory() {
        return new EnumMap<>(inventory);
    }
}

class CashAcceptor {
    private List<Money> acceptedBills;

    public CashAcceptor() {
        this.acceptedBills = new ArrayList<>();
    }

    public void acceptBill(Money bill) {
        // Validate bill
        acceptedBills.add(bill);
    }

    public Money getTotalAccepted() {
        Money total = new Money(0);
        for (Money bill : acceptedBills) {
            total = total.add(bill);
        }
        return total;
    }

    public void clear() {
        acceptedBills.clear();
    }
}

class CheckScanner {
    public Check scanCheck() {
        // Simulate check scanning
        return new Check("123456", "987654321", "1234567890", new Money(100), LocalDate.now());
    }

    public boolean validateCheck(Check check) {
        // Basic validation
        return check.getAmount().isPositive();
    }
}

class ReceiptPrinter {
    private int paperLevel;

    public ReceiptPrinter() {
        this.paperLevel = 100;
    }

    public void printReceipt(TransactionReceipt receipt) {
        if (paperLevel > 0) {
            System.out.println("Printing receipt:");
            System.out.println(receipt.format());
            paperLevel--;
        }
    }

    public boolean hasPaper() {
        return paperLevel > 0;
    }

    public void refillPaper() {
        paperLevel = 100;
    }
}

// ============================================================================
// Chain of Responsibility for Denomination Dispensing
// ============================================================================

interface DenominationDispenser {
    void setNext(DenominationDispenser next);
    DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory);
}

class HundredDollarDispenser implements DenominationDispenser {
    private DenominationDispenser next;

    @Override
    public void setNext(DenominationDispenser next) {
        this.next = next;
    }

    @Override
    public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
        int remaining = amount.getAmount().intValue();
        int available = inventory.getOrDefault(CashDenomination.HUNDRED, 0);
        int needed = remaining / 100;
        int toDispense = Math.min(needed, available);

        Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
        if (toDispense > 0) {
            dispensed.put(CashDenomination.HUNDRED, toDispense);
            remaining -= toDispense * 100;
        }

        if (remaining > 0 && next != null) {
            DispenseResult nextResult = next.dispense(new Money(remaining), inventory);
            if (nextResult.isSuccess()) {
                dispensed.putAll(nextResult.getDispensedBills());
                return new DispenseResult(true, "Cash dispensed", dispensed);
            }
        }

        if (remaining == 0) {
            return new DispenseResult(true, "Cash dispensed", dispensed);
        }

        return new DispenseResult(false, "Cannot dispense exact amount", null);
    }
}

class FiftyDollarDispenser implements DenominationDispenser {
    private DenominationDispenser next;

    @Override
    public void setNext(DenominationDispenser next) {
        this.next = next;
    }

    @Override
    public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
        int remaining = amount.getAmount().intValue();
        int available = inventory.getOrDefault(CashDenomination.FIFTY, 0);
        int needed = remaining / 50;
        int toDispense = Math.min(needed, available);

        Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
        if (toDispense > 0) {
            dispensed.put(CashDenomination.FIFTY, toDispense);
            remaining -= toDispense * 50;
        }

        if (remaining > 0 && next != null) {
            DispenseResult nextResult = next.dispense(new Money(remaining), inventory);
            if (nextResult.isSuccess()) {
                dispensed.putAll(nextResult.getDispensedBills());
                return new DispenseResult(true, "Cash dispensed", dispensed);
            }
        }

        if (remaining == 0) {
            return new DispenseResult(true, "Cash dispensed", dispensed);
        }

        return new DispenseResult(false, "Cannot dispense exact amount", null);
    }
}

class TwentyDollarDispenser implements DenominationDispenser {
    private DenominationDispenser next;

    @Override
    public void setNext(DenominationDispenser next) {
        this.next = next;
    }

    @Override
    public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
        int remaining = amount.getAmount().intValue();
        int available = inventory.getOrDefault(CashDenomination.TWENTY, 0);
        int needed = remaining / 20;
        int toDispense = Math.min(needed, available);

        Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
        if (toDispense > 0) {
            dispensed.put(CashDenomination.TWENTY, toDispense);
            remaining -= toDispense * 20;
        }

        if (remaining == 0) {
            return new DispenseResult(true, "Cash dispensed", dispensed);
        }

        return new DispenseResult(false, "Cannot dispense exact amount", null);
    }
}

class DispenseResult {
    private boolean success;
    private String message;
    private Map<CashDenomination, Integer> dispensedBills;

    public DispenseResult(boolean success, String message, Map<CashDenomination, Integer> dispensedBills) {
        this.success = success;
        this.message = message;
        this.dispensedBills = dispensedBills;
    }

    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public Map<CashDenomination, Integer> getDispensedBills() { return dispensedBills; }
}

// ============================================================================
// Banking Domain
// ============================================================================

interface BankingService {
    boolean validateCard(Card card);
    boolean authenticatePIN(Card card, String pin);
    Account getAccount(String accountNumber);
    TransactionResult processTransaction(Transaction transaction);
    void notifyCardRetained(Card card);
}

class Card {
    private String cardNumber;
    private String cardHolderName;
    private LocalDate expiryDate;
    private String encryptedPIN;
    private CardType cardType;
    private String accountNumber;

    public Card(String cardNumber, String cardHolderName, LocalDate expiryDate,
                String encryptedPIN, CardType cardType, String accountNumber) {
        this.cardNumber = cardNumber;
        this.cardHolderName = cardHolderName;
        this.expiryDate = expiryDate;
        this.encryptedPIN = encryptedPIN;
        this.cardType = cardType;
        this.accountNumber = accountNumber;
    }

    public boolean isExpired() {
        return LocalDate.now().isAfter(expiryDate);
    }

    public boolean validatePIN(String pin) {
        // In real implementation, compare encrypted values
        return encryptedPIN.equals(pin);
    }

    public String getCardNumber() { return cardNumber; }
    public String getAccountNumber() { return accountNumber; }
    public CardType getCardType() { return cardType; }
}

abstract class Account {
    protected String accountNumber;
    protected String accountHolderName;
    protected Money availableBalance;
    protected Money totalBalance;
    protected Money dailyWithdrawalLimit;
    protected Money todayWithdrawn;
    protected LocalDate lastWithdrawalDate;

    public Account(String accountNumber, String accountHolderName) {
        this.accountNumber = accountNumber;
        this.accountHolderName = accountHolderName;
        this.availableBalance = new Money(0);
        this.totalBalance = new Money(0);
        this.dailyWithdrawalLimit = new Money(1000); // $1000 default
        this.todayWithdrawn = new Money(0);
        this.lastWithdrawalDate = LocalDate.now();
    }

    public boolean canWithdraw(Money amount) {
        // Reset daily limit if new day
        if (!lastWithdrawalDate.equals(LocalDate.now())) {
            todayWithdrawn = new Money(0);
            lastWithdrawalDate = LocalDate.now();
        }

        Money newTotal = todayWithdrawn.add(amount);
        return availableBalance.isGreaterThanOrEqual(amount) &&
               dailyWithdrawalLimit.isGreaterThanOrEqual(newTotal);
    }

    public boolean withdraw(Money amount) {
        if (!canWithdraw(amount)) {
            return false;
        }

        availableBalance = availableBalance.subtract(amount);
        totalBalance = totalBalance.subtract(amount);
        todayWithdrawn = todayWithdrawn.add(amount);
        return true;
    }

    public void deposit(Money amount) {
        availableBalance = availableBalance.add(amount);
        totalBalance = totalBalance.add(amount);
    }

    public abstract Money getAvailableBalance();
    public abstract Money getTotalBalance();

    public String getAccountNumber() { return accountNumber; }
    public String getAccountHolderName() { return accountHolderName; }
}

class CheckingAccount extends Account {
    private Money overdraftLimit;
    private boolean hasDebitCard;

    public CheckingAccount(String accountNumber, String accountHolderName, Money overdraftLimit) {
        super(accountNumber, accountHolderName);
        this.overdraftLimit = overdraftLimit;
        this.hasDebitCard = true;
    }

    @Override
    public Money getAvailableBalance() {
        return availableBalance.add(overdraftLimit);
    }

    @Override
    public Money getTotalBalance() {
        return totalBalance;
    }
}

class SavingsAccount extends Account {
    private double interestRate;
    private int withdrawalsThisMonth;
    private static final int MAX_WITHDRAWALS_PER_MONTH = 6;

    public SavingsAccount(String accountNumber, String accountHolderName, double interestRate) {
        super(accountNumber, accountHolderName);
        this.interestRate = interestRate;
        this.withdrawalsThisMonth = 0;
    }

    @Override
    public boolean canWithdraw(Money amount) {
        return super.canWithdraw(amount) && withdrawalsThisMonth < MAX_WITHDRAWALS_PER_MONTH;
    }

    @Override
    public boolean withdraw(Money amount) {
        if (super.withdraw(amount)) {
            withdrawalsThisMonth++;
            return true;
        }
        return false;
    }

    @Override
    public Money getAvailableBalance() {
        return availableBalance;
    }

    @Override
    public Money getTotalBalance() {
        return totalBalance;
    }

    public double getInterestRate() { return interestRate; }
}

class Customer {
    private String customerId;
    private String name;
    private String email;
    private String phoneNumber;
    private List<Account> accounts;
    private CustomerStatus status;

    public Customer(String customerId, String name, String email, String phoneNumber) {
        this.customerId = customerId;
        this.name = name;
        this.email = email;
        this.phoneNumber = phoneNumber;
        this.accounts = new ArrayList<>();
        this.status = CustomerStatus.ACTIVE;
    }

    public void addAccount(Account account) {
        accounts.add(account);
    }

    public List<Account> getAccounts() { return accounts; }
}

// ============================================================================
// Transactions (Strategy Pattern with Template Method)
// ============================================================================

abstract class Transaction {
    protected String transactionId;
    protected LocalDateTime timestamp;
    protected Account account;
    protected TransactionStatus status;
    protected Money amount;

    public Transaction(Account account) {
        this.transactionId = UUID.randomUUID().toString();
        this.timestamp = LocalDateTime.now();
        this.account = account;
        this.status = TransactionStatus.PENDING;
    }

    // Template method
    public final TransactionResult execute() {
        if (!validate()) {
            return new TransactionResult(false, "Validation failed", null, null);
        }

        status = TransactionStatus.IN_PROGRESS;
        TransactionResult result = performTransaction();

        if (result.isSuccess()) {
            status = TransactionStatus.COMPLETED;
        } else {
            status = TransactionStatus.FAILED;
        }

        logTransaction();
        return result;
    }

    protected abstract TransactionResult performTransaction();
    public abstract boolean validate();

    protected void logTransaction() {
        // Log to audit trail
        System.out.println("Transaction logged: " + transactionId);
    }

    public TransactionReceipt createReceipt(Money balanceAfter) {
        return new TransactionReceipt(transactionId, timestamp, getTransactionType(),
                                      amount, balanceAfter, "ATM Location");
    }

    protected abstract TransactionType getTransactionType();

    @Override
    public String toString() {
        return String.format("Transaction[id=%s, type=%s, amount=%s, status=%s]",
                           transactionId, getTransactionType(), amount, status);
    }
}

class BalanceInquiry extends Transaction {
    public BalanceInquiry(Account account) {
        super(account);
        this.amount = new Money(0);
    }

    @Override
    protected TransactionResult performTransaction() {
        Money available = account.getAvailableBalance();
        Money total = account.getTotalBalance();

        String message = String.format("Available: %s, Total: %s", available, total);
        TransactionReceipt receipt = createReceipt(available);

        return new TransactionResult(true, message, available, receipt);
    }

    @Override
    public boolean validate() {
        return account != null;
    }

    @Override
    protected TransactionType getTransactionType() {
        return TransactionType.BALANCE_INQUIRY;
    }
}

class Withdrawal extends Transaction {
    private Money requestedAmount;
    private CashDispenser cashDispenser;
    private Map<CashDenomination, Integer> dispensedBills;

    public Withdrawal(Account account, CashDispenser cashDispenser) {
        super(account);
        this.cashDispenser = cashDispenser;
    }

    public void setAmount(Money amount) {
        this.requestedAmount = amount;
        this.amount = amount;
    }

    @Override
    protected TransactionResult performTransaction() {
        // Check if ATM has enough cash
        if (!cashDispenser.canDispense(requestedAmount)) {
            return new TransactionResult(false, "Insufficient cash in ATM", null, null);
        }

        // Debit account
        if (!account.withdraw(requestedAmount)) {
            return new TransactionResult(false, "Insufficient funds or limit exceeded", null, null);
        }

        // Dispense cash
        DispenseResult dispenseResult = cashDispenser.dispenseCash(requestedAmount);
        if (!dispenseResult.isSuccess()) {
            // Rollback account debit
            account.deposit(requestedAmount);
            return new TransactionResult(false, "Failed to dispense cash", null, null);
        }

        this.dispensedBills = dispenseResult.getDispensedBills();
        Money balanceAfter = account.getAvailableBalance();
        TransactionReceipt receipt = createReceipt(balanceAfter);

        return new TransactionResult(true, "Cash dispensed successfully", balanceAfter, receipt);
    }

    @Override
    public boolean validate() {
        return account != null &&
               requestedAmount != null &&
               requestedAmount.isPositive() &&
               account.canWithdraw(requestedAmount);
    }

    @Override
    protected TransactionType getTransactionType() {
        return TransactionType.WITHDRAWAL;
    }
}

class CashDeposit extends Transaction {
    private CashAcceptor cashAcceptor;
    private Money totalAmount;

    public CashDeposit(Account account, CashAcceptor cashAcceptor) {
        super(account);
        this.cashAcceptor = cashAcceptor;
    }

    @Override
    protected TransactionResult performTransaction() {
        totalAmount = cashAcceptor.getTotalAccepted();

        if (!totalAmount.isPositive()) {
            return new TransactionResult(false, "No cash deposited", null, null);
        }

        account.deposit(totalAmount);
        this.amount = totalAmount;

        Money balanceAfter = account.getAvailableBalance();
        TransactionReceipt receipt = createReceipt(balanceAfter);

        cashAcceptor.clear();

        return new TransactionResult(true, "Cash deposited successfully", balanceAfter, receipt);
    }

    @Override
    public boolean validate() {
        return account != null && cashAcceptor != null;
    }

    @Override
    protected TransactionType getTransactionType() {
        return TransactionType.CASH_DEPOSIT;
    }
}

class CheckDeposit extends Transaction {
    private Check check;
    private Money checkAmount;

    public CheckDeposit(Account account, Check check) {
        super(account);
        this.check = check;
        this.checkAmount = check.getAmount();
        this.amount = checkAmount;
    }

    @Override
    protected TransactionResult performTransaction() {
        // Check deposits are typically held for clearing
        // Credit account with pending status
        account.deposit(checkAmount);

        Money balanceAfter = account.getAvailableBalance();
        TransactionReceipt receipt = createReceipt(balanceAfter);

        return new TransactionResult(true, "Check deposited (pending clearance)", balanceAfter, receipt);
    }

    @Override
    public boolean validate() {
        return account != null && check != null && checkAmount.isPositive();
    }

    @Override
    protected TransactionType getTransactionType() {
        return TransactionType.CHECK_DEPOSIT;
    }
}

class Transfer extends Transaction {
    private Account sourceAccount;
    private Account destinationAccount;
    private Money transferAmount;
    private TransferType transferType;

    public Transfer(Account sourceAccount) {
        super(sourceAccount);
        this.sourceAccount = sourceAccount;
    }

    public void setDestinationAccount(Account dest) {
        this.destinationAccount = dest;
    }

    public void setTransferAmount(Money amount) {
        this.transferAmount = amount;
        this.amount = amount;
    }

    public void setTransferType(TransferType type) {
        this.transferType = type;
    }

    @Override
    protected TransactionResult performTransaction() {
        // Debit source account
        if (!sourceAccount.withdraw(transferAmount)) {
            return new TransactionResult(false, "Insufficient funds in source account", null, null);
        }

        // Credit destination account
        try {
            destinationAccount.deposit(transferAmount);
        } catch (Exception e) {
            // Rollback source debit
            sourceAccount.deposit(transferAmount);
            return new TransactionResult(false, "Failed to credit destination account", null, null);
        }

        Money balanceAfter = sourceAccount.getAvailableBalance();
        TransactionReceipt receipt = createReceipt(balanceAfter);

        return new TransactionResult(true, "Transfer completed successfully", balanceAfter, receipt);
    }

    @Override
    public boolean validate() {
        return sourceAccount != null &&
               destinationAccount != null &&
               transferAmount != null &&
               transferAmount.isPositive() &&
               sourceAccount.canWithdraw(transferAmount);
    }

    @Override
    protected TransactionType getTransactionType() {
        return TransactionType.TRANSFER;
    }
}

class TransactionResult {
    private boolean success;
    private String message;
    private Money balanceAfter;
    private TransactionReceipt receipt;

    public TransactionResult(boolean success, String message, Money balanceAfter, TransactionReceipt receipt) {
        this.success = success;
        this.message = message;
        this.balanceAfter = balanceAfter;
        this.receipt = receipt;
    }

    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public Money getBalanceAfter() { return balanceAfter; }
    public TransactionReceipt getReceipt() { return receipt; }
}

class TransactionReceipt {
    private String transactionId;
    private LocalDateTime timestamp;
    private TransactionType type;
    private Money amount;
    private Money balanceAfter;
    private String atmLocation;

    public TransactionReceipt(String transactionId, LocalDateTime timestamp, TransactionType type,
                             Money amount, Money balanceAfter, String atmLocation) {
        this.transactionId = transactionId;
        this.timestamp = timestamp;
        this.type = type;
        this.amount = amount;
        this.balanceAfter = balanceAfter;
        this.atmLocation = atmLocation;
    }

    public String format() {
        StringBuilder sb = new StringBuilder();
        sb.append("======= TRANSACTION RECEIPT =======\n");
        sb.append("Transaction ID: ").append(transactionId).append("\n");
        sb.append("Date/Time: ").append(timestamp).append("\n");
        sb.append("Type: ").append(type).append("\n");
        sb.append("Amount: ").append(amount).append("\n");
        sb.append("Balance After: ").append(balanceAfter).append("\n");
        sb.append("Location: ").append(atmLocation).append("\n");
        sb.append("===================================\n");
        return sb.toString();
    }
}

// ============================================================================
// Supporting Classes
// ============================================================================

class Check {
    private String checkNumber;
    private String routingNumber;
    private String accountNumber;
    private Money amount;
    private LocalDate date;

    public Check(String checkNumber, String routingNumber, String accountNumber, Money amount, LocalDate date) {
        this.checkNumber = checkNumber;
        this.routingNumber = routingNumber;
        this.accountNumber = accountNumber;
        this.amount = amount;
        this.date = date;
    }

    public Money getAmount() { return amount; }
    public String getCheckNumber() { return checkNumber; }
}

class AuditLog {
    private String atmId;
    private List<LogEntry> entries;

    public AuditLog(String atmId) {
        this.atmId = atmId;
        this.entries = new ArrayList<>();
    }

    public void log(String eventType, String details) {
        LogEntry entry = new LogEntry(UUID.randomUUID().toString(), LocalDateTime.now(),
                                      atmId, eventType, details);
        entries.add(entry);
        System.out.println("Audit Log: " + eventType + " - " + details);
    }

    private static class LogEntry {
        String logId;
        LocalDateTime timestamp;
        String atmId;
        String eventType;
        String details;

        LogEntry(String logId, LocalDateTime timestamp, String atmId, String eventType, String details) {
            this.logId = logId;
            this.timestamp = timestamp;
            this.atmId = atmId;
            this.eventType = eventType;
            this.details = details;
        }
    }
}

Python

from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, date, timedelta
from decimal import Decimal
from enum import Enum
from typing import List, Optional, Dict
from uuid import uuid4


# ============================================================================
# Enums
# ============================================================================

class TransactionType(Enum):
    BALANCE_INQUIRY = "balance_inquiry"
    WITHDRAWAL = "withdrawal"
    CASH_DEPOSIT = "cash_deposit"
    CHECK_DEPOSIT = "check_deposit"
    TRANSFER = "transfer"


class TransactionStatus(Enum):
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"
    ROLLED_BACK = "rolled_back"


class CardType(Enum):
    DEBIT = "debit"
    CREDIT = "credit"
    ATM = "atm"


class CustomerStatus(Enum):
    ACTIVE = "active"
    BLOCKED = "blocked"
    SUSPENDED = "suspended"
    CLOSED = "closed"


class CardReaderStatus(Enum):
    IDLE = "idle"
    CARD_INSERTED = "card_inserted"
    READING = "reading"
    ERROR = "error"


class CashDenomination(Enum):
    TWENTY = 20
    FIFTY = 50
    HUNDRED = 100

    @property
    def value_amount(self) -> int:
        return self.value


class TransferType(Enum):
    CHECKING_TO_SAVINGS = "checking_to_savings"
    SAVINGS_TO_CHECKING = "savings_to_checking"
    TO_EXTERNAL_ACCOUNT = "to_external_account"


# ============================================================================
# Value Objects
# ============================================================================

@dataclass(frozen=True)
class Money:
    """Immutable money value object"""
    amount: Decimal
    currency: str = "USD"

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("Amount cannot be negative")

    def add(self, other: 'Money') -> 'Money':
        self._validate_currency(other)
        return Money(self.amount + other.amount, self.currency)

    def subtract(self, other: 'Money') -> 'Money':
        self._validate_currency(other)
        return Money(self.amount - other.amount, self.currency)

    def multiply(self, factor: int) -> 'Money':
        return Money(self.amount * factor, self.currency)

    def is_greater_than(self, other: 'Money') -> bool:
        self._validate_currency(other)
        return self.amount > other.amount

    def is_greater_than_or_equal(self, other: 'Money') -> bool:
        self._validate_currency(other)
        return self.amount >= other.amount

    def is_positive(self) -> bool:
        return self.amount > 0

    def _validate_currency(self, other: 'Money') -> None:
        if self.currency != other.currency:
            raise ValueError("Currency mismatch")

    def __str__(self) -> str:
        return f"{self.currency} {self.amount:.2f}"


# ============================================================================
# ATM State Pattern
# ============================================================================

class ATMState(ABC):
    @abstractmethod
    def insert_card(self, atm: 'ATM', card: 'Card') -> None:
        pass

    @abstractmethod
    def enter_pin(self, atm: 'ATM', pin: str) -> None:
        pass

    @abstractmethod
    def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
        pass

    @abstractmethod
    def eject_card(self, atm: 'ATM') -> None:
        pass


class IdleState(ATMState):
    def insert_card(self, atm: 'ATM', card: 'Card') -> None:
        if card.is_expired():
            atm.screen.show_error("Card expired")
            atm.card_reader.eject_card()
            return

        atm.current_card = card
        atm.set_state(CardInsertedState())
        atm.screen.display_message("Please enter your PIN")

    def enter_pin(self, atm: 'ATM', pin: str) -> None:
        atm.screen.show_error("Please insert card first")

    def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
        atm.screen.show_error("Please insert card first")

    def eject_card(self, atm: 'ATM') -> None:
        pass  # No card to eject


class CardInsertedState(ATMState):
    MAX_PIN_ATTEMPTS = 3

    def __init__(self) -> None:
        self.pin_attempts = 0

    def insert_card(self, atm: 'ATM', card: 'Card') -> None:
        atm.screen.show_error("Card already inserted")

    def enter_pin(self, atm: 'ATM', pin: str) -> None:
        card = atm.current_card
        valid = atm.banking_service.authenticate_pin(card, pin)

        if valid:
            account = atm.banking_service.get_account(card.account_number)
            session = Session(card, account)
            atm.active_session = session
            atm.set_state(AuthenticatedState())
            atm.screen.display_message("Authentication successful")

            menu_options = [
                "1. Balance Inquiry",
                "2. Withdraw Cash",
                "3. Deposit Cash",
                "4. Transfer Funds"
            ]
            atm.screen.display_menu(menu_options)
        else:
            self.pin_attempts += 1
            if self.pin_attempts >= self.MAX_PIN_ATTEMPTS:
                atm.card_reader.retain_card()
                atm.banking_service.notify_card_retained(card)
                atm.screen.show_error("Card retained due to incorrect PIN")
                atm.set_state(IdleState())
            else:
                atm.screen.show_error(f"Incorrect PIN. Attempt {self.pin_attempts} of {self.MAX_PIN_ATTEMPTS}")

    def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
        atm.screen.show_error("Please enter PIN first")

    def eject_card(self, atm: 'ATM') -> None:
        atm.card_reader.eject_card()
        atm.set_state(IdleState())


class AuthenticatedState(ATMState):
    def insert_card(self, atm: 'ATM', card: 'Card') -> None:
        atm.screen.show_error("Session already active")

    def enter_pin(self, atm: 'ATM', pin: str) -> None:
        atm.screen.show_error("Already authenticated")

    def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
        session = atm.active_session
        if session.is_expired():
            atm.screen.show_error("Session expired. Please restart.")
            self.eject_card(atm)
            return

        session.update_activity()
        transaction = self._create_transaction(transaction_type, session.account, atm)
        atm.set_state(TransactionState(transaction))
        atm.execute_transaction(transaction)

    def eject_card(self, atm: 'ATM') -> None:
        atm.end_session()
        atm.card_reader.eject_card()
        atm.set_state(IdleState())
        atm.screen.display_message("Thank you for using our ATM")

    def _create_transaction(self, transaction_type: TransactionType,
                          account: 'Account', atm: 'ATM') -> 'Transaction':
        if transaction_type == TransactionType.BALANCE_INQUIRY:
            return BalanceInquiry(account)
        elif transaction_type == TransactionType.WITHDRAWAL:
            return Withdrawal(account, atm.cash_dispenser)
        elif transaction_type == TransactionType.CASH_DEPOSIT:
            return CashDeposit(account, atm.cash_acceptor)
        elif transaction_type == TransactionType.TRANSFER:
            return Transfer(account)
        else:
            raise ValueError("Unknown transaction type")


class TransactionState(ATMState):
    def __init__(self, transaction: 'Transaction') -> None:
        self.current_transaction = transaction

    def insert_card(self, atm: 'ATM', card: 'Card') -> None:
        atm.screen.show_error("Transaction in progress")

    def enter_pin(self, atm: 'ATM', pin: str) -> None:
        pass  # Handle PIN for transaction authorization if needed

    def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
        atm.screen.show_error("Please complete current transaction")

    def eject_card(self, atm: 'ATM') -> None:
        atm.screen.show_error("Please complete transaction first")


# ============================================================================
# Session Management
# ============================================================================

class Session:
    TIMEOUT_SECONDS = 120  # 2 minutes

    def __init__(self, card: 'Card', account: 'Account') -> None:
        self.session_id = str(uuid4())
        self.card = card
        self.account = account
        self.start_time = datetime.now()
        self.last_activity_time = datetime.now()

    def is_expired(self) -> bool:
        timeout = timedelta(seconds=self.TIMEOUT_SECONDS)
        return datetime.now() > self.last_activity_time + timeout

    def update_activity(self) -> None:
        self.last_activity_time = datetime.now()


# ============================================================================
# ATM Core
# ============================================================================

class ATM:
    def __init__(self, atm_id: str, location: str) -> None:
        self.atm_id = atm_id
        self.location = location
        self.current_state: ATMState = IdleState()
        self.active_session: Optional[Session] = None
        self.current_card: Optional[Card] = None

        # Hardware components
        self.card_reader = CardReader()
        self.pin_pad = PINPad()
        self.screen = Screen()
        self.cash_dispenser = CashDispenser()
        self.cash_acceptor = CashAcceptor()
        self.check_scanner = CheckScanner()
        self.receipt_printer = ReceiptPrinter()

        # Services
        self.banking_service: Optional['BankingService'] = None
        self.audit_log = AuditLog(atm_id)

    def insert_card(self, card: 'Card') -> None:
        self.current_state.insert_card(self, card)

    def enter_pin(self, pin: str) -> None:
        self.current_state.enter_pin(self, pin)

    def select_transaction(self, transaction_type: TransactionType) -> None:
        self.current_state.select_transaction(self, transaction_type)

    def execute_transaction(self, transaction: 'Transaction') -> None:
        if not transaction.validate():
            self.screen.show_error("Transaction validation failed")
            return

        result = transaction.execute()

        if result.success:
            if self.receipt_printer.has_paper():
                self.receipt_printer.print_receipt(result.receipt)
            self.screen.display_message("Transaction successful")
            self.audit_log.log("TRANSACTION_SUCCESS", str(transaction))
        else:
            self.screen.show_error(f"Transaction failed: {result.message}")
            self.audit_log.log("TRANSACTION_FAILED", result.message)

        # Return to authenticated state
        self.set_state(AuthenticatedState())

        # Display menu again
        menu_options = [
            "1. Balance Inquiry",
            "2. Withdraw Cash",
            "3. Deposit Cash",
            "4. Transfer Funds",
            "5. Exit"
        ]
        self.screen.display_menu(menu_options)

    def eject_card(self) -> None:
        self.current_state.eject_card(self)

    def end_session(self) -> None:
        self.active_session = None
        self.current_card = None

    def set_state(self, state: ATMState) -> None:
        self.current_state = state


# ============================================================================
# Hardware Components
# ============================================================================

class CardReader:
    def __init__(self) -> None:
        self.status = CardReaderStatus.IDLE
        self.inserted_card: Optional['Card'] = None

    def read_card(self) -> Optional['Card']:
        self.status = CardReaderStatus.READING
        self.status = CardReaderStatus.CARD_INSERTED
        return self.inserted_card

    def eject_card(self) -> None:
        self.inserted_card = None
        self.status = CardReaderStatus.IDLE

    def retain_card(self) -> None:
        self.inserted_card = None
        self.status = CardReaderStatus.IDLE

    def is_card_present(self) -> bool:
        return self.inserted_card is not None


class PINPad:
    def get_encrypted_pin(self) -> str:
        # Simulate secure PIN entry
        return "encrypted_pin"

    def clear(self) -> None:
        pass


class Screen:
    def __init__(self) -> None:
        self.current_message = ""

    def display_message(self, message: str) -> None:
        self.current_message = message
        print(f"Screen: {message}")

    def display_menu(self, options: List[str]) -> None:
        print("Screen Menu:")
        for option in options:
            print(f"  {option}")

    def show_balance(self, available: Money, total: Money) -> None:
        print(f"Available Balance: {available}")
        print(f"Total Balance: {total}")

    def show_error(self, error: str) -> None:
        print(f"ERROR: {error}")

    def clear(self) -> None:
        self.current_message = ""


class CashDispenser:
    def __init__(self) -> None:
        self.inventory: Dict[CashDenomination, int] = {
            CashDenomination.TWENTY: 100,
            CashDenomination.FIFTY: 50,
            CashDenomination.HUNDRED: 30
        }

        # Setup chain of responsibility
        hundred = HundredDollarDispenser()
        fifty = FiftyDollarDispenser()
        twenty = TwentyDollarDispenser()

        hundred.set_next(fifty)
        fifty.set_next(twenty)

        self.dispenser_chain = hundred

    def dispense_cash(self, amount: Money) -> 'DispenseResult':
        if not self.can_dispense(amount):
            return DispenseResult(False, "Insufficient cash in ATM", {})

        result = self.dispenser_chain.dispense(amount, self.inventory)

        if result.success:
            # Update inventory
            for denomination, count in result.dispensed_bills.items():
                self.inventory[denomination] -= count

        return result

    def can_dispense(self, amount: Money) -> bool:
        total_available = sum(
            denom.value * count
            for denom, count in self.inventory.items()
        )
        return total_available >= int(amount.amount)

    def refill(self, bills: Dict[CashDenomination, int]) -> None:
        for denomination, count in bills.items():
            self.inventory[denomination] = self.inventory.get(denomination, 0) + count

    def is_low_on_cash(self) -> bool:
        total = sum(self.inventory.values())
        return total < 50


class CashAcceptor:
    def __init__(self) -> None:
        self.accepted_bills: List[Money] = []

    def accept_bill(self, bill: Money) -> None:
        self.accepted_bills.append(bill)

    def get_total_accepted(self) -> Money:
        total = Money(Decimal('0'))
        for bill in self.accepted_bills:
            total = total.add(bill)
        return total

    def clear(self) -> None:
        self.accepted_bills.clear()


class CheckScanner:
    def scan_check(self) -> 'Check':
        # Simulate check scanning
        return Check("123456", "987654321", "1234567890",
                    Money(Decimal('100')), date.today())

    def validate_check(self, check: 'Check') -> bool:
        return check.amount.is_positive()


class ReceiptPrinter:
    def __init__(self) -> None:
        self.paper_level = 100

    def print_receipt(self, receipt: 'TransactionReceipt') -> None:
        if self.paper_level > 0:
            print("Printing receipt:")
            print(receipt.format())
            self.paper_level -= 1

    def has_paper(self) -> bool:
        return self.paper_level > 0

    def refill_paper(self) -> None:
        self.paper_level = 100


# ============================================================================
# Chain of Responsibility for Denomination Dispensing
# ============================================================================

class DenominationDispenser(ABC):
    def __init__(self) -> None:
        self.next_dispenser: Optional['DenominationDispenser'] = None

    def set_next(self, next_dispenser: 'DenominationDispenser') -> None:
        self.next_dispenser = next_dispenser

    @abstractmethod
    def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
        pass


class HundredDollarDispenser(DenominationDispenser):
    def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
        remaining = int(amount.amount)
        available = inventory.get(CashDenomination.HUNDRED, 0)
        needed = remaining // 100
        to_dispense = min(needed, available)

        dispensed = {}
        if to_dispense > 0:
            dispensed[CashDenomination.HUNDRED] = to_dispense
            remaining -= to_dispense * 100

        if remaining > 0 and self.next_dispenser:
            next_result = self.next_dispenser.dispense(Money(Decimal(remaining)), inventory)
            if next_result.success:
                dispensed.update(next_result.dispensed_bills)
                return DispenseResult(True, "Cash dispensed", dispensed)

        if remaining == 0:
            return DispenseResult(True, "Cash dispensed", dispensed)

        return DispenseResult(False, "Cannot dispense exact amount", {})


class FiftyDollarDispenser(DenominationDispenser):
    def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
        remaining = int(amount.amount)
        available = inventory.get(CashDenomination.FIFTY, 0)
        needed = remaining // 50
        to_dispense = min(needed, available)

        dispensed = {}
        if to_dispense > 0:
            dispensed[CashDenomination.FIFTY] = to_dispense
            remaining -= to_dispense * 50

        if remaining > 0 and self.next_dispenser:
            next_result = self.next_dispenser.dispense(Money(Decimal(remaining)), inventory)
            if next_result.success:
                dispensed.update(next_result.dispensed_bills)
                return DispenseResult(True, "Cash dispensed", dispensed)

        if remaining == 0:
            return DispenseResult(True, "Cash dispensed", dispensed)

        return DispenseResult(False, "Cannot dispense exact amount", {})


class TwentyDollarDispenser(DenominationDispenser):
    def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
        remaining = int(amount.amount)
        available = inventory.get(CashDenomination.TWENTY, 0)
        needed = remaining // 20
        to_dispense = min(needed, available)

        dispensed = {}
        if to_dispense > 0:
            dispensed[CashDenomination.TWENTY] = to_dispense
            remaining -= to_dispense * 20

        if remaining == 0:
            return DispenseResult(True, "Cash dispensed", dispensed)

        return DispenseResult(False, "Cannot dispense exact amount", {})


@dataclass
class DispenseResult:
    success: bool
    message: str
    dispensed_bills: Dict[CashDenomination, int]


# ============================================================================
# Banking Domain
# ============================================================================

class BankingService(ABC):
    @abstractmethod
    def validate_card(self, card: 'Card') -> bool:
        pass

    @abstractmethod
    def authenticate_pin(self, card: 'Card', pin: str) -> bool:
        pass

    @abstractmethod
    def get_account(self, account_number: str) -> 'Account':
        pass

    @abstractmethod
    def process_transaction(self, transaction: 'Transaction') -> 'TransactionResult':
        pass

    @abstractmethod
    def notify_card_retained(self, card: 'Card') -> None:
        pass


class Card:
    def __init__(self, card_number: str, card_holder_name: str, expiry_date: date,
                 encrypted_pin: str, card_type: CardType, account_number: str) -> None:
        self.card_number = card_number
        self.card_holder_name = card_holder_name
        self.expiry_date = expiry_date
        self.encrypted_pin = encrypted_pin
        self.card_type = card_type
        self.account_number = account_number

    def is_expired(self) -> bool:
        return date.today() > self.expiry_date

    def validate_pin(self, pin: str) -> bool:
        return self.encrypted_pin == pin


class Account(ABC):
    def __init__(self, account_number: str, account_holder_name: str) -> None:
        self.account_number = account_number
        self.account_holder_name = account_holder_name
        self.available_balance = Money(Decimal('0'))
        self.total_balance = Money(Decimal('0'))
        self.daily_withdrawal_limit = Money(Decimal('1000'))
        self.today_withdrawn = Money(Decimal('0'))
        self.last_withdrawal_date = date.today()

    def can_withdraw(self, amount: Money) -> bool:
        # Reset daily limit if new day
        if self.last_withdrawal_date != date.today():
            self.today_withdrawn = Money(Decimal('0'))
            self.last_withdrawal_date = date.today()

        new_total = self.today_withdrawn.add(amount)
        return (self.available_balance.is_greater_than_or_equal(amount) and
                self.daily_withdrawal_limit.is_greater_than_or_equal(new_total))

    def withdraw(self, amount: Money) -> bool:
        if not self.can_withdraw(amount):
            return False

        self.available_balance = self.available_balance.subtract(amount)
        self.total_balance = self.total_balance.subtract(amount)
        self.today_withdrawn = self.today_withdrawn.add(amount)
        return True

    def deposit(self, amount: Money) -> None:
        self.available_balance = self.available_balance.add(amount)
        self.total_balance = self.total_balance.add(amount)

    @abstractmethod
    def get_available_balance(self) -> Money:
        pass

    @abstractmethod
    def get_total_balance(self) -> Money:
        pass


class CheckingAccount(Account):
    def __init__(self, account_number: str, account_holder_name: str,
                 overdraft_limit: Money) -> None:
        super().__init__(account_number, account_holder_name)
        self.overdraft_limit = overdraft_limit
        self.has_debit_card = True

    def get_available_balance(self) -> Money:
        return self.available_balance.add(self.overdraft_limit)

    def get_total_balance(self) -> Money:
        return self.total_balance


class SavingsAccount(Account):
    MAX_WITHDRAWALS_PER_MONTH = 6

    def __init__(self, account_number: str, account_holder_name: str,
                 interest_rate: float) -> None:
        super().__init__(account_number, account_holder_name)
        self.interest_rate = interest_rate
        self.withdrawals_this_month = 0

    def can_withdraw(self, amount: Money) -> bool:
        return (super().can_withdraw(amount) and
                self.withdrawals_this_month < self.MAX_WITHDRAWALS_PER_MONTH)

    def withdraw(self, amount: Money) -> bool:
        if super().withdraw(amount):
            self.withdrawals_this_month += 1
            return True
        return False

    def get_available_balance(self) -> Money:
        return self.available_balance

    def get_total_balance(self) -> Money:
        return self.total_balance


class Customer:
    def __init__(self, customer_id: str, name: str, email: str, phone_number: str) -> None:
        self.customer_id = customer_id
        self.name = name
        self.email = email
        self.phone_number = phone_number
        self.accounts: List[Account] = []
        self.status = CustomerStatus.ACTIVE

    def add_account(self, account: Account) -> None:
        self.accounts.append(account)


# ============================================================================
# Transactions (Strategy Pattern with Template Method)
# ============================================================================

class Transaction(ABC):
    def __init__(self, account: Account) -> None:
        self.transaction_id = str(uuid4())
        self.timestamp = datetime.now()
        self.account = account
        self.status = TransactionStatus.PENDING
        self.amount = Money(Decimal('0'))

    def execute(self) -> 'TransactionResult':
        """Template method"""
        if not self.validate():
            return TransactionResult(False, "Validation failed", None, None)

        self.status = TransactionStatus.IN_PROGRESS
        result = self.perform_transaction()

        if result.success:
            self.status = TransactionStatus.COMPLETED
        else:
            self.status = TransactionStatus.FAILED

        self.log_transaction()
        return result

    @abstractmethod
    def perform_transaction(self) -> 'TransactionResult':
        pass

    @abstractmethod
    def validate(self) -> bool:
        pass

    def log_transaction(self) -> None:
        print(f"Transaction logged: {self.transaction_id}")

    def create_receipt(self, balance_after: Money) -> 'TransactionReceipt':
        return TransactionReceipt(
            self.transaction_id,
            self.timestamp,
            self.get_transaction_type(),
            self.amount,
            balance_after,
            "ATM Location"
        )

    @abstractmethod
    def get_transaction_type(self) -> TransactionType:
        pass

    def __str__(self) -> str:
        return f"Transaction[id={self.transaction_id}, type={self.get_transaction_type()}, " \
               f"amount={self.amount}, status={self.status}]"


class BalanceInquiry(Transaction):
    def __init__(self, account: Account) -> None:
        super().__init__(account)
        self.amount = Money(Decimal('0'))

    def perform_transaction(self) -> 'TransactionResult':
        available = self.account.get_available_balance()
        total = self.account.get_total_balance()

        message = f"Available: {available}, Total: {total}"
        receipt = self.create_receipt(available)

        return TransactionResult(True, message, available, receipt)

    def validate(self) -> bool:
        return self.account is not None

    def get_transaction_type(self) -> TransactionType:
        return TransactionType.BALANCE_INQUIRY


class Withdrawal(Transaction):
    def __init__(self, account: Account, cash_dispenser: CashDispenser) -> None:
        super().__init__(account)
        self.cash_dispenser = cash_dispenser
        self.requested_amount: Optional[Money] = None
        self.dispensed_bills: Dict[CashDenomination, int] = {}

    def set_amount(self, amount: Money) -> None:
        self.requested_amount = amount
        self.amount = amount

    def perform_transaction(self) -> 'TransactionResult':
        # Check if ATM has enough cash
        if not self.cash_dispenser.can_dispense(self.requested_amount):
            return TransactionResult(False, "Insufficient cash in ATM", None, None)

        # Debit account
        if not self.account.withdraw(self.requested_amount):
            return TransactionResult(False, "Insufficient funds or limit exceeded", None, None)

        # Dispense cash
        dispense_result = self.cash_dispenser.dispense_cash(self.requested_amount)
        if not dispense_result.success:
            # Rollback account debit
            self.account.deposit(self.requested_amount)
            return TransactionResult(False, "Failed to dispense cash", None, None)

        self.dispensed_bills = dispense_result.dispensed_bills
        balance_after = self.account.get_available_balance()
        receipt = self.create_receipt(balance_after)

        return TransactionResult(True, "Cash dispensed successfully", balance_after, receipt)

    def validate(self) -> bool:
        return (self.account is not None and
                self.requested_amount is not None and
                self.requested_amount.is_positive() and
                self.account.can_withdraw(self.requested_amount))

    def get_transaction_type(self) -> TransactionType:
        return TransactionType.WITHDRAWAL


class CashDeposit(Transaction):
    def __init__(self, account: Account, cash_acceptor: CashAcceptor) -> None:
        super().__init__(account)
        self.cash_acceptor = cash_acceptor
        self.total_amount: Optional[Money] = None

    def perform_transaction(self) -> 'TransactionResult':
        self.total_amount = self.cash_acceptor.get_total_accepted()

        if not self.total_amount.is_positive():
            return TransactionResult(False, "No cash deposited", None, None)

        self.account.deposit(self.total_amount)
        self.amount = self.total_amount

        balance_after = self.account.get_available_balance()
        receipt = self.create_receipt(balance_after)

        self.cash_acceptor.clear()

        return TransactionResult(True, "Cash deposited successfully", balance_after, receipt)

    def validate(self) -> bool:
        return self.account is not None and self.cash_acceptor is not None

    def get_transaction_type(self) -> TransactionType:
        return TransactionType.CASH_DEPOSIT


class CheckDeposit(Transaction):
    def __init__(self, account: Account, check: 'Check') -> None:
        super().__init__(account)
        self.check = check
        self.check_amount = check.amount
        self.amount = self.check_amount

    def perform_transaction(self) -> 'TransactionResult':
        # Check deposits are typically held for clearing
        self.account.deposit(self.check_amount)

        balance_after = self.account.get_available_balance()
        receipt = self.create_receipt(balance_after)

        return TransactionResult(True, "Check deposited (pending clearance)", balance_after, receipt)

    def validate(self) -> bool:
        return (self.account is not None and
                self.check is not None and
                self.check_amount.is_positive())

    def get_transaction_type(self) -> TransactionType:
        return TransactionType.CHECK_DEPOSIT


class Transfer(Transaction):
    def __init__(self, source_account: Account) -> None:
        super().__init__(source_account)
        self.source_account = source_account
        self.destination_account: Optional[Account] = None
        self.transfer_amount: Optional[Money] = None
        self.transfer_type: Optional[TransferType] = None

    def set_destination_account(self, dest: Account) -> None:
        self.destination_account = dest

    def set_transfer_amount(self, amount: Money) -> None:
        self.transfer_amount = amount
        self.amount = amount

    def set_transfer_type(self, transfer_type: TransferType) -> None:
        self.transfer_type = transfer_type

    def perform_transaction(self) -> 'TransactionResult':
        # Debit source account
        if not self.source_account.withdraw(self.transfer_amount):
            return TransactionResult(False, "Insufficient funds in source account", None, None)

        # Credit destination account
        try:
            self.destination_account.deposit(self.transfer_amount)
        except Exception as e:
            # Rollback source debit
            self.source_account.deposit(self.transfer_amount)
            return TransactionResult(False, "Failed to credit destination account", None, None)

        balance_after = self.source_account.get_available_balance()
        receipt = self.create_receipt(balance_after)

        return TransactionResult(True, "Transfer completed successfully", balance_after, receipt)

    def validate(self) -> bool:
        return (self.source_account is not None and
                self.destination_account is not None and
                self.transfer_amount is not None and
                self.transfer_amount.is_positive() and
                self.source_account.can_withdraw(self.transfer_amount))

    def get_transaction_type(self) -> TransactionType:
        return TransactionType.TRANSFER


@dataclass
class TransactionResult:
    success: bool
    message: str
    balance_after: Optional[Money]
    receipt: Optional['TransactionReceipt']


class TransactionReceipt:
    def __init__(self, transaction_id: str, timestamp: datetime,
                 transaction_type: TransactionType, amount: Money,
                 balance_after: Money, atm_location: str) -> None:
        self.transaction_id = transaction_id
        self.timestamp = timestamp
        self.type = transaction_type
        self.amount = amount
        self.balance_after = balance_after
        self.atm_location = atm_location

    def format(self) -> str:
        lines = [
            "======= TRANSACTION RECEIPT =======",
            f"Transaction ID: {self.transaction_id}",
            f"Date/Time: {self.timestamp}",
            f"Type: {self.type.value}",
            f"Amount: {self.amount}",
            f"Balance After: {self.balance_after}",
            f"Location: {self.atm_location}",
            "==================================="
        ]
        return "\n".join(lines)


# ============================================================================
# Supporting Classes
# ============================================================================

@dataclass
class Check:
    check_number: str
    routing_number: str
    account_number: str
    amount: Money
    date: date


class AuditLog:
    def __init__(self, atm_id: str) -> None:
        self.atm_id = atm_id
        self.entries: List['LogEntry'] = []

    def log(self, event_type: str, details: str) -> None:
        entry = LogEntry(str(uuid4()), datetime.now(), self.atm_id, event_type, details)
        self.entries.append(entry)
        print(f"Audit Log: {event_type} - {details}")


@dataclass
class LogEntry:
    log_id: str
    timestamp: datetime
    atm_id: str
    event_type: str
    details: str

This implementation demonstrates:

  • State Pattern for ATM operational states (Idle, CardInserted, Authenticated, Transaction)
  • Strategy Pattern for different transaction types with polymorphism
  • Template Method Pattern for transaction execution flow
  • Chain of Responsibility for optimal cash denomination dispensing
  • Value Object Pattern for Money with immutability and currency safety
  • Session management with timeout handling for security
  • SOLID Principles applied throughout
  • Complete hardware abstraction for all ATM components
  • Comprehensive error handling and rollback mechanisms
  • Audit logging for all transactions and events
  • Security features including PIN validation, card retention, and session timeouts
  • Multi-account support with different rules for Checking and Savings
  • Transaction atomicity with proper rollback on failures

Comments