Is there a way to loop through child entity before being committed (Composition)?

Hey guys!
I’m new to Cuba and i would like your help in a bottleneck i am facing.

I have two entities Invoice and InvoiceLineItems that have a composition relation.
On the Invoice screen, when the user selects the client for the invoice , the InvoiceLineItems will be created automatically, with default values, from the client’s subscribed services. Then the user will edit the InvoiceLineItems and add the actual values.

I successfully implemented the functionality of summing the total field of the InvoiceLineItems and adding it to the amount field of the Invoice before committing, only when the Invoice is saved first then the lineitems edited.
What i would like to achieve, is the ability to loop through invoiceLineItems before the invoice is saved. Because the user will create the invoice set the client which will generate the invoicelineitems and then edit each line to set the appropriate values and after that he save the invoice.
I understand that in a composition relation the child entity will be committed when the parent entity is committed which is why the list in the precommit() method is empty in my code below in the case where the user did not save first the invoice.

Is there a way to loop through the child entity list before they are being committed ?

Invoice edit screen Controller:

script
package com.company.ccpay.web.invoice;

import com.haulmont.bali.util.ParamsMap;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.actions.EditAction;
import com.haulmont.cuba.gui.data.CollectionDatasource;
import com.haulmont.cuba.gui.data.Datasource;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

import com.company.ccpay.entity.Invoice;
import com.company.ccpay.entity.SubscribedServices;
import com.company.ccpay.entity.InvoiceLineitems;
import com.company.ccpay.entity.Invoicestatus;

public class InvoiceEdit extends AbstractEditor<Invoice> {
	
	@Named("invoicelineTable.edit")
	private EditAction  InvoiceLineEdit;
	
	private DataManager dataManager = AppBeans.get(DataManager.class);
	
	@Inject
	private Datasource<Invoice> invoiceDs; 
	
	@Inject
	private CollectionDatasource<InvoiceLineitems, Integer> invoicelineDs;
	
	@Inject
	private Metadata metadata;

	@Override
	 protected void postInit() {
	  super.postInit();
	  
	  Map<String, Object> params = ParamsMap.of("invoice", getItem());
	  InvoiceLineEdit.setWindowParams(params);
	  
	  invoiceDs.addItemPropertyChangeListener(e -> {
		  
		  Invoice curinvoice = (Invoice) e.getItem();
		  
		  if (e.getProperty().equals("client")) {
			  LoadContext<SubscribedServices> loadContext = LoadContext.create(SubscribedServices.class)
				      .setQuery(LoadContext.createQuery("select i from ccpay$SubscribedServices i where i.client.id = :client")
				        .setParameter("client", curinvoice.getClient().getId()));	  
			  List<SubscribedServices> subservice = dataManager.loadList(loadContext);
			  
			  for (SubscribedServices line : subservice) {
				  InvoiceLineitems invoiceline= metadata.create(InvoiceLineitems.class);
				  invoiceline.setInvoice(curinvoice);
				  invoiceline.setServiceName(line.getServiceName());
				  invoiceline.setQuantity(BigDecimal.ONE);
				  invoiceline.setUniteprice(BigDecimal.ONE);
				  invoiceline.setTotal(BigDecimal.ONE);
				  invoiceline.setDescription(line.getDescription());
				  invoicelineDs.addItem(invoiceline);
			  }
			  
			  invoicelineDs.commit();
			  invoicelineDs.refresh();
			  
		  }
		  
	  });
 
	 Invoice inv = invoiceDs.getItem();
	 inv.setStatus(Invoicestatus.UnderProcess);
	 invoiceDs.setItem(inv);	  
	}
}

InvoiceLinItems edit screen controller:

script
package com.company.ccpay.web.invoicelineitems;

import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.gui.WindowParam;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.data.Datasource;

import java.math.BigDecimal;
import java.util.List;

import javax.inject.Inject;

import com.company.ccpay.entity.Invoice;
import com.company.ccpay.entity.InvoiceLineitems;

public class InvoiceLineitemsEdit extends AbstractEditor<InvoiceLineitems> {
	
	@Inject
	private Datasource<InvoiceLineitems> invoiceLineitemsDs;
	
	
	private DataManager dataManager = AppBeans.get(DataManager.class);
	
	
	@WindowParam(required = true)
	private Invoice invoice;

	@Override
	protected boolean preCommit() {
	   InvoiceLineitems lineitem = getItem();
	   BigDecimal total = new BigDecimal(0);  
	    	
	   LoadContext<InvoiceLineitems> loadContext = LoadContext.create(InvoiceLineitems.class)
				    .setQuery(LoadContext.createQuery("select i from ccpay$InvoiceLineitems i where i.invoice.id = :invoice")
				      .setParameter("invoice", lineitem.getInvoice().getId()));	  
			List<InvoiceLineitems> lineservice = dataManager.loadList(loadContext);
	    	if(lineservice != null)
	    	{
			for (InvoiceLineitems line : lineservice) {
				total= total.add((BigDecimal)line.getTotal());
			}
	    	}
	    	
	   invoice.setAmount(total);
	  return true;
	  }

