In diesem Abschnitt werden die Stellen aufgezeigt, an denen spezifische Anpassungen und Erweiterungen vorgenommen werden können. Mögliche Anpassungen werden entlang aus der Praxis bekannter Problemstellungen dokumentiert.

 

ProblemstellungBeschreibungBeispiel
ZuordnungslogikZuordnung von Referenzen zu Bankumsätzen

Anstelle der Referenznummer (Rechnungsnummer) soll eine modifizierte Referenznummer

im Verwendungszweck der Bankumsätze gesucht werden.

Bsp.: Bei der Referenz "R13.101" wird zusätzlich nach "R13101" und "R13 101" gesucht.

Eindeutigkeitsprüfung

Existenzprüfung beim MT940-Import

Weitere Datensatzfelder sollen bei der Eindeutigkeitsprüfung während des Imports von

MT940-Dateien berücksichtigt werden.

ZahlungsbedingungenPrüfung, ob die Zahlungsbedingungen erfüllt sindDie Logik zur Prüfung, ob die Zahlungsbedingungen erfüllt sind, soll angepasst werden.

Tabelle 4.7.4: Übersicht, spezifische Anpassungen und Erweiterungen

 

4.8.1 Zuordnungslogik

Die Zuordnung von eingehenden Bankumsätzen zu Referenzobjekten (Rechnungen, o.ä.) ist in der Methode findMatchingReferences() der abstrakten Klasse org.nuclet.mt940.logic.AbstractMT940Logic realisiert. Die Zuordnung erfolgt standardmäßig auf Grundlage der herausgestellten Referenznummer der Referenzobjekte:

Diese Logik wird durch den Ausdruck boBankTransaction.getInformationToAccountOwner().indexOf(strReference) > 0 (aus dem Code-Segment, s.u.) beschrieben.

Soll diese Regelung modifiziert werden (bspw. dahingehend, dass die Prüfung weniger strikt erfolgen soll), ist hier anzusetzen. Nach Möglichkeit sollte die Änderung nicht direkt in der Klasse AbstractMT940Logic vorgenommen werden. Die dafür vorgesehene Sourcecode-Stelle in der konkreten Implementierung MT940Logic ist entsprechend mit einem @replace!-Tag markiert.

 

Es wird empfohlen, die Änderungen in der konkreten Implementierung MT940Logic oder in einer eigenen Klasse vorzunehmen, die von der abstrakten Klasse AbstractMT940Logic erbt.


    /**
     * Matches the given bank transaction with a map of references: 
     * 
     * If the transaction's field "information to account owner" contains the reference, the related 
     * BusinessObject will be memorised, i.e. a list of all matching entries will be returned.
     * 
     * @note A more sophisticated, application specific, behaviour might be implemented in
     * dedicated subclasses
     * 
     * @param boBankTransaction a BusinessObject of type <code>BankTransaction</code>
     * @param mpReferences a map of references to be matched
     * @return a list of all BusinessObject that relate to a matching reference
     * 
     * @throws BusinessException might be thrown by implementing classes in case of errors or other exceptions
     */
    protected List<BusinessObject> findMatchingReferences(final BankTransaction boBankTransaction, 
        final Map<BusinessObject, String> mpReferences) 
    throws 
        BusinessException        
    {
        final ArrayList<BusinessObject> lstMatchingReferences = new ArrayList<BusinessObject>();
        final Set<BusinessObject> stReferencedBusinessObjects = mpReferences.keySet();
        
        // Check, if a matching reference exists...
        for (final BusinessObject businessObject : stReferencedBusinessObjects) {
            final String strReference = mpReferences.get(businessObject);
                
            if (boBankTransaction.getInformationToAccountOwner().indexOf(strReference) > 0) {
                log("matching reference found: " + strReference);
             
                lstMatchingReferences.add(businessObject);
            }
        }
        return lstMatchingReferences;
    }

 

4.8.2 Eindeutigkeitsprüfung

Die Prüfung, ob ein eingehender Datensatz aus dem MT940-Import bereits in der Datenbank vorhanden ist, wird in der Methode doesBankTransactionAlreadyExist() der abstrakten Klasse org.nuclet.mt940.logic.AbstractMT940Logic realisiert. Standardmäßig werden bei diesem Abgleich die folgenden Attribute berücksichtigt:

