<?php

/**
 * 20/10/2015
 * @author Victor Novy
 */

namespace Teknisa\PebLibs\Services;

use \Teknisa\PebLibs\Services\DateUtil;
use \Doctrine\ORM\EntityManager;
use \Zeedhi\Framework\DB\StoredProcedure\Param;
use \Zeedhi\Framework\DB\StoredProcedure\StoredProcedure;
use \Estoque\Entities\Lanctoestoq;
use \Estoque\Entities\Movilote;
use \Estoque\Entities\Posestdia;
use \Estoque\Entities\Itlanctoest;
use \Geral\Entities\Produto;
use \Geral\Entities\Paramfilial;
use \Geral\Entities\Prodfili;

class ValidacoesEstoqueService {
    /* Identificador do tipo de operacao que o sistema esta sendo realizado => 'E' para 'Lançamento de Entrada' */
    const ENTRADA       = 'E';
    /* Identificador do tipo de operacao que o sistema esta sendo realizado => 'S' para 'Lançamento de Saida'   */
    const SAIDA         = 'S';
    /* Identificador do tipo de operacao que o sistema esta sendo realizado => 'A' para 'Ajuste de Inventario'
     * e 'Implantacao de Estoque'
     */
    const AJUSTE_IMP    = 'A';
    const TIPO_CUSTO_PADRAO = '03';
    const ESTOQUE_VAZIO = ' ';

    protected $entityManager;
    protected $crudService;
    protected $environment;
    protected $newCode;
    protected $irregularProducts = array();

    private $lanctoestoq;

    public function __construct (EntityManager $entityManager, $crudService, $environment, $newCode) {
        $this->entityManager = $entityManager;
        $this->crudService = $crudService;
        $this->environment = $environment;
        $this->newCode = $newCode;
    }

