Documentação técnica e operacional · 28 outubro 2026

ASER_TECH

Como os dados chegam do OpenEdge ao SQL Server, como as tabelas se ligam entre si, quando cada sincronismo corre e o que fazer quando algo falha.

33tabelas SQL
24tabelas de negócio
9marcadores incrementais
404colunas
30chaves primárias
3chaves estrangeiras
Base ASER_TECH · compatibilidade 150 · collation Latin1_General_CI_AS · recovery model FULL. Estrutura lida dos catálogos sys.* (só SELECTs). Os horários vêm dos scripts do repositório, não do Agendador de Tarefas do servidor.
01

Mapa do modelo

As tabelas agrupadas por área de negócio e as ligações principais. Clica numa tabela para abrir a ficha com todas as colunas.

Artigos Encomendas e Ptelin Documentos e devoluções Garantias Clientes e catálogos Financeiro e controlo teReferencia 2 FK inid_cab · reconciliação copia ligações inPteCar=1 inPteCar=0 · inid_encomenda FK · no máximo 1 veículo por garantia Art_Referencias Art_Stocks Art_Localizacao Art_PVP Art_Marcas Art_Familias Art_Seccao Catálogos ligados por código (teMarca / teFamilia / teSeccao) Cli_EncomendasCab Cli_EncomendasLin Cli_Ptelin sequência OE_LinhasGlobaisId IDs de linha partilhados, sem colisões Cli_DocumentosCab Cli_DocumentosLin Cli_DevolucoesLin Cli_Garantias Cli_garantia_veiculo Referências a faturas/créditos por código, não por ID (ver relações de domínio). Clientes Zonas Vendedores Utilizadores Cli_Modo_Entrega Cli_Modo_Pagamento Cliente = teEmpresa + inCodigo (nunca por inid) Cli_Conta_Corrente Cli_Pagamentos sem importador identificado Cli_Rappel 9 marcadores OE_*Syncponto de leitura de cada incremental Conta corrente = destino do cobro OpenEdge. Cli_Rappel: bonificação calculada pelas regras RapV*.
FK declarada no SQL Ligação calculada pelo importador (sem FK) Relação de domínio, por código (não verificada) Tabela sem importador
02

Fluxo de dados

Cada sincronismo lê o OpenEdge, valida em staging e grava no SQL Server. Os incrementais guardam o ponto onde ficaram num marcador.

OpenEdgeTabelas PUB.* (referencia, cobro, garantia…)
→
Importador importar_*.pyLê full ou delta por datas; valida chaves, tipos e comprimentos em staging
→
ASER_TECHInsere / atualiza; consoante a tabela, inativa ou elimina ausentes
→
Marcador OE_*SyncAvança só quando os dados ficam confirmados; preview e rollback não o mexem
Full significa ler tudo e comparar, não apagar e recriar a tabela. As linhas existentes mantêm os seus IDs.

Cadeia operacional: 7 etapas em série

1Encomendascabeçalhos → linhasfull incr.
2Ptelinmedcatpteserfull incr.
3Documentoscabeçalhos → linhas → reconciliaçãofull incr.
4Devoluçõesapenas EFCfull incr.
5Garantiasgarantiasempre full
6VeículosgarantiaVehicsempre full
7Conta correntecobrofull incr.
⏹ Para no primeiro erro 🔒 Lock SQL oe_cadeia_operacional sobre toda a sequência ✓ Cada etapa faz commit próprio: uma falha não desfaz as anteriores Fora da cadeia: catálogos, localizações, referências, preços, stocks e clientes
03

Horários: um dia típico

Hora local do servidor. São as definições dos scripts agendar_*.ps1, não uma leitura das tarefas instaladas.

