Currently Adempiere ships outbound orders as soon as they fulfill the delivery rule criteria of the order, not considering if there are other earlier but not fulfilled orders that should have had the items shipped if the order was fulfilled. This means that orders can be starved.
This rules that make up this behaviour in Adempiere is called Delivery Policy.
This article describes the technical aspects of Delivery Policy. To read how to set up and use Delivery policy see Delivery Policy Guide.
- 1 No hold
- 2 Strict order
- 3 Solution
- 4 Use cases
- 5 Files affected
- 6 Database objects affected
- 7 Known issues
- 8 Other solutions
- 9 See also
The current Delivery Policy I choose to call "No Hold" meaning that no items are held for a specific customer and orders are shipped as soon as they are fulfilled. This means that some orders might never be fulfilled and the risk of starvation increases the bigger the more items there are on the order.
The Delivery Policy "Strict order" means that material must be allocated to the order before it is shipped. Only material on hand can be allocated. Material is allocated in the order defined by the orders which are:
- Priority of the order
- Date of the order line (ie when it was created)
In Compiere's version of their WMS they have added the following field to order line and storage:
QtyAllocated represents the number of physically available items (On Hand) that have been allocated to a specific order. The sum of QtyAllocated for all locators of a given product can never be more than what is physically on hand.
This is in contrast to the value "QtyReserved" which is the sum of all orders for a specific product that hasn't yet been delivered. If there's not enough in stock QtyReserved is more than what's in stock.
In this solution we add the field QtyAllocated to COrderLine and MStorage just as they have done in Compiere.
The allocation process would be a process that is run whenever the allocation of a product is changed. The allocation process will only run if Delivery Policy is "Strict order". Allocation process is run at the following points in time:
- Order complete / voided
- Receipt complete / voided
- Inventory adjustment complete / voided
The process can be run either on just the products affected or in general (allocate all). Normally "allocate all" would only be required when switching Delivery Policy.
Allocation is done according to:
- Order priority
- Order (line)date
Delivery policy should be a setting on Client and Organizational level. The organizational level will override the client setting.
If delivery policy is "No hold", Adempiere will work as it always has (backwards compatible).
If delivery policy is set to "Strict order" only orders that have items allocated will be shipped and no more than what's allocated will be shipped. The allocation only applies to physical items that are stocked.
This new functionality will have impact on the following scenarios:
Reserve stock happens when the order is prepared. An order can always reserve stock whether or not there's enough material on hand. Allocating can only happen when there's enough material on hand. One option (which I will go for in this stage) is to not allocate stock at this point. If material is allocated at this point, it must also be unallocated if the order is cancelled.
Delete order line
If an order line is reserved (and possible allocated), the reservation and allocation must be cleared.
Increase quantity on order
If the quantity is increased, the allocation must be increased with the difference. This is no problem since it is covered by the regular allocation process.
Decrease quantity on order
This is not detected by the regular allocation process. It is also desireable to "release" the allocation as quick as possible so this should be done as soon as the order is prepared or completed. Release of allocation is done in the MOrder.reserveStock method.
Allocate Sales Orders
This is a process that considers all completed sales orders and allocates products on hand in a defined order. Highest priority and oldest orders gets allocated first. The reason why allocation is not done at the complete order step is that the complete order step only considers the order at hand at the moment. One possible solution could be that if there's no shortage of products, allocation will happen when the order is completed. So allocation could happen in these scenarios:
- Start of allocation process.
- When an order is completed (if there's no shortage of stock).
- When a material receipt is completed.
When shipments are generated, they will be generated based on what is allocated. I've put some effort into the view m_inout_candidate_v to make sure only true inout candidates show up on the list.
The inout candidate view now considers:
- Delivery Rule of the order
- Delivery Policy
When a shipment is shipped the number of allocated items in stock must decrease with the same amount that is delivered. If not, we'll have more allocated items than what is in stock.
File to modify: MInOut
There's a process called StorageCleanup. It creates movement transactions for unbalanced inventory, ie to fill upp locations with negative on hand quantity. The best would be if we can prevent negative on hand quantity. This process doesn't work in 3.5.4a. When run it only creates empty move lines.
org.adempiere.process.AllocateSalesOrders org.adempiere.model.I_AD_ClientInfo - property DeliveryPolicy org.adempiere.model.I_AD_OrgInfo - property DeliveryPolicy org.adempiere.model.I_C_OrderLine - property QtyAllocated org.adempiere.model.I_M_Storage - property QtyAllocated org.adempiere.model.MInOut - update QtyAllocated on order when shipments completed org.adempiere.model.MInventory - Adjust for change in MStorage.add org.adempiere.model.MMovement - Adjust for change in MStorage.add org.adempiere.model.MOrder - Adjust for change in MStorage.add (reserveStock) org.adempiere.model.MOrderLine - new method allocateOnHand(toAllocate) org.adempiere.model.MOrgInfo - property DeliveryPolicy org.adempiere.model.MProjectIssue - Adjust for change in MStorage.add org.adempiere.model.MStorage - property QtyAllocated / change in function add org.adempiere.model.X_AD_ClientInfo - property DeliveryPolicy org.adempiere.model.X_AD_OrgInfo - property DeliveryPolicy org.adempiere.model.MClientInfo - added constants org.adempiere.model.X_C_OrderLine - property QtyAllocated org.adempiere.model.X_M_Storage - property QtyAllocated org.adempiere.validator.MaterialReceiptModelValidator - Allocate at receipt org.compiere.process.InOutGenerate - Consider delivery policy org.compiere.process.M_Production_Run - Adjust for change in MStorage.add org.compiere.process.StorageCleanup - Cleanup allocations org.eevolution.model.MDDOrder - Adjust for change in MStorage.add org.compiere.apps.form.Match - Adjust for change in MStorage.add
Database objects affected
- view m_product_stock_v
- function isshippable(product_id numeric)
- function get_delivery_policy(warehose_id numeric)
- function is_inout_candidate_orderline
- function is_inout_candidate_order
- function get_allocated_on_order
- view m_inout_candidate_v
Delivery policy doesn't yet handle BOM:s.
There are other solutions developed to this problem.
Lieferdisposition is a solution developed by Metas. It's developed as a module that can be integrated with a standard Adempiere release. It replaces the current shipment generator with Metas shipment generator.
This solution don't work if you use shipment confirmations.
Libero Warehouse Management
Libero Warehouse Management supports delivery policy according to the documentation:
If all you want is to make sure customer's orders are not starved (delivery policy = Strict order), the Libero WMS might be overkill.