Soll diese Regelung modifiziert werden (bspw. dahingehend, dass die Prüfung weniger strikt erfolgt), ist hier anzusetzen. Nach Möglichkeit sollte die Änderung nicht direkt in der Klasse AbstractMT940Logic vorgenommen werden. Die dafür vorgesehene Sourcecode-Stelle in der konkreten Implementierung MT940Logic ist entsprechend mit einem @replace!-Tag markiert.

 

Es wird empfohlen, die Änderungen in der konkreten Implementierung MT940Logic oder in einer eigenen Klasse vorzunehmen, die von der abstrakten Klasse AbstractMT940Logic erbt.


    /**
     * Checks, if a representation of the given bank transaction has already been stored to the database.
     * 
     * Identifiying criteria could be a combination of:
     * 
     * - entry date
     * - value date
     * - amount
     * - debit credit mark
     * - business transaction type code / bank transaction type
     * - information to account owner 
     * 
     * @param transaction a given bank transaction, representated by a <code>Mt940Transaction</code> object
     * 
     * @return true, if a representation of the given bank transaction has already been stored to the database
     * 
     * @throws BusinessException might be thrown in case of errors or other exceptions
     */
    public boolean doesBankTransactionAlreadyExist(MT940Transaction transaction) throws BusinessException
    {
        Query<BankTransaction> qryGetExistingBankTransactions = QueryProvider.create(BankTransaction.class);          
        final DebitCreditMark debitCreditMark = debitCreditMarkFacade.getDebitCreditMark(transaction.getDebitCreditMark().getName());
        BankTransactionType bankTransactionType = null;
        
        if (transaction.getBusinessTransactionTypeCode() != null) {
            bankTransactionType = bankTransactionTypeFacade.getBankTransactionType(transaction.getBusinessTransactionTypeCode());
            
            qryGetExistingBankTransactions.where(BankTransaction.EntryDate.eq(transaction.getEntryDate()))
               .and(BankTransaction.ValueDate.eq(transaction.getValueDate()))
               .and(BankTransaction.Amount.eq(transaction.getAmount()))
               .and(BankTransaction.DebitCreditMark.eq(debitCreditMark.getId()))
               .and(BankTransaction.BankTransactionType.eq(bankTransactionType.getId()))
               .and(BankTransaction.InformationToAccountOwner.eq(transaction.getInformationToAccountOwner()));
        } else {
        
            qryGetExistingBankTransactions.where(BankTransaction.EntryDate.eq(transaction.getEntryDate()))
                   .and(BankTransaction.ValueDate.eq(transaction.getValueDate()))
                   .and(BankTransaction.Amount.eq(transaction.getAmount()))
                   .and(BankTransaction.DebitCreditMark.eq(debitCreditMark.getId()))
                   .and(BankTransaction.InformationToAccountOwner.eq(transaction.getInformationToAccountOwner()));
        }
                                                                                                                       
        final List<BankTransaction> lstBankTransactions = QueryProvider.execute(qryGetExistingBankTransactions);               
        
        return (lstBankTransactions != null && lstBankTransactions.size() > 0);
    }

 

4.8.3 Zahlungsbedingungen

sfdfs

package org.nuclet.mt940.rule;
 
 
import java.math.BigDecimal; 
import java.util.Calendar; 
import java.util.Collections; 
import java.util.Date;
import java.util.GregorianCalendar; 
import java.util.List; 
 
import org.nuclos.api.rule.UpdateFinalRule;
import org.nuclos.api.context.UpdateContext;
import org.nuclos.api.annotation.Rule;
import org.nuclos.api.exception.BusinessException;
import org.nuclos.api.provider.BusinessObjectProvider;
import org.nuclos.api.provider.QueryProvider;
import org.nuclos.api.provider.StatemodelProvider;

import org.nuclet.common.rule.AbstractRule;   

import org.nuclet.mt940.*;
import org.nuclet.mt940.facade.ReferenceFacade;
import org.nuclet.mt940.logic.MT940Logic;
import org.nuclet.mt940.statemodel.ReferenceStatemodel;
import org.nuclet.mt940.util.BankTransactionRefComparator;
import org.nuclet.mt940.wrapper.AbstractConditionsOfPaymentWrapper;
import org.nuclet.mt940.wrapper.AbstractReferenceWrapper;
 
