Pragiva — Tecnologia na prática

Controle de numeração: quando o próximo número sai da sequência

Controle de numeração: quem entrega o próximo código, por que um código digitado com letra desalinha a sequência e como corrigir pela APCFG110.

Protheus Guia de processo 6 min de leitura

Assista o vídeo

Nesta página6 seções

O número que aparece sozinho quando você clica em Incluir num pedido de venda não é calculado lendo a tabela naquela hora. Ele vem de um controle de numeração que guarda, por tabela e por filial, qual é o próximo. Quando esse controle e a tabela discordam, a sequência pula, repete ou vira letra. A correção é feita na rotina APCFG110, e o erro só não volta se a causa na tabela também for tratada.

Quem entrega o próximo número

No dicionário, o campo do número do pedido de venda, , tem como inicializador padrão:

ADVPL / TLPP
GetSXENum("SC5","C5_NUM")

A função GetSXENum(cAlias, cCampo, cAliasSXE, nOrdem, cGroup, cBranch) pede ao controle o próximo número da tabela; no inicializador acima só os dois primeiros parâmetros são informados. Ela trabalha em par com duas outras: ConfirmSX8() efetiva o número quando o registro é gravado, e RollBackSX8() devolve o número quando a inclusão é abandonada. Cada inclusão aberta pega o seu número. É por isso que dois vendedores incluindo pedido ao mesmo tempo não recebem o mesmo código.

Quem guarda a sequência pode ser uma de duas coisas:

ModoOnde fica a sequênciaO que a TOTVS diz dele
Tabelas SXE e SXFArquivos de controle gerenciados pelo sistema de arquivosModo antigo, bem mais lento, e sujeito a corromper numa queda de energia
License ServerNa memória do License Server, enquanto ele está no arO padrão; obrigatório nas releases mais recentes e, com dicionário no banco, em qualquer release

A escolha fica no appserver.ini do License Server, na seção [LicenseServer]: Enablenumber=1 é o License Server, Enablenumber=0 são as tabelas SXE e SXF.

O que acontece quando o License Server reinicia

Este é o ponto que explica quase todo salto de numeração.

  • Parada normal: o License Server grava as últimas sequências num arquivo (LSNumber.val) e, ao voltar, carrega esse arquivo. A sequência continua de onde parou. A geração desse arquivo pode ser desligada com Numberval=0 na seção [LicenseServer] do appserver.ini do License Server.
  • Parada inesperada (queda de energia, processo derrubado): o arquivo não é gerado. Ao voltar, o License Server busca a última numeração de cada tabela no banco e recomeça a partir dela.
  • Troca do modo SXE/SXF para License Server: mesma coisa. Ele busca a última numeração de cada tabela no banco.

Ou seja: enquanto o License Server está no ar, a sequência vem da memória dele. Quando ele precisa reconstruí-la, a sequência vem do maior código que estiver gravado na tabela.

Como uma letra desalinha a sequência

Um cenário comum: os pedidos 000001, 000002 e 000003 entraram normalmente. No quarto, alguém apaga o número sugerido e digita A.

O campo aceita. Nada no dicionário padrão restringe o C5_NUM a dígitos: a máscara é @X e a validação, NaoVazio().And.ExistChav("SC5"). Enquanto o License Server segue no ar, nada parece errado: o próximo pedido recebe o número que estava em memória.

O problema aparece na primeira vez que a sequência precisa ser reconstruída a partir do banco. Na ordem da chave, letra vem depois de dígito, então o maior código gravado é o A, e não o 000003. A próxima numeração passa a ser calculada a partir dele. O incremento de campo caractere do Protheus, a função Soma1(), não se limita a dígitos: avança também por letras. A sequência numérica se perde, e cada novo pedido nasce depois do A.

Duas conclusões práticas:

  1. Corrigir o próximo número sem tratar o registro errado resolve só até a próxima reconstrução. Enquanto o pedido A existir na tabela, ele volta a ser “o último” sempre que o License Server precisar ler o banco. Bloquear o pedido não muda isso, porque o registro continua lá. É preciso excluir o registro, se ainda der, ou ensinar ao sistema qual é o último número válido, com o CRIASXE mais abaixo.
  2. Para achar o culpado, procure código fora do padrão:
Confira no seu ambienteSQL
SELECT C5_FILIAL, C5_NUM, C5_EMISSAO
  FROM SC5010
 WHERE C5_NUM LIKE '%[^0-9]%'
   AND D_E_L_E_T_ = ' '
 ORDER BY C5_FILIAL, C5_NUM

O LIKE '%[^0-9]%' pega qualquer código com um caractere que não seja dígito. É sintaxe do SQL Server. O 010 no nome da tabela é o sufixo da empresa e muda de ambiente para ambiente.

Para ver o que o sistema vai considerar “o último” numa reconstrução, olhe os maiores códigos de uma filial, na ordem da chave:

Confira no seu ambienteSQL
SELECT TOP 5 C5_FILIAL, C5_NUM, C5_EMISSAO
  FROM SC5010
 WHERE C5_FILIAL = '01'
   AND D_E_L_E_T_ = ' '
 ORDER BY C5_NUM DESC

Se o primeiro da lista não for o último pedido de verdade, você achou a origem do salto.

Corrigindo a sequência pela APCFG110

A rotina de acerto é a APCFG110. Ao executá-la, ela mostra a numeração atual e a próxima numeração das tabelas, e as duas podem ser editadas para corrigir a sequência.

Se ela não estiver no menu do seu Configurador, a TOTVS orienta incluí-la como item de menu: programa APCFG110, módulo Configurador. O passo a passo de criar o item está em Menus do Protheus.

