sexta-feira, 31 de janeiro de 2014

ADVPL-Importador de cep

Importar CEPS e transformar tudo em faixa

Um belo dia, precisei que o banco me enviasse uma faixa de cep para saber quais municípios o banco atenderia.
Para minha surpresa, o banco me enviou um arquivo parecido com isso :

01007000;SAO PAULO;SP;SIM
01007010;SAO PAULO;SP;SIM
01007020;SAO PAULO;SP;SIM
01007030;SAO PAULO;SP;SIM
01007906;SAO PAULO;SP;SIM
01008000;SAO PAULO;SP;SIM
01008010;SAO PAULO;SP;SIM
01008900;SAO PAULO;SP;SIM
01008908;SAO PAULO;SP;SIM

O arquivo é muito maior, este é só um exemplo.

O que o banco enviou, foi o cep de todas as ruas daquele município para eu popular em meu banco de dados.

Como eu precisava de uma FAIXA fiz o seguinte :
ZZ_FAIXAINI e ZZ_FAIXAFIM mas desta forma :
01007-000 até 01007-906 e deixar somente este registro no banco de dados.

Então, fiz um programinha para fazer isto. Vocês podem alterar conforme a necessidade, mas vale como exemplo.

#INCLUDE 'PROTHEUS.CH'
#INCLUDE "RWMAKE.CH"
#INCLUDE "TBICONN.CH"
#INCLUDE "protheus.ch"

USER FUNCTION XIMPORTA
Local cArq    := "CEP_AGENCIA.TXT"
Local aRange  := {}
Local cDir    := "E:\"
Local cCol1     := ""
Local cCol2     := ""

Local pCol1     := ""
Local pCol2     := ""

Local aTables   := { "SZZ" }
Local pAnte

RPCSetType( 3 )
RpcSetEnv ( "01", "01", Nil, Nil, "FAT", Nil, aTables )

Private aErro := {}

Conout("Carregando arquivo ...")
FT_FUSE(cDir+cArq)
FT_FGOTOP()

// Carrega a linha previamente
cLinha := FT_FREADLN()
aadd(aRange,{"",""})

pCol1   := Substr(cLinha,1,5)
pCol2   := Substr(cLinha,6,3)
pAnte   := Substr(cLinha,6,3)
While !FT_FEOF()
   
    cCol1   := Substr(cLinha,1,5)
    cCol2   := Substr(cLinha,6,3)
   
    While cCol1 == pCol1
       
        aRange[Len(aRange)][01]:=cCol1 + cCol2 // COLUNA 1
        aRange[Len(aRange)][02]:=cCol1 + cCol2 // COLUNA 2
       
        pAnte   := Substr(cLinha,6,3)
        FT_FSKIP()
        cLinha := FT_FREADLN()
       
        pCol1   := Substr(cLinha,1,5)
        pCol2   := Substr(cLinha,6,3)
       
    Enddo

    aRange[Len(aRange)][02]:=cCol1 + pAnte // COLUNA 2
   
    Conout("Range novo " + aRange[Len(aRange)][01] + "-" + aRange[Len(aRange)][02])

    SZZ->(dbAppend(),.T.)
    SZZ->ZZ_FILIAL := SPACE(02)
    SZZ->ZZ_BANCO  := "745"
    SZZ->ZZ_CEPINI := aRange[Len(aRange)][01]
    SZZ->ZZ_CEPFIM := aRange[Len(aRange)][02]
    SZZ->(MSUNLOCK())
    aadd(aRange,{"",""})
    pCol1   := Substr(cLinha,1,5)
    pCol2   := Substr(cLinha,6,3)
EndDo
FT_FUSE()
RETURN

OBSERVAÇÕES GERAIS

Esta rotina foi feita para rodar fora do Protheus.

Abaixo segue um exemplo de como fiz :


ADVPL-Trabalhando com JOBS

Como utilizar jobs no Protheus

Existe um tipo de JOB que é possível trabalhar no Protheus que eu particularmente utilizo muito.