/** 
 * @name CheckBankTransactionRef   
 * @description Checks if the changes done to the given bank transaction should result in subsequent changes on its references
 * @usage       
 * @change  
 * 
 * 
 * @version 1.2
 * @date 20.09.2013
 * @nuclet org.nuclet.MT940
 * @nucletversion 1.2.0
 * @sincenucletversion 1.0.0
 * @since 11.03.2013
 * @author frank.lehmann@nuclos.de
 * 
 */
@Rule(name="CheckBankTransactionRef", description="Checks if the changes done to the given bank transaction should result in subsequent changes on its references")
public class CheckBankTransactionRef extends AbstractRule implements UpdateFinalRule 
{
    private static final BigDecimal BGD_ONEHUNDRED = new BigDecimal("100.0");
    
    public void updateFinal(UpdateContext context) throws BusinessException 
    {
        initialize(context);
        
        checkReferences(context);        
        
    }
    
    
    /**
     * Check, if the changes done to the given bank transaction should result in further
     * changes and/or state changes of the references <code>BusinessObject</code>
     * 
     * @param context the current context
     * 
     * @throws BusinessException, in case an error or exception occurs
     */
    private void checkReferences(UpdateContext context) throws BusinessException
    {
        final BankTransaction boBankTransaction = context.getBusinessObject(BankTransaction.class);
        final List<BankTransactionRef> lstReferences = boBankTransaction.getBankTransactionRef();
         
        for (final BankTransactionRef ref : lstReferences) {
            final Long lngReferenceId = ref.getReferenceId(); 
            final AbstractReferenceWrapper reference = ReferenceFacade.getInstance().getById(lngReferenceId);
             
            if (ReferenceFacade.getInstance().getSourceStateNumbers(ReferenceStatemodel.StateChange.PaymentReceived)
                    .contains(reference.getNuclosStateNumber())) {

                Date datPaymentDate = checkFirstPriorityConditions(boBankTransaction, reference);
                
                if (datPaymentDate == null) {
                    datPaymentDate = checkSecondAndThirdPriorityConditions(reference);
                } 

                if (datPaymentDate != null) {
                    reference.setPaymentDate(datPaymentDate);
                    BusinessObjectProvider.update(reference.getBusinessObject());
                    StatemodelProvider.changeState(reference.getBusinessObject(), 
                        ReferenceFacade.getInstance().getDestinationState(ReferenceStatemodel.StateChange.PaymentReceived));                    
                }                
             }
        }
    }
    
    /**
     * Checks, if the first priority conditions have been fulfilled, i.e.
     * 
     * 1a. the reference is marked as "accept first incoming payment" 
     * 
     * and
     * 
     * 1b. an incoming payment has been assigned
     *
     * @param boBankTransaction A <code>BusinessObject</code> of type <code>BankTransaction</code>
     * @param reference An object of type <code>AbstractReferenceWrapper</code>, encapuslating the
     * reference object (client billing, invoice, etc.)
     * 
     * @return The payment date, if the reference is marked as "accept first incoming payment" and 
     * an incoming payment has been assigned
     * 
     */
    private Date checkFirstPriorityConditions(final BankTransaction boBankTransaction, final AbstractReferenceWrapper reference)
    {        
        final Long lngDebitCreditMarkId = boBankTransaction.getDebitCreditMarkId();
        final DebitCreditMark boDebitCreditMark = QueryProvider.getById(DebitCreditMark.class, lngDebitCreditMarkId);

        if (boDebitCreditMark.getSign().intValue() > 0 && reference.getAcceptFirstIncomingPayment()) {
            return boBankTransaction.getEntryDate();
        } else {
            return null;
        }
    }
    
