Listener to update data of another entity

Hi
I have specific problem, i don’t think it is a bug but more about “how-to”. I might be very simple to someone here.
Let me explain. I have purchase Order and MaterialReceipt entities which are as follows:

PurchaseOrder
PurchaseOrderLine (composite)
MaterialReceipt
MaterialReceiptLine (composite)

When I create a MaterialReceipt transaction in reference to PurchaseOrder, the program will check if all PO quantity has been received. If yes, the close the PO.

To do that I am using Listener of MaterialReceipt entity to trigger a service that sums the PO quantity and Received quantity including current receipts. If they are equal, change the status of the entity PurchaseOrder.

What I am getting that when the sum of quantities from entity PurchaseOrderLine is done, it doesn’t include the currently received quantity, most likely because it is not committed yet, I see only previously received quantity. The question is, when/after I have committed transaction in MaterialReceipt entity how can I get sum of previously committed quantity and current transaction quantity?

Hi Mortoza,

Your code calculating quantity should work in the same currently open transaction. Make sure you don’t start new transactions with persistence.createTransaction().

Hi Konstantin @knstvk
In order to simplify what I am trying to achieve, I have used the sale sample app from your sample store and added additional entities, screen, listeners.

I have moved the method from service to the listener in order to ensure the same transaction is used. Here the code that I have used in OrderLine

 Query query = persistence.getEntityManager().createQuery(
            "select SUM(e.quantity)-SUM(e.doQuantity) from sales$OrderLine e " +
                    "where e.order.id = ?1");
    query.setParameter(1, salesOrder.getId());
    BigDecimal balance = (BigDecimal) query.getFirstResult();

    TypedQuery<Order> query2 = persistence.getEntityManager().createQuery("select e from sales$Order e " +
            "where e.id = ?1", Order.class);
    query2.setParameter(1, salesOrder.getId());

    Order so = query2.getFirstResult();

    if(balance !=null)
        if(balance.doubleValue()>0){
            so.setOrderStatus(OrderStatus.PARTIALLY_USED);
        }else{
            so.setOrderStatus(OrderStatus.CLOSED);
        }

About the app:
The Delivery Order is using Order (i.e. Sales Order). A delivery order can use the sales order partially or fully. When a Delivery order is created or modified, the corresponding sales order line (OrderLine) is updated with DoQuantity. To start with, create a new order and use that order to create a Delivery Order.

When The OrderLine Entity is updated, the above method is executed to calculate total order quantity and DoQuantity. If order Quantity > DoQuantity then the Order status field should be updated as PARTIALLY_USER otherwise CLOSED. However, even I am issuing the full quantity, the status is updated from OPEN to PARTIALLY_USED.

I have attached the updated sample app. If you want after resolving this issue, feel free to upload this version to your sample store.
sample-sales.zip (116.7 KB)

Mortoza

Hi Mortoza,

Thank you for the test project and the explanation.

The problem stems from the fact that your queries defined in entity listeners run on the database which does not yet contain changes that have been made to entities in memory. This is explained here in the docs. In theory, you could set FlushModeType.AUTO to the queries, and then ORM would flush memory changes to the database prior to the query execution. But it would hit performance because of additional database operations, and also fire BeforeInsert entity listeners for the entities being saved first time, which may introduce some complexity.

Better fully use the persistence context: you already have all changes in memory, so just traverse your data model and do calculations. For example, DeliveryOrderLineEntityListener updating related OrderLine is shown below:

@Component("sales_DeliveryOrderLineEntityListener")
public class DeliveryOrderLineEntityListener implements BeforeDeleteEntityListener<DeliveryOrderLine>, BeforeInsertEntityListener<DeliveryOrderLine>, BeforeUpdateEntityListener<DeliveryOrderLine> {

    @Inject
    private PersistenceTools persistenceTools;

    @Override
    public void onBeforeDelete(DeliveryOrderLine entity, EntityManager entityManager) {
        // related entities can be detached, so if you want to update them, merge first
        OrderLine line = entityManager.merge(entity.getOrderLine());
        line.setDoQuantity(line.getDoQuantity().subtract(entity.getQuantity()));
    }

    @Override
    public void onBeforeInsert(DeliveryOrderLine entity, EntityManager entityManager) {
        OrderLine line = entityManager.merge(entity.getOrderLine());
        line.setDoQuantity(line.getDoQuantity().add(entity.getQuantity()));
    }

    @Override
    public void onBeforeUpdate(DeliveryOrderLine entity, EntityManager entityManager) {
        OrderLine line = entityManager.merge(entity.getOrderLine());
        BigDecimal oldDoQty = (BigDecimal) persistenceTools.getOldValue(entity, "quantity");
        if (oldDoQty == null)
            oldDoQty = BigDecimal.ZERO;
        line.setDoQuantity(line.getDoQuantity().subtract(oldDoQty).add(entity.getQuantity()));
    }
}

OrderLineEntityListener updating the order state depending on doQuantity:

@Component("sales_OrderLineEntityListener")
public class OrderLineEntityListener implements BeforeDeleteEntityListener<OrderLine>, BeforeInsertEntityListener<OrderLine>, BeforeUpdateEntityListener<OrderLine> {

    @Override
    public void onBeforeDelete(OrderLine entity, EntityManager entityManager) {
        updateSalesOrderHeaderStatus(entity, entityManager);
    }

    @Override
    public void onBeforeInsert(OrderLine entity, EntityManager entityManager) {
        updateSalesOrderHeaderStatus(entity, entityManager);
    }

    @Override
    public void onBeforeUpdate(OrderLine entity, EntityManager entityManager) {
        updateSalesOrderHeaderStatus(entity, entityManager);
    }

    private void updateSalesOrderHeaderStatus(OrderLine orderLine, EntityManager em){
        Order order = em.merge(orderLine.getOrder());

        BigDecimal doQty = order.getLines().stream()
                .map(OrderLine::getDoQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        if (doQty.equals(BigDecimal.ZERO)) {
            order.setOrderStatus(OrderStatus.OPEN);
            return;
        }

        BigDecimal qty = order.getLines().stream()
                .map(OrderLine::getQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        if (qty.compareTo(doQty) > 0) {
            order.setOrderStatus(OrderStatus.PARTIALLY_USED);
        } else {
            order.setOrderStatus(OrderStatus.CLOSED);
        }
    }
}

See the whole project attached.
sample-sales.zip (125.7 KB)

Hi Konstantin
Thank you so much. It’s working perfectly.

If you want this new version of sales sample can replace the existing one in your sample app repository which may help some other community members.

regards
Mortoza

OK, we’ll consider this!

1 Like