	@Override
	 protected void postInit() {
	  super.postInit();
	  
	  invoiceLineitemsDs.addItemPropertyChangeListener(e-> {
		  
		  InvoiceLineitems curInvLine = (InvoiceLineitems) e.getItem();
		  
		  if (e.getProperty().equals("quantity")) {
			  
			  if (invoiceLineitemsDs.getItem().getUniteprice() !=null)
			  {
				  curInvLine.setTotal(curInvLine.getUniteprice().multiply(curInvLine.getQuantity()));
				  invoiceLineitemsDs.setItem(curInvLine);
			  }
			  
		  } else if (e.getProperty().equals("uniteprice")){
			  
			  if(invoiceLineitemsDs.getItem().getQuantity() !=null) {
				  curInvLine.setTotal(curInvLine.getUniteprice().multiply(curInvLine.getQuantity()));
				  invoiceLineitemsDs.setItem(curInvLine);
			  }
		  }
		  invoiceLineitemsDs.commit();
		  
		  
	  });
	  
	}
}

Thanks in Advance!

Hi,

generally you can use the preCommit hook method in the controller (probably of the invoice in your case). Note that the AbstractEditor will not work in the case of REST API usage.

Another possibility would be to create an EntityListener (probably onBeforeInsert and onBeforeUpdate) which will get the item with the corresponding child items.

Bye
Mario

If you attach a sample project demonstrating the problem, we’ll try to explain you how to fix it .

Hey guys!
Thank you for your reply.

I have attached a sample of the project.
The scenario: user creates Invoice, update the InvoiceLines and saves.
The functionality i implemented is that when the user updates the invoicelines the lines totals are summed and stored in the Amount field of the Invoice.

The problem i am facing is that when the user first creates the Invoice and InvoiceLines the amount is storing “0”. And when the user edits the InvoiceLines (after the invoice is saved) the loop is taking the previously stored instance of InvoiceLines (not the edited one) and doing the summation.

I do understand that this behavior is due to the way i am using loadcontext (see below), which is loading either nothing (on the first creation of the invoice), hence the “0” and then taking the previously commited instance(case when the user edits a saved invoice) and summing it.

script

@Override
	protected boolean preCommit() {
	
		InvoiceLine lineitem = getItem();
		   BigDecimal total = new BigDecimal(0); 
		
		   LoadContext<InvoiceLine> loadContext = LoadContext.create(InvoiceLine.class)
				    .setQuery(LoadContext.createQuery("select i from sampleinvoice$InvoiceLine i where i.invoice.id = :invoice")
				    		.setParameter("invoice", lineitem.getInvoice().getId()));	  
			List<InvoiceLine> lineservice = dataManager.loadList(loadContext);
	    	if(lineservice != null)
	    	{
			for (InvoiceLine line : lineservice) {
				total= total.add((BigDecimal)line.getTotal());
			}
	    	}
	    	
	   invoice.setAmount(total);
		return true;
	}

what i would like to know is that if there is a way to loop through the current edited instance (instead of the committed instance) because this will be invoked before committing .

Thank you in advance,
Jade

Sry guys it seems i am not able to upload my sample files because it is 71 mb.
please follow the link below to download the sample.

https://we.tl/5wXf9dvFsh

First of all, please use zipProject Gradle task for archiving sample projects in the future. You can invoke it from Search dialog in Studio (Alt+/).

The calculation of total amount can be done in the Invoice editor as follows:


public class InvoiceEdit extends AbstractEditor<Invoice> {

    @Named("invoicelineTable.edit")
    private EditAction invoicelineEdit;

    @Named("invoicelineTable.create")
    private CreateAction invoicelineTableCreate;

    @Named("invoicelineTable.remove")
    private RemoveAction invoicelineTableRemove;

    @Inject
    private CollectionDatasource<InvoiceLine, Integer> invoicelineDs;

    @Override
    protected void postInit() {
        invoicelineTableCreate.setAfterCommitHandler(entity -> calculateTotal());
        invoicelineEdit.setAfterCommitHandler(entity -> calculateTotal());
        invoicelineTableRemove.setAfterRemoveHandler(removedItems -> calculateTotal());
    }

    private void calculateTotal() {
        BigDecimal amount = BigDecimal.ZERO;
        for (InvoiceLine invoiceLine : invoicelineDs.getItems()) {
            amount = amount.add(invoiceLine.getTotal());
        }
        getItem().setAmount(amount);
    }
}

So there is no need to do anything in the InvoiceLineEdit controller - you just recalculate the amount after each action with the lines.

1 Like

Hey Konstantin,

Thanks a lot for your answer, worked perfectly!

And thanks for the tips regarding the zipProject Gradle task, will be using it in the future.

Bye,
Jade