Execução full Full repetido Janela de incrementais
GrupoFullIncrementalInstaladorNotas
Cadeia operacionalDiário 02:0008:05–22:05, a cada 5 minagendar_cadeia_operacional.ps17 etapas; garantias/veículos sempre full. Inclui veículos, devoluções e conta corrente.
ReferênciasDiário 03:0008:00–19:00, a cada horaagendar_referencias.ps112 incrementais/dia
PVPsDiário 03:0008:03–19:03, a cada horaagendar_pvps.ps112 incrementais/dia
Catálogos (8 tabelas)Diário 05:00—agendar_full_auxiliares.ps1Tarefa «OE Catalogos - full»
LocalizaçõesDiário 05:00—agendar_full_auxiliares.ps1«OE Localizacoes - full», independente dos catálogos
StocksDiário 05:3008:00–19:00, a cada 10 minagendar_stocks.ps167 incrementais previstos/dia
Clientes08:30–19:30, a cada 30 min—agendar_clientes.ps123 leituras full/dia
Garantias (tarefa independente)Seg–sáb 08:15–19:15, a cada hora—agendar_garantias.ps112 full/dia, além das garantias da cadeia
Rappel de vendasSeg–sáb 08:20–19:20, a cada hora—agendar_rappel.ps1Cálculo completo a partir do OE; 12 execuções/dia
Garantias lidas duas vezes: a cadeia não remove a tarefa independente «OE Garantias - full». Se estiver instalada, há leituras a mais e uma tentativa concorrente pode falhar por lock. Confirmar no servidor se deve continuar ativa.
Comportamento das tarefas
MultipleInstances=IgnoreNew, limite de 12 h, conta S4U. Se a execução anterior ainda estiver a correr, o disparo seguinte é ignorado, por isso «previsto» não é «garantido». Uma execução pode terminar depois do fim da janela. As tarefas das 03:00 e das 05:00 são independentes e podem sobrepor-se.
Agendadores antigos substituídos pela cadeia
GrupoFull antigoIncremental antigoScript
EncomendasDom 16:00 cab. / 16:02 linhas08:05–22:05 / 08:07–22:07, 5 minagendar_encomendas.ps1
PtelinDom 16:0708:09–22:09, 5 minagendar_ptelin.ps1
DocumentosDom 16:09 / linhas 16:11 (parametrizável)08:11–22:11 / 08:13–22:13, 5 minagendar_documentos.ps1

O instalador da cadeia espera que estas tarefas terminem e depois remove-as. Não as voltar a instalar em paralelo. Os scripts ficam no repositório só como referência.

04

Sincronismos

Uma ficha por tabela de destino: de onde vem, como é identificada cada linha, como se lê o delta e o que acontece aos registos que desaparecem da origem.

Regras que afetam os resultados

Conta correnteDelta por fedoc/fecob/fesit/feest desde a última execução confirmada, com 1 dia de sobreposição. Alterações que não mexem nessas datas só entram no full diário. Sem marcador faz full automático; delta vazio é válido; full vazio aborta.
Outros incrementaisReferências, PVPs, stocks, encomendas, Ptelin, documentos e devoluções precisam de um full inicial para criar o marcador. Campos nulos ou alterações sem mudança de data podem escapar ao delta; o full periódico reconcilia.
Eliminação de encomendasSó por mensagens de eliminação confirmada em reporte-lin (empresa, ano, série/contador, número). Linhas e cabeçalho são eliminados numa transação. Uma ausência na origem, por si só, não é tratada como eliminação.
Anulação de documentosfac-alm-c1.fillerUPD identifica os anulados. Remove linhas antes do cabeçalho. A importação EFC remove apenas as suas devoluções.
Preços e quantidadesDecimal / SQL money com validação de precisão. Documentos: preço unitário descontado = pvpne / canmov (ROUND_HALF_UP); quantidade 0/NULL aborta. Encomendas: total da linha = pvp1 × canser. REAL/FLOAT continuam aproximados onde o schema os usa.
Clientescliente ⨝ direccion por cif + dup; cliente-serie por emp + cli com sef=VEN. Não deduplica só por NIF; correspondências ausentes ou ambíguas abortam. O código postal mantém os zeros.
UtilizadoresChave teUtilizador. loativo = 0 quando fecbaja é NULL e 1 quando existe baixa. Não inverter, apesar do nome da coluna.
Garantias e veículosGarantias primeiro, depois veículos. scest/ssest/saest: S=1, vazio=0, NULL preservado; cerrada é booleano. O veículo herda o inid da garantia. dhdata_compra é datetime2(7) e aceita datas anteriores a 1753.
Validação e stagingValida chaves nulas/duplicadas, tipos e comprimentos antes de gravar. Bloqueios temporários do OE têm repetição nos loaders que a implementam, com limpeza do staging parcial.
CatálogosAs 8 tabelas são preparadas e confirmadas numa só transação. Marcas, famílias e secções usam boAtivo; vendedores, utilizadores, modos e zonas fazem apenas INSERT/UPDATE.
05

Relações

Há três níveis de confiança: as FKs impostas pelo SQL, as ligações que os importadores calculam e as relações de domínio que existem só por código.

① FKs declaradas: ativas, validadas, sem CASCADE

Cli_EncomendasLininId_Cab
↓
Cli_EncomendasCabinId

FK_Cli_EncomendasLin_Cab_ID

Cli_EncomendasLininDocumento_Numero + teContador
↓
Cli_EncomendasCabinDocumento_Numero + teContador