    /**
     * Realiza a validacao da data de lancemento com base nas parametrizacoes feitas pelo cliente.
     * Estas parametrizacoes podem ser encontradas na
     * Tela => 'Parametros de Estoque' => Tabela: 'PARAMGERAL' E 'PARAMESTOQUE'.
     * Essas validacoes servem para controlar as datas que serao efetuados os lancamentos.
     *
     * periodo valido¹ - Este periodo seria a 'data de processamento' da filial menos e/ou mais a quantidade de dias
     * que esta parametrizado
     *
     * @param $filial
     * @param $dtLancamento
     * @param $operacao
     * @param $sistema
     * @param $dtEmissao
     * @throws \Exception
     */
    public function validaDataLancamento($filial, $dtLancamento, $operacao, $sistema, $dtEmissao) {
        try {
            $organizacao = $this->environment->getNrOrgTrab();

            $dadosProcessFilial = $this->retornaDadosProcessFilial($filial, $organizacao);

            $dataProcess = $dadosProcessFilial['DTPROCESSA'];

            $utDtProcessAnt = $dadosProcessFilial['IDLANCDTMEN'];
            $utDtProcessPos = $dadosProcessFilial['IDLANCDTMAI'];
            $utDtProcessAntFilial = $dadosProcessFilial['IDLANCDTMENFIL'];
            $utDtProcessPosFilial = $dadosProcessFilial['IDLANCDTMAIFIL'];

            $qtDiasLancAntEntrada = $dadosProcessFilial['NRDIASENTR'];
            $qtDiasLancAntEntradaFilial = $dadosProcessFilial['NRDIASENTRFIL'];
            $qtDiasLancPostEntrada = $dadosProcessFilial['NRDIASENTRPD'];
            $qtDiasLancPostEntradaFilial = $dadosProcessFilial['NRDIASENTRPDFIL'];

            $qtDiasLancAntSaida = $dadosProcessFilial['NRDIASSAID'];
            $qtDiasLancAntSaidaFilial = $dadosProcessFilial['NRDIASSAIDFIL'];
            $qtDiasLancPostSaida = $dadosProcessFilial['NRDIASSAIDPD'];
            $qtDiasLancPostSaidaFilial = $dadosProcessFilial['NRDIASSAIDPDFIL'];

            $qtDiasLancAntAjuste = $dadosProcessFilial['NRDIASAJUS'];
            $qtDiasLancPostAjuste = $dadosProcessFilial['NRDIASAJUSPD'];
            $qtDiasLancAntAjusteFilial = $dadosProcessFilial['NRDIASAJUSFIL'];
            $qtDiasLancPostAjusteFilial = $dadosProcessFilial['NRDIASAJUSPDFIL'];
            $dtLancamento = DateUtil::getDataDeString($dtLancamento, DateUtil::FORMATO_BRASILEIRO, true);
        
            $dtEmissao = DateUtil::getDataDeString($dtEmissao, DateUtil::FORMATO_BRASILEIRO, true);
            $dataProcess = DateUtil::getDataDeString(DateUtil::truncateString($dataProcess), DateUtil::FORMATO_BRASILEIRO, true);

            $isDtLancAfterDtProcess = DateUtil::comparaDatas($dtLancamento, $dataProcess, DateUtil::DEPOIS);
            $isEqual = DateUtil::comparaDatas($dtLancamento, $dataProcess, DateUtil::IGUAL);

            if ($utDtProcessAnt === 'N' && $utDtProcessAntFilial === 'N' && !($isDtLancAfterDtProcess || $isEqual))
                throw new \Exception ('Data de lançamento deve ser maior ou igual à data de processamento.', 400);

            if ($utDtProcessPos === 'N' && $utDtProcessPosFilial === 'N' && !(!$isDtLancAfterDtProcess || $isEqual))
                throw new \Exception ('Data de lançamento deve ser menor ou igual à data de processamento.', 400);

            switch ($operacao) {
                case self::ENTRADA :
                    if(!$isDtLancAfterDtProcess) {
                        $this->validaPeriodoLancamento($utDtProcessAntFilial == 'S'? $utDtProcessAntFilial: $utDtProcessAnt, $dtLancamento, $dataProcess, $utDtProcessAntFilial == 'S'? $qtDiasLancAntEntradaFilial : $qtDiasLancAntEntrada );
                    }else {
                        $this->validaPeriodoLancamento($utDtProcessPosFilial == 'S'? $utDtProcessPosFilial: $utDtProcessPos, $dtLancamento, $dataProcess, $utDtProcessPosFilial == 'S'? $qtDiasLancPostEntradaFilial : $qtDiasLancPostEntrada, DateUtil::DEPOIS);
                    }

                    if (DateUtil::comparaDatas($dtEmissao, $dtLancamento, DateUtil::DEPOIS))
                        throw new \Exception('Data de entrada deve ser maior ou igual a data emissão.', 400);

                    break;
                case self::SAIDA :
                    if ($sistema <> 'FAI' || $sistema <> 'FAC' || $sistema <> 'FAZ'){
                        if (!$isDtLancAfterDtProcess) {
                            $this->validaPeriodoLancamento($utDtProcessAntFilial == 'S'? $utDtProcessAntFilial: $utDtProcessAnt, $dtLancamento, $dataProcess, $utDtProcessAntFilial == 'S'? $qtDiasLancAntSaidaFilial : $qtDiasLancAntSaida);
                        } else {
                            $this->validaPeriodoLancamento($utDtProcessPosFilial == 'S'? $utDtProcessPosFilial : $utDtProcessPos, $dtLancamento, $dataProcess, $utDtProcessPosFilial == 'S'? $qtDiasLancPostSaidaFilial : $qtDiasLancPostSaida, DateUtil::DEPOIS);
                        }
                    }
                    break;
                case self::AJUSTE_IMP :
                    if (!$isDtLancAfterDtProcess) {
                        $this->validaPeriodoLancamento($utDtProcessAntFilial == 'S'? $utDtProcessAntFilial : $utDtProcessAnt, $dtLancamento, $dataProcess, $utDtProcessAntFilial == 'S'? $qtDiasLancAntAjusteFilial : $qtDiasLancAntAjuste);
                    } else {
                        $this->validaPeriodoLancamento($utDtProcessPosFilial == 'S'? $utDtProcessPosFilial : $utDtProcessPos, $dtLancamento, $dataProcess, $utDtProcessPosFilial == 'S'? $qtDiasLancPostAjusteFilial : $qtDiasLancPostAjuste, DateUtil::DEPOIS);
                    }
                    break;
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }
    
    public function validaProdutoInativo($cdfilial, $nrlancestq, $nrorg){
        try{
            $products = $this->buscaItlanctoest($cdfilial, $nrlancestq, $nrorg);
            $paramfilial = Paramfilial::findOneByCdfilialAndNrorg($cdfilial, $nrorg);
            $permiteProdInativo = $paramfilial->getIdperproinat() ?? 'N';
            
            if($permiteProdInativo === 'S')
                return true;
              
              foreach($products as $prod){
                   $cdProduto   = $prod->getCdproduto();
                   $idTipoMovi  = $prod->getIdtipomovi();
                   if ($idTipoMovi > 2) {
                        $produto = Produto::findOneByCdproduto($cdProduto);
                        
                        if (!$produto){
                            throw new \Exception("Produto {$cdProduto} não encontrado.");
                        }
                        if ($produto->getIdprodativpr() == 'N'){
                            throw new \Exception("Produto {$cdProduto} está inativo e não pode ser movimentado.");
                        }
                        
                        $prodfili = Prodfili::findOneByCdprodutoAndCdfilial($cdProduto, $cdfilial);
                        if ($prodfili->getIdprodativ() == 'I') {
                            throw new \Exception("Produto {$cdProduto} está inativo para filial {$cdfilial} e não pode ser movimentado.");
                        }
                    }
              }
        }catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * Validação do tipo 'Lancamento Entrada'
     * Verifica se a data do lancamento que esta sendo realizado esta dentro do perido valido¹ e se 'data de emissão'
     * não é anterior a 'data do lancamento'.
     *
     * @param $dtLancamento
     * @param $dataProcess
     * @param $utDtProcessAnt
     * @param $qtDiasLancAntEntrada
     * @param $utDtProcessPos
     * @param $qtDiasLancPostEntrada
     * @param $utDtProcessAntFilial
     * @param $qtDiasLancAntEntradaFilial
     * @param $utDtProcessPosFilial
     * @param $qtDiasLancPostEntradaFilial
     * @param $dtEmissao
     * @param $isDtLancAfterDtProcess
     * @param $isEqual
     * @throws \Exception
     */
    private function validaDataLancamentoEntrada(
        $dtLancamento, $dataProcess, $utDtProcessAnt, $qtDiasLancAntEntrada, $utDtProcessPos,
        $qtDiasLancPostEntrada, $utDtProcessAntFilial, $qtDiasLancAntEntradaFilial,
        $utDtProcessPosFilial, $qtDiasLancPostEntradaFilial, $dtEmissao,
        $isDtLancAfterDtProcess, $isEqual
    ) {
        if(!$isDtLancAfterDtProcess) {
            $this->validaPeriodoLancamento($utDtProcessAnt, $dtLancamento, $dataProcess, $qtDiasLancAntEntrada);
            $this->validaPeriodoLancamento($utDtProcessAntFilial, $dtLancamento, $dataProcess, $qtDiasLancAntEntradaFilial);
        } else {
            $this->validaPeriodoLancamento($utDtProcessPos, $dtLancamento, $dataProcess, $qtDiasLancPostEntrada, DateUtil::DEPOIS);
            $this->validaPeriodoLancamento($utDtProcessPosFilial, $dtLancamento, $dataProcess, $qtDiasLancPostEntradaFilial, DateUtil::DEPOIS);
        }

        if (DateUtil::comparaDatas($dtEmissao, $dtLancamento, DateUtil::DEPOIS))
            throw new \Exception('Data de entrada deve ser maior ou igual a data emissão.', 400);
    }

    /**
     * Validação do tipo 'Lancamento Saida'
     * Verifica se a data do lancamento que esta sendo realizado esta dentro do perido valido¹.
     *
     * @param $dtLancamento
     * @param $dataProcess
     * @param $utDtProcessAnt
     * @param $qtDiasLancAntSaida
     * @param $utDtProcessPos
     * @param $qtDiasLancPostSaida
     * @param $utDtProcessAntFilial
     * @param $qtDiasLancAntSaidaFilial
     * @param $utDtProcessPosFilial
     * @param $qtDiasLancPostSaidaFilial
     * @param $isDtLancAfterDtProcess
     * @param $isEqual
     * @throws \Exception
     */
    private function validaDataLancamentoSaida(
        $dtLancamento, $dataProcess, $utDtProcessAnt, $qtDiasLancAntSaida, $utDtProcessPos,
        $qtDiasLancPostSaida, $utDtProcessAntFilial, $qtDiasLancAntSaidaFilial,
        $utDtProcessPosFilial, $qtDiasLancPostSaidaFilial, $isDtLancAfterDtProcess, $isEqual
    ){
        if (!$isDtLancAfterDtProcess) {
            $this->validaPeriodoLancamento($utDtProcessAnt, $dtLancamento, $dataProcess, $qtDiasLancAntSaida);
            $this->validaPeriodoLancamento($utDtProcessAntFilial, $dtLancamento, $dataProcess, $qtDiasLancAntSaidaFilial);
        } else {
            $this->validaPeriodoLancamento($utDtProcessPos, $dtLancamento, $dataProcess, $qtDiasLancPostSaida, DateUtil::DEPOIS);
            $this->validaPeriodoLancamento($utDtProcessPosFilial, $dtLancamento, $dataProcess, $qtDiasLancPostSaidaFilial, DateUtil::DEPOIS);
        }
    }

    /**
     * Validação do tipo 'Lancamento Saida'
     * Verifica se a data do lancamento que esta sendo realizado esta dentro do perido valido¹.
     *
     * @param $dtLancamento
     * @param $dataProcess
     * @param $utDtProcessAnt
     * @param $qtDiasLancAntAjuste
     * @param $utDtProcessPos
     * @param $qtDiasLancPostAjuste
     * @param $utDtProcessAntFilial
     * @param $qtDiasLancAntAjusteFilial
     * @param $utDtProcessPosFilial
     * @param $qtDiasLancPostAjusteFilial
     * @param $isDtLancAfterDtProcess
     * @param $isEqual
     * @throws \Exception
     */
    private function validaDataLancamentoAjuste(
        $dtLancamento, $dataProcess, $utDtProcessAnt, $qtDiasLancAntAjuste, $utDtProcessPos,
        $qtDiasLancPostAjuste, $utDtProcessAntFilial, $qtDiasLancAntAjusteFilial,
        $utDtProcessPosFilial, $qtDiasLancPostAjusteFilial, $isDtLancAfterDtProcess, $isEqual
    ){
        if (!$isDtLancAfterDtProcess) {
            $this->validaPeriodoLancamento($utDtProcessAnt, $dtLancamento, $dataProcess, $qtDiasLancAntAjuste);
            $this->validaPeriodoLancamento($utDtProcessAntFilial, $dtLancamento, $dataProcess, $qtDiasLancAntAjusteFilial);
        } else {
            $this->validaPeriodoLancamento($utDtProcessPos, $dtLancamento, $dataProcess, $qtDiasLancPostAjuste, DateUtil::DEPOIS);
            $this->validaPeriodoLancamento($utDtProcessPosFilial, $dtLancamento, $dataProcess, $qtDiasLancPostAjusteFilial, DateUtil::DEPOIS);
        }
    }

    /**
     * Verifica se a data do lancamento que esta sendo realizado esta dentro do perido valido¹.
     *
     * @param $utDtProcessAntPos
     * @param \DateTime $dtLancamento
     * @param \DateTime $dataProcess
     * @param $quantDias
     * @param int $tipoComparacao
     * @throws \Exception
     */
    private function validaPeriodoLancamento(
        $utDtProcessAntPos, \DateTime $dtLancamento, \DateTime $dataProcess,
        $quantDias, $tipoComparacao = DateUtil::ANTES
    ){
        if ($tipoComparacao === DateUtil::ANTES)
            $dataProcess = DateUtil::subtraiIntervalo($dataProcess, $quantDias);
        else
            $dataProcess = DateUtil::adicionaIntervalo($dataProcess, $quantDias);
        
        if ($utDtProcessAntPos === 'S' && DateUtil::comparaDatas($dtLancamento, $dataProcess, $tipoComparacao))
            throw new \Exception ('Data de lançamento fora do período permitido.', 400);
    }

    /* ******************** Atualizador de Estoque ******************** */

    /**
     * Função que realiza a execução da atualização de
     * estoque. Possui como comportamento padrão a
     * atualização de todos os itens de um lançamento de
     * uma unidade, com verificação de irregularidades e
     * abortamento das operações no caso de invalidez.
     *
     * Permite porém que a verificação de irregularidades
     * não seja efetuada ou que as operações não sejam
     * abortadas após a verificação através da passagem
     * dos parâmetros "verifyIrregularities" e
     * "abortAtIrregularities". Note que o último não
     * pode ser verdadeiro se o primeiro for falso.
     *
     * Permite também que sejam selecionados um ou mais
     * itens para a execução da atualização de estoque
     * através da passagem de seus códigos na variável
     * "seqItemCodes". Ela é nula no comportamento
     * padrão, o que garante que todos os itens do
     * lançamento sejam atualizados.
     *
     * @throws \Exception
     *
     * @param  string     $cdfilial
     * @param  string     $nrlancestq
     * @param  boolean    $verifyIrregularities
     * @param  boolean    $abortAtIrregularities
     * @param  array      $seqItemCodes
     *
     * @return array
     */
    public function atualizaEstoque(
        $cdfilial, $nrlancestq, $consolidate = false, $verifyIrregularities = true, $abortAtIrregularities = true, $seqItemCodes = null
    ) {
        try {
            $connection = $this->entityManager->getConnection();

            #starts main, outer transaction
            $connection->beginTransaction();

            try {
                #starts first inner transaction
                $connection->beginTransaction();

                $nrorg = $this->environment->getNrOrgTrab();
                $lanctoestoq = $this->buscaLanctoestoq($cdfilial, $nrlancestq, $nrorg);
                
                $this->validaProdutoInativo($cdfilial, $nrlancestq, $nrorg);

                #marks all items as appurtenant to stock update
                $this->atualizaTodosItensLancamentoEstoque($cdfilial, $lanctoestoq->getNrlancestq(), $nrorg, $seqItemCodes, $consolidate);

                $dadosProcess = $this->retornaDadosProcessFilial($lanctoestoq->getCdfilimovi(), $nrorg);

                #separate dates
                $dtprocessa = $dadosProcess['DTPROCESSA'];
                $dtlancestq = $lanctoestoq->getDtlancestq();

                #process dates
                $dtprocessa = $this->getDateObject($dtprocessa);
                $dtlancestq = $this->getDateObject($dtlancestq);

                #order dates
                $date = $this->getOrderedDates($dtprocessa, $dtlancestq);

                #updates stock
                $this->executaProcedureAtualizaEstoque($lanctoestoq->getCdfilial(), $lanctoestoq->getCdfilimovi(), $nrlancestq, $date["INITIAL"], $date["FINAL"]);
                
                #concludes first inner transaction
                $connection->commit();
            } catch (\Exception $e) {
                #rollbacks operations in this transaction
                $connection->rollback();

                #throws error to cancel outer transaction
                throw $e;
            }

            #Please note that two separate transactions are
            #required for the correct operation of the function
            #This second step, where the procedure is executed
            #again, only works if the first one has already
            #been commited.

            try {
                #starts second inner transaction
                $connection->beginTransaction(); 

                #checks flags
                if($verifyIrregularities){
                    if($abortAtIrregularities)
                        #verifies irregularities and throws exception if any is found
                        $this->verificaIrregularidades($lanctoestoq, $seqItemCodes);
                    else {
                        #verifies irregularities and updates items for them to be ignored if any is found
                        $this->atualizaItensIrregulares($lanctoestoq, $seqItemCodes);

                        #updates stock with new parametrizations
                        $this->executaProcedureAtualizaEstoque($lanctoestoq->getCdfilial(), $lanctoestoq->getCdfilimovi(), $nrlancestq, $date["INITIAL"], $date["FINAL"]);
                    }
                }
                #concludes second inner transaction
                $connection->commit();
            } catch (\Exception $e) {
                #rollbacks operations in this transaction
                $connection->rollback();
                #throws error to cancel outer transaction
                throw $e;
            }

            $irregularProductCodes = array();
            $irregularProducts = $this->retornaProdutosIrregulares($lanctoestoq, $seqItemCodes);
            

            #separates irregular product codes
            foreach ($irregularProducts as $product) {
                $irregularProductCodes[] = $product->getCdproduto();
            }
            #concludes outer transaction and all operations
            $connection->commit();
        } catch (\Exception $e) {
            #rollbacks every operation
            $connection->rollback();
            #throws exception for controller to catch
            throw $e;
        }

        #returns any existent irregular product codes
        return $irregularProductCodes;
    }

    public function getIrregularProducts() {
        return $this->irregularProducts;
    }

    public function retornaProdutosIrregulares(Lanctoestoq $lanctoestoq, $seqItens = null) {
        $nrmovilote = $lanctoestoq->getCdfilial().$lanctoestoq->getNrlancestq();
        $produtosIrregulares = Movilote::findByNrmoviloteAndNrorg(
            $nrmovilote, $lanctoestoq->getNrorg()
        );
        
        if(!is_null($seqItens)) {
            $itlanctoest = $this->buscaItlanctoest(
                $lanctoestoq->getCdfilial(), $lanctoestoq->getNrlancestq(),
                $lanctoestoq->getNrorg()
            );

            $seqProds = array();
            foreach($itlanctoest as $item)
                if(in_array($item->getNrsequitem(), $seqItens))
                    $seqProds[] = $item->getCdproduto();

            $produtosIrregularesInSeqItens = array();

            foreach ($produtosIrregulares as $produto)
                if(in_array($produto->getCdproduto(), $seqProds))
                    $produtosIrregularesInSeqItens[] = $produto;

            return $produtosIrregularesInSeqItens;
        }

        /** @var \Estoque\Entities\Movilote $produtosIrregulares */
        return $produtosIrregulares;
    }

    /**
     * Procedimento que verifica se o estoque apresenta
     * alguma irregularidade, lançando uma exceção caso
     * alguma seja encontrada.
     *
     * @throws \Exception
     *
     * @param  Lanctoestoq $lanctoestoq
     * @param  array       $seqItens
     */
    public function verificaIrregularidades(Lanctoestoq $lanctoestoq, $seqItemCodes = null) {
        $this->irregularProducts = array();
        #checks for irregularities
        $irregularProducts = $this->retornaProdutosIrregulares($lanctoestoq, $seqItemCodes);

        #TODO Verify a better message
        if (!empty($irregularProducts)) {
            $this->irregularProducts = $irregularProducts;
            throw new \Exception ('A confirmação da operação faria com que a posição de estoque de alguns produtos se tornasse irregular. Analise o relatório de Movimentação de Estoque desses produtos.', 500);
            
        }
    }

    /**
     * Procedimento que atualiza os itens irregulares de
     * um lançamento de estoque, retirando-os do conjunto
     * de itens pertencentes à evolução de estoque.
     *
     * @param  Lanctoestoq $lanctoestoq
     * @param  array       $seqItens
     */
    public function atualizaItensIrregulares(Lanctoestoq $lanctoestoq, $seqItemCodes = null) {
        #checks for irregularities
        $irregularProducts = $this->retornaProdutosIrregulares($lanctoestoq, $seqItemCodes);

        #if there's any
        if(!empty($irregularProducts)) {
            #separates irregular product codes
            $productCodes = array();
            foreach($irregularProducts as $product)
                $productCodes[] = $product->getCdproduto();

            #gets possibly irregular items
            $itlanctoest = $this->buscaItlanctoest(
                $lanctoestoq->getCdfilial(), $lanctoestoq->getNrlancestq(),
                $lanctoestoq->getNrorg(), 'S'
            );

            if(!is_null($itlanctoest)) {
                $seqItemCodes = array();
                foreach($itlanctoest as $item)
                    #checks every item, searching for irregular products
                    if(in_array($item->getCdproduto(), $productCodes))
                        #separes item codes that are related to irregular products
                        $seqItemCodes[] = $item->getNrsequitem();

                #updates irregular items
                $this->atualizaItensLancamentoEstoque($itlanctoest, 'N', $seqItemCodes);
            }
        }
    }
    
    
    private function atualizaItensItensSimilares($itlanctoest){
        foreach($itlanctoest as $item){
            $cdprodmovi = $item->getCdprodmovi();
            $cdfilial   = $item->getCdfilial();
            $nrlancestq = $item->getNrlancestq();
            $equalProducts = Itlanctoest::findByCdprodmoviAndCdfilialAndNrlancestq($cdprodmovi, $cdfilial, $nrlancestq);
            foreach($equalProducts as $prodToUpdate){
                $prodToUpdate->setIdevolestoq('N');
                $this->entityManager->persist($prodToUpdate);
                $this->entityManager->flush($prodToUpdate);
            }
        }
    }

    /**
     * Procedimento que atualiza os itens de um
     * lançamento para a evolução/atualização de estoque.
     *
     * Permite a passagem de um parâmetro que define
     * quais itens dentre os recebidos podem/devem ser
     * atualizados.
     *
     * @param array  $itlanctoest
     * @param string $idevolestoq
     * @param array  $seqItemCodes
     */
    private function atualizaItensLancamentoEstoque($itlanctoest, $idevolestoq = 'S', $seqItemCodes = null, $consolidate = false) {
        $itlanctoest = $this->consolidadeToStockProducts($itlanctoest, false);
        
        //Após primeira consolidação de todos produtos de compra em um só, consolida em produto de estoque caso escolhido pelo usuario em tela.
        
        if($consolidate)
            $itlanctoest = $this->consolidadeToStockProducts($itlanctoest, true);

        #iterates over every received item
        foreach ($itlanctoest as $item) {
            #checks whether item should be updated
            if(is_null($seqItemCodes) || in_array($item->getNrsequitem(), $seqItemCodes)) {
                #updates item and persists information
                $item->setIdevolestoq($idevolestoq);
                $this->entityManager->persist($item);
                $this->entityManager->flush($item);
                if($idevolestoq == 'S')
                    $this->atualizaProdlcestoq($item->getCdfilimovi(), $item->getCdalmoxarife(), $item->getCdlocalestoq(), $item->getCdproduto());
            }
            if(is_null($item->getIdtipomovi())){
                $item->setIdtipomovi('1');
                $this->entityManager->persist($item);
                $this->entityManager->flush($item);
            }
        }
    }

    /**
     * Procedimento que atualiza todos os itens de um
     * lançamento de estoque independentemente de sua
     * regularidade para que participem na evolução de
     * estoque, respeitando os códigos definidos no
     * parâmetros $seqItemCodes. Se este for omitido,
     * todos os itens serão atualizados sem discriminação
     *
     * @param  string $cdfilial
     * @param  string $nrlancestq
     * @param  string $nrorg
     * @param  array  $seqItemCodes
     */
    private function atualizaTodosItensLancamentoEstoque($cdfilial, $nrlancestq, $nrorg, $seqItemCodes = null, $consolidate = false) {
        #gets all items from the received stock launch
        $itlanctoest = $this->buscaItlanctoest($cdfilial, $nrlancestq, $nrorg, 'N');
        $this->atualizaItensItensSimilares($itlanctoest);
        $itlanctoest = $this->buscaItlanctoest($cdfilial, $nrlancestq, $nrorg, 'N');
        #updates every item
        if(!is_null($itlanctoest))
            $this->atualizaItensLancamentoEstoque($itlanctoest, 'S', $seqItemCodes, $consolidate);
    }

    /**
     * Procedimento que executa a procedure de
     * atualização de estoque.
     *
     * @param  string $cdfilial
     * @param  string $nrlancto
     * @param  date   $dtinicial
     * @param  date   $dtfinal
     */
    public function executaProcedureAtualizaEstoque($cdfilial, $cdfilimovi, $nrlancto, $dtinicial, $dtfinal) {
        #creates procedure
        $procedure = new StoredProcedure($this->entityManager->getConnection(), 'ATUALIZA_ESTOQUE');
        
        #sets all parameters
        $procedure->addParam(new Param('P_FILIMOVI', Param::PARAM_INPUT, $cdfilimovi, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_DTINICIAL', Param::PARAM_INPUT, $dtinicial, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_DTFINAL', Param::PARAM_INPUT, $dtfinal, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_FILILANCTO', Param::PARAM_INPUT, $cdfilial, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_NRLANCTO', Param::PARAM_INPUT, $nrlancto, Param::PARAM_TYPE_STR));

        #execute procedure
        $procedure->execute();
    }
    
    /**
     * Procedimento que executa a procedure de
     * atualização de custo.
     *
     * @param  string $cdfilial
     * @param  string $nrlancto
     * @param  date   $dtinicial
     * @param  date   $dtfinal
     */
    public function executaProcedureCustoLiquido($filial, $lancto, $totalprod = 0, $diferenca = null, $gravvrbrut = 'S') {
        #creates procedure
        $procedure = new StoredProcedure($this->entityManager->getConnection(), 'CUSTO_LIQUIDO');
        
        #sets all parameters
        $procedure->addParam(new Param('P_FILIAL',     Param::PARAM_INPUT, $filial,     Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_LANCTO',     Param::PARAM_INPUT, $lancto,     Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_TOTALPROD',  Param::PARAM_INPUT, $totalprod,  Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_DIFERENCA',  Param::PARAM_INPUT, $diferenca,  Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_GRAVVRBRUT', Param::PARAM_INPUT, $gravvrbrut, Param::PARAM_TYPE_STR));

        #execute procedure
        $procedure->execute();
    }

    /**
     * ZERAR ESTOQUE
     */
    public function zeraInventario($filial,$lancamento,$idOrdemClc, $consolidate = false)
    {
        try {
            $connection = $this->entityManager->getConnection();
            $connection->beginTransaction();
            $filialLogada = $this->environment->getCdFilial();
            $cdoperador = $this->environment->getCdOperador();

            $organizacao = $this->environment->getNrOrgTrab();
            $lanctoestoq = Lanctoestoq::findOneByCdfilialAndNrlancestqAndNrorg($filial, $lancamento, $organizacao);
            if (!is_object($lanctoestoq))
                throw new \Exception ('Número do lançamento não encontrado.', 400);

            $dtLancamento = $lanctoestoq->getDtlancestq()->format('d/m/Y');
            $this->executaProcedureAjuteZeraInve(
                $lanctoestoq->getCdfilimovi(),$dtLancamento,$lancamento,$lanctoestoq->getCdfilial(),$cdoperador,$lanctoestoq->getIdtplancto(),$idOrdemClc,$lanctoestoq->getCdalmoxarife(),$lanctoestoq->getCdlocalestoq()
            );
            $this->atualizaEstoque($lanctoestoq->getCdfilial(), $lancamento, $consolidate);

            $connection->commit();
        } catch (\Exception $e) {
            $connection->rollback();
            throw $e;
        }
    }

    private function executaProcedureAjuteZeraInve(
        $filial,$dataLancto, $lancamento,$filialLogada,$operadorLogado,$idtipomovi,$idordemclc,$cdalmoxarife,$cdlocalestoq
    ) {
        $procedure = new StoredProcedure($this->entityManager->getConnection(), 'AJUSTE_ZERA_INVE');
        $procedure->addParam(new Param('P_CDFILIAL', Param::PARAM_INPUT,   $filial, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_DTIMPLANT', Param::PARAM_INPUT,  $dataLancto, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_FILILANCTO', Param::PARAM_INPUT, $filialLogada, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_NRLANCESTQ', Param::PARAM_INPUT, $lancamento, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_CDOPERADOR', Param::PARAM_INPUT, $operadorLogado, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_IDTIPOMOVI', Param::PARAM_INPUT, $idtipomovi, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_IDORDEMCLC', Param::PARAM_INPUT, $idordemclc, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_CDALMOXARIFE', Param::PARAM_INPUT, $cdalmoxarife, Param::PARAM_TYPE_STR));
        $procedure->addParam(new Param('P_CDLOCALESTOQ', Param::PARAM_INPUT, $cdlocalestoq, Param::PARAM_TYPE_STR));
        $procedure->execute();
    }

    /* ******************** Consulta o Custo e a Quantidade do Produto ******************** */
//aqui mudar o tipo de custo padrao para o da paramfilial
    public function consultaCustoQuantidadeProduto(
        $filial, $produto, $dtLancamento, $tipoCusto = self::TIPO_CUSTO_PADRAO,
        $almoxarifado = self::ESTOQUE_VAZIO, $localEstoque = self::ESTOQUE_VAZIO,
        $lote = self::ESTOQUE_VAZIO, $subLote = self::ESTOQUE_VAZIO
    ){
        
        if($lote == 'SEMLOTE'){
            $custoQuantidade = $this->consultaCustoSemLote(
                $filial, $tipoCusto, $produto, $almoxarifado, $localEstoque, $dtLancamento
            );
            
        }else{
            $custoQuantidade = $this->consultaCusto(
                $filial, $tipoCusto, $produto, $almoxarifado, $localEstoque, $lote, $subLote, $dtLancamento
            );
            
        }
        
        if (!$custoQuantidade){
            $custoQuantidade = array('QTESTOQDIA' => '0', 'VRCUSTOPROD' => '0', 'VRCUSTOPRODBRUT' => '0', 'VRCUSTOPRODLIQ' => '0');
            
        }
        
        return $custoQuantidade;
    }

    /* ******************** Funções gerais ******************** */
    
    public function getParamCustos($tipoCusto) {
        if($tipoCusto == '08' || $tipoCusto == '09'){
            $arrCustos['CUSTLIQU'] = '08';
            $arrCustos['CUSTBRUT'] = '09';
        }elseif($tipoCusto == '03' || $tipoCusto == '04'){
            $arrCustos['CUSTLIQU'] = '03';
            $arrCustos['CUSTBRUT'] = '04';
        }elseif($tipoCusto == '01' || $tipoCusto == '02'){
            $arrCustos['CUSTLIQU'] = '01';
            $arrCustos['CUSTBRUT'] = '02';
        }else{
            $arrCustos['CUSTLIQU'] = $tipoCusto;
            $arrCustos['CUSTBRUT'] = $tipoCusto;
        }
        return $arrCustos;
    }

    public function retornaDadosProcessFilial($filial, $organizacao) {
        $conditions[] = array(
            'columnName' => 'CDFILIAL',
            'operator' => '=',
            'value' => $filial,
        );
        $conditions[] = array(
            'columnName' => 'NRORG',
            'operator' => '=',
            'value' => $organizacao,
        );

        $dataSet = $this->crudService->getFilteredDataSet('paramestoque_processamento', $conditions);
        $rows = $dataSet->getRows();

        if (!count($rows))
            throw new \Exception('Parametrização geral não encontrada.', 400);

        return $rows[0];
    }

    public function setLanctoestoq(Lanctoestoq $lanctoestoq) {
        $this->lanctoestoq = $lanctoestoq;
    }

    public function getLanctoestoq() {
        return $this->lanctoestoq;
    }

    /**
     * Metodo responsável por varificar se existe a entidade "Lanctoestoq" nessa classe,
     * se não existir, realiza a busca da mesma no banco de dados.
     * Este metodo deve ser utilizado quando você já efetuou a busca em outra classe e não necessita
     * de busca-la novamente no "atualizador de estoque".
     *
     * @param $filial
     * @param $lancamento
     * @param $organizacao
     *
     * @return Lanctoestoq
     */
    public function buscaLanctoestoq($filial, $lancamento, $organizacao) {
        $lanctoestoq = Lanctoestoq::findOneByCdfilialAndNrlancestqAndNrorg($filial, $lancamento, $organizacao);
        if (!is_object($lanctoestoq)){
            $lanctoestoq = $this->getLanctoestoq();
            if (is_object($lanctoestoq)) {
                return $lanctoestoq;
            }else{
                throw new \Exception ('Número do lançamento não encontrado.', 400);
            }
        }

        $this->setLanctoestoq($lanctoestoq);
        return $lanctoestoq;
    }
    
    
    public function atualizaEstoqueUpdate($cdfilial, $nrlancestq, $rows, $consolidate = false, $verifyIrregularities= true, $abortAtIrregularities = true) {
        try{
            $connection = $this->entityManager->getConnection();
            $connection->beginTransaction();
            #starts main, outer transaction
            $seqItemCodes = array();
            foreach($rows as $row){
                $produto = Itlanctoest::findOneByCdfilialAndNrlancestqAndNrsequitemAndNrorg($row['CDFILIAL'], $row['NRLANCESTQ'], $row['NRSEQUITEM'], $this->environment->getNrOrgTrab());
                $produto->setQtlanctoest($row['QTLANCTOEST']);
                $produto->setQttotlancto($row['QTTOTLANCTO']);
                $produto->setVrunilancto($row['VRUNILANCTO']);
                $produto->setVrtotlancto($row['VRTOTLANCTO']);
                $produto->setVrlanctoest($row['VRLANCTOEST']);
                $produto->setVrlanctobrut($row['VRLANCTOBRUT']);
                $produto->setIdevolestoq('N');
                $this->entityManager->persist($produto);
                $this->entityManager->flush($produto);
                $seqItemCodes[] = $row['NRSEQUITEM'];
            }
            $retorno =  $this->atualizaEstoque(
                $cdfilial, $nrlancestq, $consolidate, $verifyIrregularities, $abortAtIrregularities, $seqItemCodes
            );
            $connection->commit();
            return $retorno;
        }catch(\Exception $e) {
            $connection->rollback();
            throw $e;
        }
    }

    /**
     * Função que retorna os itens de um lançamento de
     * estoque de acordo com os parâmetros recebidos.
     *
     * Permite que sejam trazidos apenas itens que estão
     * ou não marcados para evolução do estoque através
     * da passagem do parâmetro $idevolestoq. Se este for
     * omitido, todos os itens serão buscados.
     *
     * @param  string $cdfilial
     * @param  string $nrlancestq
     * @param  string $nrorg
     * @param  string $idevolestoq
     *
     * @return Itlanctoest
     */
    private function buscaItlanctoest($cdfilial, $nrlancestq, $nrorg, $idevolestoq = null) {
        #checks parameter existence
        if(is_null($idevolestoq))
            #searches for every existent item
            $itlanctoest = Itlanctoest::findBy(array("cdfilial"  => $cdfilial, 
                                                    "nrlancestq" => $nrlancestq, 
                                                    "nrorg"      => $nrorg
                ), array("cdproduto" => "asc")
            );
        else
            #searches for certain items
            $itlanctoest = Itlanctoest::findBy(array("cdfilial" => $cdfilial, 
                                                     "nrlancestq" => $nrlancestq,
                                                     "idevolestoq" => $idevolestoq,
                                                     "nrorg"       => $nrorg
                ), array("cdproduto" => "asc")
            );

        # @var \Estoque\Entities\Itlanctoest $itlanctoest
        return $itlanctoest;
    }

    /**
     * Função que retorna um objeto Date à partir de uma
     * string pré-formatada.
     *
     * @param  string   $dateString
     *
     * @return DateTime
     */
    public function getDateObject($dateString) {
        #checks whether date variable is already an object
        if(!is_object($dateString)) {
            #evaluates appropriate date format
            $dateFormat = (strlen($dateString) > 10) ? DateUtil::FORMATO_BRASILEIRO_DATAHORA : DateUtil::FORMATO_BRASILEIRO;

            #creates object
            $dateObject = DateUtil::getDataDeString($dateString, $dateFormat, true);
        } else
            $dateObject = $dateString;

        return $dateObject;
    }

    /**
     * Função que recebe duas datas e retorna um array
     * indicando a data anterior ("INITIAL") e a
     * posterior ("FINAL").
     *
     * @param  DateTime $date
     * @param  DateTime $otherDate
     *
     * @return array
     */
    public function getOrderedDates($dataProcessa, $dataLancamento) {
        #compares and separates dates in prior and subsequent
        
        if($dataLancamento < $dataProcessa)
            $dtInicial = $dataLancamento;
        else
            $dtInicial = $dataProcessa;
        
        
        if($dataLancamento > $dataProcessa)
            $dtFinal = $dataLancamento;
        else
            $dtFinal = $dataProcessa;
        
        if($dtFinal < $dataProcessa)
            $dtFinal = $dataProcessa;

        return array("INITIAL" => $dtInicial->format('d/m/Y'), "FINAL" => $dtFinal->format('d/m/Y'));
    }
    
    public function atualizaProdlcestoq($cdfilial, $cdalmoxarife, $cdlocalestoq, $cdproduto){
        try{
            $sql = "INSERT INTO PRODLCESTOQ
                        (CDFILIAL, CDALMOXARIFE, CDLOCALESTOQ, CDPRODUTO)
                        (SELECT :P_CDFILIAL, NVL(:P_CDALMOXARIFE, ' '), NVL(:P_CDLOCALESTOQ, ' '), :P_CDPRODUTO
                         FROM PRODFILI
                         WHERE CDFILIAL  = :P_CDFILIAL  AND
                               CDPRODUTO = :P_CDPRODUTO AND
                               NVL(IDCNTRESTOQ, 'S') <> 'N'
                         MINUS
                         SELECT CDFILIAL, CDALMOXARIFE, CDLOCALESTOQ, CDPRODUTO
                         FROM PRODLCESTOQ
                         WHERE CDFILIAL     = :P_CDFILIAL     AND
                               CDALMOXARIFE = NVL(:P_CDALMOXARIFE, ' ') AND
                               CDLOCALESTOQ = NVL(:P_CDLOCALESTOQ, ' ') AND
                               CDPRODUTO    = :P_CDPRODUTO)";
                   
            $params = array("P_CDFILIAL" => $cdfilial,
                            "P_CDALMOXARIFE" => $cdalmoxarife,
                            "P_CDLOCALESTOQ" => $cdlocalestoq,
                            "P_CDPRODUTO"  => $cdproduto);
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            return $std->execute($params);     
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insItlanctoest($cdtiporeti, $cdfilial, $nrlancestq, $nrsequitem, $cdproduto, $qtlanctoest, $vrunilancto, $vrtotlancto, $dtlancmovi, $vrlanctoest, $qttotlancto, $cdfilimovi, $cdprodmovi, $vrlanctobrut, $cdalmoxarife, $cdlocalestoq, $nrloteestq, $nrsublote, $cdoperador){
        try{
            $sql = "INSERT INTO ITLANCTOEST
                    (CDTIPORETI, CDFILIAL, NRLANCESTQ, NRSEQUITEM, CDPRODUTO, QTLANCTOEST, VRUNILANCTO, VRTOTLANCTO, 
                    IDTIPOMOVI, DTLANCMOVI, IDORDEMCLC, VRLANCTOEST, QTTOTLANCTO, CDFILIMOVI, CDPRODMOVI, 
                    VRLANCTOBRUT, IDEVOLESTOQ, CDALMOXARIFE, CDLOCALESTOQ, NRLOTEESTQ, NRSUBLOTE, CDOPERADOR)
                    VALUES
                    (:P_CDTIPORETI, :P_CDFILIAL, :P_NRLANCESTQ, :P_NRSEQUITEM, :P_CDPRODUTO, :P_QTLANCTOEST, :P_VRUNILANCTO, :P_VRTOTLANCTO, 
                    '3', :P_DTLANCMOVI, '9', :P_VRLANCTOEST, :P_QTTOTLANCTO, :P_CDFILIMOVI, :P_CDPRODMOVI, 
                    :P_VRLANCTOBRUT, 'S', :P_CDALMOXARIFE, :P_CDLOCALESTOQ, :P_NRLOTEESTQ, :P_NRSUBLOTE, :P_CDOPERADOR)";
                                       
            $params = array(
                "P_CDTIPORETI" => $cdtiporeti,
                "P_CDFILIAL" => $cdfilial,
                "P_NRLANCESTQ" => $nrlancestq,
                "P_NRSEQUITEM" => $nrsequitem,
                "P_CDPRODUTO" => $cdproduto,
                "P_QTLANCTOEST" => $qtlanctoest,
                "P_VRUNILANCTO" => $vrunilancto,
                "P_VRTOTLANCTO" => $vrtotlancto,
                "P_DTLANCMOVI" => $dtlancmovi,
                "P_VRLANCTOEST" => $vrlanctoest,
                "P_QTTOTLANCTO" => $qttotlancto,
                "P_CDFILIMOVI" => $cdfilimovi,
                "P_CDPRODMOVI" => $cdprodmovi,
                "P_VRLANCTOBRUT" => $vrlanctobrut,
                "P_CDALMOXARIFE" => $cdalmoxarife,
                "P_CDLOCALESTOQ" => $cdlocalestoq,
                "P_NRLOTEESTQ" => $nrloteestq,
                "P_NRSUBLOTE" => $nrsublote,
                "P_CDOPERADOR" => $cdoperador
                );
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            return $std->execute($params);     
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insItemReti($cdfilial, $nrretirada, $nrcardapio, $nrsequitreti, $cdproduto, $qtretirada, 
                                $cdfiitlanest, $nrlancestq, $nrsequitem, $cdtiporeti){
        try{
            $sql = "INSERT INTO ITEMRETI
                    (CDFILIAL, NRRETIRADA, NRCARDAPIO, NRSEQUITRETI, CDPRODUTO, QTRETIRADA, 
                    CDFIITLANEST, NRLANCESTQ, NRSEQUITEM, CDTIPORETI)
                    VALUES
                    (:P_CDFILIAL, :P_NRRETIRADA, :P_NRCARDAPIO, :P_NRSEQUITRETI, :P_CDPRODUTO, :P_QTRETIRADA, 
                    :P_CDFIITLANEST, :P_NRLANCESTQ, :P_NRSEQUITEM, :P_CDTIPORETI)";
                   
            $params = array("P_CDFILIAL"     => $cdfilial, 
                            "P_NRRETIRADA"   => $nrretirada, 
                            "P_NRCARDAPIO"   => $nrcardapio, 
                            "P_NRSEQUITRETI" => $nrsequitreti, 
                            "P_CDPRODUTO"    => $cdproduto, 
                            "P_QTRETIRADA"   => $qtretirada, 
                            "P_CDFIITLANEST" => $cdfiitlanest, 
                            "P_NRLANCESTQ"   => $nrlancestq, 
                            "P_NRSEQUITEM"   => $nrsequitem, 
                            "P_CDTIPORETI"   => $cdtiporeti);
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            return $std->execute($params);     
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insCardadia($cdfilimovi, $cdservico, $dtretirada){
        try{
            $novocodigocardia = $this->newCode->getCodeByTableName('CARDAPIO'.$cdfilimovi, $this->environment->getNrOrgTrab());
            $operador = $this->environment->getCdOperador();
            
            $sql = "INSERT INTO CARDADIA
                    (CDFILIAL, NRCARDAPIO, CDSERVFILI, CDOPERADOR, DTCARDAPIO, DTMESCARD)
                    VALUES
                    (:CDFILIMOVI, :NRCARDAPIO, :CDSERVICO, :CDOPERADOR, :DTRETIRADA, '01'||TO_CHAR(TO_DATE(:DTRETIRADA, 'DD/MM/YYYY HH24:MI:SS'), '/MM/YYYY'))";
                   
            $params = array("CDFILIMOVI" => $cdfilimovi,
                            "NRCARDAPIO" => $novocodigocardia,
                            "CDSERVICO"  => $cdservico,
                            "CDOPERADOR"  => $operador,
                            "DTRETIRADA"  => $dtretirada);
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            $std->execute($params);     
            return $novocodigocardia;
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insLanctoEst($cdfilial, $cdfilimovi, $dtretirada, $nrlanctoAjuste){
        try{
            
            $novocodigo = $this->newCode->getCodeByTableName('LANCTOEST'.$cdfilial, $this->environment->getNrOrgTrab());
            $operador = $this->environment->getCdOperador();
            $dslancamento = 'Retirada para Ajuste : '.$nrlanctoAjuste;
            
            $sql = "INSERT INTO LANCTOESTOQ
                    (CDFILIAL, NRLANCESTQ, IDTPLANCTO, DTLANCESTQ, CDFILIMOVI, CDOPERADOR, IDALTCUSMED, DSLANCESTQ)
                    VALUES
                    (:CDFILIAL, :NRLANCESTQ, '3', :DTRETIRADA, :CDFILIMOVI, :CDOPERADOR, 'N', :DSLANCESTQ)";
                   
            $params = array("CDFILIAL"    => $cdfilial,
                            "NRLANCESTQ"  => $novocodigo,
                            "DTRETIRADA"  => $dtretirada,
                            "CDFILIMOVI"  => $cdfilimovi,
                            "CDOPERADOR"  => $operador,
                            "DSLANCESTQ"  => $dslancamento);
                            
            $std = $this->entityManager->getConnection()->prepare($sql);
            $std->execute($params); 
        
            return $novocodigo;
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insRetiradaEsto($cdfilial, $dtretest, $cdfiliallan, $nrlancestq, $cdsetor, $cdcentcust){
        try{
            $nrRetEst = $this->newCode->getCodeByTableName('RETIESTO'.$cdfilial, $this->environment->getNrOrgTrab());
            $operador = $this->environment->getCdOperador();
            $dslancamento = 'Retirada para Ajuste : '.$nrlancestq;
            
            $sql = "INSERT INTO RETIRADAESTO
                    (CDFILIAL, NRRETEST, DTRETEST, 
                    DSRETEST, CDFILIALLAN, NRLANCESTQ, CDSETOR, CDCENTCUST, NRORDPROD, NRREQUESTO) 
                    VALUES
                    (:P_CDFILIAL, :P_NRRETEST, :P_DTRETEST, 
                     :P_DSLANCAMENTO, :P_CDFILIALLAN, :P_NRLANCESTQ, :P_CDSETOR, :P_CDCENTCUST, null, null)";
                   
            $params = array("P_CDFILIAL"       => $cdfilial,
                            "P_NRRETEST"       => $nrRetEst,
                            "P_DTRETEST"       => $dtretest,
                            "P_DSLANCAMENTO"   => $dslancamento,
                            "P_CDFILIALLAN"    => $cdfiliallan,
                            "P_NRLANCESTQ"     => $nrlancestq,
                            "P_CDSETOR"        => $cdsetor,
                            "P_CDCENTCUST"     => $cdcentcust);
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            $std->execute($params); 

            return $nrRetEst;
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insRetirada($cdfilimovi, $nrcardapio, $dtretirada, $cdfilial, $nrlacestq, $nrlanctoAjuste){
        try{
            $novocodigo = $this->newCode->getCodeByTableName('RETIRADA'.$cdfilimovi, $this->environment->getNrOrgTrab());
            $operador = $this->environment->getCdOperador();
            $dslancamento = 'Retirada para Ajuste : '.$nrlanctoAjuste;
            
            $sql = "INSERT INTO RETIRADA
                    (CDFILIAL, NRRETIRADA, NRCARDAPIO, DTRETIRADA, CDFILANCESTQ, NRLANCESTQ)
                    VALUES
                    (:CDFILIMOVI, :NOVOCODIGO, :NRCARDAPIO, :DTRETIRADA, :CDFILIAL, :NRLANCESTQ)";
                   
            $params = array("CDFILIMOVI"    => $cdfilimovi,
                            "NOVOCODIGO"  => $novocodigo,
                            "NRCARDAPIO"  => $nrcardapio,
                            "DTRETIRADA"  => $dtretirada,
                            "CDFILIAL"    => $cdfilial,
                            "NRLANCESTQ"  => $nrlacestq);
                   
            $std = $this->entityManager->getConnection()->prepare($sql);
            $std->execute($params);     
            return $novocodigo;
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function insAjurelatret($cdfilial, $nrlacestq, $nrlancestqret){
        try{
            $sql = "INSERT INTO AJURELACRET
                    (CDFILIAL, NRLANCESTQ, CDFILIALRET, NRLANCESTRET)
                    VALUES
                    (:CDFILIAL, :NRLANCESTQ, :CDFILIAL, :NRLANCESTQRET)";
                   
            $params = array("CDFILIAL"    => $cdfilial,
                            "NRLANCESTQ"  => $nrlacestq,
                            "NRLANCESTQRET"  => $nrlancestqret
                            );
            $std = $this->entityManager->getConnection()->prepare($sql);
            return $std->execute($params);     
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function deleteItemReti($cdfilial, $nrretirada, $nrcardapio, $nrsequitreti, $cdproduto){
        $sql = "DELETE FROM ITEMRETI
                WHERE CDFILIAL = :P_CDFILIAL AND
                NRRETIRADA = :P_NRRETIRADA AND
                NRCARDAPIO = :P_NRCARDAPIO AND
                NRSEQUITRETI = :P_NRSEQUITRETI AND
                CDPRODUTO = :P_CDPRODUTO";
               
        $params = array("P_CDFILIAL" => $cdfilial,
                        "P_NRRETIRADA" => $cdproduto,
                        "P_NRCARDAPIO" => $nrcardapio,
                        "P_NRSEQUITRETI" => $nrsequitreti,
                        "P_CDPRODUTO" => $cdproduto);
               
        $std = $this->entityManager->getConnection()->prepare($sql);
        return $std->execute($params);     
    }
    
    public function deleteItlanctoest($cdfilial, $cdproduto, $nrlancestq, $nrseqitem = null){
        if($nrseqitem){
            $sql = "DELETE FROM ITLANCTOEST
                    WHERE CDFILIAL = :P_CDFILIAL AND
                    NRLANCESTQ = :P_NRLANCESTQ AND
                    NRSEQUITEM = :P_NRSEQUITEM AND
                    CDPRODUTO = :P_CDPRODUTO";
            $params = array("P_CDFILIAL" => $cdfilial,
                            "P_CDPRODUTO" => $cdproduto,
                            "P_NRSEQUITEM" => $nrseqitem,
                            "P_NRLANCESTQ" => $nrlancestq);
        }else{
            $sql = "DELETE ITLANCTOEST
                    WHERE CDFILIAL   = :P_CDFILIAL
                    AND   CDPRODUTO  = :P_CDPRODUTO
                    AND   NRLANCESTQ = :P_NRLANCESTQ";
            $params = array("P_CDFILIAL" => $cdfilial,
                            "P_CDPRODUTO" => $cdproduto,
                            "P_NRLANCESTQ" => $nrlancestq);
        }
               
        $std = $this->entityManager->getConnection()->prepare($sql);
        return $std->execute($params);     
    }
    
    public function qryVerifAjuRelacRet($cdfilial, $nrlancestq){
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRLANCESTQ" => $nrlancestq
        );
        $sql = "SELECT AR.CDFILIAL, AR.NRLANCESTQ, AR.CDFILIALRET, AR.NRLANCESTRET,
                RE.NRRETIRADA, CD.NRCARDAPIO, CD.CDSERVFILI, DTRETIRADA
                FROM CARDADIA CD, RETIRADA RE, AJURELACRET AR
                WHERE (CD.NRCARDAPIO = RE.NRCARDAPIO) AND
                (CD.CDFILIAL = RE.CDFILIAL) AND
                (RE.NRLANCESTQ = AR.NRLANCESTRET) AND
                (RE.CDFILANCESTQ = AR.CDFILIAL) AND
                (AR.CDFILIALRET = :P_CDFILIAL) AND
                (AR.NRLANCESTQ = :P_NRLANCESTQ) AND
                (AR.CDFILIAL = :P_CDFILIAL)";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function qryVerifTpRet($cdfilial, $nrretirada, $nrcardapio){
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRRETIRADA" => $nrretirada,
            "P_NRCARDAPIO" => $nrcardapio
        );
        $sql = "SELECT MAX(CDTIPORETI) CDTIPORETI
                FROM ITEMRETI
                WHERE CDFIITLANEST = :P_CDFILIAL AND
                NRRETIRADA = :P_NRRETIRADA AND
                NRCARDAPIO = :P_NRCARDAPIO";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function qryVerifTpRetEst($cdfilial, $nrretirada, $nrcardapio){
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRRETIRADA" => $nrretirada,
            "P_NRCARDAPIO" => $nrcardapio
        );
        $sql = "SELECT MAX(CDTIPORETI) CDTIPORETI
                FROM ITEMRETI
                WHERE CDFIITLANEST = :P_CDFILIAL AND
                NRRETIRADA = :P_NRRETIRADA AND
                NRCARDAPIO = :P_NRCARDAPIO";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function qryVerifItLanctoEst($cdfilial, $nrlancestq, $nrsequitem){
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRLANCESTQ" => $nrlancestq,
            "P_NRSEQUITEM" => $nrsequitem
        );
        $sql = "SELECT CDPRODUTO
                FROM ITLANCTOEST
                WHERE (CDFILIAL = :P_CDFILIAL) AND
                (NRLANCESTQ = :P_NRLANCESTQ) AND
                (NRSEQUITEM = :P_NRSEQUITEM)";
                				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function qryCardapio($cdfilimovi, $cdservico, $dtretirada){
        $parameters = array(
            "CDFILIMOVI" => $cdfilimovi,
            "CDSERVICO" => $cdservico,
            "DTRETIRADA" => $dtretirada
        );
        $sql = "SELECT NRCARDAPIO
                FROM CARDADIA
                WHERE (CDFILIAL = :CDFILIMOVI) AND
                (CDSERVFILI = :CDSERVICO) AND
                (DTCARDAPIO = :DTRETIRADA)";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function qryVerifAjuRetEsto($cdfilial, $nrlancestq){
        $parameters = array(
            "P_NRLANCESTQ" => $nrlancestq,
            "P_CDFILIAL" => $cdfilial
        );
        $sql = "SELECT AR.CDFILIAL, AR.NRLANCESTQ, AR.CDFILIALRET, AR.NRLANCESTRET, RE.NRRETEST, RE.DTRETEST
                FROM RETIRADAESTO RE, AJURELACRET AR
                WHERE (AR.NRLANCESTQ = :P_NRLANCESTQ) AND
                (AR.CDFILIALRET = :P_CDFILIAL ) AND
                (AR.NRLANCESTRET = RE.NRLANCESTQ) AND
                (RE.CDFILIALLAN = :P_CDFILIAL )";
                				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $result = $std->fetchAllAssociative(); 
        return $result;
    }
    
    public function consultaCusto(
        $filial, $tipoCusto, $produto, $almoxarifado, $localEstoque, $lote, $subLote, $dtLancamento
    )
    {
        $paramCustos = $this->getParamCustos($tipoCusto);
        $sql = <<<SQL
        SELECT NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_INTPCUSTO, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV AS VRCUSTOPROD,
               NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_CUSTLIQU, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV AS VRCUSTOPRODLIQ,
               NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_CUSTBRUT, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV AS VRCUSTOPRODBRUT,
               NVL((D.QTESTOQDIA/P.VRFATOCONV), 0) AS QTESTOQDIA
          FROM PARAMFILIAL PF, PRODUTO P, POSESTDIA D, LOTE L
         WHERE P.CDPRODUTO = :P_CDPRODUTO
           AND D.CDFILIAL = :P_CDFILIAL
           AND D.CDALMOXARIFE = :P_CDALMOXARIFE
           AND D.CDLOCALESTOQ = :P_CDLOCALESTOQ
           AND D.NRLOTEESTQ = :P_NRLOTEESTQ
           AND D.NRSUBLOTE = :P_NRSUBLOTE
           AND D.CDPRODUTO = P.CDPRODESTO
           AND (
                (D.QTESTOQDIA > 0) 
                OR 
                (PF.IDPERMESTNEG = 'S')
            )
           AND PF.CDFILIAL = L.CDFILIAL
           AND D.CDFILIAL = L.CDFILIAL
           AND D.NRLOTEESTQ = L.NRLOTEESTQ
           AND D.NRSUBLOTE = L.NRSUBLOTE
           AND D.CDPRODUTO = L.CDPRODUTO
           AND D.DTPOSIESTQ = (SELECT MAX(B.DTPOSIESTQ)
                                 FROM POSESTDIA B
                                WHERE B.CDFILIAL     = D.CDFILIAL
                                  AND B.CDPRODUTO    = D.CDPRODUTO
                                  AND B.CDALMOXARIFE = D.CDALMOXARIFE
                                  AND B.CDLOCALESTOQ = D.CDLOCALESTOQ
                                  AND B.NRLOTEESTQ   = D.NRLOTEESTQ
                                  AND B.NRSUBLOTE    = D.NRSUBLOTE
                                  AND B.DTPOSIESTQ   <= TO_DATE(:P_DTLANC) + 1)
SQL;

        $params = array(
            "P_CDFILIAL" => $filial,
            "P_INTPCUSTO" => $tipoCusto,
            "P_CUSTLIQU" => $paramCustos['CUSTLIQU'],
            "P_CUSTBRUT" => $paramCustos['CUSTBRUT'],
            "P_CDALMOXARIFE" => $almoxarifado,
            "P_CDLOCALESTOQ" => $localEstoque,
            "P_CDPRODUTO" => $produto,
            "P_NRLOTEESTQ" => $lote,
            "P_NRSUBLOTE" => $subLote,
            "P_DTLANC" => $dtLancamento
        );
        
        try{
            $std = $this->entityManager->getConnection()->executeQuery($sql, $params);
            return $std->fetchAssociative();
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function consultaCustoSemLote(
        $filial, $tipoCusto, $produto, $almoxarifado, $localEstoque, $dtLancamento
    )
    {
        $paramCustos = $this->getParamCustos($tipoCusto);
        
        $sql = <<<SQL
        SELECT NVL(AVG(NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_INTPCUSTO, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV), 0) AS VRCUSTOPROD,
               NVL(AVG(NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_CUSTLIQU, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV), 0) AS VRCUSTOPRODLIQ,
               NVL(AVG(NVL(CUSTO_PRODUTO_LCEST(:P_CDFILIAL, D.CDPRODUTO, :P_CUSTBRUT, :P_DTLANC, SYSDATE,
               D.CDALMOXARIFE, D.CDLOCALESTOQ, D.NRLOTEESTQ, D.NRSUBLOTE),0) * P.VRFATOCONV), 0) AS VRCUSTOPRODBRUT,
               NVL(SUM((D.QTESTOQDIA/P.VRFATOCONV)), 0) AS QTESTOQDIA
          FROM PARAMFILIAL PF, PRODUTO P, POSESTDIA D, LOTE L
         WHERE P.CDPRODUTO = :P_CDPRODUTO
           AND D.CDFILIAL = :P_CDFILIAL
           AND D.CDALMOXARIFE = :P_CDALMOXARIFE
           AND D.CDLOCALESTOQ = :P_CDLOCALESTOQ
           AND D.CDPRODUTO = P.CDPRODESTO
           AND (
                (D.QTESTOQDIA > 0) 
                OR 
                (PF.IDPERMESTNEG = 'S')
            )
           AND PF.CDFILIAL = L.CDFILIAL
           AND D.CDFILIAL = L.CDFILIAL
           AND D.NRLOTEESTQ = L.NRLOTEESTQ
           AND D.NRSUBLOTE = L.NRSUBLOTE
           AND D.CDPRODUTO = L.CDPRODUTO
           AND D.DTPOSIESTQ = (SELECT MAX(B.DTPOSIESTQ)
                                 FROM POSESTDIA B
                                WHERE B.CDFILIAL     = D.CDFILIAL
                                  AND B.CDPRODUTO    = D.CDPRODUTO
                                  AND B.CDALMOXARIFE = D.CDALMOXARIFE
                                  AND B.CDLOCALESTOQ = D.CDLOCALESTOQ
                                  AND B.NRLOTEESTQ   = D.NRLOTEESTQ
                                  AND B.NRSUBLOTE    = D.NRSUBLOTE
                                  AND B.DTPOSIESTQ   <= TO_DATE(:P_DTLANC) + 1)
SQL;

        $params = array(
            "P_CDFILIAL" => $filial,
            "P_INTPCUSTO" => $tipoCusto,
            "P_CUSTLIQU" => $paramCustos['CUSTLIQU'],
            "P_CUSTBRUT" => $paramCustos['CUSTBRUT'],
            "P_CDALMOXARIFE" => $almoxarifado,
            "P_CDLOCALESTOQ" => $localEstoque,
            "P_CDPRODUTO" => $produto,
            "P_DTLANC" => $dtLancamento
        );

        try{
            $std = $this->entityManager->getConnection()->executeQuery($sql, $params);
            return $std->fetchAssociative();
        } catch(\Exception $e) {
            throw $e;
        }
    }
    
    public function consolidadeToStockProducts($products, $consolidate = false){
        if($consolidate){ 
            //consolidate all procuts into stock products.
            $stockProductsArray = array();
            foreach($products as $keyA => &$productA){
                if($productA->getCdprodmovi() != $productA->getCdproduto()){
                    $this->entityManager->beginTransaction();
                    $newProdEsto = new Itlanctoest();
                    $newProdEsto->setCdfilial($productA->getCdfilial());
                    $newProdEsto->setNrlancestq($productA->getNrlancestq()); 
                    $newProdEsto->setNrsequitem(($this->getMaxNrSeqItemAjuste($productA->getCdfilial(), $productA->getNrlancestq())));
                    $newProdEsto->setIdtipomovi($productA->getIdtipomovi());
                    $newProdEsto->setCdproduto($productA->getCdprodmovi());
                    $newProdEsto->setQttotlancto(0);
                    $newProdEsto->setVrunilancto(0);
                    $newProdEsto->setVrtotlancto(0);
                    $newProdEsto->setDtlancmovi($productA->getDtlancmovi());
                    $newProdEsto->setIdordemclc($productA->getIdordemclc());
                    $newProdEsto->setCdprodmovi($productA->getCdprodmovi());
                    $newProdEsto->setQtlanctoest(0);
                    $newProdEsto->setVrlanctobrut(0);
                    $newProdEsto->setVrlanctoest(0);
                    $newProdEsto->setCdfilimovi($productA->getCdfilimovi());
                    $newProdEsto->setCdalmoxarife($productA->getCdalmoxarife());
                    $newProdEsto->setCdlocalestoq($productA->getCdlocalestoq());
                    $newProdEsto->setNrloteestq($productA->getNrloteestq());
                    $newProdEsto->setNrsublote($productA->getNrsublote());
                    $newProdEsto->setNrorg($productA->getNrorg());
                    $this->entityManager->persist($newProdEsto);
                    $this->entityManager->flush($newProdEsto);
                    $this->entityManager->commit();
                    array_push($stockProductsArray, $newProdEsto);
                }
            }
            
            foreach($stockProductsArray as $stockProd){
                array_unshift($products, $stockProd);
            }
        }
        
        $indexToUnset = array();
        foreach($products as $keyA => &$productA){
            foreach($products as $keyB => &$productB){
                if( ($productB->getNrsequitem() != $productA->getNrsequitem()) && 
                    ($productB->getCdprodmovi() == $productA->getCdprodmovi()) &&
                    ($productB->getNrloteestq() == $productA->getNrloteestq()) &&
                    ($productB->getCdalmoxarife() == $productA->getCdalmoxarife()) &&
                    ($productB->getCdlocalestoq() == $productA->getCdlocalestoq()) &&
                    ($productB->getNrsublote() == $productA->getNrsublote()) &&
                    ($keyB > $keyA)
                )
                    {
                        $this->entityManager->beginTransaction();
                        $prodA = Produto::findOneByCdproduto($productA->getCdproduto());
                        $prodB = Produto::findOneByCdproduto($productB->getCdproduto());
                        
                        $sumQtlanctoest = $productA->getQtlanctoest() + $productB->getQtlanctoest();
                        $productA->setQtlanctoest($sumQtlanctoest);
                        $productA->setQttotlancto($sumQtlanctoest/$prodA->getVrfatoconv());
                        $sumVrtotlancto = $productA->getVrtotlancto() + $productB->getVrtotlancto();
                        $productA->setVrtotlancto($sumVrtotlancto);
                        $productA->setVrunilancto($sumVrtotlancto/($sumQtlanctoest == 0? 1 : $sumQtlanctoest));
                        $productA->setVrlanctoest($productA->getVrlanctoest() + $productB->getVrlanctoest());
                        $productA->setVrlanctobrut($productA->getVrlanctobrut() + $productB->getVrlanctobrut());

                        $this->entityManager->persist($productB);
                        $this->entityManager->flush($productB);
                        $productB->delete();

                        $this->entityManager->commit();
                         
                        array_push($indexToUnset, $keyB);
                    }
                        
            }

        }
        foreach($indexToUnset as $key){
            unset($products[$key]);
        }
        
        return $products;
    }
    
    public function getMaxNrSeqItemItlanctoest($cdfilial, $nrlancestq){
        return $this->getMaxNrSeqItemAjuste($cdfilial, $nrlancestq);
    }
    
    public function getMaxNrSeqItemAjuste($cdfilial, $nrlancestq) {
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRLANCESTQ" => $nrlancestq
        );
        $sql = "SELECT MAX(NRSEQUITEM) + 1 AS NRSEQUITEM
				FROM ITLANCTOEST
				WHERE NRLANCESTQ = :P_NRLANCESTQ
				AND CDFILIAL     = :P_CDFILIAL";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $maxNrSeqItem = $std->fetchAllAssociative();
        
        
        $nrsequitem = ($maxNrSeqItem[0]["NRSEQUITEM"] != null) ? $maxNrSeqItem[0]["NRSEQUITEM"] : 1;
        $nrsequitem = str_pad($nrsequitem, 4, "0", STR_PAD_LEFT);
        
        return $nrsequitem;
    }
    
    public function getMaxNrSeqItemRetirada($cdfilial, $nrretirada, $nrcardapio) {
        $parameters = array(
            "P_CDFILIAL" => $cdfilial,
            "P_NRRETIRADA" => $nrretirada,
            "P_NRCARDAPIO" => $nrcardapio
        );
        $sql = "SELECT LPAD(MAX(NRSEQUITRETI)+1, 4, '0') AS NRSEQUITRETI
                  FROM ITEMRETI
                 WHERE NRRETIRADA = :P_NRRETIRADA
                   AND NRCARDAPIO = :P_NRCARDAPIO
                   AND CDFILIAL = :P_CDFILIAL";
				
        $std = $this->entityManager->getConnection()->executeQuery($sql, $parameters);
        $maxNrSeqItem = $std->fetchAllAssociative();
        
        
        $nrsequitreti = ($maxNrSeqItem[0]["NRSEQUITRETI"] != null) ? $maxNrSeqItem[0]["NRSEQUITRETI"] : 1;
        $nrsequitreti = str_pad($nrsequitreti, 4, "0", STR_PAD_LEFT);
        
        return $nrsequitreti;
    }
    
    public function getProdutosRetirar($cdfilial, $nrlancestq) {
        $nrorg = $this->environment->getNrOrgTrab();
        $itlanctoest = $this->buscaItlanctoest($cdfilial, $nrlancestq, $nrorg);
        $arrProdutosRetirada = array();
        foreach($itlanctoest as $item) {
            $posicao = $this->consultaCustoQuantidadeProduto(
                $item->getCdfilimovi(), 
                $item->getCdprodmovi(), 
                $item->getDtlancmovi()->format('d/m/Y'),
                self::TIPO_CUSTO_PADRAO,
                $item->getCdalmoxarife(),
                $item->getCdlocalestoq(),
                $item->getNrloteestq(),
                $item->getNrsublote()
            );
            if($item->getQttotlancto() < $posicao['QTESTOQDIA']){
                $diferenca = $posicao['QTESTOQDIA'] - $item->getQttotlancto();
                $item->diferenca = $diferenca;
                array_push($arrProdutosRetirada, $item);
            }
        }
        return $arrProdutosRetirada;
    }
    
    public function processRetirada($row, $arrProdutosRetirada){
        try {
            
            $connection = $this->entityManager->getConnection();
            #starts main, outer transaction
            $connection->beginTransaction();
            $nrorg = $this->environment->getNrOrgTrab();
            $entFilial = Paramfilial::findOneByCdfilialAndNrorg($row['CDFILIMOVI'], $nrorg);
            if($row['IDTPLANCTO'] == 1)
                $calcData = $entFilial->getIdordajuinve() == 'P'? 1 : 0;
            else    
                $calcData = $entFilial->getIdordimplant() == 'P'? 1 : 0;
                
            $dtretirada = DateUtil::subtraiIntervalo($arrProdutosRetirada[0]->getDtlancmovi(), $calcData)->format('d/m/Y');
            $nrseqitem = null;

            foreach($arrProdutosRetirada as $prodRetirar){
                if(is_null($nrseqitem))
                    $nrseqitem = $this->getMaxNrSeqItemAjuste($prodRetirar->getCdfilial(), $prodRetirar->getNrlancestq());
                else
                    $nrseqitem++;
                    
                if($row['RETIESTQ'] != 'S'){
                    if(($row['CDSERVFILI']?? null) != null){
                        
                        $resultRelac = $this->qryVerifAjuRelacRet($row['CDFILIAL'], $row['NRLANCESTQ']);
                        
                        if(empty($resultRelac)){
                            $cardapio = $this->qryCardapio($row['CDFILIMOVI'], $row['CDSERVFILI'], $row['DTLANCESTQ']);
                            if(empty($cardapio)){
                                $nrcardapio = $this->insCardadia($row['CDFILIMOVI'], $row['CDSERVFILI'], $dtretirada);
                                $row['NRCARDAPIO'] = $nrcardapio;
                            }else{
                                $nrcardapio = isset($cardapio[0])? $cardapio[0]['NRCARDAPIO'] : null;
                                $row['NRCARDAPIO'] = $nrcardapio;
                            }
                            $nrlanctoestReti = $this->insLanctoEst(
                                $row['CDFILIAL'],
                                $row['CDFILIMOVI'],
                                $dtretirada,
                                $row['NRLANCESTQ']
                            );
                            $nrlanctoAjuste = $prodRetirar->getNrlancestq();
                            $codRetirada = $this->insRetirada($row['CDFILIMOVI'], $nrcardapio, $dtretirada, $row['CDFILIAL'], $nrlanctoestReti, $nrlanctoAjuste);
                            $row['NRRETIRADA'] = $codRetirada;
                            $this->insAjurelatret($row['CDFILIAL'], $row['NRLANCESTQ'], $nrlanctoestReti);
                        }else{
                            $row['NRRETIRADA'] = $row['NRRETIRADA']?? $resultRelac[0]['NRRETIRADA'];
                            $tpReti = $this->qryVerifTpRet($row['CDFILIAL'], $row['NRRETIRADA'], $row['NRCARDAPIO']);
                            $nrlanctoestReti = isset($resultRelac[0])? $resultRelac[0]['NRLANCESTRET'] : null;
                        }
                        
                        $hasLanctoest = $this->qryVerifItLanctoEst($row['CDFILIAL'], $nrlanctoestReti, $prodRetirar->getNrsequitem());
                        
                        if($hasLanctoest){
                            $this->deleteItemReti(
                                $row['CDFILIAL'],
                                $row['NRRETIRADA'],
                                $row['NRCARDAPIO'],
                                $prodRetirar->getNrsequitem(),
                                $prodRetirar->getCdproduto()
                            );
                            
                            $this->deleteItlanctoest(
                                $row['CDFILIAL'],
                                $prodRetirar->getCdproduto(),
                                $nrlanctoestReti,
                                $prodRetirar->getNrsequitem()
                            );
                        }
                        $produto = Produto::findOneByCdproduto($prodRetirar->getCdproduto());
                        $qttotlancto = $prodRetirar->diferenca / $produto->getVrfatoconv();
                        $custoProduto = $this->consultaCustoQuantidadeProduto($prodRetirar->getCdfilimovi(), $prodRetirar->getCdproduto(), $dtretirada, null, $prodRetirar->getCdalmoxarife(), $prodRetirar->getCdlocalestoq(), $prodRetirar->getNrloteestq(), $prodRetirar->getNrsublote());
                        $this->insItlanctoest(
                            $row['CDTIPORETI'],
                            $row['CDFILIAL'],
                            $nrlanctoestReti,
                            $prodRetirar->getNrsequitem(),
                            $prodRetirar->getCdproduto(),
                            $prodRetirar->diferenca,  //$prodRetirar->getQtlanctoest(),
                            $custoProduto['VRCUSTOPROD'],  //$prodRetirar->getVrunilancto(),
                            $prodRetirar->diferenca * $custoProduto['VRCUSTOPROD'], //$prodRetirar->getVrtotlancto(),
                            $dtretirada,   //$prodRetirar->getDtlancmovi()->format('d/m/Y'),
                            $prodRetirar->getVrlanctoest(),
                            $qttotlancto, //$prodRetirar->getQttotlancto(),
                            $prodRetirar->getCdfilimovi(),
                            $prodRetirar->getCdprodmovi(),
                            $prodRetirar->getVrlanctobrut(),
                            $prodRetirar->getCdalmoxarife(),
                            $prodRetirar->getCdlocalestoq(),
                            $prodRetirar->getNrloteestq(),
                            $prodRetirar->getNrsublote(),
                            $prodRetirar->getCdoperador()
                        );
                        $nrsequitreti = $this->getMaxNrSeqItemRetirada($row['CDFILIMOVI'], $row['NRRETIRADA'], $row['NRCARDAPIO']);
                        $this->insItemReti(
                            $row['CDFILIMOVI'], 
                            $row['NRRETIRADA'], 
                            $row['NRCARDAPIO'],
                            $nrsequitreti,
                            $prodRetirar->getCdproduto(),
                            $qttotlancto, //$prodRetirar->getQttotlancto(),
                            $prodRetirar->getCdfilial(),
                            $nrlanctoestReti,
                            $prodRetirar->getNrsequitem(),
                            $row['CDTIPORETI']
                        );
                                            
                        $this->atualizaEstoque($row['CDFILIAL'], $nrlanctoestReti);
                    }
                }else{ 
                    $posicao = $this->consultaCustoQuantidadeProduto(
                        $row['CDFILIMOVI'],
                        $prodRetirar->getCdproduto(),
                        $prodRetirar->getDtlancmovi()->format('d/m/Y'),
                        self::TIPO_CUSTO_PADRAO,
                        $prodRetirar->getCdalmoxarife(),
                        $prodRetirar->getCdlocalestoq(),
                        $prodRetirar->getNrloteestq(),
                        $prodRetirar->getNrsublote()
                    );
                    if($prodRetirar->getQttotlancto() < $posicao['QTESTOQDIA']){
                        $retEsto =  $this->qryVerifAjuRetEsto($row['CDFILIAL'], $row['NRLANCESTQ']);
                        if(!$retEsto){
                            $retEsto['NRLANCESTRET'] = $this->insLanctoEst(
                                $row['CDFILIAL'], 
                                $row['CDFILIMOVI'], 
                                $dtretirada,
                                $row['NRLANCESTQ']
                            );
                            $retEsto['NRRETIESTO'] = $this->insRetiradaEsto(
                                $row['CDFILIMOVI'],
                                $dtretirada,
                                $row['CDFILIAL'],
                                $retEsto['NRLANCESTRET'],
                                $row['CDSETOR'],
                                $row['CDCENTCUST']
                            );
                            $this->insAjurelatret($row['CDFILIAL'], $row['NRLANCESTQ'], $retEsto['NRLANCESTRET']);
                        }else{
                            $retEsto = $retEsto[0];
                        }
                        $hasLanctoest = $this->qryVerifItLanctoEst($row['CDFILIAL'], $retEsto['NRLANCESTRET'], $nrseqitem);
                        if($hasLanctoest){
                            $this->deleteItlanctoest($row['CDFILIAL'], $prodRetirar->getCdproduto(), $retEsto['NRLANCESTRET'], $nrseqitem);
                        }
                        
                        $produto = Produto::findOneByCdproduto($prodRetirar->getCdproduto());
                        $qttotlancto = $prodRetirar->diferenca / $produto->getVrfatoconv();
                        
                        $custoProduto = $this->consultaCustoQuantidadeProduto($prodRetirar->getCdfilimovi(), $prodRetirar->getCdproduto(), $dtretirada, null, $prodRetirar->getCdalmoxarife(), $prodRetirar->getCdlocalestoq(), $prodRetirar->getNrloteestq(), $prodRetirar->getNrsublote());

                        $this->insItlanctoest(
                            $row['CDTIPORETI'],
                            $row['CDFILIAL'],
                            $retEsto['NRLANCESTRET'],
                            $nrseqitem,
                            $prodRetirar->getCdproduto(),
                            $prodRetirar->diferenca, //$prodRetirar->getQtlanctoest(),
                            $custoProduto['VRCUSTOPROD'],  //$prodRetirar->getVrunilancto(),
                            $prodRetirar->diferenca * $custoProduto['VRCUSTOPROD'], //$prodRetirar->getVrtotlancto(),
                            $dtretirada,   //$prodRetirar->getDtlancmovi()->format('d/m/Y'),
                            $prodRetirar->getVrlanctoest(),
                            $qttotlancto, //$prodRetirar->getQttotlancto(),
                            $prodRetirar->getCdfilimovi(),
                            $prodRetirar->getCdprodmovi(),
                            $prodRetirar->getVrlanctobrut(),
                            $prodRetirar->getCdalmoxarife(),
                            $prodRetirar->getCdlocalestoq(),
                            $prodRetirar->getNrloteestq(),
                            $prodRetirar->getNrsublote(),
                            $prodRetirar->getCdoperador()
                        );
                        
                        $this->atualizaEstoque($row['CDFILIAL'], $retEsto['NRLANCESTRET']);
                    }
                }
            }
            //$connection->rollback();
            $connection->commit();
        }catch (\Exception $e) {
            #rollbacks every operation
            $connection->rollback();
            #throws exception for controller to catch
            throw $e;
        }
    }
    
    public function array_some(array $array, callable $fn) {
        foreach ($array as $value) {
            if($fn($value)) {
                return true;
            }
        }
        return false;
    }
    
    public function deletaLancamentoEstoq($lanctoestoq){
        $arrItlanctoest = $this->buscaItlanctoest($lanctoestoq->getCdfilial(), $lanctoestoq->getNrlancestq(), $this->environment->getNrOrgTrab());
        foreach($arrItlanctoest as $itlanctoest){
            $itlanctoest->delete();
        }
        $lanctoestoq->delete();
    }
}