Generate Orders may get it wrong

In our system, the Suppliers|Orders|Generate Orders function has been generating some funny quantities. After some digging, I think that there are two errors in the logic.

1. no accounting for a possible change in package size

The 'whats in the pipeline' logic looks for orders that are not CANCELLED with delivery status not FULL and sums qty ordered less qty received less qty cancelled - which gives the qty outstanding. However, this is in units of package size - and the code makes the assumption that this never changes - ie each order uses the same package size - though this could change because of orders to different suppliers, or changes in packaging by the one supplier.

The SQL currently looks in part like:

       select  productParticipation.entity_id as product_id,
               stockParticipation.entity_id as stock_id,
               sum(forderItem.quantity) as orderedQty,
               sum(receivedQty.value) as receivedQty,
               sum(cancelledQty.value) as cancelledQty,
               orderPackageSize.value as orderPackageSize

It should in fact work with stocking units rather that ordering units - ie it should look like:

       select  productParticipation.entity_id as product_id,
               stockParticipation.entity_id as stock_id,
               sum(forderItem.quantity*orderPackageSize.value) as orderedQty,
               sum(receivedQty.value*orderPackageSize.value) as receivedQty,
               sum(cancelledQty.value*orderPackageSize.value) as cancelledQty

Of course the code in OrderGenerator.java also need to be adjusted to take account of the change to stocking units - ie from:

                BigDecimal orderedQty = getDecimal("orderedQty", set);
                BigDecimal receivedQty = getDecimal("receivedQty", set);
                BigDecimal cancelledQty = getDecimal("cancelledQty", set);
                int orderPackageSize = set.getInt("orderPackageSize");
                int size = (packageSize != 0) ? packageSize : orderPackageSize;
                if (size != 0) {
                    BigDecimal decSize = BigDecimal.valueOf(size);
                    BigDecimal onOrder = orderedQty.subtract(receivedQty).subtract(cancelledQty).multiply(decSize);

                    BigDecimal current = quantity.add(onOrder); // the on-hand and on-order stock
                    BigDecimal toOrder = ZERO;
                    BigDecimal units = idealQty.subtract(current); // no. of units required to get to idealQty
                    if (!MathRules.equals(ZERO, units)) {
                        // Round up as the desired no. may be less than a packageSize, but must order a whole pack.
                        toOrder = units.divide(decSize, 0, RoundingMode.UP);
                    }

to something like:

                BigDecimal orderedQty = getDecimal("orderedQty", set);
                BigDecimal receivedQty = getDecimal("receivedQty", set);
                BigDecimal cancelledQty = getDecimal("cancelledQty", set);
                int orderPackageSize = set.getInt("orderPackageSize");
                int size = (packageSize != 0) ? packageSize : orderPackageSize;
                if (size != 0) {
                    BigDecimal decSize = BigDecimal.valueOf(size);
                    BigDecimal onOrder = orderedQty.subtract(receivedQty).subtract(cancelledQty);

                    BigDecimal current = quantity.add(onOrder); // the on-hand and on-order stock
                    BigDecimal toOrder = ZERO;
                    BigDecimal units = idealQty.subtract(current); // no. of units required to get to idealQty
                    if (!MathRules.equals(ZERO, units)) {
                        // Round up as the desired no. may be less than a packageSize, but must order a whole pack.
                        toOrder = units.divide(decSize, 0, RoundingMode.UP);
                    }

Note that this problem is not the cause of our funnies - but came to light as I was staring at the code.

2. no accounting for over-deliveries

If more of an item is received than is ordered, then the above logic mis-calculates the quantity required.

Assume that the current stock is 3, and the ideal qty is 10. If we previously ordered 10 and received 25, and cancelled 0, then onOrder =  10-25-0 = -15, and toOrder = 10 - (3 + (-15) ) = 22.

That is the over-delivery should be ignored, but it is not.

The problem is that onOrder should be calculated as max(0,(ordered-cancelled-received)) so that any over deliveries are ignored. In the above example orOrder would then be 0, and toOrder = 10 - (3+0) = 7

 

Note that both the above problems show themselves only in peculiar conditions. In our case we have an inexperienced logistics person causing the system to have old orders with delivery status PART which include over-deliveries.  Hence, I think that I can straighten things out by changing the order status of these old part delived orders from POSTED to CANCELLED so that the Generate Orders function will ignore them.

Regards, Tim G

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Re: Generate Orders may get it wrong

I am working through this Tim, first tip you shouldnt mix business logic with data access

sum(forderItem.quantity*orderPackageSize.value) as orderedQty,
               sum(receivedQty.value*orderPackageSize.value) as receivedQty,
               sum(cancelledQty.value*orderPackageSize.value) as cancelledQty

is doing that.

Return the raw data then mess with it in your business rules....dont calculate during Data access.

I am not exactly sure what you mean the package size is linked to each supplier stock link in the product and is not set at the product level.  ie if I order from Provet and they send me a 10ml bottle its recorded and if Lyppard sell me a 20ml bottle its recorded against in the stocksupplier relationship as package size.  This is the default - it would be wierd if it was 0 because it only gets created 2 ways...1 you enter it manaully 2 its created from a delivery...which wont allow a 0 value.

Hence its more reliable than the orderpackagesize.

 

Issue 2

I think the issue here is that in theory there is no way an Item can have a -ve qty onOrder...the amount onOrder needs to be 0 or greater.

Given that if the item is marked in the RECIEVED column it has been DELIVERED and thus processed into the current stock we can argue that any overdelivery is not relevant to the order generation therefor...

we just declare that of onOrder is -ve it is set to zero.

In otherwords fair point...

 

Openvpms is structure loosely on a Domain driven design and MVC framework...

a persistance layer...its job is to return data not modify it on the way through.

then we have the service layer...includes business rules etc - these are the rules we change but only rarely...this then feeds to the View layer...which again should not contain business logic ideally. it should just process the items and display them for the end user. The view layer is the one that generally gets tweaked the most..

 

 

 

 

Regards
 
Ben 
OpenVPMS Installer and Helper 
Ph: +61423044823 
Email: info[at]charltonit.com[dot]au

Re: Generate Orders may get it wrong

Ben - sorry for the delay in responding.

Issue 1. I should have made it clear that the sql is not mine - but rather that used by the Generate Orders button - specifically it is held in <OPENVPMS-HOME>\update\archetypes\org\openvpms\archetype\rules\supplier\Supplier.hbm.xml

So, if there are a set of orders (order status = not cancelled and not delivery status not = full) that have difference package sizes, the SQL returns the number of packages ordered, cancelled & received, - and the package size of the last (or perhaps first or perhaps random) order, and the code uses this to calculate the number of units in the order pipeline.  If the package size varies between orders, the calculated number in the pipeline will be incorrect.  Hence the need for the SQL to return the number of units ordered, cancelled & received, and for the code in OrderGenerator.java to be adjusted accordingly.

Issue 2. I think that your remark "In otherwords fair point..." means that you do agree that the code in OrderGenerator.java is not allowing for over-deliveries.

 

On the home front, we have fixed the problem by a) cancelling the old part delivered orders; b) documenting how things should be done (which lead to the How To on orders & deliveries); and c) a couple of new reports which show current stock, ideal, on-order, and to-be-ordered.

Regards, Tim G

Re: Generate Orders may get it wrong

Raise a JIRA and I'll look at it for 1.8.

Syndicate content