View Javadoc
1   package fr.ifremer.dali.ui.swing.content.home.operation;
2   
3   /*
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import fr.ifremer.dali.decorator.DecoratorService;
27  import fr.ifremer.dali.dto.DaliBeanFactory;
28  import fr.ifremer.dali.dto.DaliBeans;
29  import fr.ifremer.dali.dto.data.measurement.MeasurementDTO;
30  import fr.ifremer.dali.dto.data.sampling.SamplingOperationDTO;
31  import fr.ifremer.dali.dto.data.survey.SurveyDTO;
32  import fr.ifremer.dali.dto.enums.FilterTypeValues;
33  import fr.ifremer.dali.dto.referential.DepartmentDTO;
34  import fr.ifremer.dali.dto.referential.SamplingEquipmentDTO;
35  import fr.ifremer.dali.dto.referential.pmfm.PmfmDTO;
36  import fr.ifremer.dali.service.DaliTechnicalException;
37  import fr.ifremer.dali.service.StatusFilter;
38  import fr.ifremer.dali.ui.swing.content.home.HomeUI;
39  import fr.ifremer.dali.ui.swing.content.home.operation.add.AddOperationTableUI;
40  import fr.ifremer.dali.ui.swing.content.home.survey.SurveysTableRowModel;
41  import fr.ifremer.dali.ui.swing.util.AbstractDaliBeanUIModel;
42  import fr.ifremer.dali.ui.swing.util.DaliUI;
43  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIHandler;
44  import fr.ifremer.quadrige3.core.dao.technical.StringIterator;
45  import fr.ifremer.quadrige3.ui.swing.action.ActionFactory;
46  import fr.ifremer.quadrige3.ui.swing.component.coordinate.CoordinateEditor;
47  import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
48  import fr.ifremer.quadrige3.ui.swing.table.editor.ExtendedComboBoxCellEditor;
49  import fr.ifremer.quadrige3.ui.swing.table.editor.LocalTimeCellEditor;
50  import jaxx.runtime.SwingUtil;
51  import org.apache.commons.collections4.CollectionUtils;
52  import org.apache.commons.lang3.StringUtils;
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.jdesktop.swingx.table.TableColumnExt;
56  
57  import javax.swing.BorderFactory;
58  import javax.swing.SortOrder;
59  import javax.swing.SwingWorker;
60  import java.awt.Dimension;
61  import java.util.List;
62  import java.util.Optional;
63  import java.util.concurrent.ExecutionException;
64  import java.util.concurrent.ThreadPoolExecutor;
65  
66  import static org.nuiton.i18n.I18n.t;
67  
68  /**
69   * Controleur pour la zone des prelevements de l'ecran d'accueil
70   */
71  public class OperationsTableUIHandler extends AbstractDaliTableUIHandler<OperationsTableRowModel, OperationsTableUIModel, OperationsTableUI> {
72  
73      /**
74       * Logger.
75       */
76      private static final Log LOG = LogFactory.getLog(OperationsTableUIHandler.class);
77      private ThreadPoolExecutor executor;
78      private ExtendedComboBoxCellEditor<SamplingEquipmentDTO> samplingEquipmentCellEditor;
79      private ExtendedComboBoxCellEditor<DepartmentDTO> samplingDepartmentCellEditor;
80      private ExtendedComboBoxCellEditor<DepartmentDTO> analystDepartmentCellEditor;
81  
82      /**
83       * {@inheritDoc}
84       */
85      @Override
86      public OperationsTableModel getTableModel() {
87          return (OperationsTableModel) getTable().getModel();
88      }
89  
90      /**
91       * {@inheritDoc}
92       */
93      @Override
94      public SwingTable getTable() {
95          return getUI().getOperationsTable();
96      }
97  
98      /**
99       * {@inheritDoc}
100      */
101     @Override
102     public void beforeInit(final OperationsTableUI ui) {
103         super.beforeInit(ui);
104 
105         // create model and register to the JAXX context
106         final OperationsTableUIModel model = new OperationsTableUIModel();
107         ui.setContextValue(model);
108     }
109 
110     /**
111      * {@inheritDoc}
112      */
113     @Override
114     public void afterInit(OperationsTableUI ui) {
115 
116         initUI(ui);
117 
118         createSamplingDepartmentCellEditor();
119         createAnalystDepartmentCellEditor();
120         createSamplingEquipmentCellEditor();
121 
122         iniTable();
123         SwingUtil.setLayerUI(ui.getOperationsTableScrollPane(), ui.getTableBlockLayer());
124 
125         initListeners();
126 
127         // Init dropdown buttons
128         initActionComboBox(getUI().getAddOperationCombobox());
129         initActionComboBox(getUI().getEditCombo());
130 
131         ui.applyDataBinding(OperationsTableUI.BINDING_DUPLICATE_OPERATION_BUTTON_ENABLED);
132         ui.applyDataBinding(OperationsTableUI.BINDING_EDIT_COMBO_ENABLED);
133         ui.applyDataBinding(OperationsTableUI.BINDING_DELETE_OPERATION_BUTTON_ENABLED);
134 
135         // button border
136         getUI().getNextButton().setBorder(
137                 BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, getConfig().getColorHighlightButtonBorder()), ui.getNextButton().getBorder())
138         );
139 
140         executor = ActionFactory.createSingleThreadExecutor(ActionFactory.ExecutionMode.LATEST);
141 
142     }
143 
144     @Override
145     public void onCloseUI() {
146         executor.shutdownNow();
147         super.onCloseUI();
148     }
149 
150     /**
151      * {@inheritDoc}
152      */
153     @Override
154     protected String[] getRowPropertiesToIgnore() {
155         return new String[]{
156                 OperationsTableRowModel.PROPERTY_ERRORS,
157                 OperationsTableRowModel.PROPERTY_DIRTY,
158                 OperationsTableRowModel.PROPERTY_MEASUREMENTS_LOADED
159         };
160     }
161 
162     /**
163      * {@inheritDoc}
164      */
165     @Override
166     protected boolean isRowValid(final OperationsTableRowModel row) {
167 
168         return (!row.isEditable() || super.isRowValid(row)) & isOperationValid(row);
169     }
170 
171     private boolean isOperationValid(OperationsTableRowModel row) {
172 
173         // check name duplicates
174         if (getModel().getRowCount() >= 2) {
175             for (OperationsTableRowModel otherRow : getModel().getRows()) {
176                 if (row == otherRow) continue;
177                 if (StringUtils.isNotBlank(row.getName()) && row.getName().equals(otherRow.getName())) {
178                     // duplicate found
179                     DaliBeans.addError(row, t("dali.validator.error.operation.mnemonic.duplicate"), OperationsTableRowModel.PROPERTY_NAME);
180                 }
181             }
182         }
183 
184         // coordinates
185         if (row.getLatitude() == null ^ row.getLongitude() == null) {
186             DaliBeans.addError(row, t("dali.validator.error.coordinate.invalid"), OperationsTableRowModel.PROPERTY_LONGITUDE, OperationsTableRowModel.PROPERTY_LATITUDE);
187         }
188 
189         // positioning
190         if (row.getLatitude() != null && row.getLongitude() != null && row.getPositioning() == null) {
191             DaliBeans.addError(row, t("dali.validator.error.positioning.required"), OperationsTableRowModel.PROPERTY_POSITIONING);
192         }
193 
194         // analyst
195         if (row.getAnalyst() == null && row.getMeasurements().stream().anyMatch(measurement -> !DaliBeans.isMeasurementEmpty(measurement))) {
196             DaliBeans.addError(row, t("dali.validator.error.analyst.required"), OperationsTableRowModel.PROPERTY_ANALYST);
197         }
198 
199         // length
200         if (row.getSize() != null && row.getSizeUnit() == null) {
201             DaliBeans.addError(row, t("dali.validator.error.sizeUnit.required"), OperationsTableRowModel.PROPERTY_SIZE_UNIT);
202         }
203 
204         boolean hasNoError = DaliBeans.hasNoBlockingError(row);
205 
206         if (!hasNoError) {
207             ensureColumnsWithErrorAreVisible(row);
208         }
209 
210         return hasNoError;
211     }
212 
213     /**
214      * Initialisation des listeners.
215      */
216     private void initListeners() {
217 
218         // Listener sur le tableau
219         getModel().addPropertyChangeListener(evt -> {
220 
221             if (OperationsTableUIModel.PROPERTY_SINGLE_ROW_SELECTED.equals(evt.getPropertyName())) {
222                 // Si un seul element a ete selectionne
223                 final SamplingOperationDTO prelevement = getModel().getSingleSelectedRow();
224                 if (prelevement != null) {
225 
226                     updateSamplingDepartmentCellEditor(false);
227                     updateAnalystDepartmentCellEditor(false);
228                     updateSamplingEquipmentCellEditor(false);
229 
230                     // Sauvegarde de l identifiant du prelevement
231                     getContext().setSelectedSamplingOperationId(getModel().getSingleSelectedRow().getId());
232                 }
233 
234             } else if (OperationsTableUIModel.PROPERTY_SURVEY_EDITABLE.equals(evt.getPropertyName())) {
235 
236                 // Enable table model if survey is editable
237                 getTableModel().setReadOnly(!getModel().isSurveyEditable());
238 
239             } else if (OperationsTableUIModel.PROPERTY_MEASUREMENTS_LOADED.equals(evt.getPropertyName())) {
240 
241                 // detect pmfm columns for trawl area calculation
242                 detectPmfmsColumnsForTrawlArea();
243 
244             }
245         });
246     }
247 
248     private void detectPmfmsColumnsForTrawlArea() {
249 
250 
251     }
252 
253     /**
254      * Charge les prelevements.
255      *
256      * @param survey a {@link fr.ifremer.dali.ui.swing.content.home.survey.SurveysTableRowModel} object.
257      */
258     public void loadOperations(final SurveysTableRowModel survey) {
259 
260         // before affect new survey, need to save previous one
261         if (getTable().isEditing()) {
262             getTable().getCellEditor().stopCellEditing();
263         }
264 
265         // save actual table context before loading new survey
266         saveTableState();
267 
268         // affect new selected survey
269         getModel().setSurvey(survey);
270 
271         getModel().setLoading(true);
272         executor.execute(new SamplingOperationsLoader(survey));
273 
274     }
275 
276     /**
277      * Initialisation de la table prelevements.
278      */
279     private void iniTable() {
280 
281         // La table des prelevements
282         final SwingTable table = getTable();
283 
284         // Colonne mnemonique 
285         final TableColumnExt colName = addFixedColumn(OperationsTableModel.NAME);
286         colName.setSortable(true);
287         colName.setMinWidth(100);
288 
289         // Colonne engin
290         final TableColumnExt colSamplingEquipment = addColumn(
291                 samplingEquipmentCellEditor,
292                 newTableCellRender(OperationsTableModel.SAMPLING_EQUIPMENT),
293                 OperationsTableModel.SAMPLING_EQUIPMENT
294         );
295         colSamplingEquipment.setSortable(true);
296         colSamplingEquipment.setMinWidth(100);
297 
298         // Colonne Heure
299         final TableColumnExt colTime = addColumn(
300                 new LocalTimeCellEditor(),
301                 newTableCellRender(Integer.class, DecoratorService.TIME_IN_HOURS_MINUTES),
302                 OperationsTableModel.TIME);
303         colTime.setSortable(true);
304         colTime.setMinWidth(50);
305 
306         // Colonne commentaire
307         addCommentColumn(OperationsTableModel.COMMENT);
308 
309         //------ Colonnes optionnelles
310 
311         // Colonne Taille
312         final TableColumnExt colSize = addColumn(
313                 newNumberCellEditor(Double.class, false, DaliUI.DECIMAL7_PATTERN),
314                 newNumberCellRenderer(7),
315                 OperationsTableModel.SIZE);
316         colSize.setSortable(true);
317         colSize.setMinWidth(50);
318 
319         // Colonne Unite taille
320         final TableColumnExt colSizeUnit = addExtendedComboDataColumnToModel(
321                 OperationsTableModel.SIZE_UNIT,
322                 getContext().getReferentialService().getUnits(StatusFilter.ACTIVE),
323                 false);
324         colSizeUnit.setSortable(true);
325         colSizeUnit.setMinWidth(100);
326 
327         // Colonne service préleveur
328         final TableColumnExt colSamplingDepartment = addColumn(
329                 samplingDepartmentCellEditor,
330                 newTableCellRender(OperationsTableModel.SAMPLING_DEPARTMENT),
331                 OperationsTableModel.SAMPLING_DEPARTMENT
332         );
333         colSamplingDepartment.setSortable(true);
334         colSamplingDepartment.setMinWidth(100);
335 
336         // Colonne service analyste
337         final TableColumnExt colAnalystDepartment = addColumn(
338                 analystDepartmentCellEditor,
339                 newTableCellRender(OperationsTableModel.ANALYST),
340                 OperationsTableModel.ANALYST
341         );
342         colAnalystDepartment.setSortable(true);
343         colAnalystDepartment.setMinWidth(100);
344 
345         // Colonne niveau
346         final TableColumnExt colDepthLevel = addExtendedComboDataColumnToModel(
347                 OperationsTableModel.DEPTH_LEVEL,
348                 getContext().getReferentialService().getLevels(),
349                 false);
350         colDepthLevel.setSortable(true);
351         colDepthLevel.setMinWidth(100);
352 
353         // Colonne immersion
354         final TableColumnExt colDepth = addColumn(
355                 newNumberCellEditor(Double.class, false, DaliUI.DECIMAL2_PATTERN),
356                 newNumberCellRenderer(2),
357                 OperationsTableModel.DEPTH);
358         colDepth.setSortable(true);
359         colDepth.setMinWidth(100);
360 
361         // Colonne immersion min
362         final TableColumnExt colMinDepth = addColumn(
363                 newNumberCellEditor(Double.class, false, DaliUI.DECIMAL2_PATTERN),
364                 newNumberCellRenderer(2),
365                 OperationsTableModel.MIN_DEPTH);
366         colMinDepth.setSortable(true);
367         colMinDepth.setMinWidth(100);
368 
369         // Colonne immersion
370         final TableColumnExt colMaxDepth = addColumn(
371                 newNumberCellEditor(Double.class, false, DaliUI.DECIMAL2_PATTERN),
372                 newNumberCellRenderer(2),
373                 OperationsTableModel.MAX_DEPTH);
374         colMaxDepth.setSortable(true);
375         colMaxDepth.setMinWidth(100);
376 
377         // Colonne Coordonnées réelles
378         final TableColumnExt colLatitude = addCoordinateColumnToModel(
379                 CoordinateEditor.CoordinateType.LATITUDE_MIN,
380                 OperationsTableModel.LATITUDE);
381         colLatitude.setSortable(true);
382         colLatitude.setMinWidth(100);
383 
384         final TableColumnExt colLongitude = addCoordinateColumnToModel(
385                 CoordinateEditor.CoordinateType.LONGITUDE_MIN,
386                 OperationsTableModel.LONGITUDE);
387         colLongitude.setSortable(true);
388         colLongitude.setMinWidth(100);
389 
390         // Colonne informations de positionnement
391         final TableColumnExt colPositioning = addExtendedComboDataColumnToModel(
392                 OperationsTableModel.POSITIONING,
393                 getContext().getReferentialService().getPositioningSystems(),
394                 false);
395         colPositioning.setSortable(true);
396         colPositioning.setMinWidth(100);
397 
398         final TableColumnExt colPositioningPrecision = addColumn(
399                 OperationsTableModel.POSITIONING_PRECISION);
400         colPositioningPrecision.setSortable(true);
401         colPositioningPrecision.setMinWidth(100);
402         colPositioningPrecision.setEditable(false);
403 
404         // Modele de la table
405         final OperationsTableModel tableModel = new OperationsTableModel(getTable().getColumnModel(), true);
406         table.setModel(tableModel);
407 
408         // Initialisation de la table
409         initTable(table);
410 
411         // Les colonnes optionnelles sont invisibles
412         colSize.setVisible(false);
413         colSizeUnit.setVisible(false);
414         colDepthLevel.setVisible(false);
415         colDepth.setVisible(false);
416         colMinDepth.setVisible(false);
417         colMaxDepth.setVisible(false);
418         colLatitude.setVisible(false);
419         colLongitude.setVisible(false);
420         colPositioning.setVisible(false);
421         colPositioningPrecision.setVisible(false);
422 
423         // Tri par defaut
424         table.setSortOrder(OperationsTableModel.NAME, SortOrder.ASCENDING);
425 
426         // border
427         addEditionPanelBorder();
428     }
429 
430     private void createSamplingEquipmentCellEditor() {
431 
432         samplingEquipmentCellEditor = newExtendedComboBoxCellEditor(null, SamplingEquipmentDTO.class, false);
433 
434         samplingEquipmentCellEditor.setAction("unfilter", "dali.common.unfilter", e -> {
435             if (!askBefore(t("dali.common.unfilter"), t("dali.common.unfilter.confirmation"))) {
436                 return;
437             }
438             // unfilter location
439             updateSamplingEquipmentCellEditor(true);
440             getTable().requestFocus();
441         });
442 
443     }
444 
445     private void updateSamplingEquipmentCellEditor(boolean forceNoFilter) {
446 
447         samplingEquipmentCellEditor.getCombo().setActionEnabled(!forceNoFilter
448                 && getContext().getDataContext().isContextFiltered(FilterTypeValues.SAMPLING_EQUIPMENT));
449 
450         samplingEquipmentCellEditor.getCombo().setData(
451                 getContext().getObservationService().getAvailableSamplingEquipments(forceNoFilter));
452     }
453 
454     private void createSamplingDepartmentCellEditor() {
455 
456         samplingDepartmentCellEditor = newExtendedComboBoxCellEditor(null, DepartmentDTO.class, false);
457 
458         samplingDepartmentCellEditor.setAction("unfilter", "dali.common.unfilter", e -> {
459             if (!askBefore(t("dali.common.unfilter"), t("dali.common.unfilter.confirmation"))) {
460                 return;
461             }
462             // unfilter department
463             updateSamplingDepartmentCellEditor(true);
464             getTable().requestFocus();
465         });
466 
467     }
468 
469     private void updateSamplingDepartmentCellEditor(boolean forceNoFilter) {
470 
471         samplingDepartmentCellEditor.getCombo().setActionEnabled(!forceNoFilter
472                 && getContext().getDataContext().isContextFiltered(FilterTypeValues.DEPARTMENT));
473 
474         samplingDepartmentCellEditor.getCombo().setData(
475                 getContext().getObservationService().getAvailableDepartments(forceNoFilter));
476     }
477 
478     private void createAnalystDepartmentCellEditor() {
479 
480         analystDepartmentCellEditor = newExtendedComboBoxCellEditor(null, DepartmentDTO.class, false);
481 
482         analystDepartmentCellEditor.setAction("unfilter", "dali.common.unfilter", e -> {
483             if (!askBefore(t("dali.common.unfilter"), t("dali.common.unfilter.confirmation"))) {
484                 return;
485             }
486             // unfilter department
487             updateAnalystDepartmentCellEditor(true);
488             getTable().requestFocus();
489         });
490 
491     }
492 
493     private void updateAnalystDepartmentCellEditor(boolean forceNoFilter) {
494 
495         analystDepartmentCellEditor.getCombo().setActionEnabled(!forceNoFilter
496                 && getContext().getDataContext().isContextFiltered(FilterTypeValues.DEPARTMENT));
497 
498         analystDepartmentCellEditor.getCombo().setData(
499                 getContext().getObservationService().getAvailableDepartments(forceNoFilter));
500     }
501 
502     /**
503      * {@inheritDoc}
504      */
505     @Override
506     protected void onRowModified(int rowIndex, OperationsTableRowModel row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
507         super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
508 
509         row.setDirty(true);
510 
511         if (OperationsTableRowModel.PROPERTY_NAME.equals(propertyName)) {
512             recomputeRowsValidState();
513 
514         } else if (OperationsTableRowModel.PROPERTY_PMFMS.equals(propertyName)) {
515             // force measurements loaded to true to tell the service to save measurements
516             // in case of duplicated sampling operation, this flag is set to false, so the measurements was not saved (Mantis #0027276)
517             row.setMeasurementsLoaded(true);
518 
519         }
520 
521         saveToSurvey();
522     }
523 
524     /**
525      * <p>addSamplingOperations.</p>
526      */
527     public void addSamplingOperations() {
528 
529         AddOperationTableUI dialog = new AddOperationTableUI(getContext());
530         dialog.getHandler().load(getModel());
531         openDialog(dialog, new Dimension(1000, 232));
532 
533         // Add new rows
534         List<OperationsTableRowModel> newOperations = dialog.getModel().getRows();
535         if (dialog.getModel().isValid() && CollectionUtils.isNotEmpty(newOperations)) {
536 
537             // Mantis #38330 Use label auto generation only if enabled by configuration
538             if (getConfig().isSamplingOperationAutoLabelEnable()) {
539                 StringIterator labelIterator = StringIterator.newStringIteratorByProperty(getModel().getRows(), SamplingOperationDTO.PROPERTY_NAME);
540                 newOperations.forEach(newOperation -> newOperation.setName(labelIterator.next()));
541             }
542 
543             getModel().addRows(newOperations);
544             getModel().setModify(true);
545 
546             recomputeRowsValidState();
547             saveToSurvey();
548 
549             // force focus on first empty name (Mantis #40749)
550             for (OperationsTableRowModel row : getModel().getRows()) {
551                 if (StringUtils.isBlank(row.getName())) {
552                     getTable().setTerminateEditOnFocusLost(false); // Trick to prevent focus owner change (even if setFocusOnCell is invoked later)
553                     setFocusOnCell(row);
554                     break;
555                 }
556             }
557         }
558 
559     }
560 
561     /**
562      * <p>removeSamplingOperations.</p>
563      */
564     public void removeSamplingOperations() {
565 
566         if (getModel().getSelectedRows().isEmpty()) {
567             LOG.warn("Aucun prelevement de selectionne");
568             return;
569         }
570 
571         // Demande de confirmation avant la suppression
572         boolean canContinue = askBeforeDelete(t("dali.action.delete.samplingOperation.title"), t("dali.action.delete.samplingOperation.message"));
573 
574         if (canContinue) {
575 
576             // check measurement
577             boolean hasMeasurement = false;
578             for (OperationsTableRowModel row : getModel().getSelectedRows()) {
579 
580                 if (!row.isMeasurementsEmpty()) {
581                     for (MeasurementDTO measurement : row.getMeasurements()) {
582                         if (measurement.getQualitativeValue() != null || measurement.getNumericalValue() != null) {
583                             hasMeasurement = true;
584                             break;
585                         }
586                     }
587                 }
588             }
589 
590             if (hasMeasurement) {
591                 canContinue = askBeforeDelete(t("dali.action.delete.samplingOperation.measurement.title"), t("dali.action.delete.samplingOperation.measurement.message"));
592             }
593         }
594 
595         if (canContinue) {
596 
597             // Suppression des lignes
598             getModel().deleteSelectedRows();
599 
600             recomputeRowsValidState(true);
601             forceRevalidateModel();
602 
603             saveToSurvey();
604         }
605     }
606 
607     /**
608      * <p>saveToSurvey.</p>
609      */
610     private void saveToSurvey() {
611 
612         // save analyst in every measurements (Mantis #42782)
613         for (OperationsTableRowModel rowModel: getModel().getRows()) {
614             if (!rowModel.isMeasurementsEmpty()) {
615                 rowModel.getMeasurements().forEach(measurement -> measurement.setAnalyst(rowModel.getAnalyst()));
616             }
617         }
618 
619         // save sampling operations in parent model
620         getModel().getSurvey().setSamplingOperations(getModel().getBeans());
621         getModel().getSurvey().setSamplingOperationsLoaded(true);
622 
623         // force model modify
624         getModel().firePropertyChanged(AbstractDaliBeanUIModel.PROPERTY_MODIFY, null, true);
625     }
626 
627     /**
628      * {@inheritDoc}
629      */
630     @Override
631     protected void onRowsAdded(List<OperationsTableRowModel> addedRows) {
632         super.onRowsAdded(addedRows);
633 
634         if (addedRows.size() == 1) {
635             OperationsTableRowModel rowModel = addedRows.get(0);
636 
637             rowModel.setDirty(true);
638 
639             // Add default name
640             if (StringUtils.isBlank(rowModel.getName()) && getConfig().isSamplingOperationAutoLabelEnable()) {
641                 rowModel.setName(StringIterator.newStringIteratorByProperty(getModel().getRows(), SamplingOperationDTO.PROPERTY_NAME).next());
642             }
643 
644             // create all empty measurements
645             rowModel.addAllPmfms(getModel().getPmfms());
646             for (PmfmDTO pmfm : getModel().getPmfms()) {
647                 MeasurementDTO measurement = DaliBeanFactory.newMeasurementDTO();
648                 measurement.setPmfm(pmfm);
649                 rowModel.addMeasurements(measurement);
650             }
651 
652             // Add sampling department from strategy (Mantis #49600)
653             if (rowModel.getSamplingDepartment() == null) {
654                 rowModel.setSamplingDepartment(getContext().getProgramStrategyService().getSamplingDepartmentOfAppliedStrategyBySurvey(getModel().getSurvey()));
655             }
656 
657             // The analyst from strategy (Mantis #42617)
658             if (rowModel.getAnalyst() == null) {
659                 rowModel.setAnalyst(getContext().getProgramStrategyService().getAnalysisDepartmentOfAppliedStrategyBySurvey(getModel().getSurvey()));
660             }
661 
662             getModel().setModify(true);
663             setFocusOnCell(rowModel);
664         }
665     }
666 
667     void duplicateSelectedOperation() {
668 
669         if (getModel().getSelectedRows().size() == 1 && getModel().getSingleSelectedRow() != null) {
670 
671             getModel().setModify(true);
672 
673             // Add focus on duplicate row
674             setFocusOnCell(getModel().addNewRow(DaliBeans.duplicate(getModel().getSingleSelectedRow().toBean())));
675 
676             // Set new beans in survey
677             getModel().getSurvey().setSamplingOperations(getModel().getBeans());
678         }
679 
680     }
681 
682     /**
683      * <p>onNext.</p>
684      */
685     public void onNext() {
686 
687         HomeUI homeUI = getUI().getParentContainer(HomeUI.class);
688         getContext().getActionEngine().runAction(homeUI.getSurveysTableUI().getEditSurveyButton());
689     }
690 
691     private class SamplingOperationsLoader extends SwingWorker<List<PmfmDTO>, Void> {
692 
693         final SurveyDTO survey;
694 
695         private SamplingOperationsLoader(SurveyDTO survey) {
696             this.survey = survey;
697         }
698 
699         @Override
700         protected List<PmfmDTO> doInBackground() {
701             List<PmfmDTO> pmfms = null;
702 
703             if (survey != null) {
704 
705                 // load sampling operations, check loaded first to avoid unnecessary call to service
706                 if (!survey.isSamplingOperationsLoaded()) {
707                     getContext().getObservationService().loadSamplingOperationsFromSurvey(survey, false /* don't load individual measurements */);
708                 }
709 
710                 // Load pmfms from strategies
711                 pmfms = getContext().getProgramStrategyService().getPmfmsForSamplingOperationBySurvey(survey);
712 
713                 // Load other Pmfms in sampling operations
714                 if (!survey.isSamplingOperationsEmpty()) {
715                     // populate pmfms from strategy to sampling operation and vice versa
716                     for (SamplingOperationDTO samplingOperation : survey.getSamplingOperations()) {
717                         DaliBeans.fillListsEachOther(samplingOperation.getPmfms(), pmfms);
718                     }
719                 }
720             }
721 
722             return pmfms;
723         }
724 
725         @Override
726         protected void done() {
727             try {
728                 if (isCancelled()) {
729                     return;
730                 }
731 
732                 // Set model properties
733                 getModel().setPmfms(get());
734 
735                 // Initialiser la table des prelevements
736                 addPmfmColumns(
737                         getModel().getPmfms(),
738                         SamplingOperationDTO.PROPERTY_PMFMS,
739                         DecoratorService.NAME_WITH_UNIT,
740                         OperationsTableModel.TIME);
741 
742                 boolean notEmpty = CollectionUtils.isNotEmpty(getModel().getPmfmColumns());
743 
744                 if (survey != null && !survey.isSamplingOperationsEmpty()) {
745                     // Chargement du tableau avec les prelevements
746                     getModel().setBeans(survey.getSamplingOperations());
747 
748                     if (notEmpty) {
749                         for (OperationsTableRowModel row : getModel().getRows()) {
750                             // set analyst from first non null measurement
751                             Optional<MeasurementDTO> measurementFound = row.getMeasurements().stream().filter(measurement -> measurement.getAnalyst() != null).findFirst();
752                             measurementFound.ifPresent(measurementDTO -> row.setAnalyst(measurementDTO.getAnalyst()));
753 
754                         }
755                     }
756 
757                     recomputeRowsValidState();
758 
759                 } else {
760 
761                     // reset all
762                     getModel().setBeans(null);
763                 }
764                 forceRevalidateModel();
765 
766                 // restore table state from swing session
767                 restoreTableState();
768 
769                 // hide analyst column if no pmfm (Mantis #42617)
770 //                forceColumnVisibleAtLastPosition(OperationsTableModel.ANALYST, notEmpty);
771                 // Don't force position (Mantis #49939)
772                 forceColumnVisible(OperationsTableModel.ANALYST, notEmpty);
773 
774                 // set columns with errors visible (Mantis #40752)
775                 ensureColumnsWithErrorAreVisible(getModel().getRows());
776 
777             } catch (InterruptedException | ExecutionException e) {
778                 throw new DaliTechnicalException(e.getMessage(), e);
779             } finally {
780                 getModel().setLoading(false);
781                 getModel().setMeasurementsLoaded(true);
782             }
783         }
784     }
785 }