Por diversas vezes tentei utilizar o JOB padrão da Totvs e fiquei muito frustrado por não funcionar como gostaria e também não tenho um total controle do que esta rodando por traz desse job.

Desta forma, tenho trabalhado com outro tipo de JOB. É o mesmo princípio de um Job de Tss, quase isso.

Trabalhando com JOBS

Para criar um JOB, primeiramente é necessário criar uma rotina no Protheus e apontar esta rotina no início do Protheus. Abaixo descrevo melhor .

MONTAGEM DA ROTINA


User Function WTJobQualquer
Local nVolta:=0
ConOut( dtoc( Date() )+" "+Time()+" Iniciando o job...." )   
While !KillApp()
    nVolta++
    ConOut( dtoc( Date() )+" "+Time()+" Executando os comandos" )

    If nVolta > 10
        volta:=0
        ConOut( dtoc( Date() )+" "+Time()+" Fim do Job" ) // Joga a mensagem no console
        Exit
    Endif

Enddo
Return

Obs : O nome do programa deve iniciar com WT

CRIAR UM SERVIÇO NO WINDOWS/LINUX PARA RODAR O SEU JOB

Crie um ambiente separado de preferência em outro RPO. Sugiro isso, porque as vezes precisamos fazer alguma modificação no ambiente de produção ou de teste e isso não vai interferir no outro. Este procedimento não é obrigatório.

Tendo este ambiente montado, é necessário adicionar as seguintes linhas :

[ONSTART]
Jobs=NOME_CHAVE // Vínculo com a linha abaixo
RefreshRate=10   // Tempo para atualização do JOB

[NOME_CHAVE]
main=U_WTJobQualquer()  // Nome do programa
environment=BSTESTE  // Nome do ambiente
instances=1

Note, que o nome BSTESTE é o nome do ambiente que ira rodar. É o mesmo princípio de uma base de produção.

Como sugestão, deixe um serviço separado do serviço de produção. Isso evita sobrecarga, não deixa o Protheus lento, não interfere na produção e como é um JOB, você pode parar este JOB a qualquer momento sem interferir no ambiente de produção.

Importante

Por se tratar de um JOB, fica por sua conta montar a abertura da empresa, a abertura da tabela, etc.

quinta-feira, 30 de janeiro de 2014

PROTHEUS-Visualizar licenças consumidas no Protheus

Como visualizar licenças consumidas no Protheus

Utilizando o SmartClient, ao entrar no Protheus, trocar o nome SIGAMDI / SIGAADV por TOTVSMNTLIC

Você vera algo parecido com isto


O programa ira exibir todas as licenças que estão sendo consumidas e quais são os usuários que estão utilizando.

quarta-feira, 29 de janeiro de 2014

Montando um MBROWSE super simples e rápido


Utilização do MBROWSE

Local cVldAlt := ".T."
Local cVldExc := ".T."

Private cString := "ZZ1" // Nome da tabela a ser utilizada

// O campo aRotina é um campo padrão onde pode se personalizar os botões do programa

PRIVATE aRotina   := {        { "Pesquisar" , "AXPESQUI" ,0,1 } ,;
                       { "Visulizar"     , "AXVISUAL"  ,0,2 } ,;
                       { "Incluir"         , "AXINCLUI",0,3 } ,;
                       { "Alterar"        , "AXALTERA",0,4 } ,;
                       { "Ecluir"         , "AXDELETA",0,8 }}

dbSelectArea("ZZ1")
dbSetOrder(1)

MBROWSE( 6, 1, 22, 75, "ZZ1")

PROTHEUS-Controle de semáforos no Protheus

Controle de semáforos no Protheus

Muitas vezes precisamos que os usuários acessem uma única rotina por fez. 
Para isto, existe uma forma para controlar o semáforo.

No início do código, você pode utilizar o seguinte trecho

While !( MayIUseCode( "PIRIPAQUE","ALEKSANDRO" ) )
      Sleep( 5000 )
End While

... continuação do código

Eu precisei de utilizar um semáforo destes, para que não ficasse sem exibir nada na tela, eu coloquei um splash screen para dar uma satisfação do que esta acontecendo.

