PERSONAL CURRENT ACCOUNT

UK

PCA-UK

  • Straight Through Processing
  • Technical debt?
  • Experian
  • UK Addresses
  • Lessons learned

Process flow

What needs to be done?

  • create product (BaProduct*)
  • assign EBAN
  • set main account holder
  • set address holder
  • set blocks (deposit, withdrawal)
  • authenticate (twice for joint account)
  • check creditability
  • init nil balance, InternetProduct, assign digipass, signatureStatus, request card, add card holder role, operationalDate
  • send credit letter or signature capture form plus reminders after 10 days
  • detect received documents (poller for D3)
  • request ID docs plus reminder letter (template)
  • send welcome mail, SMIB, SMS
  • create 25+ different contact events
class ManageRelationBean {
  public RequestData approveRequest(RequestData request) {
    return new RequestController().approveRetailRequest(request, context);
  }
}
class RequestController {
  public void approveRetailRequest(RequestData request, EJBContext context) {
    ...
    RequestFollowUpStrategy strategy = RequestFollowUpStrategy.createRetailStrategy(...);
    strategy.setCoworkerIDForContactEvent(...);
    strategy.setCreateOpeningLetter(...);
    strategy.setCreateRemoteUser(true);
    strategy.setNewAccessProviderRequired(false);

    validateAndFollowUpSTPRequest(request, strategy, context);
    ...
    processor.process(request, strategy, context);
  }
}
class RequestFollowUpProcessorForOpenPersonalCurrentAccountUK {
  public void process(RequestData request, RequestFollowUpStrategy strategy, EJBContext context) {
    BaProductData baProduct = initBaProduct(request, strategy, context);
    addDonationTarget(request, baProduct);
    baProduct.setHasTotalDepositBlock(true);
    ...
    addCardHolder(requestPersonRole.getPersonID(), baProduct);
    createOpeningLetter(request, baProduct, strategy, context);
    ...
    createDebitLimitChangeProposal(request, baProduct, debitLimitAmount, context);
    new BankAccountController().changeBaProduct(baProduct, context);
  }
}
class BankAccountController {
  public void changeBaProduct(BaProduct baProduct, EJBContext context) {
    try {
      final BaProductData oldBaProduct = new BaProductController().findBaProductCompleteWithBaProductTypeAndAdmin(data.getPrimKey(), context);
      oldBaProduct.setBaProductGeneralTermsData(new BaProductGeneralTermsTableManager().
          findBaProductGeneralTerms(oldBaProduct.getBaProductGeneralTermsData().getPrimKey(), context));

      if (oldBaProduct.getBaProductTypeData().isHasOverdraftTerms()) {
        oldBaProduct.setBaProductOverdraftTermsData(new BaProductOverdraftTermsTableManager().
            findBaProductOverdraftTerms(oldBaProduct.getBaProductOverdraftTermsData().getPrimKey(), context));
      }

      if (data.isLoanAccount()) {
        oldBaProduct.setBaProductLoanTermsData(new BaProductLoanTermsTableManager().
            findBaProductLoanTerms(oldBaProduct.getBaProductLoanTermsData().getPrimKey(), context));
      }

      if (data.isParticipationAccount()) {
        oldBaProduct.setBaProductParticipationTermsData(new BaProductParticipationTermsTableManager().
            findBaProductParticipationTerms(oldBaProduct.getBaProductParticipationTermsData().getPrimKey(), context));
      }

      if (data.isGuaranteeAccount()) {
        oldBaProduct.setBaProductGuaranteeTermsData(new BaProductGuaranteeTermsTableManager().
            findBaProductGuaranteeTerms(oldBaProduct.getBaProductGuaranteeTermsData().getPrimKey(), context));
      }

      // in BaProductChangeEventTableManager we need the _original_ ProductData from
      // the database, so we need retrieve and fill it here
      oldBaProduct.setProductData(new ProductTableManager().findProductCompleteWithProductTypeAndAdmin(oldBaProduct.getPrimKey(), context));

      // Now change the 'ancestor' product
      ProductData product = data.getProductData();

      // Prepare for validation (set validationAdministrationId and/or validationCountryId), also related objects
      data.prepareForValidation();

      if (product != null) {
        product = new ProductTableManager().changeProduct(product, context);
      } else {
        throw new ApplicationException("The BaProductData needs a ProductData to exist.");
      }

      final String administrationID = data.getBaProductTypeData().getBaAdministrationData().getAdministrationID();


      // Change BaProduct
      data.determineTotalBlockForNoIdentityCheck();
      data.suppressStatementCreationIfApplicable();
      data.determineSignatureCardStatusForAccount();

      final BaProductData newData = changeRecord(data, context);
      //copy values of original object
      newData.setProposals(data.getProposals());
      newData.setProductTypeChange(data.isProductTypeChange());

      // Some values on 'data' are used later to generate changeevents according to newData.
      // These values are temporary and not persisted to the database, so we
      // should hand them over manually
      newData.setInterestValueDateForChangeEvent(data.getInterestValueDateForChangeEvent());
      newData.setConfigurationRemarkForChangeEvent(data.getConfigurationRemarkForChangeEvent());

      // Apply changes of the product object to the new BaProduct object
      newData.setProductData(product);

      // Make sure that BaProductType is passed on to the new data
      newData.setBaProductTypeData(data.getBaProductTypeData());

      // Change BaProductGeneralTermsData
      if (data.getBaProductGeneralTermsData() != null) {
        if (data.getBaProductGeneralTermsData().isNewRecord()) {
          newData.setBaProductGeneralTermsDataWithBackReference(new BaProductGeneralTermsTableManager().
              createBaProductGeneralTerms(data.getBaProductGeneralTermsData(), context));
        } else {
          newData.setBaProductGeneralTermsDataWithBackReference(new BaProductGeneralTermsTableManager().
              changeBaProductGeneralTerms(data.getBaProductGeneralTermsData(), context));
        }
      } // generalterms are never removed, so no remove call required

      // Change BaProductOverdraftTerms object
      if (data.getBaProductOverdraftTermsData() != null) {
        if (data.getBaProductOverdraftTermsData().isNewRecord()) {
          if (data.isProductTypeChange()) {
            newData.setBaProductOverdraftTermsDataWithBackReference(new BaProductOverdraftTermsTableManager().
                createBaProductOverdraftTermsForProductTypeChange(data.getBaProductOverdraftTermsData(), context));
          } else {
            newData.setBaProductOverdraftTermsDataWithBackReference(new BaProductOverdraftTermsTableManager().
                createBaProductOverdraftTerms(data.getBaProductOverdraftTermsData(), context));
          }
        } else {
          newData.setBaProductOverdraftTermsDataWithBackReference(new BaProductOverdraftTermsTableManager().
              changeBaProductOverdraftTerms(data.getBaProductOverdraftTermsData(), context));
        }
      } else if (oldBaProduct.getBaProductOverdraftTermsData() != null) {
        new BaProductOverdraftTermsTableManager().removeBaProductOverdraftTerms(
            oldBaProduct.getBaProductOverdraftTermsData().getPrimKey(), context);
      }

      // Change BaProductCharges object
      final BaProductChargesData userCharges = data.getBaProductChargesData();
      if (userCharges != null) {
        if (userCharges.isNewRecord()) {
          // If the old productType does not allow charges and the new productType does, but for some reason the product
          // already has charges, we need to update these charges instead of create a new charges object (which would fail).
          BaProductChargesData newCharges = null;
          try {
            BaProductChargesData oldCharges = new BaProductChargesTableManager().findBaProductCharges(data.getPrimKey(), ServerContext.getDummyContext());

            // Since no exception was thrown, the product itself *does* have charges, so we update these instead of
            // creating new charges. First we should copy some properties from the original charges object to the new
            // charges object.
            userCharges.setCreateDate(oldCharges.getCreateDate());
            userCharges.setCreateUser(oldCharges.getCreateUser());
            userCharges.setChangeDate(oldCharges.getChangeDate());
            userCharges.setChangeUser(oldCharges.getChangeUser());
            userCharges.setRowVersion(oldCharges.getRowVersion());
            newCharges = new BaProductChargesTableManager().changeBaProductCharges(userCharges, context);

          } catch (TritonFinderException ignored) {
            // The product itself has no charges either, so we can create a new Charges object.
            newCharges = new BaProductChargesTableManager().createBaProductCharges(userCharges, context);
          } finally {
            newData.setBaProductChargesDataWithBackReference(newCharges);
          }
        } else {
          newData.setBaProductChargesDataWithBackReference(new BaProductChargesTableManager().changeBaProductCharges(userCharges, context));
        }
      } else if (oldBaProduct.getBaProductChargesData() != null) {
        // First, remove all BaProductBatchCharges records for the account.
        new BaProductBatchChargesTableManager().removeBaProductBatchChargesForProduct(product.getProductID(), context);

        new BaProductChargesTableManager().removeBaProductCharges(
            oldBaProduct.getBaProductChargesData().getPrimKey(), context);
      }

      if (data.isBaProductTypeHasCharges() &&
          data.getBaProductTypeData().getBaProductTypeChargesData() != null &&
          data.getBaProductTypeData().getBaProductTypeChargesData().isHasChargesUK()) {
        BaProductChargesUKData newChargesUKData = data.getBaProductChargesUKData();
        if (newChargesUKData != null) {
          final BaProductChargesUKData chargesUKData = new BaProductChargesUKTableManager().find(data.getPrimKey(), context);

          chargesUKData.setSuppressChargeUDD(newChargesUKData.isSuppressChargeUDD());
          chargesUKData.setSuppressChargeMonthlyFee(newChargesUKData.isSuppressChargeMonthlyFee());
          chargesUKData.setSuppressChargeUnpaidCheques(newChargesUKData.isSuppressChargeUnpaidCheques());
          chargesUKData.setSuppressChargeStatementCopy(newChargesUKData.isSuppressChargeStatementCopy());
          chargesUKData.setSuppressChargeCopyOfRecords(newChargesUKData.isSuppressChargeCopyOfRecords());
          chargesUKData.setSuppressChargeStoppedCheque(newChargesUKData.isSuppressChargeStoppedCheque());
          chargesUKData.setSuppressChargeCHAPS(newChargesUKData.isSuppressChargeCHAPS());
          chargesUKData.setSuppressChargeForeign(newChargesUKData.isSuppressChargeForeign());

          new BaProductChargesUKTableManager().save(chargesUKData, context);
        }
      }

      // Change BaProductDirectDebit object
      if (data.getBaProductDirectDebitData() != null) {
        if (data.getBaProductDirectDebitData().isNewRecord()) {
          newData.setBaProductDirectDebitDataWithBackReference(new BaProductDirectDebitTableManager().
              create(data.getBaProductDirectDebitData(), context));
        } else {
          newData.setBaProductDirectDebitDataWithBackReference(new BaProductDirectDebitTableManager().
              change(data.getBaProductDirectDebitData(), context));
        }
      } else if (oldBaProduct.getBaProductDirectDebitData() != null) {
        new BaProductDirectDebitTableManager().remove(
            oldBaProduct.getBaProductDirectDebitData().getPrimKey(), context);
      }

      // Change BaProductCreditRisk object
      if (data.getBaProductCreditRiskData() != null) {
        if (data.getBaProductCreditRiskData().isNewRecord()) {
          newData.setBaProductCreditRiskDataWithBackReference(new BaProductCreditRiskTableManager().
              create(data.getBaProductCreditRiskData(), context));
        } else {
          newData.setBaProductCreditRiskData(new BaProductCreditRiskTableManager().
              change(data.getBaProductCreditRiskData(), context));
        }
      } else if (oldBaProduct.getBaProductCreditRiskData() != null) {
        // TODO -- Do we need a remove for Credit Risk objects??!?!
//        new BaProductCreditRiskTableManager().remove(oldBaProduct.getBaProductCreditRiskData().getPrimKey(), context);
      }

      // Change BaProductChequeBook object
      if (data.getBaProductChequeBookData() != null) {
        if (data.getBaProductChequeBookData().isNewRecord()) {
          newData.setBaProductChequeBookDataWithBackReference(new BaProductChequeBookTableManager().
              createBaProductChequeBook(data.getBaProductChequeBookData(), context));
        } else {
          newData.setBaProductChequeBookDataWithBackReference(new BaProductChequeBookTableManager().
              changeBaProductChequeBook(data.getBaProductChequeBookData(), administrationID, context));
        }
      }

      // Change BaProductCreditBook object
      if (data.getBaProductCreditBookData() != null) {
        if (data.getBaProductCreditBookData().isNewRecord()) {
          newData.setBaProductCreditBookDataWithBackReference(new BaProductCreditBookTableManager().
              createBaProductCreditBook(data.getBaProductCreditBookData(), administrationID, context));
        } else {
          newData.setBaProductCreditBookDataWithBackReference(new BaProductCreditBookTableManager().
              changeBaProductCreditBook(data.getBaProductCreditBookData(), administrationID, context));
        }
      }

      final boolean isNL = AdministrationConstants.isBankNL(newData.getProductData().getProductTypeData().getAdministrationID());


      final boolean totalBlockForNoIdentityCheckChanged = oldBaProduct.isHasTotalBlockForNoIdentityChk() != newData.isHasTotalBlockForNoIdentityChk();

      // Change BaProductStatementTermsData
      final BaProductStatementTermsData baProductStatementTermsData = data.getBaProductStatementTermsData();
      if (baProductStatementTermsData != null) {
        if (isNL && totalBlockForNoIdentityCheckChanged && !newData.isHasTotalBlockForNoIdentityChk()) {
          baProductStatementTermsData.setSuppressCreate(false);
        }

        if (baProductStatementTermsData.isNewRecord()) {
          // Creating a baProductStatementTerms record also results in creation of a new 'Reserved' statement
          // If the administration uses periodical statements the periodBeginDate of the statment has to be
          // filled with the date of product type change
          LocalDate periodBeginDate = null;
          if (newData.getBaProductTypeData().getBaAdministrationData().isHasPeriodOnStatement()) {
            periodBeginDate = newData.getProductData().getLastProductTypeChangeDate();
          }
          newData.setBaProductStatementTermsDataWithBackReference(new BaProductStatementTermsTableManager().
              createBaProductStatementTerms(baProductStatementTermsData, periodBeginDate, context));
        } else {
          // update BaProductStatementTerms
          if (newData.getBaProductTypeData().getBaAdministrationData().isHasPeriodOnStatement() &&
              !baProductStatementTermsData.isSuppressCreate() &&
              oldBaProduct.getBaProductStatementTermsData().isSuppressCreate()) {
            // The admin uses fixed periods on statements and SuppressCreate is switched off:
            // --------------------------------------------------------------------------------
            // The upcoming statement will contain only change events belonging to the period for which the
            // periodEndDate is calculated as nextCreateDueDate - 1 day.
            // When the statement was suppressed for a long time the nextCreateDueDate is way in the past.
            // We need to set the nextCreateDueDate to the begin date of the current period to prevent
            // the change events created while suppression was active will be split-up over 2 statements (see OBI-1085).
            LocalDate newNextCreateDueDate =
                StatementUtil.calculateBeginDateOfCurrentPeriod(administrationID,
                    TDate.today(),
                    baProductStatementTermsData.getCreateFrequency(),
                    baProductStatementTermsData.getCreateDayOfWeek1(),
                    baProductStatementTermsData.getCreateDateOfMonth1());

            // Safety check: only update nextCreateDueDate when new calculated day is after the 'original' nextCreateDueDate
            if (newNextCreateDueDate != null &&
                newNextCreateDueDate.isAfter(oldBaProduct.getBaProductStatementTermsData().getNextCreateDueDate())) {
              baProductStatementTermsData.setNextCreateDueDate(newNextCreateDueDate);
            }
          }

          newData.setBaProductStatementTermsDataWithBackReference(new BaProductStatementTermsTableManager().
              changeBaProductStatementTerms(baProductStatementTermsData, context));
        }
        // Also set changed BaProductStatementTerms on the old data object, because later on a reference
        // to BaProductStatementTerms may be retrieved and changed through the old data again, which else
        // causes an update conflict.
        data.setBaProductStatementTermsDataWithBackReference(newData.getBaProductStatementTermsData());
      } else if (oldBaProduct.getBaProductStatementTermsData() != null) {
        new BaProductStatementTermsTableManager().
            removeBaProductStatementTerms(oldBaProduct.getBaProductStatementTermsData().getPrimKey(), context);
      }

      // Change BaProductCodaboxStatementTermsData
      if (data.getBaProductCodaboxStatementTerms() != null) {
        BaProductCodaboxStatementTermsData codaboxTerms = data.getBaProductCodaboxStatementTerms();
        new BaProductCodaboxStatementTermsTableManager().changeCodaboxStatementTerms(codaboxTerms, context);
      }

      // Change BaProductTelFaxEmail
      if (data.getBaProductTelFaxEmailData() != null) {
        if (data.getBaProductTelFaxEmailData().isNewRecord()) {
          newData.setBaProductTelFaxEmailData(new BaProductTelFaxEmailTableManager().
              createBaProductTelFaxEmail(data.getBaProductTelFaxEmailData(), context));
        } else {
          newData.setBaProductTelFaxEmailData(new BaProductTelFaxEmailTableManager().
              changeBaProductTelFaxEmail(data.getBaProductTelFaxEmailData(), context));
        }
      } else if (oldBaProduct.getBaProductTelFaxEmailData() != null) {
        new BaProductTelFaxEmailTableManager().removeBaProductTelFaxEmail(
            oldBaProduct.getBaProductTelFaxEmailData().getPrimKey(), context);
      }

      // Change BaProductTxExportTerms
      if (data.getBaProductTxExportTermsData() != null) {
        if (data.getBaProductTxExportTermsData().isNewRecord()) {
          newData.setBaProductTxExportTermsData(new BaProductTxExportTermsTableManager().
              createBaProductTxExportTerms(data.getBaProductTxExportTermsData(), context));
        } else {
          newData.setBaProductTxExportTermsData(new BaProductTxExportTermsTableManager().
              changeBaProductTxExportTerms(data.getBaProductTxExportTermsData(), context));
        }
      } else if (oldBaProduct.getBaProductTxExportTermsData() != null) {
        new BaProductTxExportTermsTableManager().removeBaProductTxExportTerms(
            oldBaProduct.getBaProductTxExportTermsData().getPrimKey(), context);
      }

      // Change BaProductLoanTerms object
      if (data.getBaProductLoanTermsData() != null) {
        Validate.isTrue(!data.getBaProductLoanTermsData().isNewRecord(), "you can't add loan terms to an existing account");
        newData.setBaProductLoanTermsDataWithBackReference(new BaProductLoanTermsTableManager().changeBaProductLoanTerms(data.getBaProductLoanTermsData(), context));
      } else {
        Validate.isTrue(oldBaProduct.getBaProductLoanTermsData() == null, "you can't remove the loan terms of an account");
      }

      // Change BaProductParticipationTerms object
      if (data.getBaProductParticipationTermsData() != null) {
        if (data.getBaProductParticipationTermsData().isNewRecord()) {
          newData.setBaProductParticipationTermsDataWithBackReference(new BaProductParticipationTermsTableManager().
              createBaProductParticipationTerms(data.getBaProductParticipationTermsData(), context));
        } else {
          newData.setBaProductParticipationTermsDataWithBackReference(new BaProductParticipationTermsTableManager().
              changeBaProductParticipationTerms(data.getBaProductParticipationTermsData(), context));
        }
      } else if (oldBaProduct.getBaProductParticipationTermsData() != null) {
        new BaProductParticipationTermsTableManager().removeBaProductParticipationTerms(
            oldBaProduct.getBaProductParticipationTermsData().getPrimKey(), context);
      }

      // Change BaProductGuaranteeTerms object
      if (data.getBaProductGuaranteeTermsData() != null) {
        if (data.getBaProductGuaranteeTermsData().isNewRecord()) {
          newData.setBaProductGuaranteeTermsDataWithBackReference(new BaProductGuaranteeTermsTableManager().
              createBaProductGuaranteeTerms(data.getBaProductGuaranteeTermsData(), context));
        } else {
          newData.setBaProductGuaranteeTermsDataWithBackReference(new BaProductGuaranteeTermsTableManager().
              changeBaProductGuaranteeTerms(data.getBaProductGuaranteeTermsData(), context));
        }
      } else if (oldBaProduct.getBaProductGuaranteeTermsData() != null) {
        new BaProductGuaranteeTermsTableManager().removeBaProductGuaranteeTerms(
            oldBaProduct.getBaProductGuaranteeTermsData().getPrimKey(), context);
      }

      // Change BaProductISA object
      if (data.getBaProductISAData() != null) {

        if (data.getBaProductISAData().isNewRecord()) {
          newData.setBaProductISADataWithBackReference(new BaProductISATableManager().
              createBaProductISA(data.getBaProductISAData(), context));
        } else {
          if (isaDeclarationStateChangedToComplete(data, oldBaProduct)) {
            data.getBaProductISAData().setDeclarationChannel(BaProductISATableManager.determineDeclarationChannel(context));
            data.getBaProductISAData().setDeclaredBy(ServerContext.getLoginName(context));
          }
          newData.setBaProductISADataWithBackReference(new BaProductISATableManager().
              changeBaProductISA(data.getBaProductISAData(), context));
        }
        new BankAccountController().closeIsaRedeclarationRemoteUserMessagesIfApplicable(data.getProductData(), context);
      } else if (oldBaProduct.getBaProductISAData() != null) {
        new BaProductISATableManager().removeBaProductISA(
            oldBaProduct.getBaProductISAData().getPrimKey(), context);
      }

      // Create InternetAccountData object.
      // Cannot be changed, must always be changed with a change proposal.
      // Cannot be removed, the end date must be set when it isn't an internet account anymore.
      // This is done with a change proposal.
      if (data.getInternetAccountData() != null) {
        data.getInternetAccountData().setProductID(newData.getProductID());
        if (data.getInternetAccountData().isNewRecord()) {
          newData.setInternetAccountDataWithBackReference(new InternetAccountTableManager().
              createInternetAccountWithCounterAccounts(data.getInternetAccountData(), context));
        } else {
          // Though the InternetAccountData will not be changed, the counteraccount list should be.
          new InternetAccountTableManager().updateInternetAccountCounterAccountList(data.getInternetAccountData(),
              data.getInternetAccountData().getInternetAccountCounterAccountList(),
              context);
          // And the rejected add proposals.
          new InternetAccountTableManager().updateInternetAccountCounterAccountChangeProposalList(data.getInternetAccountData(),
              data.getInternetAccountData().getInternetAccountCounterAccountChangeProposalList(),
              context);
          // Set the old InternetAccountData is the newData.
          newData.setInternetAccountData(data.getInternetAccountData());
        }
      }

      if (isNL && data.getProductData().getInternetProductData() != null) {
        newData.getProductData().setInternetProductData(new InternetProductTableManager().changeInternetProduct(data.getProductData().getInternetProductData(), context));
      }

      // Change BaProductClearingNL object
      if (data.getBaProductClearingNLData() != null) {
        if (data.getBaProductClearingNLData().isNewRecord()) {
          newData.setBaProductClearingNLDataWithBackReference(new BaProductClearingNLTableManager().
              create(data.getBaProductClearingNLData(), context));
        } else {
          newData.setBaProductClearingNLDataWithBackReference(new BaProductClearingNLTableManager().
              change(data.getBaProductClearingNLData(), context));
        }
      } else if (oldBaProduct.getBaProductClearingNLData() != null) {
        new BaProductClearingNLTableManager().remove(
            oldBaProduct.getBaProductClearingNLData().getPrimKey(), context);
      }

      // Note: updating BaProductCardTerms list (is never done via the normal wizard).
      // Copy the list from the original object, since we need it below.
      newData.setBaProductCardTermsList(data.getBaProductCardTermsList());

      // Note: updating BaProductCard list (is never done via the normal wizard).
      // Copy the list from the original object, since we need it below.
      newData.setBaProductCardList(data.getBaProductCardList());

      // Signature card is received? Call PA143 (other blockparams are checked in BA036 Execute proposal Change Block)
      if (new SignatureCardService(administrationID).hasSignatureStatusOnProductLevel() &&
          oldBaProduct.getSignatureCard().isBlocking() && !newData.getSignatureCard().isBlocking()) {
        // signatureCard changed into received or not required
        // reset transaction state to entry approved

        new PaymentsController().resetTransactionStateToEntryApproved(
            newData.getProductData().getProductTypeData().getAdministrationID(), newData.getProductID(), false, context);
      }

      if (isNL && newData.isCurrentAccount()) {
        updateCardTerms(oldBaProduct, newData, context);
      }

      final boolean isUK = AdministrationConstants.isBankUK(newData.getProductData().getProductTypeData().getAdministrationID());
      if (isUK && newData.isCurrentAccount()) {
        removeRedundantCardHolderRoles(oldBaProduct, newData, context);
        updateCardsUK(oldBaProduct, newData, context);
      }

      // Automatically add counter accounts to savings accounts if hasTotalBlockForIdentityCheck has changed to not blocking
      if (isNL && totalBlockForNoIdentityCheckChanged && !newData.isHasTotalBlockForNoIdentityChk()) {
        CounterAccountLinker.link(data, context);
      }

      // Create BaProductFixedTermContract list
      if (data.getBaProductFixedTermContractList() != null) {
        newData.setBaProductFixedTermContractList(new BaProductFixedTermContractTableManager().
            updateBaFixedTermContractListForBaProduct(data, context));
      } // BA459 is not for FTA, so only updates required, no add/remove

      // Update BaProductOloBookletOrder list
      if (data.getBaProductOloBookletOrderList() != null) {
        newData.setBaProductOloBookletOrderList(new BaProductOloBookletOrderTableManager().
            updateBaProductOloBookletOrderListForBaProduct(data, context));
      }

      //check for bank NL if there is the totalBlockForIdentiyCheck is removed
      if (isNL && newData.getProposals() != null && totalBlockForNoIdentityCheckChanged && !newData.isHasTotalBlockForNoIdentityChk()) {
        //make system-created debit limit change proposal visible
        BaProductDtLimitChangeProposalData cp = newData.getProposals().getDebitLimitChangeProposal();
        if (cp != null && cp.isInitiated()) {
          cp.setIcStatus(ChangeProposalICStatus.SUBMITTED);
          new ChangeProposalTableManager().changeChangeProposal(cp.getChangeProposalData(), context);
        }
      }

      final SDDBlockByDebtorService sddBlockByDebtorService = new SDDBlockByDebtorService();
      if (sddBlockByDebtorService.isBlockByDebtorTypeChangedToBlacklist(oldBaProduct, newData)) {
        sddBlockByDebtorService.deactivateWhitelist(newData, context);
      }

      // Add change events
      new BaProductConfigEventTableManager().addBaProductConfigEvents(oldBaProduct, newData, context);

      //if step-in service former account number or start date is changed, make a contact event for printing an step in service letter
      if (StringUtil.isStringDefined(data.getStepInServiceFormerIBAN()) &&
          (!data.getStepInServiceFormerIBAN().equals(oldBaProduct.getStepInServiceFormerIBAN()) ||
              !DateUtil.equalDates(data.getStepInServiceBeginDate(), oldBaProduct.getStepInServiceBeginDate()))) {
        // Flush CE tags cache
        TemplateDataCache.flushBaProductCache(data.getProductID());

        addStepInServiceContactEvent(data, context);
      }

      updateOptimisticLockValue(newData, context);

      return newData;
    } catch (TritonCreateException | TritonRemoveException | TritonSaveException e) {
      context.setRollbackOnly();
      throw new RelationshipException(e.getTritonMessages(), e);
    }
  }
}
class RequestAction {
  public void approveRequest(RequestData request)  {
    ...
    manageRelation.approveRequest(request);
    ...
  }
}
class ManageRelationBean {
  public void approveRequest(RequestData request)  {
    storePortfolioEvent(REQUEST_APPROVED, request.getRequestID());
  }
}
class PortfolioEngine {
  public void processPortfolioPostProcessingEvents() {
    new PortfolioTableManager()
        .getEvensToProcess()
        .forEach(event -> portfolioWorker.processEvent(event));
  }
}
class PortfolioWorker {
  
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void processEvent(final PortfolioData processItem) {
    try {
      switch (processItem.getEvent()) {
        case REQUEST_APPROVED:
          portfolioController.processApprvedRequest(processItem);
          break;
        ...
      }
      markEventAsProcessed(processItem);
    } catch (EJBException e) {
      markEventAsError(processItem);
    }
}
class RequestAction {
  public void approveRequest(RequestData request)  {
    ...
    manageRelation.approveRequest(request);
    ...
  }
}

Gliffy to the rescue

TODO

  • improve logging and monitoring
  • improve error handling
  • disaster recovery (auto-retry)
  • make coupling even looser
  • dynamically link functional blocks
  • introduce BPML

Experian

authentication and credit check

Experian

  • AuthenticatePlus
  • Blueprint = authentication + credit check
  • CAIS report (BKR)
  • QAS

Blueprint

  • Thou shall call this only once...
  • Web versus Screenless
  • Original Date of Birth?!
  • Experian Support Window

UK Addresses

UK Addresses

  • fuzzy matching
  • Experian: "In Ireland it's even worse..."

Lessons learned

  • Loose coupling, high cohesion
  • Explicitly test process flow
  • Have (and regularly run!) regression tests
  • Test happy paths, but focus on error scenario's
  • When dividing work over two teams:
    Share one branch (or do not refactor!)
Made with Slides.com