Implementing the Accounting Analysis Pattern


In this post, I will implement accounting analysis pattern.  While this pattern is called “accounting”, it isn’t strictly limited to traditional accounting systems, but can also be used in inventory, utility billing, payroll, and others.   The basic idea of accounting pattern is events will be translated by posting rules into entries which are stored in an account.   More information about accounting analysis pattern can be found at http://martinfowler.com/apsupp/accounting.pdf.

I will use a hypothetical motorcycle service station (in Indonesia, this is called “bengkel motor”) work orders processing as an example.   Work order is created when customer requests his motorcycle to be repaired.   This work order will then join a queue, waiting to be processed by a free mechanic.   While repairing the motorcycle, mechanic sometimes need to request replacement for broken spare part.   Those requests should be added to the work order.

Accounting pattern is quite abstract, so implementation in this post is based on my perception and fine-tuned for the case above.  I see a work order as an account.   Its entries consist of service cost, spare part price, amount of discount, registration fee and others.  For the sake of simplicity, I will exclude spare part replacements in this implementation.   The balance of the work order account is the amount that should be paid by customer.   Entries are created based on work order’s events.   Examples of such events are repair request, spare part replacement request, reparation complete, and payment.   These events usually don’t have pricing information, so posting rules will translate these events into entries which have an amount.

I’m using Griffon 1.2 and simple-jpa 0.4.1 for the implementation. The complete source code for this post can be found at https://docs.google.com/file/d/0B-_rVDnaVRCbb2pKRmk4djctMTQ/edit?usp=sharing.

I will start by defining abstract classes that represent the fundamental classes in account pattern, as shown below:

Abstract Classes In Accounting Pattern

Abstract Classes In Accounting Pattern

To keep it simple, I represent AccountEventType and EntryType as an enumeration; by the way, simple-jpa 0.4.1 scaffolding will generate the enumeration as a combo box.   In a real world application, they are often represented by entities.

Here is the source code of Account.groovy:

package domain

import ...

@DomainModel
@Entity
@Canonical
abstract class Account {

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="account")
    List<Entry> entries = []

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="account")
    List<AccountingEvent> events = []

    abstract Number balance()

}

I will start from Account implementation as shown below:

The Account

The Account

Every WorkOrder has a WorkType, Customer and Pricing. WorkType and Customer are obvious, but why there is a Pricing class? The purpose of Pricing is to store price-related information including promotions and discounts. They can change from time to time, so it is better if I have the history records.
This is the source code of WorkType.groovy:

package domain

import ...

@DomainModel
@Entity
@Canonical
class WorkType {

    @NotEmpty @Size(min=2, max=2)
    String codeName

    @NotEmpty @Size(min=3, max=50)
    String name

    String toString() {
        "$codeName - $name"
    }

}

The picture below is the basic CRUD screen for WorkType class:

WorkType screen

WorkType screen

This is the source code of Customer.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
class Customer {

    @NotEmpty @Size(min=3, max=50)
    String name

    @NotEmpty @Size(min=6, max=15)
    String plateNumber

}

Customer class in real world application usually has more attributes.   The screen for Customer is a simple CRUD, but with one additional button that allow user to create a new WorkOrderEvent per customer.

Customer Screen

Customer Screen

The last domain class referenced by WorkOrder is Pricing which is a container for one or more PostingRule instances.  In accounting pattern, PostingRule will create Entry instances based on an AccountingEvent.  This is the source code of PostingRule.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
abstract class PostingRule {

    @NotBlank @Size(min=3, max=50)
    String name

    @NotNull @Enumerated
    EntryType type

    @NotNull @Enumerated
    AccountingEventType targetEventType

    abstract void process(AccountingEvent event)

}

AccountingEvent will invoke PostingRule’s process() method to generate one or more instances of Entry.  User can configure event type (AccountingEventType) that will trigger the execution of PostingRule.  For example, PostingRule for calculating down payment should be executed on AccountingEventType.REGISTRATION. PostingRule for calculating discount should be executed on the final event (AccountingEventType.PAYMENT), otherwise the discount won’t be calculated based on total amount.

I will create two subclasses from PostingRule as shown in the picture below:

The PostingRule

The PostingRule

This is the source code of WorkTypeCostPR.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
class WorkTypeCostPR extends PostingRule {

    @NotNull @ElementCollection
    Map<WorkType, BigDecimal> priceList = [:]

    WorkTypeCostPR() {}

    WorkTypeCostPR(String name, EntryType type, 
             AccountingEventType targetEventType, 
             Map<WorkType, BigDecimal> priceList) {
        super(name, type, targetEventType)
        this.priceList = priceList
    }