terça-feira, 28 de janeiro de 2014

A importância de um DBA

A importância de um DBA prestando serviços de suporte no Protheus

Muitas empresas por questão de economia ou falta de conhecimento dos gestores / diretoria, acreditam que basta adquirir o ERP que todos os seus problemas serão resolvidos.

Um ERP não tem utilidade alguma sem o banco de dados. O mesmo pode se dizer que um banco de dados sozinho sem uma ferramenta que processe as informações que esta armazenada nele, também não tem muita utilidade.
O banco até funciona sem o ERP mas o ERP sem o banco não funciona.

O Protheus assim como outros ERP processam / inclui / exclui muitos dados e isso acaba por deixar o banco de dados lento, funcionando incorretamente e o mais comum que vejo, é muita volta para extrair um dado muito simples. Devido a sua arquitetura o Protheus não consegue utilizar plenamente todos os recursos do banco de dados. Por mais que o DBA tente, sempre vai ficar alguma coisa que o Protheus não consegue fazer mas, o Dba consegue chegar muito próximo disso.

A maioria das consultorias estão preocupadas em apontar as OS dos seus consultores, custe o que custar. Em uma atualização de release ou virada de versão, pouca análise no banco de dados é feita.

Exemplo, muitas vezes o Protheus após uma atualização de release ou troca de versão, o Protheus como qualquer outro ERP adiciona colunas e insere muitos dados em tabelas de grande volume e consulta de dados. Mas... sera que o banco aguenta ?.

Para ilustrar melhor o que quero dizer, vou explicar melhor como o Protheus trabalha com o banco de dados.

Top Connect

O top connect tem a finalidade de fazer a ligação entre o Protheus e o banco de dados. Ele serve como interpretador de comandos SQL com diversos bancos de dados.
Um comando executado no ORACLE deveria em tese funcionar em um SQL, sem a necessidade de alterar a syntax do comando SQL.
Na prática isso não se sustenta muito mas se for utilizar o Protheus puro sem muita customização pesada, ele funciona perfeitamente.

CHAR X VARCHAR2

Por algum motivo que eu sinceramente desconheço, o Protheus trabalha utilizando o valor total da definição de uma coluna. Exemplo :
Se eu tenho um cadastro de clientes onde existem duas colunas, NOME CHAR(50), ENDERECO CHAR(30), o campo por ser CHAR, utilizando ou não os 50 caracteres, para o banco de dados o total é de 50. Então se um nome possui somente 1 caractere para o banco é 50. Imagine então se em uma atualização do Protheus, um desses compatibilizadores criar 10 novas colunas com o campo CHAR ?.
Agora imagine se o espaço livre em disco do banco for muito pequeno ?.  Provavelmente a operação não sera concluída e o seu ERP juntamente do banco de dados irão parar.

Mas... tem alguma forma de eu transformar um CHAR em VARCHAR2 para economizar espaço no meu banco de dados ?.

A resposta para isso é NÃO !!!!.

O Protheus trabalha com o ADVPL que é um clipper melhorado.
As buscas de um dado no Protheus é feita por DBSEEK. Não tem um WHERE no código. "em alguns tem", mas o Protheus foi construido para trabalhar de uma forma que os campos são CONCATENADOS e ai é que entra o Top connect. O Top connect ira interpretar este DBSEEK em um WHERE na linguagem SQL.

Exemplificando...

SA1->(dbSetOrder(5)) equivale por exemplo á A1_FILIAL + A1_CODIGO + A1_LOJA.
Pode ser que o código da loja possua 1 aé 6 caracteres. Para o Protheus não se perder, ele deixa o A1_CODIGO sempre com 6 digitos. Mesmo deixando a coluna como VARCHAR2, o Protheus iria deixar espaços em branco até completar 6 digitos.

Mas... aonde entra o DBA ?

Na análise, no levantamento de requisitos, na verificação de índices, na desfragmentação das tabelas, na consulta mais otimizada de um select, etc.
Inserir registros loucamente em um banco de dados é rápido e simples, o problema é extrair estas informações de lá.

