<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Relatório de Saldos e Movimentação]]></title><description><![CDATA[<h2>Introdução</h2>
<p dir="auto">O objetivo desse manual é documentar tudo o que envolve o processo do cálculo mas de uma forma mais técnica, para facilitar os testes do suporte ou futuras correções de bugs pelo desenvolvimento.</p>
<p dir="auto">A forma como é calculado os Saldos e Movimentações dos produtos foi alterada. Anteriormente o relatório calculava toda a movimentação no momento da impressão, o que gerava lentidão quando o cliente possuía uma base de dados muito grande.</p>
<p dir="auto">A partir de agora os <strong>saldos serão calculados diariamente</strong>, evitando que o relatório tenha uma gama de dados grande para processar.</p>
<h2>Onde ficam armazenados os saldos?</h2>
<p dir="auto">Os saldos e movimentações serão armazenados na tabela <strong><code>SaldosEstoque</code></strong> que contém num único registro todas as movimentações do produto no dia, como: saldo anterior, entradas, saídas, ajustes e vendas.</p>
<p dir="auto"><strong>Por exemplo:</strong></p>
<pre><code class="language-sql">SELECT * FROM SaldosEstoque
</code></pre>
<p dir="auto">Resultado:</p>
<pre><code>idSaldosEstoque    salEmpresa    salProduto    salDataMovimento    salSaldoAnterior    
     38705             1            1059         27/12/2016           0,00    
salEntradas    salSaidas    salAjusteMais    salAjusteMenos    salVendas
   300,00           0,00         0,00             0,00           0,00    
</code></pre>
<p dir="auto">A rotina que calcula os saldos e armazena na tabela <code>SaldosEstoque</code> é a sp <strong><code>dbo.spc_mov_processa_saldos_estoque.sql</code></strong>.</p>
<h2>Quem executa a rotina de cálculo?</h2>
<p dir="auto">Quem executa a sp é o <strong>AutomatizeGO</strong>, através do plugin <strong><code>SaldoEstoque.dll</code></strong>.</p>
<p dir="auto">O <strong>processamento</strong> é feito a <strong>cada 1 hora</strong> e filtrará <strong>somente movimentos</strong> que foram alterados a <strong>mais</strong> de meia hora, isso para evitar rodar a rotina com o cliente ainda digitando um movimento.</p>
<p dir="auto">O resultado da execução do AutomatizeGO pode ser verificada no <strong>log</strong> do mesmo em: <code>C:\Arquivos de Programas\Automatize\log\AutomatizeGO\</code>.</p>
<p dir="auto"><strong>Exemplo:</strong></p>
<p dir="auto">Caso o processamento ocorra com <strong>sucesso</strong>:</p>
<pre><code>28/11 14:39:13,807 [30] INFO AutomatizeGo.ServiceGo - Info plugin AutomatizeGo.Plugins.SaldoEstoque, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null:
EXEC SALDO ESTOQUE
</code></pre>
<p dir="auto">Abaixo um exemplo caso ocorra um <strong>problema</strong>:</p>
<pre><code>28/11 14:04:26,034 [6] INFO AutomatizeGo.ServiceGo - Info plugin AutomatizeGo.Plugins.SaldoEstoque, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null:
NÃO ENCONTRADO TABELA E SP DE PROCESSAMENTO DO SALDO
</code></pre>
<p dir="auto">Note que o erro foi <code>"NÃO ENCONTRADO TABELA E..."</code>. Para esse erro ocorrer, uma das seguintes situações deve ter ocorrido:</p>
<ul>
<li>A tabela <code>SaldosEstoque</code> não existir no banco;</li>
<li>A sp <code>dbo.spc_mov_processa_saldos_estoque.sql</code> não exista;</li>
<li>O campo <code>MovimentoProdutoItem.mpiSaldoProcessado</code> não exista (<a href="/topic/347">abaixo</a> esse campo é explicado);</li>
<li>O campo <code>MovimentoProdutoItemComposicao.mpcSaldoProcessado</code> não exista (<a href="/topic/347">abaixo</a> esse campo é explicado).</li>
</ul>
<p dir="auto">Na primeira execução, caso a tabela <code>SaldosEstoque</code> esteja vazia, a sp irá recalcular toda a movimentação de todos os produtos e armazenar nela. Sendo assim, para fins de teste, caso haja a necessidade de se recalcular tudo novamente, basta apagar a tabela da seguinte forma:</p>
<pre><code class="language-sql">truncate table SaldosEstoque
</code></pre>
<p dir="auto">O procedimento pode demorar um pouco (em alguns testes com bases grandes, menos de 3 minutos) dependendo da quantidade de movimentos do cliente, então é necessário verificar o log do AutomatizeGO se a mensagem <code>"EXEC SALDO ESTOQUE"</code> foi apresentada ou algum outro erro.</p>
<h2>Em que momento serão calculados os saldos dos produtos?</h2>
<p dir="auto">O saldo é calculado nas seguintes situações:</p>
<ul>
<li>logo na primeira execução do plugin do AutomatizeGO ao ver que a tabela <code>SaldosEstoque</code> está vazia</li>
<li>Ao alterar a situação do movimento</li>
<li>Ao excluir um movimento</li>
<li>Ao incluir um produto no movimento</li>
<li>Ao alterar a quantidade de um produto dentro de um movimento</li>
<li>Ao excluir um produto do movimento</li>
</ul>
<p dir="auto">Para permitir esses monitoramentos foram criados os seguintes <strong>gatilhos</strong> (triggers):</p>
<ul>
<li><strong>dbo.tu_MovimentoProduto.sql</strong>
<ul>
<li>é disparado quando houver alteração na situação do movimento.
<ul>
<li>um registro será criado na tabela temporária <code>TmpSaldosEstoque</code> para que o AutomatizeGO processe essa alteração.</li>
</ul>
</li>
</ul>
</li>
<li><strong>dbo.td_MovimentoProduto.sql</strong>
<ul>
<li>é disparado quando houver exclusão do movimento
<ul>
<li>da mesma foram um registro será criado na tabela temporária <code>TmpSaldosEstoque</code> para que o AutomatizeGO processe essa exclusão.</li>
</ul>
</li>
</ul>
</li>
<li><strong>dbo.tu_MovimentoProdutoItem.sq</strong>
<ul>
<li>é disparado ao alterar a quantidade de um produto dentro do movimento.
<ul>
<li>os campos <code>MovimentoProdutoItem.mpiSaldoProcessado</code> e <code>MovimentoProdutoItemComposicao.mpcSaldoProcessado</code> serão setados para 0 (zero), para que o AutomatizeGO processe a alteração</li>
</ul>
</li>
</ul>
</li>
<li><strong>dbo.td_MovimentoProdutoItem.sql</strong>
<ul>
<li>é disparado excluir um produto do movimento.
<ul>
<li>um registro será criado na tabela temporária <code>TmpSaldosEstoque</code> para cada produto e cada composição, para que o AutomatizeGO processe essa alteração.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>Cliente possui centralização, como funciona?</h2>
<p dir="auto">A rotina de cálculo irá rodar nas filiais e, na central caso essa esteja indicada no campo <code>Node.NodeDest</code> (<a href="/topic/347">abaixo</a>).</p>
<p dir="auto">Após rodar a sp na filial os registros da tabela <code>SaldosEstoque</code> são enviados para a central para permitir visualizar nela o relatório de <strong>Saldos e Movimentação</strong> da filial.</p>
<p dir="auto">Nunca os dados serão enviados da central para as filiais ou de uma filial para outra.</p>
<p dir="auto">Não é possível visualizar o relatório de uma filial estando na outra. Por exemplo, a filial 1 não conseguirá ver da 2 e vice-versa. Na central será possível ver das duas filiais.</p>
<h3>Bom, se a rotina sempre vai rodar nas filiais, então em que situação ela rodará também na central?</h3>
<p dir="auto">Para isso teremos que verificar a tabela <code>Node</code> na central.</p>
<p dir="auto"><strong>Exemplo de central que irá rodar</strong>:</p>
<pre><code class="language-sql">select * from node
</code></pre>
<p dir="auto"><strong>Resultado:</strong></p>
<pre><code>KEY = corp       nodeDest = 4 
</code></pre>
<p dir="auto"><strong>Exemplo de central que <em>não</em> irá rodar:</strong></p>
<pre><code class="language-sql">select * from node
</code></pre>
<p dir="auto"><strong>Resultado:</strong></p>
<pre><code>KEY = corp       nodeDest = NULL
</code></pre>
<h2>Posso testar o cálculo sem que seja pelo AutomatizeGO?</h2>
<p dir="auto">Sim, como o AutomatizeGO irá rodar o cálculo dos saldos a cada 1 hora, talvez seja conveniente utilizar o script abaixo.</p>
<p dir="auto">Não esqueça de trocar a filial no início do script, caso necessário:</p>
<pre><code class="language-sql">SET NOCOUNT ON
  
DECLARE @idEmpresa  int
    
set @idempresa = 1  -- &lt;&lt; TROQUE A FILIAL AQUI &gt;&gt;

    
-- se não tem registro na tabela então ja recalcula tudo pela primeira vez
if NOT exists(select top 1 1 from SaldosEstoque)
BEGIN
    print 'Primeiro recalculo'
    exec spc_mov_processa_saldos_estoque @idempresa=@idEmpresa, @dataInicial='01/01/2000'

    UPDATE MovimentoProdutoItem SET
        mpiSaldoProcessado = 1
    
    UPDATE MovimentoProdutoItemComposicao SET
        mpcSaldoProcessado = 1  
    RETURN
END

-- exclui os temporarios processados
delete from TmpSaldosEstoque where saeProcessado = 1


-- PROCURA pelos registros não processados do movimento para executar a spc_mov_processa_saldos_estoque
DECLARE @curEmpresa int, @curProduto int, @curDt smalldatetime
        , @curIdMpi int, @curIdMpc int, @curProcessado bit, @curidTmpSaldos int
        , @msgerro varchar(2000)

DECLARE @TBPROC TABLE  (
    idEmpresa int,
    prod int,
    dt datetime,
    idMpi int,
    idMpc int,
    sysInc datetime,
    processado bit,
    idTmpSaldos int
)
    
INSERT INTO @TBPROC (idEmpresa, prod, dt, idMpi, idMpc, sysInc, processado, idTmpSaldos)
    SELECT DISTINCT 
            idEmpresa = mvpEmpresa
        , prod = MPI.mpiProduto
        , Dt = CAST(FLOOR(CAST(MVP.mvpData AS FLOAT)) AS DATETIME) --retira a hora da data
        , IDMPI = idMovimentoProdutoItem
        , null 
        , isnull(mpi.sysRegistro, mpi.sysInc)
        , 0
        , null
    FROM MovimentoProdutoItem MPI
        INNER JOIN MovimentoProduto MVP ON MVP.idMovimentoProduto   = MPI.mpiMovimentoProduto 
    where MVP.mvpEmpresa = @idEmpresa
        and isnull(MPI.mpiSaldoProcessado,0) = 0
        and (IsNull(MVP.mvpSituacao, 0) in (0,9))

    UNION ALL
            
    -- composição
    SELECT DISTINCT 
            idEmpresa = mvpEmpresa
        , MPC.mpcProduto
        , Dt = CAST(FLOOR(CAST(MVP.mvpData AS FLOAT)) AS DATETIME) 
        , null
        , idMovimentoProdutoItemComposicao
        , isnull(mpi.sysRegistro, mpi.sysInc)
        , 0
        , null
    FROM MovimentoProdutoItemComposicao MPC
        INNER JOIN MovimentoProdutoItem MPI ON MPI.idMovimentoProdutoItem   = MPC.mpcMovimentoProdutoItem
        INNER JOIN MovimentoProduto     MVP ON MVP.idMovimentoProduto       = MPI.mpiMovimentoProduto 

    where MVP.mvpEmpresa = @idEmpresa
        and isnull(MPC.mpcSaldoProcessado,0) = 0
        and (IsNull(MVP.mvpSituacao, 0) in (0,9))
         

    UNION ALL 

    SELECT DISTINCT
          saeEmpresa
        , saeProduto
        , CAST(FLOOR(CAST(saeData AS FLOAT)) AS DATETIME)
        , null
        , null
        , sysInc
        , 0
        , idTmpSaldosEstoque
    FROM TmpSaldosEstoque
    WHERE saeProcessado = 0
      and saeEmpresa = @idEmpresa


    ORDER BY idEmpresa, prod, dt


select * from @TBPROC
    

 -- DEFINE UM CURSOR DINAMICO PORQUE VOU ALTERAR OS DADOS DA COLUNA
 -- PROCESSADO E QUERO QUE REFLITA NOS PROCESSAMENTOS SEGUINTES
DECLARE curMov CURSOR LOCAL DYNAMIC 
    FOR SELECT 
            idEmpresa ,
            prod ,
            dt ,
            processado 
        FROM @TBPROC
        --WHERE (sysInc is null or sysInc &lt; dateadd(minute, -30, current_timestamp)) -- pega somente o que foi modificado a mais de 30min

--
-- ABRE O CURSOR E PERCORRE OS MOVIMENTOS
--    
OPEN curMov
FETCH NEXT FROM curMov INTO @curEmpresa, @curProduto, @curDt, @curProcessado
            
WHILE @@FETCH_STATUS = 0
BEGIN
    --print 'Processa?: ' + cast( @curProduto as varchar) + ' | ' + convert(varchar(12), @curDt, 103) + ' &gt; ' + CAST(@curProcessado as varchar)
    
    if @curProcessado = 0
    begin 
        --print 'Calculando...'
        begin try
            exec dbo.spc_mov_processa_saldos_estoque 
                  @idEmpresa    = @curEmpresa
                , @DataInicial  = @curDt
                , @DataFinal    = null
                , @idProduto    = @curProduto
        end try
        begin catch
            SET @MSGErro = 'Erro processando salods: ' +
                            ERROR_MESSAGE()+' /L:' + cast(error_line() as varchar)
            RAISERROR(@MSGErro, 16, 1)
            GOTO SAIR
        end catch

        -- a spc_mov_processa_saldos_estoque processa todos os movimentos
        -- do produto a partir de uma data, então se existe mais algum 
        -- registro do produto com uma data superior a @curDt, vou marcar como processado

        UPDATE MovimentoProdutoItem SET
            mpiSaldoProcessado = 1
        WHERE IDMovimentoProdutoItem IN (
            SELECT idMpi
            FROM @TBProc
            WHERE idEmpresa = @curEmpresa
              and prod = @curProduto
              and dt &gt;= @curDt
        )

        --composição
        UPDATE MovimentoProdutoItemComposicao SET
            mpcSaldoProcessado = 1
        WHERE idMovimentoProdutoItemComposicao IN (
            SELECT idMpc
            FROM @TBProc
            WHERE idEmpresa = @curEmpresa
              and prod = @curProduto
              and dt &gt;= @curDt
        )     

        -- temporaria com os excluidos
        UPDATE TmpSaldosEstoque SET
            saeProcessado = 1
        WHERE idTmpSaldosEstoque IN (
            SELECT idTmpSaldos
            FROM @TBProc
            WHERE idEmpresa = @curEmpresa
              and prod = @curProduto
              and dt &gt;= @curDt
        )     


        UPDATE @TBPROC SET
            processado = 1
        WHERE idEmpresa = @curEmpresa
            and prod = @curProduto
            and dt &gt;= @curDt
    end

    FETCH NEXT FROM curMov INTO @curEmpresa, @curProduto, @curDt, @curProcessado
END

SAIR:
CLOSE curMov
DEALLOCATE curMov
</code></pre>
<h2>Considerações finais</h2>
<p dir="auto"><strong>Qual versão do AutomatizeGO que possui o plugin <code>SaldoEstoque.dll</code>?</strong></p>
<p dir="auto">A partir da 5.0.6.</p>
<hr />
<p dir="auto"><em>Migrado da wiki (<code>saldos_e_movimentacoes</code>). Última edição em 01/12/2017; autores: cassol.</em></p>
]]></description><link>https://forum.automatize.solutions/topic/347/relatório-de-saldos-e-movimentação</link><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 16:17:53 GMT</lastBuildDate><atom:link href="https://forum.automatize.solutions/topic/347.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 15 Oct 2026 14:22:03 GMT</pubDate><ttl>60</ttl></channel></rss>