Na rotina:

  1. Localize a tabela com problema.
  2. Em Números em uso, dê um duplo clique no número e troque pelo próximo correto, por exemplo 000004.
  3. Em Próximos números, informe o número que vem depois dele, 000005. Os dois precisam ficar coerentes entre si.
  4. Faça uma inclusão de teste e confira o número sugerido.

A área se chama Números em uso / Números liberados. Ver mais de um número ali para a mesma tabela não é, por si, sinal de erro. No controle de numeração, um número reservado fica “em uso” até ser confirmado, e o número devolvido por RollBackSX8() fica “liberado” para ser entregue de novo.

CRIASXE: dizer ao sistema qual é o último número válido

O ponto de entrada CRIASXE existe para trocar a regra de descoberta do próximo número. Ele roda justamente nos momentos em que a sequência é reconstruída: perda das tabelas SXE/SXF e reinício do License Server sem o arquivo de sequências.

ItemValor
PARAMIXB[1]Alias da tabela
PARAMIXB[2]Campo que recebe a numeração
PARAMIXB[3]Filial e nome da tabela no banco
PARAMIXB[4]Índice usado na busca
RetornoO número a usar. Nil aplica a regra padrão

Duas regras da documentação: devolver Nil mantém o comportamento padrão, e o ponto de entrada nunca deve devolver texto vazio.

Como o mesmo ponto de entrada atende todas as tabelas com numeração automática, o exemplo trata apenas a ZA1, o cadastro de motoristas da série, e devolve Nil para o resto:

ADVPL / TLPP
#Include "Protheus.ch"

/*/{Protheus.doc} CRIASXE
Define o próximo número quando a numeração automática é reconstruída
(perda da SXE/SXF ou reinício do License Server sem o arquivo de
sequências). Trata só a ZA1; as outras tabelas seguem a regra padrão.
@type user function
@return character, próximo código da ZA1, ou Nil para a regra padrão
/*/
User Function CRIASXE()

    Local aArea  := {}
    Local cQuery := ""
    Local cTmp   := ""
    Local cRet   := Nil
    Local nTam   := 0

    If Type("PARAMIXB") <> "A" .Or. Len(PARAMIXB) < 1
        Return Nil
    EndIf

    // Qualquer outra tabela: Nil devolve a decisão para a regra padrão.
    If PARAMIXB[1] <> "ZA1"
        Return Nil
    EndIf

    aArea := GetArea()
    nTam  := TamSX3("ZA1_COD")[1]

    // Maior código que começa por dígito. Um código digitado com letra
    // por engano deixa de ser tratado como o último.
    cQuery := "SELECT MAX(ZA1_COD) ULTIMO"
    cQuery += "  FROM " + RetSqlName("ZA1")
    cQuery += " WHERE ZA1_FILIAL = '" + xFilial("ZA1") + "'"
    cQuery += "   AND ZA1_COD < 'A'"
    cQuery += "   AND D_E_L_E_T_ = ' '"
    cQuery := ChangeQuery(cQuery)

    cTmp := GetNextAlias()
    MPSysOpenQuery(cQuery, cTmp)

    // Nunca devolver texto vazio: tabela sem registro começa do 1.
    If Empty((cTmp)->ULTIMO)
        cRet := StrZero(1, nTam)
    Else
        cRet := Soma1(AllTrim((cTmp)->ULTIMO))
    EndIf

    (cTmp)->(DbCloseArea())
    RestArea(aArea)

Return cRet

Três cuidados com ele:

  • Ele não conserta a sequência atual. Só age na próxima reconstrução. O acerto de agora continua sendo pela APCFG110.
  • A filial vem do contexto. O exemplo usa xFilial("ZA1"), que devolve a filial da sessão. A ZA1 da série é compartilhada, então a filial é vazia e a regra vale para todas.
  • O filtro ZA1_COD < 'A' é uma escolha para este cadastro. Ele assume que código válido começa por dígito. Se a sua tabela usa código com letra de propósito, a regra precisa ser outra.

Para a sintaxe de ChangeQuery, MPSysOpenQuery e RetSqlName, veja consultas SQL em ADVPL.

Na sua própria rotina: confirmar ou devolver

Quem grava registro por código, fora do cadastro padrão, precisa fechar o par que o GetSXENum abriu:

ADVPL / TLPP
Local cCod := GetSXENum("ZA1", "ZA1_COD")

// ... monta e grava o registro com cCod ...

If lGravou
    ConfirmSX8()
Else
    RollBackSX8()
EndIf

Todo número pedido precisa terminar num dos dois: ConfirmSX8() quando o registro foi gravado, RollBackSX8() quando não foi. Caminho de erro que sai da função sem nenhum dos dois deixa a reserva sem desfecho.

Se a sequência de algum cadastro seu pula sem explicação, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — SXE/SXF, controle de numeração
  2. TDNtdn.totvs.com

    TDN — Numeração automática pelo License Server
  3. TDNtdn.totvs.com

    TDN — PE CRIASXE, Controle de Numeração
  4. TDNtdn.totvs.com

    TDN — CriaSXE, Controle de Numeração
  5. TDNtdn.totvs.com

    TDN — Soma1, criação de sequencial do campo tipo caracter
  6. Central de Atendimentocentraldeatendimento.totvs.com

    Central de Atendimento TOTVS — Rotina APCFG110
  7. Central de Atendimentocentraldeatendimento.totvs.com

    Central de Atendimento TOTVS — Acerto de Numeração Automática

Continue por aqui