CREATE OR REPLACE FUNCTION public.calculacmc(integer, integer, integer, timestamp without time zone, numeric, numeric, character varying, integer, boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
declare
  filial         alias for $1;
  codigolancto   alias for $2;
  codigomaterial alias for $3;
  datalancto     alias for $4;
  quantidade     alias for $5;
  valorunitario  alias for $6;
  tipolancto     alias for $7;
  arredondamento alias for $8;
  cancelado      alias for $9;

  tipo_lancto_entrada character varying := upper('e');
  tipo_lancto_saida character varying := upper('s');
  casas_arredondamento integer := 4;

  aux tipovalorestoque;
  valorunitaux numeric(15,4);
  qtdeaux numeric(15,4);
  valortotalaux numeric(15,4);
  somavaloraux numeric(15,4);
  somaqtdeaux numeric(15,4);
  novounitarioaux numeric(15,4);
  codigoproximaentradaaux integer := null;
  dataproximaentradaaux date := null;
  primeirocalculoaux boolean := false;

  auxcursor refcursor;
  auxrecord record;
begin
  if (cancelado) then --se o lancamento foi cancelado
    --busca a primeira entrada apos o lancamento
    select lce.lce_codigo
          ,lce.lce_datahora
      into codigoproximaentradaaux
          ,dataproximaentradaaux
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and upper(lce.lce_tipo) = tipo_lancto_entrada
       and date_trunc('day', lce.lce_datahora) >= date_trunc('day', datalancto)
       and lce.lce_codigo > codigolancto
     order by lce.lce_datahora
             ,lce.lce_codigo
     limit 1;

    if (codigoproximaentradaaux is null or dataproximaentradaaux is null) then
      codigoproximaentradaaux := codigolancto;
      dataproximaentradaaux := datalancto;
    end if;

    --busca a quantidade total e o ultimo valor unitario ate o lancamento imediatamente anterior ao proximo lancamento de entrada
    select sum(case when upper(lce.lce_tipo) = tipo_lancto_entrada then coalesce(cmc.cmc_quantidade, 0) else coalesce(cmc.cmc_quantidade, 0) * (-1) end)
      into aux.quantidade
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and date_trunc('day', lce.lce_datahora) <= date_trunc('day', dataproximaentradaaux)
       and lce.lce_codigo < codigoproximaentradaaux;

    select coalesce(cmc.cmc_valorunitario, 0)
      into aux.valor
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and upper(lce.lce_tipo) = tipo_lancto_entrada
       and date_trunc('day', lce.lce_datahora) <= date_trunc('day', datalancto)
       and lce.lce_codigo < codigoproximaentradaaux
     order by lce.lce_datahora desc
             ,lce.lce_codigo desc
     limit 1;

    --verifica se os valores foram encontrados, e, caso não tenham sido: ainda não foi feito nenhum lançamento de entrada para o material
    --deve-se então utilizar os valores de última compra do material
    if (aux.valor is null or aux.quantidade is null) then
      select mat_valorultcompra
            ,mat_qtdeultcompra
        into aux.valor
            ,aux.quantidade
        from material
       where fil_codigo = filial
         and mat_codigo = codigomaterial;
    end if;

    aux.quantidade = coalesce(aux.quantidade, 0);
    aux.valor = coalesce(aux.valor, 0);
    novounitarioaux := round(aux.valor, casas_arredondamento, arredondamento);

    --busca os cmcs dos lancamentos subsequentes
      open auxcursor
       for
    select *
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       --and upper(lce.lce_tipo) = tipo_lancto_entrada
       and date_trunc('day', lce.lce_datahora) >= date_trunc('day', datalancto)
       and lce.lce_codigo > codigolancto
     order by lce.lce_datahora
             ,lce.lce_codigo;


    

  elsif (upper(tipolancto) = tipo_lancto_saida) then --se o lancamento for de saida
    --busca o valor atual do cmc
    select cmc.cmc_valorunitario
          ,cmc.cmc_quantidade
      into aux
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and upper(lce.lce_tipo) = tipo_lancto_entrada
     order by lce.lce_datahora desc
             ,lce.lce_codigo desc
     limit 1;

    --verifica se os valores foram encontrados, e, caso não tenham sido: ainda não foi feito nenhum lançamento de entrada para o material
    --deve-se então utilizar os valores de última compra do material
    if (aux.valor is null or aux.quantidade is null) then
      select mat_valorultcompra
            ,mat_qtdeultcompra
        into aux.valor
            ,aux.quantidade
        from material
       where fil_codigo = filial
         and mat_codigo = codigomaterial;
    end if;

    valorunitaux := round(aux.valor, casas_arredondamento, arredondamento);
    qtdeaux := round(quantidade, casas_arredondamento, arredondamento);
    valortotalaux := round(valorunitaux * qtdeaux, casas_arredondamento, arredondamento);

     --insere na tabela de cmc
    insert into lanctoestoquecmc
               (fil_codigo
               ,cmc_codigo
               ,lce_codigo
               ,cmc_valorunitario
               ,cmc_quantidade
               ,cmc_valor
               ,cmc_valorunitarioentrada)
        values (filial
               ,nextval('lanctoestoquecmc_' || filial || '_sequence')
               ,codigolancto
               ,valorunitaux
               ,qtdeaux
               ,valortotalaux
               ,null);

  elsif (upper(tipolancto) = tipo_lancto_entrada) then --se o lancamento for de entrada
    --busca o saldo total ate a data
    select sum(coalesce(case when upper(lce.lce_tipo) = tipo_lancto_entrada then cmc.cmc_quantidade else cmc.cmc_quantidade * (-1) end, 0))
      into aux.quantidade
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and date_trunc('day', lce.lce_datahora) <= date_trunc('day', datalancto);

    --busca o ultimo valor unitario ate a data
    select coalesce(cmc.cmc_valorunitario, 0)
      into aux.valor
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and upper(lce.lce_tipo) = tipo_lancto_entrada
       and date_trunc('day', lce.lce_datahora) <= date_trunc('day', datalancto)
     order by lce.lce_datahora desc
             ,lce.lce_codigo desc
     limit 1;

    --verifica se os valores foram encontrados, e, caso não tenham sido: ainda não foi feito nenhum lançamento de entrada para o material
    --deve-se então utilizar os valores de última compra do material
    if (aux.valor is null or aux.quantidade is null) then
      select mat_valorultcompra
            ,mat_qtdeultcompra
        into aux.valor
            ,aux.quantidade
        from material
       where fil_codigo = filial
         and mat_codigo = codigomaterial;
    end if;

    aux.quantidade = coalesce(aux.quantidade, 0);
    aux.valor = coalesce(aux.valor, 0);

    --calcula os novos valores
    aux.quantidade := round(aux.quantidade, casas_arredondamento, arredondamento);
    aux.valor := round(aux.valor * aux.quantidade, casas_arredondamento, arredondamento) + round(valorunitario * quantidade, casas_arredondamento, arredondamento);
    aux.quantidade := round(aux.quantidade, casas_arredondamento, arredondamento) + round(quantidade, casas_arredondamento, arredondamento);
    novounitarioaux := case when aux.quantidade = 0 or aux.valor = 0 then 0 else aux.valor / aux.quantidade end;
    novounitarioaux := round(novounitarioaux, casas_arredondamento, arredondamento);

    --insere na tabela de cmc
    insert into lanctoestoquecmc
               (fil_codigo
               ,cmc_codigo
               ,lce_codigo
               ,cmc_valorunitario
               ,cmc_quantidade
               ,cmc_valor
               ,cmc_valorunitarioentrada)
         values (filial
                ,nextval('lanctoestoquecmc_' || filial || '_sequence')
                ,codigolancto
                ,novounitarioaux
                ,quantidade
                ,round(novounitarioaux * quantidade, casas_arredondamento, arredondamento)
                ,valorunitario);

    --busca os cmcs dos lancamentos subsequentes
    open auxcursor
       for
    select *
      from lanctoestoque lce
      join lanctoestoquecmc cmc on lce.fil_codigo = cmc.fil_codigo and lce.lce_codigo = cmc.lce_codigo
     where lce.fil_codigo = filial
       and lce.mat_codigo = codigomaterial
       and lce.lce_datahoracancelamento is null
       and upper(lce.lce_tipo) = tipo_lancto_entrada
       and date_trunc('day', lce.lce_datahora) > date_trunc('day', datalancto)
     order by lce.lce_datahora
               ,lce.lce_codigo;

  end if;


  --caso o lancamento tenha sido cancelado ou seja de entrada, deve-se fazer o reprocessamento dos lancamentos subsequentes
  if (cancelado or upper(tipolancto) = tipo_lancto_entrada) then
    loop
      fetch auxcursor into auxrecord;
      if (auxrecord is null) then
        close auxcursor;
        exit;
      end if;

      if (upper(auxrecord.lce_tipo) = tipo_lancto_saida) then
        if (primeirocalculoaux) then
          aux.quantidade := round(aux.quantidade, casas_arredondamento, arredondamento) - round(auxrecord.cmc_quantidade, casas_arredondamento, arredondamento);
        end if;

        continue;
      end if;

      --calcula os novos valores
      aux.valor := round(aux.valor * aux.quantidade, casas_arredondamento, arredondamento) + round(auxrecord.cmc_valorunitarioentrada * auxrecord.cmc_quantidade, casas_arredondamento, arredondamento);        
      aux.quantidade := round(aux.quantidade, casas_arredondamento, arredondamento) + round(auxrecord.cmc_quantidade, casas_arredondamento, arredondamento);

      novounitarioaux := case when aux.valor = 0 or aux.quantidade = 0 then 0 else aux.valor / aux.quantidade end;
      novounitarioaux := round(novounitarioaux, casas_arredondamento, arredondamento);
      aux.valor = novounitarioaux;

      --atualiza os cmcs
      update lanctoestoquecmc
         set cmc_valorunitario = novounitarioaux
            ,cmc_valor = round(novounitarioaux * auxrecord.cmc_quantidade, casas_arredondamento, arredondamento)
       where fil_codigo = filial
         and cmc_codigo = auxrecord.cmc_codigo;

      primeirocalculoaux := true;
    end loop;

  end if;

end;
$function$
; --[lode]

CREATE OR REPLACE FUNCTION public.calculasaldoestoque(integer, integer, integer, integer, timestamp without time zone, timestamp without time zone, character varying, numeric, numeric, character varying, character varying, boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
declare
  filial             alias for $1;
  codigolancto       alias for $2;
  codigomaterial     alias FOR $3;
  codigocelula       alias FOR $4;
  datalancto         alias FOR $5;
  dataconciliacao    alias for $6;
  tipolancto         alias FOR $7;
  quantidade         alias FOR $8;
  valorunitario      alias FOR $9;
  tipooperacaolancto alias FOR $10;
  situacaolancto     alias FOR $11;
  cancelado          alias FOR $12;

  TIPO_OPERACAO_TRANSFERENCIA CHARACTER VARYING := 'T';

  dataaux DATE;
  vsaldoestoque tiposaldoestoque;
  operacao NUMERIC(15,4);
  gerarfinanceiro boolean;
  arredondamento integer;

begin
  if (filial is null) then
    raise exception 'A filial nao foi informada.';
  end if;

  if (codigolancto is null) then
    raise exception 'O codigo do lancamento nao foi informado.';
  end if;

  if (codigomaterial is null) then
    raise exception 'O codigo do material nao foi informado.';
  end if;

  if (codigocelula is null) then
    raise exception 'O codigo da celula nao foi informado.';
  end if;

  if (tipolancto is null) then
    raise exception 'O tipo do lancamento nao foi informado.';
  end if;  

  IF (upper(situacaolancto) = 'A' AND datalancto IS NULL) THEN
    RAISE EXCEPTION 'A data do lancamento nao foi informada.';
  END IF;

  IF (upper(situacaolancto) = 'O' AND dataconciliacao IS NULL) THEN
    RAISE EXCEPTION 'A data de conciliacao do lancamento nao foi informada.';
  END IF;

  --caso seja cancelamento de lancamento de entrada, deve-se verificar se o estoque nao ficara negativo
  if (tipolancto = 'E' AND cancelado) then
    if (verificapodecancelarlanctoestoque(filial, codigomaterial, codigocelula, datalancto, quantidade) = FALSE) then
      RAISE EXCEPTION 'Nao e possivel fazer o cancelamento da entrada, pois, o estoque ficaria negativo.';
    end if;
  end if;

  --data a ser usada
  dataaux := COALESCE(dataconciliacao, datalancto);

  --operacao de estoque
  operacao := CASE WHEN upper(tipolancto) = 'E' THEN quantidade ELSE quantidade * (-1) END;
  operacao := CASE WHEN cancelado then operacao * (-1) else operacao end;

  --busca o saldo de estoque da data
  vsaldoestoque := saldoestoquematerial(filial, codigomaterial, dataaux);

  --se nao ha saldo anterior a data e a operacao e de subtracao (e nao esta cancelada), deve-se lancar uma excecao
  if (tipolancto = 'S' AND not cancelado AND (vsaldoestoque is null OR vsaldoestoque.saldo < quantidade)) THEN
    raise exception 'Nao ha saldo em estoque suficiente para realizar o lancamento de saida.';
  end if;

  --caso haja saldo na data
  if (vsaldoestoque is not null and vsaldoestoque.data = dataaux) then
    --atualiza o saldo da data e das datas subsequentes
    UPDATE saldoestoque
       SET sde_saldo  = sde_saldo + operacao
     WHERE fil_codigo = filial
       AND mat_codigo = codigomaterial
       and sde_data   >= dataaux;

  else
    --insere o saldo na data
    INSERT INTO saldoestoque
               (fil_codigo
               ,mat_codigo
               ,sde_data
               ,sde_saldo
               ,sde_totalpeps
               ,sde_totalueps
               ,sde_totalcmc)
    values (filial
           ,codigomaterial
           ,dataaux
           ,COALESCE(vsaldoestoque.saldo, 0) + operacao
           ,0
           ,0
           ,0);

    --atualiza os saldos subsequentes
    UPDATE saldoestoque
       SET sde_saldo  = sde_saldo + operacao
     WHERE fil_codigo = filial
       AND mat_codigo = codigomaterial
       and sde_data   > dataaux;

  end if;

  --caso o lancamento esteja conciliado, deve-se movimentar o estoque das celulas
  if (upper(situacaolancto) = 'O') then
    execute calculasaldoestoquecelula(filial, codigolancto, codigomaterial, codigocelula, dataconciliacao, tipolancto, quantidade, cancelado);
  end if;


  --verifica se o parametro para gerar financeiro esta ativado
  select CASE WHEN COALESCE(par_perguntarvalorestoque, 'N') = 'N' THEN false else true end
    into gerarfinanceiro
    from parametros;

  if (gerarfinanceiro) then
    --busca o arredondamento configurado nos parametros gerais
    select par_tipoarredondamentopreco
      into arredondamento
      from parametros;

    if (tipooperacaolancto <> TIPO_OPERACAO_TRANSFERENCIA) then
      --invoca o calculo do CMC
      execute calculacmc(filial, codigolancto, codigomaterial, datalancto, quantidade, valorunitario, tipolancto, arredondamento, cancelado);

      --invoca o calculo do PEPS
      execute calculapeps(filial, codigolancto, codigomaterial, datalancto, quantidade, valorunitario, tipolancto, arredondamento, cancelado);

      --invoca o calculo do UEPS
      execute calculaueps(filial, codigolancto, codigomaterial, datalancto, quantidade, valorunitario, tipolancto, arredondamento, cancelado);
    end if;

    --invoca o calculo do saldo de estoque geral (da data do lancamento em diante)
    execute atualizavalorsaldo(filial, codigomaterial, datalancto, arredondamento);

    --invoca o calculo do saldo de estoque da celula (da data do lancamento em diante)
    --execute atualizavalorsaldocelula(filial, codigomaterial, datalancto, arredondamento);
    execute atualizavalorsaldoestoquecelula(filial, codigomaterial, datalancto, arredondamento);
  end if;
      
end;
$function$
; --[lode]

insert into atualizacoes(atu_nome) values ('v24000001_080124.sql'); --[lode]

alter table senhagerada alter gui_id drop not null;
 
alter table totensenha add tot_som varchar(2000);
 
insert into atualizacoes(atu_nome) values ('v24000002_090124.sql'); --[lode]