package it.integry.integrywmsnative.gest.spedizione;

import androidx.databinding.ObservableArrayList;
import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Optional;
import com.annimon.stream.Stream;

import org.jetbrains.annotations.NotNull;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import javax.inject.Inject;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.CommonConst;
import it.integry.integrywmsnative.core.class_router.ClassRouter;
import it.integry.integrywmsnative.core.class_router.interfaces.IOrdiniVendita;
import it.integry.integrywmsnative.core.data_recover.ColliDataRecoverService;
import it.integry.integrywmsnative.core.exception.InvalidLUException;
import it.integry.integrywmsnative.core.exception.NoArtsFoundException;
import it.integry.integrywmsnative.core.exception.NoLUFoundException;
import it.integry.integrywmsnative.core.exception.NoPrintersFoundException;
import it.integry.integrywmsnative.core.exception.NoResultFromBarcodeException;
import it.integry.integrywmsnative.core.exception.ScannedPositionNotExistException;
import it.integry.integrywmsnative.core.expansion.AtomicBigDecimal;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.expansion.RunnableArgss;
import it.integry.integrywmsnative.core.expansion.RunnableArgsss;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUBaseOperationsListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUPrintListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILoadingListener;
import it.integry.integrywmsnative.core.model.CommonModelConsts;
import it.integry.integrywmsnative.core.model.FiltroOrdineDTO;
import it.integry.integrywmsnative.core.model.MtbAart;
import it.integry.integrywmsnative.core.model.MtbColr;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.MtbDepoPosizione;
import it.integry.integrywmsnative.core.model.MtbPartitaMag;
import it.integry.integrywmsnative.core.model.dto.PickDataDTO;
import it.integry.integrywmsnative.core.model.secondary.GestioneEnum;
import it.integry.integrywmsnative.core.report.ReportManager;
import it.integry.integrywmsnative.core.rest.consumers.ArticoloRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.OrdiniRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PosizioniRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PrinterRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.Ean128Model;
import it.integry.integrywmsnative.core.rest.model.Ean13PesoModel;
import it.integry.integrywmsnative.core.rest.model.OrdineUscitaInevasoDTO;
import it.integry.integrywmsnative.core.rest.model.SitArtOrdDTO;
import it.integry.integrywmsnative.core.utility.UtilityBarcode;
import it.integry.integrywmsnative.core.utility.UtilityBigDecimal;
import it.integry.integrywmsnative.core.utility.UtilityDate;
import it.integry.integrywmsnative.core.utility.UtilityPosizione;
import it.integry.integrywmsnative.core.utility.UtilityString;
import it.integry.integrywmsnative.gest.spedizione.exceptions.CurrentMonoLUPositionIsNotCorrectException;
import it.integry.integrywmsnative.gest.spedizione.exceptions.InvalidPesoKGException;
import it.integry.integrywmsnative.gest.spedizione.exceptions.NotCurrentYearLUException;
import it.integry.integrywmsnative.gest.spedizione.exceptions.OrdersLoadException;
import it.integry.integrywmsnative.gest.spedizione.model.PickedQuantityDTO;
import it.integry.integrywmsnative.gest.spedizione.model.PickingObjectDTO;

public class SpedizioneViewModel {

    private List<OrdineUscitaInevasoDTO> mTestateOrdini;
    private MutableLiveData<List<PickingObjectDTO>> mPickingList = new MutableLiveData<>();
    private List<MtbColt> mColliRegistrati = new ArrayList<>();

    private Listener mListener;

    private GestioneEnum mDefaultGestioneOfUL = null;
    private int mDefaultSegnoCol = -1;
    private String mDefaultCodMdep = null;
    private List<FiltroOrdineDTO> mDefaultFiltroOrdine = new ArrayList<>();
    private String mDefaultCodAnagOfUL = null;
    private String mDefaultRagSocOfUL = null;
    private String mDefaultRifOrdOfUL = null;
    private Integer mDefaultNumOrdOfUL = null;
    private Date mDefaultDataOrdOfUL = null;
    private String mDefaultCodJFasOfUL = null;
    private Integer mDefaultCausaleOfUL = null;

    private boolean mEnableGiacenza;
    private boolean mEnableCheckPartitaMag;
    private boolean mCanOverflowOrderQuantity;
    private boolean mShouldAskPesoLU;
    private String mReportNameSpedizioneChiudiOrdine;
    private boolean mIsNewLU;
    private boolean mEnableQuantityReset;
    private boolean mUseQtaOrd;

    private MtbColt mCurrentMtbColt = null;

    private Integer mMtbColtSessionID;
    private final ArticoloRESTConsumer mArticoloRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final ColliDataRecoverService mColliDataRecoverService;
    private final OrdiniRESTConsumer mOrdiniRestConsumerService;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final PrinterRESTConsumer mPrinterRESTConsumer;
    private final PosizioniRESTConsumer mPosizioneRESTConsumer;

    @Inject
    public SpedizioneViewModel(ArticoloRESTConsumer articoloRESTConsumer,
                               BarcodeRESTConsumer barcodeRESTConsumer,
                               ColliDataRecoverService colliDataRecoverService,
                               OrdiniRESTConsumer ordiniRESTConsumer,
                               ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer,
                               PrinterRESTConsumer printerRESTConsumer,
                               PosizioniRESTConsumer posizioniRESTConsumer) {
        this.mArticoloRESTConsumer = articoloRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mColliDataRecoverService = colliDataRecoverService;
        this.mOrdiniRestConsumerService = ordiniRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mPrinterRESTConsumer = printerRESTConsumer;
        this.mPosizioneRESTConsumer = posizioniRESTConsumer;
    }


    public void init(String codMdep,
                     boolean enableGiacenza,
                     boolean enableCheckPartitaMag,
                     boolean shouldAskPesoLU,
                     boolean canOverflowOrderQuantity,
                     List<SitArtOrdDTO> pickingList,
                     List<OrdineUscitaInevasoDTO> testateOrdini,
                     List<MtbColt> colliRegistrati,
                     String reportNameSpedizioneChiudiOrdine,
                     GestioneEnum gestioneCol,
                     int segnoCol,
                     Integer defaultCausaleUL,
                     boolean enableQuantityReset,
                     boolean useQtaOrd) {
        this.sendOnLoadingStarted();

        this.mDefaultCodMdep = codMdep;
        this.mTestateOrdini = testateOrdini;
        this.mColliRegistrati = colliRegistrati;
        this.mEnableGiacenza = enableGiacenza;
        this.mEnableCheckPartitaMag = enableCheckPartitaMag;
        this.mCanOverflowOrderQuantity = canOverflowOrderQuantity;
        this.mShouldAskPesoLU = shouldAskPesoLU;
        this.mReportNameSpedizioneChiudiOrdine = reportNameSpedizioneChiudiOrdine;
        this.mDefaultCausaleOfUL = defaultCausaleUL;
        this.mEnableQuantityReset = enableQuantityReset;
        this.mUseQtaOrd = useQtaOrd;

        if (enableGiacenza) {
            mOrdiniRestConsumerService.getSuggestedPickingList(this.mDefaultCodMdep, pickingList, pickingObjectList -> {
                this.mPickingList.postValue(pickingObjectList);
                this.sendOnLoadingEnded();
            }, ex -> this.sendError(new OrdersLoadException(ex)));
        } else {
            getEmptyPickingList(pickingList, pickingObjectList -> {
                this.mPickingList.postValue(pickingObjectList);
                this.sendOnLoadingEnded();
            });
        }

        this.mDefaultGestioneOfUL = gestioneCol;
        this.mDefaultSegnoCol = segnoCol;

        this.initDefaultVars();
    }


    private void getEmptyPickingList(List<SitArtOrdDTO> sitArtOrdList, RunnableArgs<List<PickingObjectDTO>> onComplete) {
        List<String> codMarts = Stream.of(sitArtOrdList)
                .map(SitArtOrdDTO::getCodMart)
                .toList();

        this.mArticoloRESTConsumer.getByCodMarts(codMarts, listMtbAarts -> {
            List<PickingObjectDTO> pickingList = Stream.of(sitArtOrdList)
                    .map(sitArtOrdDTO -> {
                        MtbAart mtbAart = null;

                        for (MtbAart mtbAartItem : listMtbAarts) {
                            if (mtbAartItem.getCodMart().equalsIgnoreCase(sitArtOrdDTO.getCodMart())) {
                                mtbAart = mtbAartItem;
                                break;
                            }
                        }

                        return new PickingObjectDTO()
                                .setSitArtOrdDTO(sitArtOrdDTO)
                                .setMtbAart(mtbAart);
                    })
                    .toList();

            onComplete.run(pickingList);
        }, this::sendError);
    }

    private void initDefaultVars() {
        if (this.mTestateOrdini != null && this.mTestateOrdini.size() > 0) {

            if (mDefaultGestioneOfUL == null) {
                //Definizione della gestione collo di default
                List<GestioneEnum> foundGestioni = Stream.of(mTestateOrdini)
                        .map(OrdineUscitaInevasoDTO::getGestioneEnum)
                        .withoutNulls()
                        .distinct()
                        .toList();

                if (foundGestioni != null && foundGestioni.size() > 1) {
                    return;
                } else if (foundGestioni != null && foundGestioni.size() == 1) {
                    mDefaultGestioneOfUL = foundGestioni.get(0);
                } else {
                    mDefaultGestioneOfUL = GestioneEnum.VENDITA;
                }
            }

            //Definizione del filtro ordine da applicare a tutti i colli generati
            Stream.of(mTestateOrdini).forEach(x -> {
                FiltroOrdineDTO filtro = new FiltroOrdineDTO()
                        .setDataOrd(x.getDataOrdD())
                        .setNumOrd(x.getNumOrd())
                        .setGestioneOrd(x.getGestione())
                        .setDataCons(x.getDataConsD());

                if (!mDefaultFiltroOrdine.contains(filtro)) mDefaultFiltroOrdine.add(filtro);
            });


            //Definizione del codAnag
            List<String> foundCodAnag = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getCodAnagOrd)
                    .distinctBy(x -> x)
                    .toList();

            if (foundCodAnag != null && foundCodAnag.size() == 1) {
                mDefaultCodAnagOfUL = foundCodAnag.get(0);
            }


            //Definizione del codAnag
            List<String> foundRagSoc = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getRagSocOrd)
                    .distinctBy(x -> x)
                    .toList();

