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, C5_NUM, tem como
inicializador padrão:
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:
| Modo | Onde fica a sequência | O que a TOTVS diz dele |
|---|---|---|
| Tabelas SXE e SXF | Arquivos de controle gerenciados pelo sistema de arquivos | Modo antigo, bem mais lento, e sujeito a corromper numa queda de energia |
| License Server | Na memória do License Server, enquanto ele está no ar | O 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 comNumberval=0na seção[LicenseServer]doappserver.inido 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:
- Corrigir o próximo número sem tratar o registro errado resolve só até a
próxima reconstrução. Enquanto o pedido
Aexistir 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 oCRIASXEmais abaixo. - Para achar o culpado, procure código fora do padrão:
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_NUMO 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:
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 DESCSe 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:
- Localize a tabela com problema.
- Em Números em uso, dê um duplo clique no número e troque pelo próximo
correto, por exemplo
000004. - Em Próximos números, informe o número que vem depois dele,
000005. Os dois precisam ficar coerentes entre si. - 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.
| Item | Valor |
|---|---|
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 |
| Retorno | O 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:
#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 cRetTrê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. AZA1da 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:
Local cCod := GetSXENum("ZA1", "ZA1_COD")
// ... monta e grava o registro com cCod ...
If lGravou
ConfirmSX8()
Else
RollBackSX8()
EndIfTodo 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
TDNtdn.totvs.com
TDN — SXE/SXF, controle de numeraçãoTDNtdn.totvs.com
TDN — Numeração automática pelo License ServerTDNtdn.totvs.com
TDN — PE CRIASXE, Controle de NumeraçãoTDNtdn.totvs.com
TDN — CriaSXE, Controle de NumeraçãoTDNtdn.totvs.com
TDN — Soma1, criação de sequencial do campo tipo caracterCentral de Atendimentocentraldeatendimento.totvs.com
Central de Atendimento TOTVS — Rotina APCFG110Central de Atendimentocentraldeatendimento.totvs.com
Central de Atendimento TOTVS — Acerto de Numeração Automática
Continue por aqui
- Vídeo
ProtheusTutorial
Menus do Protheus: como incluir uma rotina customizada no módulo
Menus do Protheus: como pôr uma User Function no menu de um módulo pelo Configurador, gerar sem estragar o padrão e conferir por SQL onde ficou.