FK_Pdc_EncomendasLin_Cab

Cli_garantia_veiculoinid + Innumero_garantia
↓
Cli_Garantiasinid + innumero_garantia

FK_Cli_garantia_veiculo_Garantias

DELETE/UPDATE: NO_ACTION. Numa FK composta, componentes NULL não garantem que o pai exista.

② Ligações calculadas pelos importadores

③ Relações de domínio (por código, não verificadas nos dados)

DeParaCamposAtenção
Estas listas não provam que todos os registos tenham correspondência: não foram contados órfãos. Campos com nomes parecidos ou IDs com o mesmo valor não são, por si só, uma relação.
06

Tabelas e colunas

As 33 tabelas, com todas as colunas, chaves, índices e restrições. Pesquisa por tabela, coluna, tipo ou campo de origem. «Sem mapeamento» não significa que a coluna não seja usada: pode ser preenchida pela aplicação.

07

Marcadores e objetos auxiliares

Fotografia de 28/10/2026, 11:57, não estado em tempo real. ultima_inicio é o ponto de partida do próximo delta.

OE_DocumentoLinhaSync tem uma entrada para documentos e outra para devoluções: cada uma tem o seu próprio ponto de leitura.

Sequência dbo.OE_LinhasGlobaisId

Fornece IDs comuns a Cli_EncomendasLin e Cli_Ptelin, para que inid_encomenda seja inequívoco quando lido com inPteCar.

Início126 419
Valor atual129 106
Tipo · cicloint · incremento 1 · sem ciclo
2 triggers de proteção

Após INSERT/UPDATE, cada tabela verifica (com applock) que o ID não está a ser usado na outra. Os importadores usam IDENTITY_INSERT e lock transacional. Não existem views, procedures nem funções.

TR_Cli_EncomendasLin_IdGlobal
CREATE TRIGGER [dbo].[TR_Cli_EncomendasLin_IdGlobal] ON [dbo].[Cli_EncomendasLin]
AFTER INSERT, UPDATE AS
BEGIN
    SET NOCOUNT ON;
    IF NOT EXISTS (SELECT 1 FROM inserted) RETURN;
    DECLARE @r int;
    EXEC @r=sys.sp_getapplock @Resource=N'oe_ids_ptelin_encomendas:dbo',
        @LockMode='Exclusive', @LockOwner='Transaction', @LockTimeout=300000;
    IF @r<0 THROW 51001, 'Nao foi possivel validar o ID global.', 1;
    IF EXISTS (SELECT 1 FROM inserted i JOIN [dbo].[Cli_Ptelin] o WITH (UPDLOCK,HOLDLOCK) ON o.inId=i.inId)
        THROW 51002, 'ID ja utilizado na outra tabela: Ptelin/EncomendasLin.', 1;
END
TR_Cli_Ptelin_IdGlobal
CREATE TRIGGER [dbo].[TR_Cli_Ptelin_IdGlobal] ON [dbo].[Cli_Ptelin]
AFTER INSERT, UPDATE AS
BEGIN
    SET NOCOUNT ON;
    IF NOT EXISTS (SELECT 1 FROM inserted) RETURN;
    DECLARE @r int;
    EXEC @r=sys.sp_getapplock @Resource=N'oe_ids_ptelin_encomendas:dbo',
        @LockMode='Exclusive', @LockOwner='Transaction', @LockTimeout=300000;
    IF @r<0 THROW 51001, 'Nao foi possivel validar o ID global.', 1;
    IF EXISTS (SELECT 1 FROM inserted i JOIN [dbo].[Cli_EncomendasLin] o WITH (UPDLOCK,HOLDLOCK) ON o.inId=i.inId)
        THROW 51002, 'ID ja utilizado na outra tabela: Ptelin/EncomendasLin.', 1;
END
08

Operação e manutenção

Ativar os horários no servidor

  1. Atualizar o projeto a partir de asertech_sync, preservando o .env do servidor. Instalar requirements.txt se necessário. Ficheiros alterados a 28/10: executar_cadeia_operacional.py, executar_devolucoes.py, executar_conta_corrente.py, importar_conta_corrente.py, conta_corrente_estado.py, agendar_cadeia_operacional.ps1.
  2. Validar a definição sem instalar (PowerShell como administrador):
    .\agendar_cadeia_operacional.ps1 -ValidateOnly
  3. Se ainda não houver marcador das devoluções na base de destino:
    python executar_devolucoes.py --mode full
  4. Registar a cadeia (full 02:00, incremental 08:05–22:05):
    .\agendar_cadeia_operacional.ps1 -Force
    Não é só leitura: verifica ligações e colunas e prepara os IDs comuns.
  5. Confirmar o que ficou instalado:
    Get-ScheduledTask -TaskName 'OE Cadeia Operacional*' |
        Select-Object TaskName, State, Triggers
    Get-ScheduledTask -TaskName 'OE Cadeia Operacional*' |
        Get-ScheduledTaskInfo