    /**
     * Checks, if the second or third priority conditions have been fulfilled, i.e.
     * 
     * 2. the reference's total amount has been balanced by incoming payments
     * 
     * or
     * 
     * 3. the cash discount conditions are met
     *
     * @param reference An object of type <code>AbstractReferenceWrapper</code>, encapuslating the
     * reference object (client billing, invoice, etc.)
     * 
     * @return The payment date, if the second or third priority conditions have been fulfilled, i.e.
     * 
     */
    private Date checkSecondAndThirdPriorityConditions(final AbstractReferenceWrapper reference)
    {        
        final List<BankTransactionRef> lstBankTransactionRef = reference.getBankTransactionRef();
        final AbstractConditionsOfPaymentWrapper conditionsOfPayment = reference.getConditionsOfPayment();
        Calendar calCashDiscountLimit = null;
        BigDecimal bgdTotalAmountDicounted = BigDecimal.ZERO;
        
        if (conditionsOfPayment != null && conditionsOfPayment.getCashDiscount() != null) {
            bgdTotalAmountDicounted = reference.getTotalAmountGross().subtract(
                    reference.getTotalAmountGross().multiply(conditionsOfPayment.getCashDiscount()).divide(BGD_ONEHUNDRED, 10, BigDecimal.ROUND_HALF_UP)
                );
                
            if (conditionsOfPayment.getCashDiscountPeriod() != null) {
                calCashDiscountLimit = new GregorianCalendar();
                calCashDiscountLimit.setTime(reference.getDateOfInvoice());
                calCashDiscountLimit.add(Calendar.DAY_OF_YEAR, conditionsOfPayment.getCashDiscountPeriod());
            }
        }
                
        if (lstBankTransactionRef != null) {
            Collections.sort(lstBankTransactionRef, new BankTransactionRefComparator());
        
            Date datPaymentDate = null;                               
            Date datDiscountedPaymentDate = null;
            boolean bHasReferenceAmountBeenBalanced = false;
            boolean bHasDiscountedReferenceAmountBeenBalanced = false;
            BigDecimal bgdTotalPaymentAmount = BigDecimal.ZERO;
                        
            for (final BankTransactionRef boBankTransactionRef : lstBankTransactionRef) {                 
                final Long lngOtherBankTransactionId = boBankTransactionRef.getBankTransactionId();
                final BankTransaction boOtherBankTransaction = QueryProvider.getById(BankTransaction.class, lngOtherBankTransactionId);
                final Long lngOtherDebitCreditMarkId = boOtherBankTransaction.getDebitCreditMarkId();
                final DebitCreditMark boOtherDebitCreditMark = QueryProvider.getById(DebitCreditMark.class, lngOtherDebitCreditMarkId);                                       
                 
                if (boOtherDebitCreditMark.getSign().intValue() > 0) {
                     bgdTotalPaymentAmount = bgdTotalPaymentAmount.add(boOtherBankTransaction.getAmount());
    
                     if (!bHasReferenceAmountBeenBalanced && 
                         bgdTotalPaymentAmount.doubleValue() >= reference.getTotalAmountGross().doubleValue()) {
                         bHasReferenceAmountBeenBalanced = true;
                         datPaymentDate = boOtherBankTransaction.getEntryDate();
                     }
                
                     if (!bHasDiscountedReferenceAmountBeenBalanced && 
                         !BigDecimal.ZERO.equals(bgdTotalPaymentAmount) &&
                         bgdTotalPaymentAmount.doubleValue() >= bgdTotalPaymentAmount.doubleValue()) {
                         bHasDiscountedReferenceAmountBeenBalanced = true;
                         datDiscountedPaymentDate = boOtherBankTransaction.getEntryDate();
                     }
                } else {
                     bgdTotalPaymentAmount = bgdTotalPaymentAmount.subtract(boOtherBankTransaction.getAmount());
    
                     if (bHasReferenceAmountBeenBalanced && 
                         bgdTotalPaymentAmount.doubleValue() < reference.getTotalAmountGross().doubleValue()) {
                         bHasReferenceAmountBeenBalanced = false;
                         datPaymentDate = null;
                      }

                     if (bHasDiscountedReferenceAmountBeenBalanced && 
                         !BigDecimal.ZERO.equals(bgdTotalPaymentAmount) &&
                         bgdTotalPaymentAmount.doubleValue() < bgdTotalPaymentAmount.doubleValue()) {
                         bHasDiscountedReferenceAmountBeenBalanced = false;
                         datDiscountedPaymentDate = null;
                     }
                }
            }

            if (bgdTotalPaymentAmount.doubleValue() >= reference.getTotalAmountGross().doubleValue()) {
               return datPaymentDate;
            } else if (bgdTotalPaymentAmount.doubleValue() < reference.getTotalAmountGross().doubleValue()
                       && datDiscountedPaymentDate != null && calCashDiscountLimit != null
                       && datDiscountedPaymentDate.before(calCashDiscountLimit.getTime())) {
                return datDiscountedPaymentDate;
            } else {
                return null;
            }
        } else {
            return null;
        }        
    }
}