            if (foundRagSoc != null && foundRagSoc.size() == 1) {
                mDefaultRagSocOfUL = foundRagSoc.get(0);
            }


            //Definizione del rifOrd
            List<String> foundRifOrd = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getRifOrd)
                    .distinctBy(x -> x)
                    .toList();

            if (foundRifOrd != null && foundRifOrd.size() == 1) {
                mDefaultRifOrdOfUL = foundRifOrd.get(0);
            }


            //Definizione del numOrd
            List<Integer> foundNumOrd = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getNumOrd)
                    .withoutNulls()
                    .distinctBy(x -> x)
                    .toList();

            if (foundNumOrd != null && foundNumOrd.size() == 1) {
                mDefaultNumOrdOfUL = foundNumOrd.get(0);
            }


            //Definizione del dataOrd
            List<Date> foundDataOrd = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getDataOrdD)
                    .withoutNulls()
                    .distinctBy(x -> x)
                    .toList();

            if (foundDataOrd != null && foundDataOrd.size() == 1) {
                mDefaultDataOrdOfUL = foundDataOrd.get(0);
            }


            //Definizione del codJfas
            List<String> foundCodJfas = Stream.of(mTestateOrdini)
                    .map(OrdineUscitaInevasoDTO::getCodJfas)
                    .distinctBy(x -> x)
                    .toList();

            if (foundCodJfas != null && foundCodJfas.size() == 1) {
                mDefaultCodJFasOfUL = foundCodJfas.get(0);
            }

        }
    }

    private void sendOnLoadingStarted() {
        if (this.mListener != null) mListener.onLoadingStarted();
    }

    private void sendOnLoadingEnded() {
        if (this.mListener != null) mListener.onLoadingEnded();
    }

    private void sendError(Exception ex) {
        if (this.mListener != null) mListener.onError(ex);
    }

    private void sendLUOpened(MtbColt mtbColt) {
        if (this.mListener != null) mListener.onLUOpened(mtbColt);
    }

    private void sendLUPesoRequired(String codTcol, BigDecimal netWeightKG, BigDecimal grossWeightKG, RunnableArgsss<String, BigDecimal, BigDecimal> onComplete) {
        if (this.mListener != null)
            mListener.onLUPesoRequired(codTcol, netWeightKG, grossWeightKG, onComplete);
    }

    private void sendLUPrintRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) mListener.onLUPrintRequest(onComplete);
    }

    private void sendLUPrintError(Exception ex, Runnable onComplete) {
        if (this.mListener != null) mListener.onLUPrintError(ex, onComplete);
    }

    private void sendLUClosed() {
        if (this.mListener != null) mListener.onLUClosed();
    }

    private void sendFilterApplied(String newValue) {
        if (this.mListener != null) mListener.onFilterApplied(newValue);
    }

    private void sendFilterRemoved() {
        if (this.mListener != null) mListener.onFilterRemoved();
    }

    private void sendMtbColrDeleteRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) mListener.onMtbColrDeleteRequest(onComplete);
    }

    private void sendOnItemDispatched(PickingObjectDTO pickingObjectDTO,
                                      MtbAart mtbAart,
                                      BigDecimal initialNumCnf,
                                      BigDecimal initialQtaCnf,
                                      BigDecimal initialQtaTot,
                                      BigDecimal totalQtaOrd,
                                      BigDecimal totalNumCnfOrd,
                                      BigDecimal qtaCnfOrd,
                                      BigDecimal totalQtaToBeTaken,
                                      BigDecimal totalNumCnfToBeTaken,
                                      BigDecimal qtaCnfToBeTaken,
                                      BigDecimal totalQtaAvailable,
                                      BigDecimal totalNumCnfAvailable,
                                      BigDecimal qtaCnfAvailable,
                                      String partitaMag,
                                      Date dataScad,
                                      boolean canOverflowOrderQuantity,
                                      boolean canBatchLotBeChanged,
                                      RunnableArgss<PickedQuantityDTO, Boolean> onComplete) {
        if (this.mListener != null) mListener.onItemDispatched(pickingObjectDTO,
                mtbAart,
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                totalQtaOrd,
                totalNumCnfOrd,
                qtaCnfOrd,
                totalQtaToBeTaken,
                totalNumCnfToBeTaken,
                qtaCnfToBeTaken,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                canOverflowOrderQuantity,
                canBatchLotBeChanged,
                onComplete);
    }

    private void sendOnRowSaved() {
        if (this.mListener != null) mListener.onRowSaved();
    }

    private void sendLUPositionChangeRequest(RunnableArgss<Boolean, MtbDepoPosizione> onComplete) {
        if (this.mListener != null) mListener.onLUPositionChangeRequest(onComplete);
    }

    private void sendBatchLotSelectionRequest(List<MtbPartitaMag> availableBatchLots, RunnableArgs<MtbPartitaMag> onComplete) {
        if (this.mListener != null)
            mListener.onBatchLotSelectionRequest(availableBatchLots, onComplete);
    }

    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        //Se non c'è una UL aperta
        if (mCurrentMtbColt == null) {

            //Se è un'etichetta anonima
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                //Se il collo non esiste allora lo creo associandolo a questa etichetta anonima
                //invece se esiste apro un collo nuovo e cerco gli articoli presenti nell'ul
                //dell'etichetta anonima
                this.executeEtichettaAnonimaNotOpenedLU(barcodeScanDTO, onComplete);

            } else if (UtilityBarcode.isEtichettaPosizione(barcodeScanDTO)/* && mDefaultSegnoCol == 1*/) {
                this.processBarcodePosizioneNotOpenedLU(barcodeScanDTO, onComplete);

            } else {
                this.processBarcodeNotOpenedLU(barcodeScanDTO, onComplete);
            }

        } else {
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                //Cerco gli articoli presenti nell'ul dell'etichetta anonima
                this.executeEtichettaLU(barcodeScanDTO.getStringValue(), onComplete);

            } else {
                this.processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete);
            }
        }
    }

    private void processBarcodePosizioneNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        MtbDepoPosizione foundPosizione = UtilityPosizione.getFromCache(barcodeScanDTO.getStringValue());

        if (foundPosizione != null) {
            if (foundPosizione.isFlagMonoCollo()) {

                this.mPosizioneRESTConsumer.getBancaliInPosizione(foundPosizione, mtbColtList -> {
                    if (mtbColtList == null || mtbColtList.size() != 1) {
                        //Nessuna UL trovata oppure più UL nella stessa posizione
                        this.sendError(new NoLUFoundException());
                    } else {
                        mColliMagazzinoRESTConsumer.getByTestata(mtbColtList.get(0), true, false, mtbColt -> {

                            if (mtbColt.getSegno() != mDefaultSegnoCol) {
                                this.sendError(new InvalidLUException());
                                onComplete.run();
                            } else {
                                //Reset of existant mtbColrs
                                mtbColt.setMtbColr(new ObservableArrayList<>());

                                this.mCurrentMtbColt = mtbColt;
                                mMtbColtSessionID = this.mColliDataRecoverService.startNewSession(mtbColt, mTestateOrdini);
                                this.mIsNewLU = false;

                                this.sendLUOpened(mtbColt);

                                onComplete.run();
                            }

                        }, this::sendError);
                    }
                }, this::sendError);

            } else {
                //La posizione non è Mono-UL
                this.sendError(new CurrentMonoLUPositionIsNotCorrectException());
            }

        } else {
            //Nessuna posizione trovata con questo barcode
            this.sendError(new ScannedPositionNotExistException());
        }
    }

    private void processBarcodeNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        this.createNewLU(
                null,
                null,
                () -> processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete));
    }

    private void processBarcodeAlreadyOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        if (UtilityBarcode.isEanPeso(barcodeScanDTO)) {
            //Cerco tramite etichetta ean 128 (che può indicarmi un articolo o una UL)
            this.executeEtichettaEanPeso(barcodeScanDTO, onComplete);


        } else if (UtilityBarcode.isEtichettaPosizione(barcodeScanDTO)) {
            //Cerco l'UL presente all'interno della posizione
            this.executeEtichettaPosizione(barcodeScanDTO.getStringValue(), onComplete);


        } else if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            //Cerco tramite etichetta ean 128 (che può indicarmi un articolo o una UL)
            this.executeEtichettaEan128(barcodeScanDTO, onComplete);


        } else {
            this.loadArticolo(barcodeScanDTO.getStringValue(), null, onComplete);
        }
    }


    private void executeEtichettaAnonimaNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        mColliMagazzinoRESTConsumer.getBySSCC(barcodeScanDTO.getStringValue(), true, false, mtbColt -> {

            if (mtbColt == null) {

                if (!UtilityBarcode.isEtichettaAnonimaOfCurrentYear(barcodeScanDTO.getStringValue())) {
                    this.sendError(new NotCurrentYearLUException());
                } else {

                    this.createNewLU(
                            UtilityBarcode.getNumColloFromULAnonima(barcodeScanDTO.getStringValue()),
                            CommonConst.Config.DEFAULT_ANONYMOUS_UL_SERIE, onComplete);
                }
            } else {

                this.createNewLU(
                        null,
                        null,
                        () -> searchArtFromUL(mtbColt, onComplete)
                );
            }

        }, this::sendError);
    }

    private void executeEtichettaPosizione(String posizione, Runnable onComplete) {
        MtbDepoPosizione foundPosizione = UtilityPosizione.getFromCache(posizione);

        if (foundPosizione != null) {
            if (foundPosizione.isFlagMonoCollo()) {

                this.mPosizioneRESTConsumer.getBancaliInPosizione(foundPosizione, mtbColtList -> {

                    if (mtbColtList == null || mtbColtList.size() != 1) {
                        //Nessuna UL trovata oppure più UL nella stessa posizione
                        this.sendError(new NoLUFoundException());

                    } else {
                        mColliMagazzinoRESTConsumer.getByTestata(mtbColtList.get(0), true, false, mtbColt -> {
                            if (mtbColt != null && mtbColt.getMtbColr() != null && mtbColt.getMtbColr().size() > 0) {
                                searchArtFromUL(mtbColt, onComplete);
                            } else {
                                this.sendError(new NoArtsFoundException());
                            }

                        }, this::sendError);
                    }

                }, this::sendError);

            } else {
                //La posizione non è Mono-UL
                this.sendError(new CurrentMonoLUPositionIsNotCorrectException());
            }

        } else {
            //Nessuna posizione trovata con questo barcode
            this.sendError(new ScannedPositionNotExistException());

        }
    }

    private void executeEtichettaLU(String SSCC, Runnable onComplete) {
        mColliMagazzinoRESTConsumer.getBySSCC(SSCC, true, false, mtbColt -> {

            if (mtbColt != null && mtbColt.getMtbColr() != null && mtbColt.getMtbColr().size() > 0) {
                if (mtbColt.getSegno() != -1) {
                    searchArtFromUL(mtbColt, onComplete);
                } else this.sendError(new InvalidLUException());
            } else {
                this.sendError(new NoArtsFoundException());
            }

        }, this::sendError);
    }


    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        mBarcodeRESTConsumer.decodeEan128(barcodeScanDTO, ean128Model -> {

            String barcodeProd = null;

            if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) barcodeProd = ean128Model.Sscc;
            if (!UtilityString.isNullOrEmpty(ean128Model.Gtin)) barcodeProd = ean128Model.Gtin;
            if (!UtilityString.isNullOrEmpty(ean128Model.Content))
                barcodeProd = ean128Model.Content;


            if (!UtilityString.isNullOrEmpty(barcodeProd)) {

                if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) {
                    this.executeEtichettaLU(ean128Model.Sscc, onComplete);
                } else {
                    if (barcodeProd.startsWith("0") || barcodeProd.startsWith("9")) {
                        barcodeProd = barcodeProd.substring(1);
                    }

                    PickDataDTO pickDataDTO = PickDataDTO.fromEan128(ean128Model);
                    this.loadArticolo(barcodeProd, pickDataDTO, onComplete);
                }
            } else {
                //EAN 128 non completo o comunque mancano i riferimenti al prodotto
                onComplete.run();
            }
        }, this::sendError);
    }

    private void executeEtichettaEanPeso(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        try {
            Ean13PesoModel ean13PesoModel = Ean13PesoModel.fromBarcode(barcodeScanDTO.getStringValue());

            this.loadArticolo(ean13PesoModel.getPrecode(), PickDataDTO.fromEan128(ean13PesoModel.toEan128()), onComplete);
        } catch (Exception ex) {
            this.sendError(ex);
        }
    }

    private void loadArticolo(String barcodeProd, PickDataDTO pickData, Runnable onComplete) {
        if (barcodeProd.length() == 14) {
            barcodeProd = UtilityBarcode.convertITF14toEAN13(barcodeProd);
        }

        this.mArticoloRESTConsumer.getByBarcodeProd(barcodeProd, mtbAartList -> {

            if (mtbAartList != null && mtbAartList.size() > 0) {
                this.searchArtFromAnag(mtbAartList.get(0), pickData, onComplete);
            } else {
                this.sendError(new NoResultFromBarcodeException());
            }

        }, this::sendError);
    }

    private void searchArtFromUL(MtbColt scannedUL, Runnable onComplete) {

        final List<PickingObjectDTO> pickingList = mPickingList.getValue();
        final List<PickingObjectDTO> matchPickingObject = new ArrayList<>();

        if (mEnableGiacenza) {

            Stream.of(pickingList)
                    .forEach(pickingObjectDTO -> {
                        if (Stream.of(pickingObjectDTO.getMtbColts())
                                .anyMatch(x -> Objects.equals(x.getNumCollo(), scannedUL.getNumCollo()) &&
                                        x.getDataColloS().equals(scannedUL.getDataColloS()) &&
                                        x.getSerCollo().equalsIgnoreCase(scannedUL.getSerCollo()) &&
                                        (scannedUL.getGestioneEnum() == GestioneEnum.ACQUISTO || scannedUL.getGestioneEnum() == GestioneEnum.LAVORAZIONE))) {

                            matchPickingObject.add(pickingObjectDTO);
                        }
                    });
        }

        //Controllo se nel collo ho degli articoli che corrispondono per codice / taglia / colore / lotto
        Stream.of(scannedUL.getMtbColr())
                .filter(x -> !UtilityString.isNullOrEmpty(x.getCodMart()))
                .forEach(x -> {

                    for (PickingObjectDTO pickingObject : pickingList) {
                        //Da verificare se il controllo per partita deve essere sempre effettuato
                        if (UtilityString.equalsIgnoreCase(x.getCodMart(), pickingObject.getSitArtOrdDTO().getCodMart()) &&
                                UtilityString.equalsIgnoreCase(x.getCodTagl(), pickingObject.getSitArtOrdDTO().getCodTagl()) &&
                                UtilityString.equalsIgnoreCase(x.getCodCol(), pickingObject.getSitArtOrdDTO().getCodCol()) &&
                                (!mEnableCheckPartitaMag || UtilityString.equalsIgnoreCase(x.getPartitaMag(), pickingObject.getSitArtOrdDTO().getPartitaMag()) || UtilityString.isNullOrEmpty(pickingObject.getSitArtOrdDTO().getPartitaMag()))) {

                            if (!matchPickingObject.contains(pickingObject)) {
                                matchPickingObject.add(pickingObject);
                            }

                        }
                    }
                });

        for (PickingObjectDTO matchedObject : matchPickingObject) {
            MtbColt cloneMtbColt = (MtbColt) scannedUL.clone();
            ObservableArrayList<MtbColr> cloneMtbColrs = (ObservableArrayList<MtbColr>) cloneMtbColt.getMtbColr().clone();


            Stream.of(cloneMtbColt.getMtbColr())
                    .filter(x -> !(UtilityString.equalsIgnoreCase(x.getCodMart(), matchedObject.getSitArtOrdDTO().getCodMart()) &&
                            UtilityString.equalsIgnoreCase(x.getCodTagl(), matchedObject.getSitArtOrdDTO().getCodTagl()) &&
                            UtilityString.equalsIgnoreCase(x.getCodCol(), matchedObject.getSitArtOrdDTO().getCodCol()) &&
                            (!mEnableCheckPartitaMag || UtilityString.equalsIgnoreCase(x.getPartitaMag(), matchedObject.getSitArtOrdDTO().getPartitaMag()) || UtilityString.isNullOrEmpty(matchedObject.getSitArtOrdDTO().getPartitaMag()))))
                    .forEach(cloneMtbColrs::remove);

            cloneMtbColt.setMtbColr(cloneMtbColrs);

            PickDataDTO tempPickData = new PickDataDTO()
                    .setSourceMtbColt(cloneMtbColt);

            matchedObject.setTempPickData(tempPickData);
        }


        this.loadMatchedRows(matchPickingObject);
        onComplete.run();
    }


    private void searchArtFromAnag(MtbAart mtbAart, PickDataDTO pickData, Runnable onComplete) {
        final List<PickingObjectDTO> pickingList = mPickingList.getValue();

        List<PickingObjectDTO> matchPickingObject = Stream.of(pickingList)
                .filter(x -> UtilityString.equalsIgnoreCase(mtbAart.getCodMart(), x.getSitArtOrdDTO().getCodMart()))
                .filter(x -> {
                    MtbPartitaMag mtbPartitaMag = pickData != null && pickData.getManualPickDTO() != null && pickData.getManualPickDTO().getMtbPartitaMag() != null ? pickData.getManualPickDTO().getMtbPartitaMag() : null;
                    return !mEnableCheckPartitaMag || (mtbPartitaMag != null && UtilityString.equalsIgnoreCase(x.getSitArtOrdDTO().getPartitaMag(), mtbPartitaMag.getPartitaMag()));
                })
                .toList();

        for (PickingObjectDTO pickingObjectDTO : matchPickingObject) {
            pickingObjectDTO.setTempPickData(pickData);
        }

        this.loadMatchedRows(matchPickingObject);
        onComplete.run();
    }


    private void loadMatchedRows(List<PickingObjectDTO> matchedRows) {
        if (matchedRows == null || matchedRows.size() == 0) {
            this.sendError(new NoArtsFoundException());
        } else if (matchedRows.size() == 1) {
            PickingObjectDTO matchedItem = matchedRows.get(0);


            if (matchedItem.getMtbColts() != null && matchedItem.getMtbColts().size() > 1) {

                List<PickingObjectDTO> pickingList = mPickingList.getValue();

                Stream.of(pickingList)
                        .filter(x -> x != matchedItem)
                        .forEach(x -> x.setHidden(true));

                this.sendFilterApplied(null);
                this.getPickingList().postValue(pickingList);
            } else {
                MtbColt refMtbColt = matchedItem.getRefMtbColt();
                MtbColr refMtbColr = refMtbColt != null ? matchedItem.getRefMtbColt().getMtbColr().get(0) : null;
                this.dispatchOrdineRow(matchedItem, refMtbColt, refMtbColr, false);
            }

        } else {
            List<PickingObjectDTO> pickingList = mPickingList.getValue();

            for (int i = 0; i < pickingList.size(); i++) {
                if (!matchedRows.contains(pickingList.get(i))) {
                    pickingList.get(i).setHidden(true);
                }
            }

            this.sendFilterApplied(null);
            this.getPickingList().postValue(pickingList);

        }
    }


    public void dispatchOrdineRow(final PickingObjectDTO pickingObjectDTO, MtbColt refMtbColt, MtbColr refMtbColr, boolean canPartitaMagBeChanged) {

        //TODO: Al posto di prelevare la prima riga bisognerebbe controllare se c'è ne una che corrisponde con la partita richiesta
//        MtbColr refMtbColr = refMtbColt != null ? refMtbColt.getMtbColr().get(0) : null;


        if (pickingObjectDTO.getTempPickData() != null &&
                pickingObjectDTO.getTempPickData().getSourceMtbColt() != null &&
                pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr() != null) {

            List<MtbColr> scannedMtbColrs = pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr();

            List<MtbPartitaMag> availableBatchLots = Stream.of(scannedMtbColrs)
                    .filter(x -> x.getCodMart().equalsIgnoreCase(pickingObjectDTO.getMtbAart().getCodMart()) && (
                                    x.getPartitaMag() == null ||
                                    UtilityString.isNullOrEmpty(x.getMtbPartitaMag().getDataScadS()) ||
                                    UtilityDate.getDateInstance().before(x.getMtbPartitaMag().getDataScadD())
                            ))
                    .distinctBy(MtbColr::getPartitaMag)
                    .map(x -> x.getPartitaMag() == null ? new MtbPartitaMag().setCodMart(x.getMtbAart().getCodMart()) : x.getMtbPartitaMag())
                    .toList();

            //Controllo se una delle partite combacia con quella che voglio evadere
            if(refMtbColr != null && refMtbColr.getMtbPartitaMag() != null) {
                MtbPartitaMag refMtbPartitaMag = refMtbColr.getMtbPartitaMag();

                Optional<MtbPartitaMag> matchPartitaMag = Stream.of(availableBatchLots)
                        .filter(availableBatchLot -> refMtbPartitaMag.getPartitaMag().equalsIgnoreCase(availableBatchLot.getPartitaMag()))
                        .findFirst();

                if(matchPartitaMag.isPresent()) {
                    availableBatchLots.clear();
                    availableBatchLots.add(refMtbPartitaMag);
                }
            }

            if(availableBatchLots.size() > 1) {
                this.sendOnLoadingStarted();

                List<MtbPartitaMag> tmp = new ArrayList<>();

                if(Stream.of(availableBatchLots).anyMatch(x -> !UtilityString.isNullOrEmpty(x.getDataScadS()))) {
                    tmp.addAll(
                            Stream.of(availableBatchLots)
                                    .filter(x -> !UtilityString.isNullOrEmpty(x.getDataScadS()) && UtilityDate.getDateInstance().before(x.getDataScadD()))
                                    .sortBy(MtbPartitaMag::getDataScadD)
                                    .toList());
                }

                if(Stream.of(availableBatchLots).anyMatch(x -> UtilityString.isNullOrEmpty(x.getDataScadS()))) {
                    tmp.addAll(
                            Stream.of(availableBatchLots)
                                    .filter(x -> UtilityString.isNullOrEmpty(x.getDataScadS()))
                                    .sortBy(MtbPartitaMag::getPartitaMag)
                                    .toList());
                }

                this.sendBatchLotSelectionRequest(tmp, selected -> {
                    this.sendOnLoadingEnded();

                    if(selected != null) {
                        MtbColr scannedMtbColr = Stream.of(pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr())
                                .filter(x -> x.getCodMart().equalsIgnoreCase(pickingObjectDTO.getMtbAart().getCodMart()) &&
                                        ((x.getPartitaMag() != null && selected.getPartitaMag() != null && x.getPartitaMag().equalsIgnoreCase(selected.getPartitaMag())) ||
                                                (x.getPartitaMag() == null && selected.getPartitaMag() == null)))
                                .toList()
                                .get(0);

                        dispatchOrdineRowOnPostBatchLotSelection(pickingObjectDTO, refMtbColt, refMtbColr, scannedMtbColr, canPartitaMagBeChanged);
                    }
                });

            } else {
                dispatchOrdineRowOnPostBatchLotSelection(pickingObjectDTO, refMtbColt, refMtbColr, scannedMtbColrs.get(0), canPartitaMagBeChanged);
            }

        } else {
            dispatchOrdineRowOnPostBatchLotSelection(pickingObjectDTO, refMtbColt, refMtbColr, null, canPartitaMagBeChanged);
        }


    }

    private void dispatchOrdineRowOnPostBatchLotSelection(final PickingObjectDTO pickingObjectDTO, MtbColt refMtbColt, MtbColr refMtbColr, MtbColr scannedMtbColr, boolean canPartitaMagBeChanged) {
        BigDecimal totalQtaOrd = pickingObjectDTO.getSitArtOrdDTO().getQtaOrd();
        BigDecimal totalNumCnfOrd = pickingObjectDTO.getSitArtOrdDTO().getNumCnfOrd();
        BigDecimal qtaCnfOrd = pickingObjectDTO.getSitArtOrdDTO().getQtaCnfOrd();

        AtomicBigDecimal numCnfWithdrawRows = new AtomicBigDecimal();
        AtomicBigDecimal qtaColWithdrawRows = new AtomicBigDecimal();

        Stream.of(pickingObjectDTO.getWithdrawMtbColrs())
                .forEach(row -> {
                    numCnfWithdrawRows.addAndGet(row.getNumCnf());
                    qtaColWithdrawRows.addAndGet(row.getQtaCol());
                });

        BigDecimal numCnfDaEvadere = pickingObjectDTO.getSitArtOrdDTO().getNumCnfOrd().subtract(numCnfWithdrawRows.getBigDecimalValue());
        BigDecimal qtaDaEvadere = pickingObjectDTO.getSitArtOrdDTO().getQtaOrd().subtract(qtaColWithdrawRows.getBigDecimalValue());
        BigDecimal qtaCnfDaEvadere = qtaCnfOrd;

        numCnfDaEvadere = UtilityBigDecimal.getGreaterBetween(numCnfDaEvadere, BigDecimal.ZERO);
        qtaDaEvadere = UtilityBigDecimal.getGreaterBetween(qtaDaEvadere, BigDecimal.ZERO);

        BigDecimal numCnfDaPrelevare = null;
        BigDecimal qtaColDaPrelevare = null;
        BigDecimal qtaCnfDaPrelevare = null;


        BigDecimal totalQtaAvailable = null;
        BigDecimal totalNumCnfAvailable = null;
        BigDecimal qtaCnfAvailable = null;

        String partitaMag = null;
        Date dataScad = null;


        if (refMtbColt == null && pickingObjectDTO.getMtbColts() != null && pickingObjectDTO.getMtbColts().size() == 1) {
            refMtbColt = pickingObjectDTO.getMtbColts().get(0);
        }

//        MtbColr refMtbColr = null;
//
//        if (scannedMtbColr != null && refMtbColt != null && refMtbColt.getMtbColr().size() > 0) {
//            Optional<MtbColr> optionalMtbColr = Stream.of(refMtbColt.getMtbColr())
//                    .filter(y -> UtilityString.equalsIgnoreCase(y.getCodMart(), scannedMtbColr.getCodMart()) &&
//                            UtilityString.equalsIgnoreCase(y.getCodTagl(), scannedMtbColr.getCodTagl()) &&
//                            UtilityString.equalsIgnoreCase(y.getCodCol(), scannedMtbColr.getCodCol()) &&
//                            (!mEnableCheckPartitaMag || UtilityString.equalsIgnoreCase(y.getPartitaMag(), scannedMtbColr.getPartitaMag()) || UtilityString.isNullOrEmpty(scannedMtbColr.getPartitaMag())))
//                    .findFirst();
//
//            if (optionalMtbColr.isPresent()) refMtbColr = optionalMtbColr.get();
//        }


        if(refMtbColr != null) {
            if (pickingObjectDTO.getMtbAart().isFlagQtaCnfFissaBoolean()) {
                if (UtilityBigDecimal.equalsOrLowerThan(refMtbColr.getQtaCol(), qtaDaEvadere)) {
                    numCnfDaPrelevare = refMtbColr.getNumCnf();
                    qtaColDaPrelevare = refMtbColr.getQtaCol();
                } else {
                    numCnfDaPrelevare = numCnfDaEvadere;
                    qtaColDaPrelevare = qtaDaEvadere;
                }

                qtaCnfDaPrelevare = refMtbColr.getQtaCnf();
            } else {
                if (UtilityBigDecimal.equalsOrLowerThan(refMtbColr.getNumCnf(), numCnfDaEvadere)) {
                    numCnfDaPrelevare = refMtbColr.getNumCnf();
                    qtaCnfDaPrelevare = refMtbColr.getQtaCnf();
                    qtaColDaPrelevare = refMtbColr.getQtaCol();
                } else {
                    numCnfDaPrelevare = numCnfDaEvadere;
                    qtaColDaPrelevare = qtaDaEvadere;
                    qtaCnfDaPrelevare = qtaCnfDaEvadere;
                }
            }
        }


        MtbColr mtbColrToUse = scannedMtbColr != null ? scannedMtbColr : refMtbColr;
        if (mtbColrToUse != null) {

            totalQtaAvailable = mtbColrToUse.getQtaCol();
            totalNumCnfAvailable = mtbColrToUse.getNumCnf();
            qtaCnfAvailable = mtbColrToUse.getQtaCnf();


            if (UtilityBigDecimal.lowerThan(numCnfDaPrelevare, BigDecimal.ZERO))
                numCnfDaPrelevare = BigDecimal.ZERO;

            if (UtilityBigDecimal.lowerThan(qtaColDaPrelevare, BigDecimal.ZERO))
                qtaColDaPrelevare = BigDecimal.ZERO;

            partitaMag = scannedMtbColr.getPartitaMag();
            dataScad = scannedMtbColr.getDataScadPartitaD();

        } else if (pickingObjectDTO.getTempPickData() != null && pickingObjectDTO.getTempPickData().getManualPickDTO() != null) {
            //Oppure le info del barcode scansionato
            PickDataDTO.ManualPickDTO manualPickDTO = pickingObjectDTO.getTempPickData().getManualPickDTO();
            MtbAart mtbAart = pickingObjectDTO.getMtbAart();


            qtaColDaPrelevare = manualPickDTO.getQtaTot();
            numCnfDaPrelevare = manualPickDTO.getNumCnf();

            if (mtbAart.isFlagQtaCnfFissaBoolean()) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();

                if (manualPickDTO.isEanPeso()) {
                    if (mtbAart.getUntMisRifPeso() == MtbAart.UntMisRifPesoEnum.M) {
                        if (UtilityBigDecimal.equalsTo(mtbAart.getPesoKg(), BigDecimal.ZERO)) {
                            this.sendError(new InvalidPesoKGException());
                        } else {
                            qtaColDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, mtbAart.getPesoKg());
                        }
                    }
                }
            } else if (qtaColDaPrelevare != null && numCnfDaPrelevare != null) {
                qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
            }


            if (qtaColDaPrelevare != null && numCnfDaPrelevare == null) {
                numCnfDaPrelevare = UtilityBigDecimal.divideToInteger(qtaColDaPrelevare, mtbAart.getQtaCnf());

                if (!mtbAart.isFlagQtaCnfFissaBoolean()) {
                    if (UtilityBigDecimal.equalsTo(numCnfDaPrelevare, BigDecimal.ZERO))
                        numCnfDaPrelevare = BigDecimal.ONE;
                    qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
                }
            } else if (numCnfDaPrelevare != null && qtaColDaPrelevare == null) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();
                qtaColDaPrelevare = UtilityBigDecimal.multiply(numCnfDaPrelevare, qtaCnfDaPrelevare);
            }

            if (manualPickDTO.getMtbPartitaMag() != null) {
                partitaMag = manualPickDTO.getMtbPartitaMag().getPartitaMag();
                dataScad = manualPickDTO.getMtbPartitaMag().getDataScadD();
            }
        }

        MtbColt finalRefMtbColt = refMtbColt;
        this.sendOnItemDispatched(
                pickingObjectDTO,
                pickingObjectDTO.getMtbAart(),
                mUseQtaOrd ? numCnfDaPrelevare : null,
                mUseQtaOrd ? qtaCnfDaPrelevare : null,
                mUseQtaOrd ? qtaColDaPrelevare : null,
                qtaDaEvadere,
                numCnfDaEvadere,
                qtaCnfDaEvadere,
                qtaColDaPrelevare,
                numCnfDaPrelevare,
                qtaCnfDaPrelevare,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                mCanOverflowOrderQuantity,
                canPartitaMagBeChanged,
                (pickedQuantityDTO, shouldCloseLU) -> {
                    this.saveNewRow(pickingObjectDTO,
                            finalRefMtbColt,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });
    }

    public void dispatchExtraItem(MtbAart mtbAart, Ean128Model ean128Model, MtbColr mtbColrToUse) {

        PickingObjectDTO pickingObjectDTO = new PickingObjectDTO()
                .setMtbAart(mtbAart)
                .setTempPickData(PickDataDTO.fromEan128(ean128Model));

        BigDecimal numCnfDaPrelevare = null;
        BigDecimal qtaColDaPrelevare = null;
        BigDecimal qtaCnfDaPrelevare = null;

        BigDecimal totalQtaAvailable = null;
        BigDecimal totalNumCnfAvailable = null;
        BigDecimal qtaCnfAvailable = null;

        BigDecimal initialNumCnf = null;
        BigDecimal initialQtaCnf = null;
        BigDecimal initialQtaTot = null;

        String partitaMag = null;
        Date dataScad = null;

        MtbColt refMtbColt = null;

        if (mtbColrToUse != null) {
            refMtbColt = new MtbColt()
                    .setDataCollo(mtbColrToUse.getDataColloD())
                    .setSerCollo(mtbColrToUse.getSerCollo())
                    .setNumCollo(mtbColrToUse.getNumCollo())
                    .setGestione(mtbColrToUse.getGestione())
                    .setMtbColr(new ObservableArrayList<>());

            refMtbColt.getMtbColr().add(mtbColrToUse);

            pickingObjectDTO
                    .setMtbAart(mtbColrToUse.getMtbAart())
                    .setTempPickData(new PickDataDTO().setSourceMtbColt(refMtbColt));

            if (mtbColrToUse.getMtbAart().isFlagQtaCnfFissaBoolean()) {
                numCnfDaPrelevare = mtbColrToUse.getNumCnf();
                qtaColDaPrelevare = mtbColrToUse.getQtaCol();
                qtaCnfDaPrelevare = mtbColrToUse.getQtaCnf();
            } else {
                numCnfDaPrelevare = mtbColrToUse.getNumCnf();
                qtaCnfDaPrelevare = mtbColrToUse.getQtaCnf();
                qtaColDaPrelevare = mtbColrToUse.getQtaCol();
            }


            totalQtaAvailable = mtbColrToUse.getQtaCol();
            totalNumCnfAvailable = mtbColrToUse.getNumCnf();
            qtaCnfAvailable = mtbColrToUse.getQtaCnf();


            if (UtilityBigDecimal.lowerThan(numCnfDaPrelevare, BigDecimal.ZERO))
                numCnfDaPrelevare = BigDecimal.ZERO;

            if (UtilityBigDecimal.lowerThan(qtaColDaPrelevare, BigDecimal.ZERO))
                qtaColDaPrelevare = BigDecimal.ZERO;

            partitaMag = mtbColrToUse.getPartitaMag();
            dataScad = mtbColrToUse.getDataScadPartitaD();

        } else if (pickingObjectDTO.getTempPickData() != null && pickingObjectDTO.getTempPickData().getManualPickDTO() != null) {
            //Oppure le info del barcode scansionato
            PickDataDTO.ManualPickDTO manualPickDTO = pickingObjectDTO.getTempPickData().getManualPickDTO();

            qtaColDaPrelevare = manualPickDTO.getQtaTot();
            numCnfDaPrelevare = manualPickDTO.getNumCnf();

            if (mtbAart.isFlagQtaCnfFissaBoolean()) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();
            } else if (qtaColDaPrelevare != null && numCnfDaPrelevare != null) {
                qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
            }


            if (qtaColDaPrelevare != null && numCnfDaPrelevare == null) {
                numCnfDaPrelevare = UtilityBigDecimal.divideToInteger(qtaColDaPrelevare, mtbAart.getQtaCnf());
                qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);

                if (!mtbAart.isFlagQtaCnfFissaBoolean()) {
                    if (UtilityBigDecimal.equalsTo(numCnfDaPrelevare, BigDecimal.ZERO))
                        numCnfDaPrelevare = BigDecimal.ONE;
                    qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
                }
            } else if (numCnfDaPrelevare != null && qtaColDaPrelevare == null) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();
                qtaColDaPrelevare = UtilityBigDecimal.multiply(numCnfDaPrelevare, qtaCnfDaPrelevare);
            }

            if (manualPickDTO.getMtbPartitaMag() != null) {
                partitaMag = manualPickDTO.getMtbPartitaMag().getPartitaMag();
                dataScad = manualPickDTO.getMtbPartitaMag().getDataScadD();
            }
        } else {
            initialNumCnf = BigDecimal.ONE;
            initialQtaCnf = mtbAart.getQtaCnf();
            initialQtaTot = UtilityBigDecimal.multiply(initialNumCnf, initialQtaCnf);
        }

        if (numCnfDaPrelevare == null && qtaColDaPrelevare == null && initialNumCnf == null && initialQtaTot == null) {
            initialNumCnf = BigDecimal.ONE;
            initialQtaCnf = mtbAart.getQtaCnf();
            initialQtaTot = UtilityBigDecimal.multiply(initialNumCnf, initialQtaCnf);
        }


        MtbColt finalRefMtbColt = refMtbColt;
        this.sendOnItemDispatched(
                pickingObjectDTO,
                pickingObjectDTO.getMtbAart(),
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                null,
                null,
                null,
                qtaColDaPrelevare,
                numCnfDaPrelevare,
                qtaCnfDaPrelevare,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                mCanOverflowOrderQuantity,
                true,
                (pickedQuantityDTO, shouldCloseLU) -> {
                    this.saveNewRow(pickingObjectDTO,
                            finalRefMtbColt,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });
    }


    public void dispatchRowEdit(final MtbColr mtbColrToEdit) {
        this.sendOnLoadingStarted();

        Optional<PickingObjectDTO> optionalPickingObjectDTO = Stream.of(this.mPickingList.getValue())
                .filter(x -> Stream.of(x.getWithdrawMtbColrs()).anyMatch(y -> y == mtbColrToEdit))
                .findSingle();

        PickingObjectDTO pickingObjectDTO = null;

        if (optionalPickingObjectDTO.isPresent()) {
            pickingObjectDTO = optionalPickingObjectDTO.get();
        }


        PickingObjectDTO finalPickingObjectDTO = pickingObjectDTO;
        loadRifULFromMtbColr(mtbColrToEdit, mtbColtRif -> {

            BigDecimal totalQtaOrd = null;
            BigDecimal totalNumCnfOrd = null;
            BigDecimal qtaCnfOrd = null;

            BigDecimal numCnfDaEvadere = null;
            BigDecimal qtaDaEvadere = null;
            BigDecimal qtaCnfDaEvadere = null;

            if (finalPickingObjectDTO != null) {
                totalQtaOrd = finalPickingObjectDTO.getSitArtOrdDTO().getQtaOrd();
                totalNumCnfOrd = finalPickingObjectDTO.getSitArtOrdDTO().getNumCnfOrd();
                qtaCnfOrd = finalPickingObjectDTO.getSitArtOrdDTO().getQtaCnfOrd();


                AtomicBigDecimal numCnfWithdrawRows = new AtomicBigDecimal(0);
                AtomicBigDecimal qtaColWithdrawRows = new AtomicBigDecimal(0);

                Stream.of(finalPickingObjectDTO.getWithdrawMtbColrs())
                        .forEach(row -> {
                            numCnfWithdrawRows.addAndGet(row.getNumCnf());
                            qtaColWithdrawRows.addAndGet(row.getQtaCol());
                        });

                numCnfDaEvadere = totalNumCnfOrd.subtract(numCnfWithdrawRows.getBigDecimalValue()).add(mtbColrToEdit.getNumCnf());
                qtaDaEvadere = totalQtaOrd.subtract(qtaColWithdrawRows.getBigDecimalValue()).add(mtbColrToEdit.getQtaCol());
                qtaCnfDaEvadere = mtbColrToEdit.getQtaCnf();
            }

            BigDecimal numCnfDaPrelevare = null;
            BigDecimal qtaColDaPrelevare = null;
            BigDecimal qtaCnfDaPrelevare = null;

            BigDecimal totalQtaAvailable = null;
            BigDecimal totalNumCnfAvailable = null;
            BigDecimal qtaCnfAvailable = null;

            String partitaMag = null;
            Date dataScad = null;


            List<MtbColr> mtbColrRifs = mtbColtRif != null &&
                    mtbColtRif.getMtbColr() != null ? mtbColtRif.getMtbColr() : null;

            MtbColr mtbColrRif = null;

            if (mtbColrRifs != null && mtbColrRifs.size() > 0) {
                //TODO: Da capire se è necessario controllare anche il cod_jcom
                Optional<MtbColr> optionalMtbColr = Stream.of(mtbColrRifs)
                        .filter(x -> UtilityString.equalsIgnoreCase(x.getCodMart(), mtbColrToEdit.getCodMart()) &&
                                UtilityString.equalsIgnoreCase(x.getCodCol(), mtbColrToEdit.getCodCol()) &&
                                UtilityString.equalsIgnoreCase(x.getCodTagl(), mtbColrToEdit.getCodTagl()) &&
                                UtilityString.equalsIgnoreCase(x.getPartitaMag(), mtbColrToEdit.getPartitaMag()))
                        .findSingle();

                mtbColrRif = optionalMtbColr.isPresent() ? optionalMtbColr.get() : null;
            }


            if (mtbColrRif != null) {

                totalQtaAvailable = mtbColrRif.getQtaCol();
                totalNumCnfAvailable = mtbColrRif.getNumCnf();
                qtaCnfAvailable = mtbColrRif.getQtaCnf();

                totalNumCnfAvailable = totalNumCnfAvailable.add(mtbColrToEdit.getNumCnf());
                totalQtaAvailable = totalQtaAvailable.add(mtbColrToEdit.getQtaCol());

            } else {

                totalQtaAvailable = mtbColrToEdit.getQtaCol();
                totalNumCnfAvailable = mtbColrToEdit.getNumCnf();
                qtaCnfAvailable = mtbColrToEdit.getQtaCnf();

            }


            if (UtilityBigDecimal.lowerThan(numCnfDaPrelevare, BigDecimal.ZERO))
                numCnfDaPrelevare = BigDecimal.ZERO;

            if (UtilityBigDecimal.lowerThan(qtaColDaPrelevare, BigDecimal.ZERO))
                qtaColDaPrelevare = BigDecimal.ZERO;


            partitaMag = mtbColrToEdit.getPartitaMag();
            dataScad = mtbColrToEdit.getDataScadPartitaD();

            this.sendOnLoadingEnded();

            this.sendOnItemDispatched(
                    finalPickingObjectDTO,
                    mtbColrToEdit.getMtbAart(),
                    mtbColrToEdit.getNumCnf(),
                    mtbColrToEdit.getQtaCnf(),
                    mtbColrToEdit.getQtaCol(),
                    qtaDaEvadere,
                    numCnfDaEvadere,
                    qtaCnfDaEvadere,
                    qtaColDaPrelevare,
                    numCnfDaPrelevare,
                    qtaCnfDaPrelevare,
                    totalQtaAvailable,
                    totalNumCnfAvailable,
                    qtaCnfAvailable,
                    partitaMag,
                    dataScad,
                    mCanOverflowOrderQuantity,
                    false,
                    (pickedQuantityDTO, shouldCloseLU) -> {
                        this.saveEditedRow(mtbColrToEdit,
                                pickedQuantityDTO.getNumCnf(),
                                pickedQuantityDTO.getQtaCnf(),
                                pickedQuantityDTO.getQtaTot(),
                                pickedQuantityDTO.getPartitaMag(),
                                pickedQuantityDTO.getDataScad(),
                                shouldCloseLU);
                    });
        });
    }

    private void loadRifULFromMtbColr(MtbColr mtbColr, RunnableArgs<MtbColt> onComplete) {
        //Se ho dei riferimenti ad una UL devo leggere la QTA ancora disponibile sulla Ul
        if (mtbColr != null &&
                !UtilityString.isNullOrEmpty(mtbColr.getGestioneRif()) &&
                !UtilityString.isNullOrEmpty(mtbColr.getSerColloRif()) &&
                !UtilityString.isNullOrEmpty(mtbColr.getDataColloRifS()) &&
                mtbColr.getNumColloRif() != null) {

            mColliMagazzinoRESTConsumer.getByChiaveCollo(
                    mtbColr.getGestioneRifEnum(),
                    mtbColr.getNumColloRif(),
                    mtbColr.getDataColloRifS(),
                    mtbColr.getSerColloRif(),
                    true,
                    false,
                    onComplete,
                    this::sendError);

        } else {
            onComplete.run(null);
        }
    }

    public void createNewLU(Integer customNumCollo, String customSerCollo, Runnable onComplete) {
        MtbColt mtbColt = new MtbColt();
        mtbColt.initDefaultFields();
        mtbColt.setGestione(mDefaultGestioneOfUL)
                .setSegno(mDefaultSegnoCol)
                .setRifOrd(mDefaultRifOrdOfUL)
                .setNumOrd(mDefaultNumOrdOfUL)
                .setDataOrdD(mDefaultDataOrdOfUL)
                .setCodAnag(mDefaultCodAnagOfUL)
                .setCodMdep(mDefaultCodMdep)
                .setRagSocCliente(mDefaultRagSocOfUL)
                .setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

        if (mDefaultGestioneOfUL == GestioneEnum.LAVORAZIONE && mDefaultSegnoCol == -1)
            mtbColt.setPosizione(mDefaultCodJFasOfUL);
        if (customNumCollo != null) mtbColt.setNumCollo(customNumCollo);
        if (!UtilityString.isNullOrEmpty(customSerCollo)) mtbColt.setSerCollo(customSerCollo);

        mtbColt.generaFiltroOrdineFromDTO(mDefaultFiltroOrdine);

        mColliMagazzinoRESTConsumer.saveCollo(mtbColt, value -> {
            mtbColt
                    .setNumCollo(value.getNumCollo())
                    .setDataCollo(value.getDataColloS())
                    .setMtbColr(new ObservableArrayList<>());

            this.mCurrentMtbColt = mtbColt;
            mMtbColtSessionID = this.mColliDataRecoverService.startNewSession(mtbColt, mTestateOrdini);
            this.mIsNewLU = true;

            if (onComplete != null) onComplete.run();
            this.sendLUOpened(mtbColt);
        }, this::sendError);

    }

    public void saveNewRow(PickingObjectDTO pickingObjectDTO, MtbColt refMtbColt, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {
        this.sendOnLoadingStarted();

        final MtbColr mtbColr = new MtbColr()
                .setCausale(mDefaultCausaleOfUL)
                .setCodMart(pickingObjectDTO.getMtbAart().getCodMart())
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad)
                .setQtaCol(qtaTot)
                .setQtaCnf(qtaCnf)
                .setNumCnf(numCnf)
                .setDescrizione(pickingObjectDTO.getMtbAart().getDescrizioneEstesa())
                .setDatetimeRow(UtilityDate.getDateInstance());

        //TODO: Al posto di prelevare la prima riga bisognerebbe controllare se c'è ne una che corrisponde con la partita richiesta
        final MtbColr mtbColrToDispatch = pickingObjectDTO.getTempPickData() != null &&
                pickingObjectDTO.getTempPickData().getSourceMtbColt() != null &&
                pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr() != null &&
                pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr().size() > 0 ?
                pickingObjectDTO.getTempPickData().getSourceMtbColt().getMtbColr().get(0) : null;

        if (mtbColrToDispatch != null) {
            if (UtilityString.isNullOrEmpty(mCurrentMtbColt.getCodTcol()))
                mCurrentMtbColt.setCodTcol(UtilityString.empty2null(pickingObjectDTO.getTempPickData().getSourceMtbColt().getCodTcol()));

            mtbColr
                    .setCodJcom(UtilityString.empty2null(mtbColrToDispatch.getCodJcom()))
                    .setSerColloRif(UtilityString.empty2null(mtbColrToDispatch.getSerCollo()))
                    .setNumColloRif(mtbColrToDispatch.getNumCollo())
                    .setGestioneRif(UtilityString.empty2null(mtbColrToDispatch.getGestione()))
                    .setDataColloRif(UtilityString.empty2null(mtbColrToDispatch.getDataColloS()));


            if (mtbColrToDispatch.getPesoNettoKg() != null) {
                //Proporzione
                BigDecimal pesoNettoKg = UtilityBigDecimal.divide(UtilityBigDecimal.multiply(qtaTot, mtbColrToDispatch.getPesoNettoKg()), mtbColrToDispatch.getQtaCol());
                mtbColr.setPesoNettoKg(pesoNettoKg);
            }

            if (mtbColrToDispatch.getPesoLordoKg() != null) {
                //Proporzione
                BigDecimal pesoLordoKg = UtilityBigDecimal.divide(UtilityBigDecimal.multiply(qtaTot, mtbColrToDispatch.getPesoLordoKg()), mtbColrToDispatch.getQtaCol());
                mtbColr.setPesoLordoKg(pesoLordoKg);
            }


        } else if (mEnableGiacenza && pickingObjectDTO.getMtbColts() != null && pickingObjectDTO.getMtbColts().size() > 0) {
            // Vecchia logica di aggancio UL (se non ho sparato una etichetta UL comunque ti aggangio la prima UL suggerita)
//            mtbColr
//                    .setCodJcom(UtilityString.empty2null(item.getCodJcom()))
//                    .setSerColloRif(UtilityString.empty2null(item.getSerCollo()))
//                    .setNumColloRif(item.getNumCollo())
//                    .setGestioneRif(UtilityString.empty2null(item.getGestione()))
//                    .setDataColloRif(UtilityString.empty2null(item.getDataColloS()));
        }

        mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

        MtbColt cloneMtbColt = (MtbColt) mCurrentMtbColt.clone();
        cloneMtbColt.setOperation(CommonModelConsts.OPERATION.UPDATE);

        cloneMtbColt.setMtbColr(new ObservableArrayList<>());
        cloneMtbColt.getMtbColr().add((MtbColr) mtbColr.clone());

        boolean shouldPrint = true;

        //Se è l'unico articolo del collo (controllo se è uguale a 0 perché ancora non è stato aggiunto nella lista delle righe)
        if (shouldCloseLU && mCurrentMtbColt.getMtbColr().size() == 0) {
            shouldPrint = false;
        }

        if (UtilityBigDecimal.equalsTo(numCnf, BigDecimal.ZERO) && UtilityBigDecimal.equalsTo(qtaTot, BigDecimal.ZERO)) {
            resetMatchedRows();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU(shouldPrint);
            return;
        }

        boolean finalShouldPrint = shouldPrint;
        mColliMagazzinoRESTConsumer.saveCollo(cloneMtbColt, value -> {

            mtbColr
                    .setDataCollo(value.getDataColloS())
                    .setNumCollo(value.getNumCollo())
                    .setGestione(value.getGestione())
                    .setSerCollo(value.getSerCollo())
                    .setRiga(value.getMtbColr().get(value.getMtbColr().size() - 1).getRiga())
                    .setUntMis(pickingObjectDTO.getMtbAart().getUntMis())
                    .setMtbAart(pickingObjectDTO.getMtbAart());


            if (mEnableGiacenza) {
                MtbColr refMtbColr = new MtbColr()
                        .setCodMart(mtbColr.getCodMart())
                        .setPartitaMag(mtbColr.getPartitaMag())
                        .setCodTagl(mtbColr.getCodTagl())
                        .setCodCol(mtbColr.getCodCol());

                if (refMtbColt != null) {

                    MtbColr originalRefMtbColr = refMtbColt.getMtbColr() != null &&
                            refMtbColt.getMtbColr().size() > 0 ?
                            refMtbColt.getMtbColr().get(0) : null;

                    if(originalRefMtbColr != null) {
                        refMtbColr.setId(originalRefMtbColr.getId());
                    }

                    refMtbColr
                            .setNumCollo(refMtbColt.getNumCollo())
                            .setDataCollo(refMtbColt.getDataColloS())
                            .setSerCollo(refMtbColt.getSerCollo())
                            .setGestione(refMtbColt.getGestione());
                }
                mtbColr.setRefMtbColr(refMtbColr);
            }

            pickingObjectDTO.getWithdrawMtbColrs().add(mtbColr);
            mCurrentMtbColt.getMtbColr().add(mtbColr);

            //Chiamato removeListFilter perché cosi mi cancella tutti i dati di pick temporanei
            resetMatchedRows();

            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU(finalShouldPrint);
        }, this::sendError);

    }

    private void saveEditedRow(MtbColr mtbColrToUpdate, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {

        this.sendOnLoadingStarted();

        MtbColt mtbColt = new MtbColt()
                .setNumCollo(mtbColrToUpdate.getNumCollo())
                .setDataCollo(mtbColrToUpdate.getDataColloS())
                .setSerCollo(mtbColrToUpdate.getSerCollo())
                .setGestione(mtbColrToUpdate.getGestione())
                .setMtbColr(new ObservableArrayList<>());
        mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

        final MtbColr mtbColr = (MtbColr) mtbColrToUpdate.clone();
        mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
        mtbColr.setRiga(null)
                .setPesoLordoKg(null)
                .setPesoNettoKg(null)
                .setNumCnf(numCnf.subtract(mtbColrToUpdate.getNumCnf()))
                .setQtaCnf(qtaCnf)
                .setQtaCol(qtaTot.subtract(mtbColrToUpdate.getQtaCol()))
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad);

        mtbColt.getMtbColr().add(mtbColr);

        this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

            mtbColr.setNumCnf(numCnf)
                    .setQtaCnf(qtaCnf)
                    .setQtaCol(qtaTot);

            Optional<PickingObjectDTO> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                    .filter(x -> Stream.of(x.getWithdrawMtbColrs()).anyMatch(y -> y == mtbColrToUpdate))
                    .findSingle();

            if (pickingObjectDTO.isPresent()) {
                pickingObjectDTO.get().getWithdrawMtbColrs().remove(mtbColrToUpdate);
                pickingObjectDTO.get().getWithdrawMtbColrs().add(mtbColr);
            }

            this.mCurrentMtbColt.getMtbColr().remove(mtbColrToUpdate);
            this.mCurrentMtbColt.getMtbColr().add(mtbColr);

            this.resetMatchedRows();
            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

        }, this::sendError);
    }


    public void deleteRow(MtbColr mtbColrToDelete) {
        this.sendMtbColrDeleteRequest(canDelete -> {

            if (canDelete) {
                this.sendOnLoadingStarted();

                MtbColt mtbColt = new MtbColt()
                        .setNumCollo(mtbColrToDelete.getNumCollo())
                        .setDataCollo(mtbColrToDelete.getDataColloS())
                        .setSerCollo(mtbColrToDelete.getSerCollo())
                        .setGestione(mtbColrToDelete.getGestione())
                        .setMtbColr(new ObservableArrayList<>());
                mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

                MtbColr mtbColr = (MtbColr) mtbColrToDelete.clone();

                if (mCurrentMtbColt.getGestioneEnum() == GestioneEnum.LAVORAZIONE) {
                    mtbColr.setOperation(CommonModelConsts.OPERATION.DELETE);
                } else {
                    mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
                    mtbColr.setQtaCol(mtbColr.getQtaCol().multiply(new BigDecimal(-1)))
                            .setNumCnf(mtbColr.getNumCnf().multiply(new BigDecimal(-1)))
                            .setRiga(null)
                            .setPesoLordoKg(null)
                            .setPesoNettoKg(null);
                }

                mtbColt.getMtbColr().add(mtbColr);

                this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

                    Optional<PickingObjectDTO> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                            .filter(x -> Stream.of(x.getWithdrawMtbColrs()).anyMatch(y -> y == mtbColrToDelete))
                            .findSingle();

                    if (pickingObjectDTO.isPresent()) {
                        pickingObjectDTO.get().getWithdrawMtbColrs().remove(mtbColrToDelete);
                    }

                    this.mCurrentMtbColt.getMtbColr().remove(mtbColrToDelete);

                    this.resetMatchedRows();
                    this.sendOnRowSaved();
                    this.sendOnLoadingEnded();

                }, this::sendError);
            }

        });
    }

    public void closeLU(boolean shouldPrint) {
        if (mCurrentMtbColt == null) return;
        this.sendOnLoadingStarted();

        mColliMagazzinoRESTConsumer.canLUBeDeleted(mCurrentMtbColt, canBeDeleted -> {

            if (canBeDeleted) {
                deleteLU(() -> {
                    this.mIsNewLU = false;
                    this.sendLUClosed();
                    this.sendOnLoadingEnded();
                });
            } else {
                if (mDefaultSegnoCol == -1) {
                    Runnable saveAction = () -> {
                        mColliMagazzinoRESTConsumer.updateDataFine(
                                mCurrentMtbColt, () -> distribuisciLU((generatedMtbColts) -> {
                                    if (shouldPrint)
                                        printCollo(generatedMtbColts, () -> this.postCloseOperations(generatedMtbColts));
                                    else {
                                        postCloseOperations(generatedMtbColts);
                                    }
                                }), this::sendError);
                    };

                    if (mShouldAskPesoLU) {
                        this.sendLUPesoRequired(mCurrentMtbColt.getCodTcol(),
                                mCurrentMtbColt.getPesoNettoKg(),
                                mCurrentMtbColt.getPesoKg(),
                                (newCodTcol, newNetWeight, newGrossWeight) -> {

                                    mCurrentMtbColt.setCodTcol(newCodTcol);
                                    mCurrentMtbColt.setPesoNettoKg(newNetWeight);
                                    mCurrentMtbColt.setPesoKg(newGrossWeight);

                                    saveAction.run();
                                });
                    } else {
                        saveAction.run();
                    }
                } else {

                    distribuisciLU(mtbColts -> {
                        if (this.mIsNewLU) {
                            this.sendLUPositionChangeRequest((shouldChangePosition, mtbDepoPosizione) -> {

                                if (shouldChangePosition) {

                                    if (mtbDepoPosizione != null) {
                                        if (mtbDepoPosizione.isFlagMonoCollo()) {
                                            this.mPosizioneRESTConsumer.getBancaliInPosizione(mtbDepoPosizione, destMtbColtList -> {
                                                if (destMtbColtList == null || destMtbColtList.size() != 1) {
                                                    //Nessuna UL trovata oppure più UL nella stessa posizione
                                                    this.sendError(new NoLUFoundException());
                                                } else {
                                                    if (destMtbColtList.get(0).getSegno() != mDefaultSegnoCol) {
                                                        this.sendError(new InvalidLUException());
                                                    } else {
                                                        moveLUtoLU(mtbColts, destMtbColtList.get(0), savedMtbColt -> {
                                                            ArrayList<MtbColt> savedMtbColtList = new ArrayList<>();
                                                            savedMtbColtList.add(savedMtbColt);
                                                            postCloseOperations(savedMtbColtList);
                                                        });
                                                    }

                                                }
                                            }, this::sendError);
                                        } else {
                                            //La posizione non è Mono-UL
                                            this.sendError(new CurrentMonoLUPositionIsNotCorrectException());
                                        }
                                    } else {
                                        //Nessuna posizione trovata con questo barcode
                                        this.sendError(new ScannedPositionNotExistException());
                                    }
                                } else {
                                    postCloseOperations(mtbColts);
                                }
                            });
                        } else {
                            postCloseOperations(mtbColts);
                        }
                    });
                }
            }
        }, this::sendError);
    }

    private void distribuisciLU(RunnableArgs<List<MtbColt>> onComplete) {
        MtbColt cloneMtbColt = (MtbColt) mCurrentMtbColt.clone();

        IOrdiniVendita ordiniVendita = ClassRouter.getInstance(ClassRouter.PATH.ORDINI_VENDITA);
        ordiniVendita.distribuisciCollo(cloneMtbColt, mTestateOrdini, mtbColts -> {
            mColliMagazzinoRESTConsumer.fillMtbAartsOfMtbColts(mtbColts, onComplete, this::sendError);
//            mColliMagazzinoRESTConsumer.getMultipleByTestate(mtbColts, false, onComplete, this::sendError);
        }, this::sendError);
    }


    private void moveLUtoLU(List<MtbColt> sourceMtbColts, MtbColt destMtbColt, RunnableArgs<MtbColt> onComplete) {
        destMtbColt.setMtbColr(new ObservableArrayList<>());

        for (MtbColt sourceMtbColt : sourceMtbColts) {
            for (MtbColr sourceMtbColr : sourceMtbColt.getMtbColr()) {
                MtbColr cloneMtbColr = (MtbColr) sourceMtbColr.clone();

                cloneMtbColr
                        .setRiga(null)
                        .setNumCollo(null)
                        .setGestione((String) null)
                        .setDataCollo(null)
                        .setSerCollo(null)
                        .setCausale(MtbColr.Causale.VERSAMENTO);

                cloneMtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
                destMtbColt.getMtbColr().add(cloneMtbColr);
                sourceMtbColr.setOperation(CommonModelConsts.OPERATION.DELETE);
            }

            sourceMtbColt.setOperation(CommonModelConsts.OPERATION.DELETE);
        }

        destMtbColt.setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);
        sourceMtbColts.add(destMtbColt);

        this.mColliMagazzinoRESTConsumer.saveColli(sourceMtbColts, data -> {
            onComplete.run(destMtbColt);
        }, this::sendError);
    }

    private void printCollo(List<MtbColt> mtbColtsToPrint, Runnable onComplete) {
        this.sendLUPrintRequest(shouldPrint -> {

            if (!shouldPrint) {
                onComplete.run();
            } else {

                this.mPrinterRESTConsumer.getAvailablePrinters(mDefaultCodMdep, PrinterRESTConsumer.Type.PRIMARIA, printerList -> {

                    if (printerList == null || printerList.size() == 0) {
                        this.sendError(new NoPrintersFoundException());
                        onComplete.run();
                        return;
                    }

                    cyclicPrint(mtbColtsToPrint.iterator(), printerList.get(0), onComplete, ex -> this.sendLUPrintError(ex, onComplete));

                }, this::sendError);

            }

        });
    }

    private void cyclicPrint(@NotNull Iterator<MtbColt> sourceMtbColts, String printerName, Runnable onComplete, RunnableArgs<Exception> onAbort) {
        if (sourceMtbColts.hasNext()) {
            singlePrint(sourceMtbColts.next(), printerName, () -> {
                cyclicPrint(sourceMtbColts, printerName, onComplete, onAbort);
            }, onAbort);
        } else {
            onComplete.run();
        }
    }

    private void singlePrint(MtbColt mtbColtToPrint, String printerName, Runnable onComplete, RunnableArgs<Exception> onFailed) {
        String reportName = ReportManager.getReportNameLUFromGestione(mtbColtToPrint.getGestioneEnum());

        this.mPrinterRESTConsumer.printCollo(
                printerName,
                mtbColtToPrint,
                1,
                reportName, onComplete, onFailed);
    }


    public void recoverLU(MtbColt mtbColt) {
        this.mCurrentMtbColt = mtbColt;
        mMtbColtSessionID = this.mColliDataRecoverService.startNewSession(mtbColt, mTestateOrdini);

        this.mCurrentMtbColt.generaFiltroOrdineFromDTO(mDefaultFiltroOrdine);

        this.resetMatchedRows();
        this.sendLUOpened(this.mCurrentMtbColt);
    }

    private void postCloseOperations(List<MtbColt> mtbColtList) {
        Stream.of(mtbColtList)
                .filter(x -> !this.mColliRegistrati.contains(x))
                .forEach(x -> this.mColliRegistrati.add(x));

        for (MtbColt mtbColt : mtbColtList) {
            Stream.of(mTestateOrdini)
                    .filter(x -> x.getNumOrd().equals(mtbColt.getNumOrd()) && x.getDataOrdD().equals(mtbColt.getDataOrdD()) && x.getGestioneEnum() == mtbColt.getGestioneEnum())
                    .forEach(x -> x.setExistColloBoolean(true));
        }

        if (mMtbColtSessionID != null)
            this.mColliDataRecoverService.closeSession(mMtbColtSessionID);
        this.mCurrentMtbColt = null;


        if (mEnableQuantityReset) {
            for (PickingObjectDTO pickingObjectDTO : mPickingList.getValue()) {

                List<MtbColr> withdrawMtbColrList = pickingObjectDTO.getWithdrawMtbColrs();

                for (MtbColr withdrawMtbColr : withdrawMtbColrList) {
                    pickingObjectDTO.getSitArtOrdDTO().setNumCnfOrd(pickingObjectDTO.getSitArtOrdDTO().getNumCnfOrd().subtract(withdrawMtbColr.getNumCnf()));
                    pickingObjectDTO.getSitArtOrdDTO().setQtaOrd(pickingObjectDTO.getSitArtOrdDTO().getQtaOrd().subtract(withdrawMtbColr.getQtaCol()));


                    if (pickingObjectDTO.getMtbColts() != null) {

                        Optional<MtbColr> optionalMtbColrReference = Stream.of(pickingObjectDTO.getMtbColts())
                                .filter(y -> withdrawMtbColr.getRefMtbColr() != null &&
                                        withdrawMtbColr.getRefMtbColr().getNumCollo().equals(y.getNumCollo()) &&
                                        withdrawMtbColr.getRefMtbColr().getDataColloD().equals(y.getDataColloD()) &&
                                        withdrawMtbColr.getRefMtbColr().getSerCollo().equals(y.getSerCollo()) &&
                                        withdrawMtbColr.getRefMtbColr().getGestioneEnum().equals(y.getGestioneEnum()) &&
                                        UtilityString.equalsIgnoreCase(withdrawMtbColr.getRefMtbColr().getCodMart(), y.getMtbColr() != null && y.getMtbColr().size() > 0 ? y.getMtbColr().get(0).getCodMart() : null) &&
                                        UtilityString.equalsIgnoreCase(withdrawMtbColr.getRefMtbColr().getCodTagl(), y.getMtbColr() != null && y.getMtbColr().size() > 0 ? y.getMtbColr().get(0).getCodTagl() : null) &&
                                        UtilityString.equalsIgnoreCase(withdrawMtbColr.getRefMtbColr().getCodCol(), y.getMtbColr() != null && y.getMtbColr().size() > 0 ? y.getMtbColr().get(0).getCodCol() : null))
                                .map(y -> y.getMtbColr().get(0))
                                .findFirst();


                        if (optionalMtbColrReference.isPresent()) {
                            //looking for real refMtbColr instance
                            MtbColr mtbColr = optionalMtbColrReference.get();

                            mtbColr.setNumCnf(mtbColr.getNumCnf().subtract(withdrawMtbColr.getNumCnf()));
                            mtbColr.setQtaCol(mtbColr.getQtaCol().subtract(withdrawMtbColr.getQtaCol()));
                        }

                    }
                }

                pickingObjectDTO.setWithdrawMtbColrs(new ArrayList<>());
            }
        }

        this.mIsNewLU = false;

        this.resetMatchedRows();
        this.sendLUClosed();
        this.sendOnLoadingEnded();
    }

    private void deleteLU(Runnable onComplete) {
        mColliMagazzinoRESTConsumer.deleteCollo(mCurrentMtbColt, () -> {
            this.mColliDataRecoverService.closeSession(mMtbColtSessionID);
            this.mCurrentMtbColt = null;

            if (onComplete != null) onComplete.run();
        }, this::sendError);
    }

    public void resetMatchedRows() {
        for (PickingObjectDTO pickingObjectDTO : this.mPickingList.getValue()) {
            pickingObjectDTO
                    .setTempPickData(null)
                    .setHidden(false);
        }

        this.mPickingList.postValue(this.mPickingList.getValue());
        this.sendFilterRemoved();
    }

    public void closeOrder() {
        this.sendOnLoadingStarted();

        Runnable onComplete = () -> this.sendOnLoadingEnded();

        this.mPrinterRESTConsumer.getAvailablePrinters(mDefaultCodMdep, PrinterRESTConsumer.Type.PRIMARIA, printerList -> {

            if (printerList == null || printerList.size() == 0) {
                this.sendError(new NoPrintersFoundException());
                onComplete.run();
                return;
            }


            cyclicPrintPackingList(
                    this.mTestateOrdini.iterator(),
                    printerList.get(0),
                    onComplete,
                    ex -> this.sendLUPrintError(ex, onComplete));

        }, this::sendError);

    }

    private void cyclicPrintPackingList(@NotNull Iterator<OrdineUscitaInevasoDTO> sourceTestateOrdineVenditaIterator, String printerName, Runnable onComplete, RunnableArgs<Exception> onAbort) {
        if (sourceTestateOrdineVenditaIterator.hasNext()) {
            singlePrintPackingList(sourceTestateOrdineVenditaIterator.next(), printerName, () -> {
                cyclicPrintPackingList(sourceTestateOrdineVenditaIterator, printerName, onComplete, onAbort);
            }, onAbort);
        } else {
            onComplete.run();
        }
    }

    private void singlePrintPackingList(OrdineUscitaInevasoDTO ordineUscitaInevasoDTO, String printerName, Runnable onComplete, RunnableArgs<Exception> onFailed) {
        HashMap<String, Object> params = new HashMap<>();
        params.put("gestione", ordineUscitaInevasoDTO.getGestione());
        params.put("num_ord", ordineUscitaInevasoDTO.getNumOrd());
        params.put("data_ord", UtilityDate.formatDate(ordineUscitaInevasoDTO.getDataOrdD(), UtilityDate.COMMONS_DATE_FORMATS.YMD_DASH));

        this.mPrinterRESTConsumer.printReport(
                printerName,
                this.mReportNameSpedizioneChiudiOrdine,
                params,
                1,
                onComplete,
                onFailed);

    }

    public MutableLiveData<List<PickingObjectDTO>> getPickingList() {
        return mPickingList;
    }

    public ArrayList<MtbColt> getCreatedMtbColts() {
        return (ArrayList<MtbColt>) this.mColliRegistrati;
    }

    public SpedizioneViewModel setListeners(Listener listener) {
        this.mListener = listener;
        return this;
    }

    public interface Listener extends ILUPrintListener, ILoadingListener, ILUBaseOperationsListener {

        void onLUPesoRequired(String codTcol, BigDecimal netWeightKG, BigDecimal grossWeightKG, RunnableArgsss<String, BigDecimal, BigDecimal> onComplete);

        void onFilterApplied(String newValue);

        void onFilterRemoved();

        void onError(Exception ex);

        void onItemDispatched(PickingObjectDTO pickingObjectDTO,
                              MtbAart mtbAart,
                              BigDecimal initialNumCnf,
                              BigDecimal initialQtaCnf,
                              BigDecimal initialQtaTot,
                              BigDecimal totalQtaOrd,
                              BigDecimal totalNumCnfOrd,
                              BigDecimal qtaCnfOrd,
                              BigDecimal totalQtaToBeTaken,
                              BigDecimal totalNumCnfToBeTaken,
                              BigDecimal qtaCnfToBeTaken,
                              BigDecimal totalQtaAvailable,
                              BigDecimal totalNumCnfAvailable,
                              BigDecimal qtaCnfAvailable,
                              String partitaMag,
                              Date dataScad,
                              boolean canOverflowOrderQuantity,
                              boolean canBatchLotBeChanged,
                              RunnableArgss<PickedQuantityDTO, Boolean> onComplete);


        void onLUPositionChangeRequest(RunnableArgss<Boolean, MtbDepoPosizione> onComplete);

        void onBatchLotSelectionRequest(List<MtbPartitaMag> availableBatchLots, RunnableArgs<MtbPartitaMag> onComplete);
    }

}