O DBA, deve ser um profissional capacitado para participar dessas alterações de versões para que não haja nenhum tipo de problema.

Consultar um DBA é um dever da consultoria contratada e uma forma inteligente do cliente se precaver de possíveis consultorias oportunistas.

Algumas respostas que muitos não sabem.
Sera que meu BACKUP esta funcionando ?.
Se eu atualizar o Protheus, quanto isso vai me custar de armazenamento ?
Porque aquele relatório demora tanto ?.

Um DBA decente com pouco conhecimento, tem a obrigação de dar o resultado destas perguntas. Mas não se engane. Não existe uma fórmula mágica que o DBA implanta na empresa e tudo vai sair funcionando as mil maravilhas. Quem dizer isso não é um bom profissional e esta te enganando.

Por isso pense bem antes de fazer certas economias. Consulte sempre um DBA.

ORACLE-Recompilar objetos inválidos

Gerar script para recompilar objetos inválidos no Oracle

O comando abaixo, lista os objetos inválidos com exceção de sinônimos, e gera um script para recompilar posteriormente.

Para gerar o script é necessário estar ou ter um grant de DBA para o usuário logado no Oracle

SELECT 'ALTER ' || OBJECT_TYPE || ' ' || OWNER || '.' || OBJECT_NAME ||
       ' COMPILE; '
  FROM DBA_OBJECTS
WHERE STATUS = 'INVALID'
   AND OBJECT_TYPE IN
       ('PROCEDURE', 'VIEW', 'TRIGGER', 'FUNCTION', 'PACKAGE')
UNION ALL
SELECT 'ALTER ' || 'PACKAGE ' || ' ' || OWNER || '.' || OBJECT_NAME ||
       ' COMPILE; '
  FROM DBA_OBJECTS
WHERE STATUS = 'INVALID'
   AND OBJECT_TYPE IN ('PACKAGE BODY')

segunda-feira, 27 de janeiro de 2014

ORACLE-Executar procedure do Oracle com o ADVPL

Como executar uma function e uma procedure do Oracle através de comandos ADVPL

Muitas vezes temos programas que são compilados no Oracle e surge a necessidade de compartilhar no Protheus.

Também por questão de performance podemos utilizar procedures ou functions rodando diretamente pelo banco.

Principais vantagens :

  • Compartilhamento de programas entre o Protheus e outras rotinas do banco
  • Melhor performance do processamento, o Protheus não acessa o banco diretamente, ele utiliza o Top Connect.
Então vamos lá.

Primeiramente precisamos de uma função compilada no Protheus retornando algum resultado. Exemplo de uma função siga o link. Como criar function e procedures.

No Protheus, chamar desta forma :

User function xRNbs(IdeProduto)
Local cQuery        :=""
Local rNbs          :=""

// Preenche a Query
cQuery := "SELECT MINHAFUNCAO(" + AllTrim(IdeProduto) + ") CODNBS FROM DUAL"

If Select("QRY") > 0
       QRY->( dbCloseArea() )
EndIf

dbUseArea( .T., "TopConn", TCGenQry(,,cQuery), "QRY", .F., .F. ) /// Abre a conexao

rNbs   := QRY->CODNBS
If Select("QRY") > 0
       QRY->( dbCloseArea() )
EndIf

// Retorna o custo do produto

Return rNbs

Esta não é a única forma de chamar o resultado de uma função no Protheus, é a que mais utilizo. Por questão de performance um excesso de chamadas na tabela DUAL, pode fazer aparecer esta tabela em seus relatórios de estatísticas do ORACLE como o AWR. Mas isso digo em um excesso, como deixar esta função dentro de um laço ou um for.

Executando uma procedure

TCSPEXEC("SF7_EXCECAO",QPED->C6_FILIAL,AllTrim(QPED->C6_PRODUTO),QPED->C5_TIPOCLI,QPED->C5_CLIENTE,QPED->C5_LOJACLI)

No exemplo acima, o Protheus chama a procedure SF7_EXCECAO que já esta compilada no banco de dados.
O primeiro parâmetro, é o nome da procedure, os demais são parâmetros da própria função compilada.

Limitações

