In my application there is an edit screen to register a payment composed by different invoices, as you can see on the next image:
When the user accepts the form, the application has to change the state and inform the payment date and the payment_id for every invoice.
Those actions are done on the edit screen controller:
@Subscribe
private void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
//al collection container a través del botó Add, no caldria executar aquest codi.
//Obtenir el cobrament del editor screen:
Cobrament vCobrament = this.cobramentDc.getItemOrNull();
if (vCobrament != null) {
//Obtenció de les factures de proveïdor del collection container del editor screen:
List<FacturaProv> facProvs = new ArrayList<FacturaProv>();
facProvs = facts_provDc.getMutableItems();
//Inicialització de variables pel bucle de factures:
BigDecimal imptotcom = BigDecimal.ZERO;
int cent = 100;
BigDecimal divisor = new BigDecimal(cent);
//Recorregut de les factures de proveïdor
for (FacturaProv facPrv : facProvs) {
//Inicialització de variables per cada iteració:
BigDecimal amount = BigDecimal.ZERO;
//Obtenció de l'índex del Collection Container d'aquesta factura:
int idx = facts_provDc.getItemIndex(facPrv);
//Acumulem l'import total de comissió de totes les factures.
imptotcom = imptotcom.add(facPrv.getImportComissio());
//Fem un merge de la factura en el data context per a que faci la traça de si aquesta entitat ha sofert modificions per
//a que s'inclogui en el commit:
FacturaProv facPrvTrack = dataContext.merge(facPrv);
//Un cop l'entitat està en el tracking, es modifica l'atribut i el data context traçarà el canvi
facPrvTrack.setCobrament(vCobrament);
facPrvTrack.setDataCobrament(vCobrament.getData());
facPrvTrack.setEstat(FacProvEstatEnum.Cobrada) ;
//Tornem a col·locar l'entitat modificada en el collection container
//facts_provDc.getMutableItems().set(idx,facPrvTrack);
facts_provDc.replaceItem(facPrvTrack);
}
//Assignem els atributs de lectura Import Total Comissió i Import Diferència del cobrament:
BigDecimal impdif = BigDecimal.ZERO;
if (vCobrament.getImportCobrat() != null){
impdif = vCobrament.getImportCobrat();
}
impdif = impdif.subtract(imptotcom);
vCobrament.setImportTotComFac(imptotcom);
vCobrament.setImportDifer(impdif);
if (impdif.compareTo(BigDecimal.ZERO) != 0){
notifications.create().withCaption("L'import cobrat (" + vCobrament.getImportCobrat().toString() + ") no coincideix amb la suma dels imports de comissió de les factures ("+ imptotcom.toString() + ")").show();
}
}
// commit and resume action
event.resume();
}
The problem I encountered was with the datacontext.merge instruction. In every iteration, the datacontext.merge replace the state of the previous entity as you can see on the debugger session:
After pressing F8:
What am I doing wrong?. I understood the datacontext.merge(T Entity) instruction only affects the entity passed by parameter.
When you merge an entity, the whole object graph with the root in this entity will be merged. I.e. all referenced entities (including collections) will become tracked.
Could you reproduce your problem on a small test project? We are keen to fix possible bugs or usability issues, but we need a reproducible scenario.
Also, what CUBA version are you using?
I’m debugging to discover in which point the datacontext.merge change the value of one attribute in the entity that was merged on the previous iteration and if it this change is coherent.
For another hand, I’ll try to create a test project, but it isn’t so easy because this project has dependencies with another one.
I’m using last version of Cuba Platform, 7.1.3, and last version of Cuba Studio, 12.3.
Debugging the code, I discovered that the problems with datacontext.merge are produced with an entity created by Cuba Platform and Cuba Studio v 6.x. I don’t know if this aspect could affect.
I think this is a related issue. If not, feel free to fork this to a new thread. I’m attempting to create a more mobile friendly experience to replace the regular Table component. The idea is to have a vertical list of GroupBoxes, each with the details of an entity. But it seems that in moving away from the ListComponent interface, I’m losing some automated data management. I also think it has something to do with the merge() method.
Attached is an example project. Steps to reproduce:
Create a new Master instance and save it. (This issue does not show when the Master is new)
Edit the same Master and create a detail. Save.
Edit that save detail, without committing the Master, putting some new value in the Notes field.
Open the editor for that detail again. The changes made in the first edit do not appear when opened a second time.
I’ve traced the EditedEntity of the EditScreen and it seems to go into setupEditedEntity correctly and returns with the reverted value. It seems to be merge() that is causing it.
Likely, it is me not fully grasping the magic that is CUBA but I figured you’d want to know in case it is a bug. If it isn’t a bug, please point me in the right direction.
The first one is particularly important in your case: the replaceItem method, which is used to update the collection container with the edited entity, didn’t affect the collection property, which lead to inconsistent graph: the master entity still had a reference to the old object. So DataContext couldn’t merge the graph correctly when you edited the same Detail entity next time.
Both issues will be fixed in 7.1.5 and 7.2.0. After that, your code should be as simple as that:
Today we are going to release 7.2.3 with lots of bug fixes. Could you test your project on that version when it is out and confirm the issue still exists? It would save me some time.
Unfortunately, I’ve just upgraded the project to release 7.2.3 but the problem persists.
Attached to this post, you would find all the material needed to reproduce the problem:
Step by step.pdf – A guide to reproduce the problem
The Cuba Platform project folder zipped ('Logistica BBDD Test.7z' file)
A MS SQL SERVER 2008 database full backup file ('BBDD Test.7z' file)
Hi Xavier,
I finally run your project and fixed it.
It’s hard to explain the root of the problem (it’s related to how DataContext merges complex object graphs), but the solution is simple and saves you from writing extra obscure code: just put into DataContext everything right after loading or creating it. After that, don’t do any merge, replaceItem and so on.
In your case, merge FacturaProv instances and add tracked instances to the data container as soon as you set a value in the debitNoteField field:
Then in onBeforeCommitChanges method just set required values to these instances, DataContext will save them:
@Subscribe
private void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
// ...
for (FacturaProv facPrv : facProvs) {
// no DataContext.merge anymore, this is all you need:
facPrv.setCobrament(vCobrament);
facPrv.setDataCobrament(vCobrament.getData());
facPrv.setEstat(FacProvEstatEnum.Cobrada) ;
// no facts_provDc.replaceItem()
}
The rule of thumb is “data containers should contain merged instances”.