    @Override
    void process(AccountingEvent event) {
        if (event instanceof WorkOrderEvent) {
            WorkOrderEntry entry = new WorkOrderEntry(date: event.whenOccured, postingRule: this,
                amount: priceList[event.account.workType], entryType: type, event: event, account: event.account)
            event.account.entries << entry
            event.resultingEntries << entry
        }
    }
}

WorkTypeCostPR has a Map which stores price for each work type. When an instance of WorkTypeCostPR is invoked by AccountingEvent, it will generate an Entry instance which contains amount (price) based on its priceList Map’s value. The picture below shows an example configuration for an instance of WorkTypeCostPR:

WorkTypeCostPR View

WorkTypeCostPR View

The next subclass is FormulaPR.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
class FormulaPR extends PostingRule {

    @NotEmpty
    String formula

    FormulaPR() {}

    FormulaPR(String name, EntryType type, 
           AccountingEventType targetEventType, String formula) {
        super(name, type, targetEventType)
        this.formula = formula
    }

    public void process(AccountingEvent event) {
        if (event instanceof WorkOrderEvent) {
            def amount = Eval.x(event.account, formula)
            WorkOrderEntry entry = new WorkOrderEntry(date: event.whenOccured, postingRule: this,
                amount: amount, entryType: type, event: event, account: event.account)
            event.account.entries << entry
            event.resultingEntries << entry
        }
    }

}

FormulaPR is a PostingRule that has a Groovy expression, as shown in the picture below:

FormulaPR View

FormulaPR View

I’m using Eval from Groovy to execute the expression.  Variable x in the expression will be substituted by the affected Account (in this case, the Account is an instance of WorkOrder).  The expression should return a Number, which will be stored as an Entry of the WorkOrder.  Groovy also has nice features that make creating Domain Specific Language (DSL) painless.  Developing a DSL for posting rule will take more time, but it is easy to use for non-technical user who don’t understand (or hate) programming.

Now I can add these rules in Pricing, as shown in the picture below:

Pricing View

Pricing View

While I can have more than one Pricing objects, only one Pricing object should be marked as active at a time.  All new Entry objects for WorkOrder will be calculated based on the active Pricing.

It is time to create WorkOrder.groovy.  This is the source code:

package domain

import ...

@DomainModel @Entity @Canonical(excludes = "events, entries")
class WorkOrder extends Account {

    @NotEmpty @Size(min=5, max=5)
    String orderNumber

    @NotNull @ManyToOne
    Customer customer

    @NotNull @ManyToOne
    Pricing pricing

    @NotNull @ManyToOne
    WorkType workType

    BigDecimal balance() {
        entries.sum { Entry entry ->
            entry.getAmount()
        } ?: 0
    }

    Collection<WorkOrderEvent> getEvent(AccountingEventType eventType) {
        events.findAll { AccountingEvent event ->
            event.eventType == eventType && event.deleted != 'Y'
        }
    }


}

WorkOrder is a subclass of Account which has one or more Entry objects in List (order is important).  In the case of WorkOrder, its Entry objects are instance of WorkOrderEntry.

The Entry

The Entry

This is the source code for Entry.groovy:

package domain

import ...

@DomainModel
@Entity
@Canonical
abstract class Entry {

    @NotNull @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    DateTime date

    @NotNull @ManyToOne
    AccountingEvent event

    @NotNull @ManyToOne
    Account account

    @NotNull @OneToOne
    PostingRule postingRule

    @NotNull @Enumerated
    EntryType entryType

    abstract Number getAmount()

}

And this is the source code for WorkOrderEntry.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
class WorkOrderEntry extends Entry {

    @NotNull
    BigDecimal amount

}

WorkOrderEntry contains only a BigDecimal attribute that store an amount.  User can’t create or edit WorkOrderEntry;  it will be created automatically from a WorkOrderEvent based on active Pricing.  Once created, it can’t be changed.

Next, I will create a subclass of AccountingEvent as shown in the picture below:

The AccountingEvent

The AccountingEvent

This is the source code of AccountingEvent.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
abstract class AccountingEvent {

    @NotNull @Enumerated
    AccountingEventType eventType

    @NotNull @Past @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    DateTime whenNoticed

    @NotNull @Past @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    DateTime whenOccured

    @NotNull @ManyToOne
    Account account

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="event")
    Set<Entry> resultingEntries = new HashSet<>()

    abstract void process()

}

Attribute whenNoticed is the time when user entered the event, while whenOccured is the actual time when it happens. This allow user to enter an event that occurred in the past.

This is the source code of WorkOrderEvent.groovy:

package domain

import ...