Não é possível chamar uma function ou procedure dentro de uma Package.
Caso for necessário, crie uma function ou procedure chamando sua Package.

Verifique também os acessos e todos os grants para o usuário do Protheus dentro do ORACLE

EXEMPLO :
GRANT EXECUTE ON MICROSIGA.SF7_EXCECAO TO USUARIO_QUE_RODA_O_PROTHEUS;

ORACLE-Criando Function, Procedure, Package, Trigger de forma básica

Criando uma Function, Procedure, Package e Trigger de forma básica

Agora que dei um exemplo de Bloco Anônimo, podemos utilizar estes blocos e compilar no Oracle como programas.
Estes programas podem estar nos formatos acima. Vai depender da funcionalidade e como ele ira ser utilizado.

Syntax básica para montar uma Function

CREATE OR REPLACE FUNCTION LISTA_TABELA(Numero TABELA.SEQUENCIAL%TYPE,
                                        Coluna NUMBER) RETURN VARCHAR IS
  -- DECLARAÇÃO DAS VARIAVEIS
  -- %TYPE SERVE PARA A COLUNA HERDAR O TIPO E CARACTERÍSTICA DA COLUNA
  COL1          TABELA.SEQUENCIAL%TYPE;
  COL2          TABELA.DESCRICAO%TYPE;
  COL3          TABELA.VALOR%TYPE;
  RETORNO_VALOR VARCHAR2(50);
Begin
  -- BEGIN, INICIA O TRECHO DA VALIDAÇÃO DO EXCEPTION. EQUIVALENTE AO TRY CATCH
  BEGIN
    SELECT T.SEQUENCIAL, T.DESCRICAO, T.VALOR
      INTO COL1, COL2, COL3
      FROM TABELA T
     WHERE T.SEQUENCIAL = Numero;
  EXCEPTION
    -- SEM REGISTROS ENCONTRADOS
    WHEN NO_DATA_FOUND THEN
      RAISE_APPLICATION_ERROR(-20001, 'Valor não encontrado');
      -- MUITOS REGISTROS ENCONTRADOS
    WHEN TOO_MANY_ROWS THEN
      RAISE_APPLICATION_ERROR(-20003, 'Muitos registros foram retornados');
      -- OUTROS ERROS
    WHEN OTHERS THEN
      RAISE_APPLICATION_ERROR(-20004, 'Erro genérico');
  END;

  -- VALIDAÇÃO.
  -- RAISE_APPLICATION_ERROR, Tem a finalidade de jogar um erro padronizado no Oracle.
  IF Coluna NOT IN (1, 2, 3) THEN
    RAISE_APPLICATION_ERROR(-20001,
                            'Parâmetro 2, somente aceita números de 1,2,3');
  End IF;

  -- Lista a coluna desejada
  IF Coluna = 1 THEN
    RETORNO_VALOR := TO_CHAR(COL1);
  ELSIF Coluna = 2 THEN
    RETORNO_VALOR := COL2;
  ELSIF Coluna = 3 THEN
    RETORNO_VALOR := TO_CHAR(COL3);
  END IF;
  -- Retorna o valor
  RETURN RETORNO_VALOR;
END;

Existem diversas formas de retornar a função, vou mostrar duas delas :
Primeira forma :

SELECT LISTA_TABELA(1,3) FROM DUAL;
SELECT LISTA_TABELA(1,2) FROM DUAL;
SELECT LISTA_TABELA(1,1) FROM DUAL;

Por bloco anônimo

DECLARE
  RETORNO VARCHAR2(50);
BEGIN
  RETORNO := LISTA_TABELA(1, 2);
  DBMS_OUTPUT.put_line(RETORNO);
END;

Note que existem regras que devem ser seguidas.
No caso de uma função simples, somente uma linha e uma coluna pode ser retornada por vez. Se fizer uma função pipelined ai a coisa muda de figura. Em breve estarei postando um exemplo completo de função pipelined.
A função pode ser retornada em uma select, a procedure não.

Em breve continuarei postando o exemplo de uma procedure.... agora tenho que trabalhar um pouco.