Guide‎ > ‎Logic Design Patterns‎ > ‎

Allocation


Context


The allocation pattern can be summarized as follows:

Allocation

Allocates a provider amount to designated recipients
creating allocation objects (a Provider/Recipient Junction) for each such allocation.


Put differently, the system receives a quantity of things (money, goods), and allocates them to a set of recipients.  Some examples:
  • Allocate a Payment to set of Purchase Orders
Allocate a provider Payment.amount to designated recipient Purchase Orders
creating allocation Payment Purchaseorder Allocation objects (a Provider/Recipient Junction) for each such allocation.

  • Allocate a Department Bonus to a set of Employees

  • Allocate an expenditure to a set of General Ledger accounts

  • Allocate a sum to a set of organizations (who, via chained allocations, allocate their allotment to designated General Ledger accounts)

You can provide procedural logic to solve this.  However, this is costly, and recurs often.  So, we seek a re-usable solution.

Solution: provide re-usable Early Action Logic


Allocate provides  methods which can be invoked as a derivation rule, which will perform the function summarized above via a single rule.  Here is the Formula business logic for Payment.amountUnDisbursed:

@EarlyAction(verbs = Verbs.INSERT
void actionAllocatePayment() {
payment.amountUnDisbursed = BusinessLogic.allocateFromTo( 
logicContext,                                                  // provider 
payment.customer.purchaseorders.findAll{ it.amountUnPaid > 0}, // recipients 
PaymentPurchaseorderAllocation.class)                          // allocation object
}



Example: Allocate a Payment to Purchase Orders

Allocation is most easily understood by this example.  When inserting a Payment, the business logic shown above operates as follows to perform the allocation:

Allocate a provider Payment.amount 
to designated recipient Purchase Orders
creating allocation Payment Purchaseorder Allocation objects (a Provider/Recipient Junction) for each such allocation.




Notes:

Step 1 is simply the business logic shown above.  This logic is invoked as a consequence of inserting a Payment row.

Step 2 summarizes the operation of the allocation rule:
  1. It reads each recipient (Purchaseorder), submitted as the second parameter.  For each:
    1. Creates a new instance of PaymentPurchaseorderAllocation (third parameter)
    2. Initializes the amount field to the remaining allocatable amount (initially 450)
    3. Forward Chains the Insert logic, which
      1. Derives the amount value  
        [
        paymentPurchaseorderAllocation.purchaseorder.amountUnPaid,
         
        paymentPurchaseorderAllocation.payment.amountUnDisbursed].min()
      2. Adjusts the sum attribute Purchaseorder.amountPaid
      3. This recomputes Purchaseorder.amountUnPaid
      4. This adjusts Customer.balance
  2. The iteration proceeds with the second Purchaseorder, decrementing Payment.amountUnDisbursed with each allocation, thus reducing it from $450 (initial Payment amount) to $50 (the amount not disbursed)
  3. There are two possible exit conditions:
    1. Recipients exhausted: in this case the allocation amount is not exhausted by the recipients; that is why allocation is a formula (not an action), so that it can return this value
    2. Amount exhausted: Conversely, we might not have processed all the Purchaseorders before exhausting the initial value; this is simply an alternate exit condition for the allocation iteration


You can capture all of this with the logicdoc, which describes 2 requirements for the Use Case:

  1. allocation requirement (described above)
  2. reduce the customers' balance
    1. Note: the rules for this second requirement are re-used from those defined for Place Order, so in actual implementation, you only would have defined the two rules for the first requirement

Controlling the Allocated Amount

Observe Note 1.3 above.  The allocation logic computes only the maximum allocatable amount, which is the entire unallocated amount.  This is rarely what is actually allocated.

You specify the proper allocated amount using derivation formulas on the allocation object.  In our example, we want to allocate the purchase orders' remaining amountUnPaid, or the remaining allocation amount if that is not sufficient.  So we specify the following on PaymentPurchaseorderAllocationLogic:

/**

* @return min (po.amountUnPaid, payment.amountUnDisbursed)

*/

@Formula

BigDecimal deriveAmount() {

if (logicContext.verb == Verb.INSERT) {

return [paymentPurchaseorderAllocation.purchaseorder.amountUnPaid,

          paymentPurchaseorderAllocation.payment.amountUnDisbursed].min()

}

}



Allocation Operation


Allocation operates as follows:
 TBD


API Alternatives

There are several alternatives for specifying allocation, noted below.

Allocation Closure

The diagram below illustrates the use of Groovy closures:



A closure is a block of code you pass in as an argument (see line 60) to a receiver.  The receiver may invoke the closure, possibly with arguments (line 48).  The closure operates in the original calling context, so has access to all variables in that context e.g, the payment bean).

Here, Allocation invokes your supplied closure for each allocation row it creates, passing the initialized allocation row as an argument.  As shown above, your code compute the allocation amount.



InsertChildrenFrom closure

Fans of ultra-minimalist design can utilize InsertIntoFrom as illustrated below (note line 56):

This illustrates that Allocation is really an InsertChildren, using a common pattern of assignments for each copied row in conjunction with a loop-exit condition.



Comments