@DomainModel @Entity @Canonical
class WorkOrderEvent extends AccountingEvent {

    @Override
    void process() {
        account.pricing.postingRules.each { PostingRule pr ->
            if (pr.targetEventType == eventType) {
                pr.process(this)
            }
        }
        account.events << this
    }

}

WorkOrderEvent in this simple implementation doesn’t have additional attributes; what it does is only overrides process() from its parent class.

To create a new WorkOrderEvent, user will select a Customer and click on “New Work Order” button as show in the picture below:

Creating a New WorkOrder

Creating a New WorkOrder

This will create a new WorkOrderEvent whose type is AccountingEventType.REGISTER.   The event will be associated with a new WorkOrder.   User can find more information in WorkOrder’s view:

WorkOrder view

WorkOrder view

To create additional events, user can click on one of “Order in Progress”, “Order Has Done”, and “Payment”.

Trigger an Event

Trigger an Event

In real world application, these buttons often have their own view.   AccountingEvent is not as simple as in this implementation; it will have more subclass in real application.   For example, “Working” event should have included a mechanic (person who handles the work order).   “Payment” event should have included cashier’s name and payment information.

To display list of events for selected WorkOrder, user can click on “Show Events” button:

Display Events For a WorkOrder

Display Events For a WorkOrder

To display list of entries for selected WorkOrder, user can click on “Show Entries” button:

Display Entries For a WorkOrder

Display Entries For a WorkOrder

Events and entries are immutable (can’t be modified).   Even when user changes the value of active pricing and posting rules, it will not affect saved entries.   This is always the desirable behavior; rule changes (including prices and discounts) should only affect new entries and leave the old entries intact.
What if user made a mistake when entering an event?   I will use reversal adjustment pattern to correct the event and its entries.  To do that, I need to add a new AccountingEvent subclass:

The RevokedEvent

The RevokedEvent

This is the source code for RevokedEvent.groovy:

package domain

import ...

@DomainModel
@Entity
@Canonical
class RevokedEvent extends AccountingEvent{

    @NotNull @OneToOne
    AccountingEvent originalEvent

    public RevokedEvent() {}

    public RevokedEvent(AccountingEvent originalEvent) {
        this.account = originalEvent.account
        this.eventType = originalEvent.eventType
        this.whenNoticed = DateTime.now()
        this.whenOccured = originalEvent.whenOccured
        this.originalEvent = originalEvent
    }

    @Override
    void process() {
        originalEvent.resultingEntries.each { entry ->
            AdjusmentEntry adjusmentEntry = new AdjusmentEntry(entry)
            originalEvent.account.entries << adjusmentEntry
            resultingEntries << adjusmentEntry
        }
        originalEvent.deleted = 'Y'
    }
}

RevokedEvent has a reference to the revoked (or deleted) event. The RevokedEvent will cause WorkOrder (our Account) to have AdjusmentEntry which zeroes the revoked event’s Entry:

The AdjusmentEntry

The AdjusmentEntry

This is the source code for AdjusmentEntry.groovy:


package domain

import ...

@DomainModel
@Entity
@Canonical
class AdjusmentEntry extends Entry {

    public AdjusmentEntry() {}

    public AdjusmentEntry(Entry adjustedEntry) {
        this.account = adjustedEntry.account
        this.date = adjustedEntry.date
        this.entryType = adjustedEntry.entryType
        this.event = adjustedEntry.event
        this.postingRule = adjustedEntry.postingRule
        this.adjustedEntry = adjustedEntry
    }

    @NotNull @OneToOne
    Entry adjustedEntry

    @Override
    Number getAmount() {
        -adjustedEntry.amount
    }
}

The picture below show the result of correcting an event:

Corrected Event

Corrected Event

Corrected Entries

Corrected Entries

A better implementation will allow user to filter out the revoked and adjustment entries.  To provide more verbose auditing information, the system should also record the user who performs correction.
Up to this point, I’ve implemented a working application based on Martin Fowler’s accounting analysis pattern.  This is the complete class diagram for my implementation:

The Class Diagram

The Class Diagram

Perihal Solid Snake
I'm nothing...

3 Responses to Implementing the Accounting Analysis Pattern

  1. Komang Hendra Santosa mengatakan:

    Mas, klo donload versi terbaru simple-jpa dmn ya?

    • Solid Snake mengatakan:

      Versi terbaru 0.4.1 barusan dirilis di portal plugin Griffon, dapat di-download dengan menggunakan perintah install-plugin. Terima kasih sudah mau mencoba🙂

  2. Ping-balik: Using Accounting Analysis Pattern For Inventory | The Solid Snake

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: