This happened to our team last week. If you’re an iDempiere developer, maybe it has happened to you as well.
Firstly, we needed to develop a custom Event Handler to manage a special case of deleting draft shipments when the originating sales order was reactivated. So far, so good, quite easy to do.
Two weeks later, there was a business case where the price on completed sales orders needed to be updated to reflect the real amount invoiced in the associated invoice after completion. This was simply another after-complete Event Handler on invoices, where we needed to programmatically reactivate the order, update the price in the corresponding order lines, and complete it again. Everything was working quite well.
Problem
However, as you might have discovered already, both event handlers were getting called when completing the invoice, which produced unwanted behavior. Every time an invoice was completed, all the draft shipments of the original order were deleted.
This was completely wrong. The first event handler should be called only when the user manually reactivated the sales order. But how could our code know that the Event Handler was being triggered by the user and not by the other event handler?
Solution
A custom Yes-No column in the table, which value was set when running the first code? That would work, but do we need to change the database just for that?
Luckily, we remembered a hidden gem developed by Heng Sin some time ago: PO attributes! That was what we needed. Instead of creating a new column in the database, all we needed was to set an attribute to the MOrder object. Something like:
private static void reactivateOrder(MOrder order) {
try {
order.set_Attribute(OrderHelper.ISMANUAL_REACTIVATE_ATTRIBUTE_NAME, false);
order.reActivateIt();
order.saveEx();
} catch (Exception e) {
throw new AdempiereException(e);
} finally {
order.set_Attribute(OrderHelper.ISMANUAL_REACTIVATE_ATTRIBUTE_NAME, null);
}
}
and that’s it. We could read that attribute from the other part of the code like this:
public static boolean isManualReactivate(MOrder order) {
if (order.get_Attribute(ISMANUAL_REACTIVATE_ATTRIBUTE_NAME) != null)
return (boolean) order.get_Attribute(ISMANUAL_REACTIVATE_ATTRIBUTE_NAME);
return true;
}
And the system could know when it needed to delete the shipments or not. This is an amazing instrument for developers that is not well known.
Would you solve this issue in a different way? Please let me know how in the comments.
Note: These attributes are stored in the cache, so it’s better to set them to null in a finally block to avoid inconsistencies.
I am a systems engineer with a great passion for open source, software development, and technology in general. I have been part of the iDempiere community since 2012. I believe the enterprise world is one of the most aggressive environments out there. Companies tend to ruthlessly compete against each other. That is why seeing competitors co-exist and cooperate in harmony in iDempiere (and OSS communities in general) is so interesting to me.