✏️ Module: Account Update

ID: account-update  |  Legacy Source: CACTUPC.cbl + bridge.py:update_account() + Flask GET|POST /account/<id>/update

This guide covers the Account Update module — the most business-rule-dense part of the system. All 11 validation rules currently enforced by the COBOL program CACTUPC must be faithfully re-implemented as explicit C# validators in the .NET Domain layer. No rule may be silently dropped or altered.

🏗️ Technical Foundation

LayerTarget ComponentLegacy Equivalent
API ControllerAccountsController.csFlask GET|POST /account/<acct_id>/update
CommandUpdateAccountCommand.csForm POST body parsed in app.py:account_update()
Command HandlerUpdateAccountCommandHandler.csbridge.py:update_account() + CACTUPC subprocess
ValidatorUpdateAccountCommandValidator.cs (FluentValidation)CACTUPC VALIDATE-INPUT section (exit code 3 = validation failure)
Domain ServiceAccountDomainService.csCACTUPC APPLY-ACCT-UPD section
React PageAccountUpdatePage.tsxtemplates/account_update.html
React ComponentAccountUpdateForm.tsxAccount fields section of account_update.html
React HookuseUpdateAccount.ts (TanStack Query mutation)Form POST + redirect in Flask

🌐 Public API

PUT /api/accounts/{id}

Updates account fields after passing all business-rule validators. Returns 200 OK on success, 400 Bad Request with error codes on validation failure, 422 Unprocessable Entity if no changes were detected.

Request Body

{
  "activeStatus": "Y",
  "addressZip": "10001",
  "groupId": "GOLD",
  "openDate": "2018-03-15",
  "expirationDate": "2028-03-15",
  "reissueDate": "2023-03-15",
  "currentBalance": 1234.56,
  "creditLimit": 5000.00,
  "cashCreditLimit": 2500.00,
  "currentCycleCredit": 0.00,
  "currentCycleDebit": 0.00
}

Validation Error Response (400)

{
  "errors": [
    { "code": "CASH_GT_CREDIT", "message": "Cash credit limit cannot exceed the credit limit." },
    { "code": "DATE_ORDER",     "message": "Open date must be on or before expiration date." }
  ]
}

No-Change Response (422)

{ "message": "No changes detected — record not updated." }

📋 Business Rules (Extracted from CACTUPC.cbl)

All rules below are extracted from app/cobol/CACTUPC.cbl VALIDATE-INPUT section. Each rule must have a corresponding unit test in AccountValidatorTests.cs.

Rules Table
CodeField(s)RuleError Message (en)
ACCT_IDaccountIdExactly 11 numeric digits, non-zero"Account ID must be exactly 11 numeric digits and non-zero."
STATUS_YNactiveStatusMust be exactly "Y" or "N" (case-sensitive)"Active status must be Y or N."
DATE_LENopenDate, expirationDate, reissueDateIf provided, must be exactly 10 characters"Date must be in YYYY-MM-DD format (10 characters)."
DATE_DASHopenDate, expirationDate, reissueDateDashes required at positions 5 and 8"Date must use dashes at positions 5 and 8 (YYYY-MM-DD)."
DATE_NUMopenDate, expirationDate, reissueDateYear, month, and day components must be numeric"Date year, month, and day must all be numeric."
DATE_CALopenDate, expirationDate, reissueDateComponents must form a valid calendar date (e.g., no Feb 30)"Date components must form a valid calendar date."
DATE_ORDERopenDate, expirationDate, reissueDateopenDate ≤ expirationDate; reissueDate between openDate and expirationDate"Open date must be on or before expiration date; reissue date must fall between them."
AMT_CHARbalance, creditLimit, cashCreditLimit, cycleCredit, cycleDebitOnly digits, one decimal point, optional leading sign"Amount must be a valid decimal number."
CASH_GT_CREDITcashCreditLimit, creditLimitCash credit limit must not exceed credit limit"Cash credit limit cannot exceed the credit limit."
ZIP_DIGaddressZipMust be exactly 5 or 9 digits (no letters, no dashes)"ZIP code must be exactly 5 or 9 digits."
GROUP_ALNUMgroupIdAlphanumeric characters and spaces only; max 10 chars"Group ID must contain only alphanumeric characters and spaces."

🛡️ No-Change Guard (Critical Business Logic)

The legacy system (Python _account_form_unchanged()) skips COBOL invocation entirely if submitted data matches the stored record. This must be replicated in the .NET handler:

// UpdateAccountCommandHandler.cs (pseudo-code)
var current = await _repository.GetByIdAsync(command.Id);
if (current.HasNoChangesComparedTo(command))
    return Result.NoChanges("No changes detected — record not updated.");

// Only reach here if data actually changed
await _validator.ValidateAndThrowAsync(command);
current.ApplyUpdate(command);
await _repository.SaveAsync();

Monetary field comparison: The legacy bridge rounds both values to 2 decimal places before comparing. The C# implementation must do the same: Math.Round(incoming, 2) == Math.Round(stored, 2).

🎯 User Story Development Patterns

US-AU-01 · Update Account Status and Group (3 pts)
As a bank clerk, I want to update an account's active status (Y/N) and group ID so that account classification stays current.
US-AU-02 · Enforce Cash Credit Limit ≤ Credit Limit (3 pts)
As the system, I want to reject any update where cash credit limit exceeds credit limit so that invalid account configurations are prevented.
US-AU-03 · Validate Date Order (5 pts)
As the system, I want to reject updates where the date ordering is invalid (open > expiry, or reissue outside open–expiry range) so that account lifecycle dates remain logically consistent.
US-AU-04 · No-Change Guard — Skip Unnecessary DB Write (3 pts)
As the system, I want to detect when submitted account data matches the stored record and skip the update operation so that unnecessary database writes and audit entries are avoided.
US-AU-05 · Multiple Validation Errors Returned Together (2 pts)
As a bank clerk, I want to see all validation errors at once (not just the first one) so that I can fix everything in a single edit.

📊 Story Complexity Guidelines

PointsExample
1–2Improve error message text. Add a "Reset to Current Values" button.
3–5Implement a single business rule end-to-end (validator + unit tests + UI error display).
8+Full audit trail for account updates: log who changed what, when, and from what value (requires AuditLog table).

⚡ Performance Requirements