A primeira conta corrente sem marcador faz full automaticamente; os restantes incrementais precisam da sua carga inicial.

Executar e diagnosticar

# Cadeia completa
python executar_cadeia_operacional.py --mode full
python executar_cadeia_operacional.py --mode incremental
# Etapas isoladas
python executar_devolucoes.py --mode incremental
python executar_conta_corrente.py --mode incremental
python executar_conta_corrente.py --mode incremental --preview
python importar_conta_corrente.py --check
# Auxiliares
python executar_full_auxiliares.py --tipo catalogos
python executar_full_auxiliares.py --tipo localizacoes
# Rappel de vendas
python executar_rappel.py --preview
python executar_rappel.py

Use os executar_*: chamar importar_*.py diretamente não deixa log no monitor. Alguns importadores preparam estrutura mesmo com --check/--preview.

Logs e monitor

.\iniciar_monitor.ps1
# Abrir http://127.0.0.1:8088

logs/cadeia_operacional mostra a ordem, os resultados e as interrupções. Há uma pasta por grupo (referencias, pvps, stocks, catalogos, localizacoes, clientes, encomendas, ptelin, documentos, devolucoes, garantias, garantia_veiculo, conta_corrente, rappel).

O monitor lê logs e tarefas do Windows, não as bases. Atualiza a cada 30 s (tarefas em cache 60 s). «Sem fecho» pode ser execução ativa ou interrompida; «sem logs» não é sucesso. -BindAddress 0.0.0.0 só em rede interna de confiança (não tem login).

Recuperação após falha

✓ Fazer
  • Ver primeiro o log da cadeia e depois o log da etapa que falhou.
  • Resolver o erro de chave, conversão, truncagem ou bloqueio antes de repetir.
  • Para corrigir atrasos, correr o full apropriado e verificar o resultado.
  • Manter ligações e credenciais só no .env. As tarefas S4U precisam de autenticação SQL, não Trusted Connection.
✗ Não fazer
  • Apagar marcadores ou renumerar IDs.
  • Correr às cegas preparar_ids_linhas_globais.py, preparar_*.sql ou --prepare-schema, porque alteram a estrutura.
  • Reinstalar os agendadores antigos de encomendas, Ptelin ou documentos.
  • Esperar que uma falha desfaça etapas anteriores, porque os commits já feitos ficam.

Pontos de atenção

Clientes: PK só em inCodigoO importador usa teEmpresa + inCodigo. Códigos repetidos entre empresas podem colidir na PK.
Cabeçalhos: número + contadorNão garante unicidade por ano/empresa. A reconciliação de documentos acrescenta ano, empresa e armazém.
inId em stocks/localizaçõesÉ o ID do artigo e repete-se em várias linhas. Nunca usar sozinho como chave da linha.
inPteCar / inid_encomendainPteCar: 1 = Ptelin, 0 = encomenda, NULL = sem origem. inid_encomenda aponta para a linha de origem e fica NULL se houver ambiguidade.
Conta correnteSó deteta de imediato as alterações que mexem nas quatro datas do delta. O full diário cobre as restantes.
Cli_PagamentosSem PK nem sincronismo neste repositório. O destino do cobro é Cli_Conta_Corrente.
Tarefa de garantias duplicadaA tarefa independente não é removida pela cadeia. Confirmar no servidor se deve continuar.
Mensagens antigasAlguns instaladores ainda falam em 3 catálogos ou no horário semanal. Prevalece o código atual e este documento.
09

Fontes, alcance e versão

Estrutura
sys.tables, sys.columns, sys.types, sys.identity_columns, sys.default_constraints, sys.computed_columns, sys.indexes, sys.index_columns, sys.foreign_keys, sys.foreign_key_columns, sys.check_constraints, sys.objects, sys.sql_modules, sys.sequences e configuração da base. Estado: tabelas OE_*Sync. Não foram exportados registos comerciais nem pessoais.
Comportamento e horários
Código local do commit abe86c2, consultado a 28/10/2026; horários dos ficheiros agendar_*.ps1. Um script existir não quer dizer que a tarefa esteja instalada no servidor. As relações de domínio estão assinaladas como não verificadas.
Ficheiros consultados (45)