Clipper On Line • Ver Tópico - Meu modo de trabalho
Página 1 de 25

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:32
por JoséQuintas
Meus dois aplicativos principais atuais:

um de imobiliária, que era em Clipper Summer 87, que venho fazendo manutenção, melhorias, etc.
...
15/01/2016  09:46            12.161 vhl123baix.prg
17/11/2015  18:16             5.425 vhl123corr.prg
02/01/2016  10:04            12.234 vhl123extr.prg
14/01/2016  02:15            24.968 VHLAL01.prg
15/12/2015  11:53             6.420 vhlqdon.prg
             122 arquivo(s)      1.275.786 bytes
               0 pasta(s)   203.726.548.992 bytes disponíveis


e um "tudo em um", com todo o restante

...
04/01/2016  21:23               848 ze_resource.prg
04/01/2016  21:23            16.217 ze_SendMailClass.prg
25/11/2015  21:44            16.181 ze_SpedAssina.prg
12/12/2015  22:09             9.223 ze_SpedCadastroClass.prg
13/12/2015  21:01            79.897 ze_SpedSefazClass.prg
09/09/2015  22:02            33.876 ze_spedxmlClass.prg
10/08/2015  22:46             2.303 ze_SpedXmlEventoMDFE.prg
13/01/2016  09:37            10.917 ze_SpedXmlMDFE.prg
05/01/2016  12:04           103.906 ze_spedxmlnfe.prg
30/12/2015  19:38             6.119 ze_webservice.prg
10/08/2015  22:45             6.435 ze_winapi.prg
30/12/2015  19:36             3.463 ze_window.prg
02/01/2016  10:37             4.903 ze_wmenu.prg
20/10/2015  19:35             6.140 ze_xmlfun.prg
             396 arquivo(s)      4.812.530 bytes
               0 pasta(s)   203.726.548.992 bytes disponíveis


É só o final do comando DIR, pra mostrar que são 122 + 396 = 518 PRGs
Cada um tem sua própria pasta.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:34
por JoséQuintas
Na pasta do JPA, que é o tudo em um, o HBP:

*.prg
-oJPA
jpa.rc
hbct.hbc
hbtip.hbc
hbhpdf.hbc
hbziparc.hbc
hbmisc.hbc
hbzebra.hbc
hbnetio.hbc
gtwvg.hbc

-workdir=c:\temp
-m
-n
-es2
-w3
-compr
-inc
-strip
-mt
-quiet
-jobs=1

#----------- nos tempos do W98
#-winuni


Notem o *.PRG.
Tudo que está na pasta está sendo usado.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:37
por JoséQuintas
Já na pasta da imobiliária muda um pouco.
Como é um aplicativo de terceiros, e não quero multiplicar fontes, faço uso de fontes da pasta JPA.
Então criei DOIS HBPS:

Primeiro HBP:
Uso o que interessa da pasta JPA, agrupo tudo como uma LIB

#----------biblioteca do jose maria
..\integra\browse.prg
..\integra\pauxiliar.prg
..\integra\pdfboletoclass.prg
..\integra\pdfboletobradescoclass.prg
..\integra\pjpnumero.prg
..\integra\pjpreguso.prg
..\integra\puti0040.prg
..\integra\rabout.prg
..\integra\errorsys.prg
..\integra\getsys.prg
..\integra\ze_ado.prg
..\integra\ze_application.prg
..\integra\ze_calculadora.prg
..\integra\ze_calendario.prg
..\integra\ze_dbase.prg
..\integra\ze_dbf.prg
..\integra\ze_func.prg
..\integra\ze_funcapp.prg
..\integra\ze_help.prg
..\integra\ze_labelclass.prg
..\integra\ze_logerr.prg
..\integra\ze_mensagem.prg
..\integra\ze_preview.prg
..\integra\ze_progressbar.prg
..\integra\ze_prompt.prg
..\integra\ze_rede.prg
..\integra\ze_xmlfun.prg
..\integra\ze_webservice.prg
..\integra\ze_winapi.prg
..\integra\ze_window.prg
..\integra\ze_wmenu.prg
..\integra\ze_FrmCadClass.prg
..\integra\ze_FrmMainClass.prg
..\integra\ze_PDFClass.prg
..\integra\ze_SendMailClass.prg
..\integra\ze_resource.prg
..\integra\ptes0170.prg
..\integra\ptes0180.prg
-olibjose

hbwin.hbc
hbct.hbc
hbhpdf.hbc
gtwvg.hbc
hbtip.hbc
hbmisc.hbc
hbziparc.hbc
hbzebra.hbc
-m
-w3
-es2
-strip
-compr
-inc
-workdir=c:\temp
-quiet

-hblib


Segundo hbp, do aplicativo propriamente dito:

libjose.hbp

*.c
*.prg
-ohl
hl.rc
-llibjose

hbwin.hbc
hbct.hbc
hbhpdf.hbc
gtwvg.hbc
hbtip.hbc
hbmisc.hbc
hbziparc.hbc
hbzebra.hbc

-I..\integra\
-m
-w3
-es2
-strip
-compr
-inc
-workdir=c:\temp
-jobs=1

#----------- nos tempos do W98
#-winuni


Acrescento exatamente os mesmos fontes de biblioteca do JPA, e mesmo include "jpa.ch"
Isso garante que estou usando minhas rotinas atualizadas nos dois.

Justamente um recurso legal do HBP é que incluo o projeto da LIB como sendo parte do projeto do aplicativo.
Caso eu mexa em algum PRG da biblioteca quando estiver mexendo no JPA, ao estar mexendo no HL, ao compilar o projeto ele também já verifica e reconstrói a LIB se for necessário.

Só me resta programar... entrar no editor de texto, alterar um fonte, e digitar C <ENTER>

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:45
por JoséQuintas
O que tem no meu C.BAT

Na pasta JPA:
del jpa.exe
hbmk2 jpa.hbp


Na pasta HL:
del hl.exe
hbmk2 hl.hbp -comp=msvc %1 %2 %3 %4


Nem deveriam estar diferentes, mas tudo bem, estou postando a realidade atual.
Compilo muito. Mais fácil digitar C<ENTER> do que hbmk2 projeto.hbp <ENTER>

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:50
por JoséQuintas
De vez em quando alguns posts me lembram que nem todo mundo faz da mesma forma que eu.
Por isso este tópico.

Agora vamos lá...
518 PRGs.
Quantas rotinas de tbrowse eu devo ter?...
Algum palpite?
Uso em praticamente todo lugar

tbrowsedb.png


Vamos ver...

browse.prg - é uma cópia do browse do Harbour, mas ajustado pra funcionar em rede

pban0100.prg - é um tbrowse específico de controle bancário

ze_dbf.prg - duas rotinas. Uso pra TUDO

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 19:57
por JoséQuintas
E o uso de GTWVG nos fontes?

prgwvg.png


Vamos ver:

jpa.prg - é o programa principal, só anotação
pbol0010.prg - é de boleto, testei usar na harupdf mas não deu certo
pusrmsg.prg - é um "messenger" que criei, não é exatamente parte do aplicativo
rmenu.prg - o programa principal - um menu opcional em Windows
ze_application.prg - nem sei porque tem isso lá, algum teste talvez
ze_apres.prg - a tela de abertura
ze_calculadora.prg - uma calculadora
ze_frmmainclass.prg - TODAS as telas do aplicativo são criadas aqui
ze_mt.prg - a parte de multithread. só pra carregar a GTWVG em cada thread

Deixo algumas anotações/testes, porque posso precisar deles algum dia.

Por falar nisso... tem um teste de hoje aí no meio... interessante.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:03
por JoséQuintas
multithread:

multithread.png


Vamos ver o uso de multithread:

jpa.prg - o "main" de tudo, pra iniciar o antigo main, que virou Sistema()
p0600nfe.prg - seria uma rotina pra baixar PDF em background, está apenas anotado
pemissor.prg - meu emissor de nfe, está apenas anotado
jpordser.prg - ordem de serviço, em background uma rotina a cada intervalo de tempo verifica ordens em atraso
jpusrmsg.prg - aquele "messenger" que já comentei
rmenu.prg - o menu principal, tinha que ter aí
ze_func.prg - apenas anotado. algum teste que só olhando o fonte pra lembrar
ze_help.prg - a rotina de help on-line, que acessa a internet. Só pra evitar que o help trave o resto
ze_mt.prg - a rotina de multithread, tinha que ter aí também

Nota:
As últimas linhas são teste, é que estão num bloco /* */, e não dá pra ver que é apenas anotação.
Cheguei a usar isso no começo do multithread, e deixei anotado pra não esquecer.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:08
por JoséQuintas
Só lembrando. 518 PRGs.
Nesta pesquisa estou olhando somente a pasta JPA.
Pela descrição de onde usa, já dá pra imaginar que no HL não vai ter nenhum fonte com essas coisas.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:17
por JoséQuintas
Chamadas da função imprime:

imprime.png


Muitas, e tem muitas função imprime(), cada relatório tem a sua.
Só declarar STATIC, e não tem que inventar nome nenhum diferente.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:20
por JoséQuintas
Quantas variáveis PUBLIC

public.png


Tirando " PUBLIC" encontrado na declaração do tbrowse do Harbour, uma única variável pública m_PROG

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:26
por JoséQuintas
Variáveis THREAD STATIC

threadstatic.png


Até me espantei. GFORCA.PRG é um joguinho de forca. Para o caso de abrir várias janelas do jogo de forca, um não atrapalhar o outro... foi exagero.

Isso é o mesmo que STATIC, mas cada thread tem a sua.
Por exemplo, duas rotinas enviando email ao mesmo tempo, uma não pode interferir na outra.

Notem que a maioria do uso é nas bibliotecas, ou na GETSYS, que não deixa de ser biblioteca.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:38
por JoséQuintas
SET KEY

setkey.png


getsys.prg - apenas anotações
PCTL* são fontes de contabilidade, ainda não deixei totalmente modernas
pedi0150 - é uma rotina especial de configuração de importação de XMLs
jpa.prg - tem uma pesquisa de empresas instaladas no disco, é especial também

sobra

rpesq.prg - rotina de pesquisa
ze_application.prg - programa principal
ze_calculadora.prg - calculadora
ze_func.prg - rotinas gerais
ze_preview.prg - preview de relatórios, quando matricial

518 PRGs, e todos eles aceitam entrar na calculadora, pesquisa, etc. com base no SET KEY

Como no JPA não tem mais nenhum SET KEY específico, com certeza no HL também não tem.
Possivelmente só em hlpesq.prg, porque ele tem rotina de pesquisa própria.
E na configuração principal, porque também tem rotina principal própria.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:49
por JoséQuintas
SAVE SCREEN

savescreen.png
savescreen.png (10.53 KiB) Visualizado 28575 vezes


Tem até demais.

integra\balanco\???? -> é apenas rotina anotada, não faz parte do aplicativo e não conta.
ptes0090.prg - é uma rotina de teste
ze_calculadora.prg
ze_calendario.prg

Tenho isso no aplicativo. Algumas rotinas deixo como PTES*
São testes que faço, e só aparecem na minha senha.
Melhor do que espalhar fontes pelo disco.
E assim me lembra de dar uma olhada se dá pra aproveitar alguma coisa.
Foi num desses que meu uso da WVG começou.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 20:59
por JoséQuintas
atualizacao.png
atualizacao.png (9.61 KiB) Visualizado 28575 vezes


JPA Update - Download Versão
O cliente usa lá, pra baixar versão da internet. Se meu site estiver fora do ar, baixa direto do meu servidor.

JPA Update - Upload Versão
Sou eu que uso aqui, pra enviar versão pra internet e pro meu servidor.
Também facilito pra mim, porque não.

Essa opção tem uma segurança extra: se eu estiver fora da minha máquina, nem na minha senha aparece.
Assim evita que eu cometa algum erro.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 21:04
por JoséQuintas
testesa.png
testesa.png (10.72 KiB) Visualizado 28575 vezes


O primeiro menu de testes
- Escolher cores para o aplicativo
- Mostrar tabela Ascii
- Gera o manual do aplicativo em PDF
- Troca de mensagens entre usuários, estilo messenger
- Mexer com a parte gráfica do aplicativo, como menu Windows, e o aplicativo falar em português
- Também alterar o estilo do pushbutton de todo apliativo
- o Aplicativo se transformar num servidor de hbnetio
- Teste pra consultar recibo na secretaria da fazenda
- Envio de TODOS os DBFs para o MySql, criando toda estrutura equivalente e transferindo dados
- Backup também, que já deixou de ser teste.
- DanfeNFE é uma geração, não oficial, do PDF da nota fiscal eletrônica. Fiz só por curiosidade.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 21:10
por JoséQuintas
testesb.png
testesb.png (5.61 KiB) Visualizado 3763 vezes


- Geração de um html pra colocar num site. Com isso, ao acessar o site o usuário vai escolhendo a configuração do computador, e a página já vai mostrando todos os cálculos. É gerado a partir do cadastro de produtos
- Teste de filtro, nem postei por aqui ainda, ficou legal
- Gerar harbourdoc.com.br, é gerar um PDF de toda documentação que está cadastrada no harbourdoc.com.br - isso mesmo é uma opção do aplicativo que tenho instalado em todos os clientes, mas só eu tenho acesso.
- Acesso remoto - uso em máquinas aonde o adminstrador escondeu o acesso ao menu do Windows
- Modo Deus - pra acesso total às configurações do Windows - mesmo motivo
- Teclado virtual - pra mostrar o teclado virtual do Windows. Pode ser interessante num telefone celular sem teclado compatível com Windows, assim uso o do próprio Windows.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 21:15
por JoséQuintas
Só pelas opções de teste, já dá pra imaginar as opções "não teste".
No meu aplicativo TUDO tem cadastro, não deixo nada pra acessar via meu "dbase" embutido.
Mesmo que use uma vez por ano, ou até menos que isso.... tá lá o cadastro.
E mesmo que só eu use, só na minha senha, tem opção.

Cadastro de UFs, por exemplo, tem lá com pushbutton, toda atualizada de acordo com meu padrão de trabalho. Quando será que vamos precisar cadastrar UF?
Tá fácil programar um cadastro, já fica tudo disponível e pronto.
Posso ou não liberar para o usuário, tanto faz.
Acho que tem até listagem disso.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 21:30
por JoséQuintas
Isso me lembrou da geração de manual.

Só o menu de opções, que seria o índice, vai até a página 8

manual.png


Não tem visual gráfico?
Tudo bem, é totalmente automático.
Aperto F1 no aplicativo, em qualquer máquina, minha ou do cliente, digito o texto e pronto, vai pro manual disponível pra todo mundo, on-line ou pra impressão.
Isso é bem melhor que um manual bonito e desatualizado.

Isso também ficou disponível pro aplicativo da imobiliária, afinal, ele herdou todas as bibliotecas do JPA.
E de acordo com a senha do usuário.
Na minha senha, completo
Na senha do administrador do cliente, todas as opções do cliente
Na senha de qualquer usuário, só do que ele pode mexer.

Não liberei pra ninguém.
Se algum dia alguém perguntar por manual impresso... eu imprimo, ou posso até liberar a opção.

Já pensei em liberar alteração pra cliente, pra eles mesmos irem fazendo anotações, mas não fiz.
Também está preparado pra isso. O sistema guarda todas as "versões" de cada texto. Se o usuário apagar tudo, eu apenas deleto o registro que ele incluiu em branco, e tá tudo restaurado.

Meu modo de trabalho

MensagemEnviado: 16 Jan 2016 21:44
por JoséQuintas
Se eu lembrar de mais alguma coisa coloco por aqui.

O mais legal do aplicativo não é o que se vê, é o que não se vê.

Ah é...
No caso de um cliente novo, basta eu colocar os XMLs de NFE numa pasta, e pedir pra importar.
O aplicativo já cadastra clientes, produtos, notas fiscais, movimentação de estoque, pedidos, financeiro, etc.
Só fica faltando dar baixa no financeiro.

Se for empresa bagunçada, XMLs espalhados pelas máquinas, tudo bem.
O sistema também permite vasculhar todas as pastas, e já organiza todos os XMLs válidos em uma pasta, separando mês a mês em subpastas.

É por aí...
Ter deixado a parte gráfica de lado, e ter me dedicado ao restante foi bom.
Depois até sobrou tempo pra mexer com a parte gráfica... rs

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 09:23
por JoséQuintas
Opções do menu:

MenuOption( "Movto" )
   MenuDrop()
   MenuOption( "Pedidos/Notas Fiscais" )
      MenuDrop()
      MenuOption( "Orçamentos/Pedidos", "P0600PED" )
      MenuOption( "Emissor JPA", "PEMISSOR" )
      MenuOption( "Nota Fiscal (Serviços)", "PNOT0010" )
      MenuOption( "Consulta a Notas Fiscais", "PNOT0020" )
      MenuOption( "Gera Pedido de Retirada", "PNOT0030" )
      MenuOption( "Rel.Romaneio de NFs", "PNOT0050" )
      MenuOption( "Manifesto Eletrônico", "PJPMDF" )


Fácil acrescentar opções, ou transferir de um lugar pro outro, ou até duplicar se necessário.
Fica tudo num único array.
Daí saem informações para:
- montagem do menu conforme senha de acesso
- configurar senhas de acesso
- menu Windows
- Help on line
- manual em PDF
Aquela única variável PUBLIC que mostrei, m_Prog, ela sai desse menu, é o nome do módulo em uso.

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 09:36
por JoséQuintas
Mostrar uma alteração do menu na prática.
Acrescentei esta linha no fonte do menu.

menu1.png
menu1.png (8.27 KiB) Visualizado 3756 vezes


salvo, e digito C <ENTER>

Executo, coloco usuário/senha e lá está ela

menu2.png


Entro na configuração de senhas/acessos e.... lá está ela

menu3.png


Só acrescentei a opção, o módulo não existe. Se eu abrir essa opção no menu:

menu4.png


Vejam, só acrescentei uma única linha no fonte.
Já tem configuração de acesso, tela pronta pra uso em multithread, com título, espaço pra mensagem, parte central limpa, está pronto pra eu trabalhar no fonte

E a Multithread? Nem mexi com isso, o menu faz isso automático.
Variáveis públicas, configurações, etc. para o módulo? Nem mexi com isso, o menu faz isso automático.

Seu eu criar um fonte com Inkey(0), basta teclar F1 e já posso atualizar as informações do help on-line para o módulo. (que também sairão no manual em PDF)

Mas por acaso dá pra fazer isso em menu Windows?

menu5.png


Meu menu Windows é opcional, mas sempre pronto pra uso. Quando digo pronto, é pronto mesmo.

se eu clicar no "JPA Update - Upload versão", isso já vai pra internet.
Se o cliente clicar no "JPA Update - Download versão", isso já vai estar na máquina dele.

Uma única linha, e dois clicks, e já fica instalado no cliente !!!!!
A opção ainda não serve pra nada, tudo bem, foi só pra mostrar a facilidade.

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 10:38
por rubens
Bom dia José...

Quando você fala em melhorias no browse do harbour para trabalhar em rede, o que você melhorou ?

Obg
Rubens

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 10:41
por JoséQuintas
Uma coisa simples: RLOCK()
Sem isso não dá pra alterar em rede, dá erro.
Eu uso muito pouco, por isso esquecia de que precisava travar o registro pra usar o browse do Harbour.
Acrescentando RLock() ficou resolvido, acho que em dois lugares do Browse.prg.

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 11:09
por JoséQuintas
Agora coloquei conteúdo no ptesforum.prg

PROCEDURE PTESFORUM

   LOCAL mCliente := Space(6), GetList := {}

   IF .NOT. AbreArquivos( "jpcadas" )
      RETURN
   ENDIF

   @ 5, 10 SAY "Cliente....." GET mCliente PICTURE "@K 99999" VALID JPCADAS1Class():Valida( @mCliente )
   READ

   CLOSE DATABASES
   RETURN


Independente de jpcadas1class(), que é classe, a rotina de validação equivale a uma função.
Antes eu usava uma função ClienteOk(), depois agrupei tudo que era sobre cliente numa única classe, mas dá no mesmo.

salvei, digitei <C> ENTER.

O fonte só tem abrir arquivo, o GET, e a validação, vamos lá ver funcionando.

A tela, ok, já era esperado

pcforum1.png


Ao digitar um cliente sem cadastro

pcforum2.png


Caso digite F9, que é pra pesquisa, o browse dos clientes.

pcforum3.png


É exatamente o fonte que postei em funcionamento.

No browse dos clientes, o mesmo disponível em todo aplicativo:
pode ser em ordem de código, ordem alfabética, ordem CNPJ, pesquisar texto, ir digitando texto e filtrando,filtro de palavras, etc.
Antigamente usava botões nesse browse, mas retirei. Achei que ficou feio, e só ocupando espaço.

Nota: por isso estranho quando vejo fazerem uma rotina de tbrowse pra cada fonte.

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 11:23
por JoséQuintas
Só voltando um pouco pra trás, esqueci de mencionar.
Quando deu aquele erro de faltar o módulo.... o sistema me enviou este email.
Mesmo na minha máquina mantenho isso.

Windows: Windows 7 6.1 SP1
Computer Name: JOSEJPA
Windows User: joseq
Logon Server: \JOSEJPA
User Domain: josejpa

Error BASE/1001 Undefined function: PTESFORUM
Called from DO(0)
Called from DOPRG(144)
Called from (b)RUNMODULE(111)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Error on 17/01/16 10:34:11
JPA: 2016.01.14.1410
Login JPA: JOSEQ
Alias:

Harbour: Harbour 3.4.0dev (0a127e0) (2016-01-15 01:54)
Compiler: Microsoft Visual C++ 16.0.40219 (32-bit)
GT: WVG
Folder: d:\CDROM\DADOS\SISTEMAS\

Windows: Windows 7 6.1 SP1
Computer Name: JOSEJPA
Windows User: joseq
Logon Server: \JOSEJPA
User Domain: josejpa

Error BASE/1001 Undefined function: PTESFORUM
Called from DO(0)
Called from DOPRG(144)
Called from (b)RUNMODULE(111)

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 11:40
por JoséQuintas
Agora voltemos ao tbrowse.

lembrando do início do aplicativo:

SET KEY K_F9 TO Pesquisa


Teclou F9, chama rotina de pesquisa, se vale pro aplicativo, vale pra esta nova tela.

cVarName     := Lower( ReadVar() )
...
CASE cVarName $ "mcliente" // .AND. m_Prog == "PTESFORUM"
   JPCADAS1Class():GridSelection()
...


A rotina de pesquisa verifica o nome da variável do GET e desvia pra rotina correspondente, nesse caso de clientes.
Chamei de GridSelection(), só porque dá pra considerar sendo uma grid, e é pra seleção.

Esta tem algo mais, mas tudo bem, já foi, vai ela mesmo.

METHOD GridSelection() CLASS JPCADAS1Class

   LOCAL nCont, nSelect := Select(), cOrdSetFocus, oTBrowse

   IF Select( "jpclista" ) == 0
      SELECT 0
      AbreArquivos( "jpclista" )
      WriteErrorLog( "Faltou abrir jpclista", 2 )
   ENDIF
   SELECT jpcadas
   oTBrowse := { ;
      { "Nome",        {|| jpcadas->cdNome } }, ;
      { "Apelido",     {|| jpcadas->cdApelido } }, ;
      { "Código",      {|| jpcadas->cdCodigo } }, ;
      { "UF",          {|| jpcadas->cdUf } }, ;
      { "Cidade",      {|| jpcadas->cdCidade } }, ;
      { "Ref.Mapa",    {|| jpcadas->cdMapa } },  ;
      { "Endereço",    {|| jpcadas->cdEndereco } }, ;
      { "Número",      {|| jpcadas->cdNumero } }, ;
      { "Complemento", {|| jpcadas->cdCompl } }, ;
      { "Cnpj",        {|| jpcadas->cdCnpj } } }
   FOR nCont = 1 TO Len( oTBrowse )
      AAdd( oTBrowse[ nCont ], {|| iif( ! Encontra( jpcadas->cdStatus, "jpclista", "numlan" ) .OR. Val( jpcadas->cdStatus ) < 2, { 1, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "0", { 3, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "1", { 4, 2 }, { 7, 2 } ) ) ) } )
   NEXT
   cOrdSetFocus := ordSetFocus()
   ordSetFocus( "jpcadas2" )
   FazBrowse( oTBrowse,, "1" )
   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD jpcadas->cdCodigo + Chr( K_ENTER )
   ENDIF
   ordSetFocus( cOrdSetFocus )
   SELECT ( nSelect )

   RETURN NIL


Achei interessante acrescentar esta parte:

   IF Select( "jpclista" ) == 0
      SELECT 0
      AbreArquivos( "jpclista" )
      WriteErrorLog( "Faltou abrir jpclista", 2 )
   ENDIF


Se esse arquivo não estiver aberto, já abre automático (esqueci dele).
Além de resolver o problema, o sistema ainda registra como erro, e me manda um email avisando que eu esqueci de colocar no fonte pra abrir esse arquivo.

nSelect := Select()
...
SELECT ( nSelect )


A rotina não sabe qual arquivo está posicionado, então salva a área atual, muda pra área de clientes e depois volta para o que estava antes.

   cOrdSetFocus := ordSetFocus()
   ordSetFocus( "jpcadas2" )
...
   ordSetFocus( cOrdSetFocus )


A rotina não sabe em qual índice está posicionado, então altera pra ordem alfabética e depois volta o que estava antes.

   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD jpcadas->cdCodigo + Chr( K_ENTER )
   ENDIF


Se foi escolhido um cliente, a rotina faz o KEYBOARD do código + ENTER. Isso vai entrar no GET.

Os campos que aparecerão no tbrowse

   oTBrowse := { ;
      { "Nome",        {|| jpcadas->cdNome } }, ;
      { "Apelido",     {|| jpcadas->cdApelido } }, ;
      { "Código",      {|| jpcadas->cdCodigo } }, ;
      { "UF",          {|| jpcadas->cdUf } }, ;
      { "Cidade",      {|| jpcadas->cdCidade } }, ;
      { "Ref.Mapa",    {|| jpcadas->cdMapa } },  ;
      { "Endereço",    {|| jpcadas->cdEndereco } }, ;
      { "Número",      {|| jpcadas->cdNumero } }, ;
      { "Complemento", {|| jpcadas->cdCompl } }, ;
      { "Cnpj",        {|| jpcadas->cdCnpj } } }


De acordo com certas condições, as cores de cada cliente serão diferentes.

   FOR nCont = 1 TO Len( oTBrowse )
      AAdd( oTBrowse[ nCont ], {|| iif( ! Encontra( jpcadas->cdStatus, "jpclista", "numlan" ) .OR. Val( jpcadas->cdStatus ) < 2, { 1, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "0", { 3, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "1", { 4, 2 }, { 7, 2 } ) ) ) } )
   NEXT


E a chamada à rotina de tbrowse propriamente dita.

   FazBrowse( oTBrowse,, "1" )


Esse detalhe a mais é porque misturo vários cadastros em um. O de clientes, usa tipo="1", é o que esse parâmetro indica

Resumindo: a rotina nova usou tudo que já estava disponível. Bastou seguir o padrão do aplicativo.
(E o sistema me avisou que esqueci de abrir o jpclista, preciso acrescentar no AbreArquivos(), é aonde tem a descrição de status de clientes, usada pra complemento nesse browse)

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 11:55
por JoséQuintas
Aproveitando:

São rotinas relativamente simples, mas o principal ORGANIZADAS, cada uma trata de seu "assunto".

pesquisa tá em pesquisa, validação tá em validação, browse de clientes nas rotinas de clientes, e o browse padrão na rotina dele.

O browse de clientes pode alterar área em uso e alterar índice, mas devolve tudo do jeito que estava antes.

Cada rotina tratando de seu "assunto", as outras não precisam se preocupar.

SET KEY
ReadVar()
SELECT
SET ORDER ( ou dbOrdSetFocus() )
KEYBOARD

Não tem nada avançado aí, tudo existe no Clipper.
Só fiz uso de coisas que sempre existiram.
Facita muito os campos de arquivo estarem padronizados, usar como ALIAS o próprio nome do arquivo, usar a ordem pelo NOME.

Atenção a isso quem usa
SELECT A, SELECT B, SELECT 1, SELECT 2, SET ORDER TO 0, SET ORDER TO 1

Ah é... em multithread sem problema, cada thread tem seu JPCADAS, por exemplo.
Se encaixou no meu padrão de programação direitinho.

Pelo que comentaram GTWVW precisa alias diferentes. Tudo bem também, se estiver padronizado, de repente só usar Substr( Alias(), 5 ) ou algo assim.
Também poderia radicalizar: a pesquisa também em thread separada, e abrir arquivo só pra ela.... não sei se facilitaria ou se compensa.

Meu modo de trabalho

MensagemEnviado: 17 Jan 2016 12:22
por JoséQuintas
Um resumo geral até aqui:

- Não preciso me preocupar com menu
- Não preciso me preocupar com tela
- Não preciso me preocupar com multithread
- Não preciso me preocupar com tbrowse que já existe
- Não preciso me preocupar com validação que já existe

- validação nova é fácil
- tbrowse novo é fácil
- o sistema se corrige automático e me avisa pra corrigir o fonte, quanto a dbf
- em caso de erro, recebo por email e também no celular, e já resolvo.

Como eu digo sempre, é deixar fonte fácil, que tudo vai mais fácil.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 10:39
por JoséQuintas
Ë repetido mas tudo bem.
Coloquei em práticas que facilitam, mas de qualquer forma faz parte do meu modo de trabalho.
Recebi este erro por email, estava tentando descobrir há algum tempo.
Pra facilitar, coloquei depois pra também gravar no erro qual o comando SQL que causou o erro.
Agora descobri qual é: não previ endereço em branco nesta opção.

rro executando comando:-2147217900 [MySQL][ODBC 3.51 Driver][mysqld-5.6.25-log]You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%' ORDER BY ENDERECO' at line 1
--------------------------------------------------------------------------------

SELECT ENDERECO, CLASSI, NUMERO FROM ALUGUEL WHERE MID( CLASSI, 1, 2 ) IN ( 'AR', 'CR', 'SR' ) AND ENDERECO LIKE ''%' ORDER BY ENDERECO;
Called from ADOCLASS:EXECUTE(218)
Called from CONSULTAALUGUELCLASS:NAVEGAALUGUEL(423)
Called from CONSULTAALUGUELCLASS:CONSULTA01(105)
Called from VHLAL01(49)
Called from DO(0)
Called from BOXMENU(519)
Called from MAINMENU(368)
Called from MAIN(161)


Na hora de mexer, cometi um erro, apaguei uma linha a mais.
Tranquilo, a checagem -w3 -es2 me avisou.

Harbour 3.4.0dev (0a127e0) (2016-01-15 01:54)
Copyright (c) 1999-2016, https://github.com/vszakats/harbour-core/
Compiling 'VHLAL01.prg'...
VHLAL01.prg(646) Warning W0032 Variable 'CPALAVRA' is assigned but not used in function 'MYSQLENDERECOLIKE(642)'
No code generated.


E depois, por precaução, dei uma conferida nas alterações pelo GIT.

alteracao.png


Um pedaço do fonte ficou diferente do outro.
Já aproveito pra deixar os dois iguais.

Então adicionais pra minha forma de trabalho:

- Meus erros por email me ajudam
- Rotinas que coloco no EXE me ajudam
- A compilação -w3 -es2 me ajuda
- O git me ajuda

Os dois primeiros comecei a usar nos tempos do Clipper.
Os dois últimos podem ser usados com o Clipper, mas só conheci no Harbour.

São vários ajudantes, disponíveis pra todo mundo, e a maioria não usa.

Comentário adicional:
O erro foi só num dos blocos.
É que aproveitei pra melhorar o outro bloco.
Sou eu que vou mexer no fonte, então estou melhorando pra mim.
Multiplique isso por 365 dias por ano, vários anos... dá pra melhorar muuuuito, só fazendo um pouquinho por vez.

Está aí: Acrescente aos meus ajudantes EU MESMO.
O melhor ajudante de um programador é ele mesmo, porque é ele próprio que pode se ajudar.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 11:14
por JoséQuintas
O único lado ruim é que não resolvi o problema.
Nos dois casos, já está testando se endereço está vazio.

Como em todos os "LIKE" tem checagem, só resta algum caractere inválido no endereço.
Vou ter que rever minha GETSYS, e a rotina de checagem, pra descobrir qual pode ser o caractere.
A não ser que o usuário lembre o que digitou.

Ou se eu conseguir colocar o Harbour pra conferir.... talvez dê....

Nota:
Pelo menos a checagem evitou de estragar mais.
É que postei aqui após a alteração, mas fui dar uma última olhada antes de finalizar, justamente por estar fazendo as duas coisas ao mesmo tempo.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:47
por JoséQuintas
Iniciar uma viagem ao passado.
Vai ser longo isso.
Está pra quem quiser comprovar.

http://www.jpatecnologia.com.br/arquivos/jpa.exe

Só alterar esse nome de EXE pelo nome dos outros EXEs.

Começar pela versão de 2002, de quase 14 anos atrás.
Olhem a pasta: só tem o EXE.

jpa1.png
jpa1.png (7.36 KiB) Visualizado 3581 vezes

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:48
por JoséQuintas
Primeira execução.

jpa2.png

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:50
por JoséQuintas
jpa3.png
jpa3.png (6.15 KiB) Visualizado 3581 vezes


Nessa época eu já tinha o backup automático, e a atualização automática de estruturas.
É o que eu vejo nessa tela.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:52
por JoséQuintas
jpa4.png


Além de atualizar estruturas, atualizar informações.
Aí dá pra ver atualizando campos novos, tabelas de documento fiscal, etc.
Ah sim, com o gráfico que mostra previsão de tempo.

Naquela época ainda não usava o ano como número de versão. é a 4.14.93

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:54
por JoséQuintas
jpa5.png


Criação automática de um usuário pra demonstração.
DEMO é de demonstração, não tem a ver com nada do inferno..... rs

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 15:56
por JoséQuintas
jpa6.png


Tela de login, usuário e senha.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:00
por JoséQuintas
jpa7.png


E chegou no menu.
É O MESMO MENU QUE USO ATÉ HOJE!!!!!!

Posso clicar com o mouse em qualquer parte do menu.

jpa8.png


Eu até sinto que não avancei quando olho o aplicativo de 14 anos atrás!!!!

Naquela época eu tinha um cliente transportadora.
Uma única opção do menu principal se abre em tudo referente à transportes.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:05
por JoséQuintas
Mas hoje uso 40 linhas, olhar um sistema de 25 linhas é algo "apertado".

Vamos ver o que dá:

jpa10.png
jpa10.png (8.18 KiB) Visualizado 3581 vezes


jpa11.png


Agora são 28 linhas.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:08
por JoséQuintas
Vamos mais.

jpa9.png
jpa9.png (7.97 KiB) Visualizado 3581 vezes


jpaxx.png


Agora são 50 linhas

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:12
por JoséQuintas
jpa1.png


Atenção ao detalhe das opções:

Envia backup pra JPA
O sistema enviava por email

Atualização via Internet
Isso mesmo, atualização online, 14 anos trás.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:16
por JoséQuintas
Voltei pra 25 linhas pra economizar no tamanho da imagem.

jpa2.png
jpa2.png (7.67 KiB) Visualizado 3581 vezes


Nessa época eu já usava funções pra facilitar o cadastro, pelo menos para os menus de cadastro.
Mouse, inicial, setinha, tanto faz.

Olha lá a tela, do mesmo jeito que uso hoje.
Título encima, mensagem embaixo, menu construindo tudo.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:18
por JoséQuintas
jpa3.png


E olha lá o mesmo que uso hoje, o F9 pra fazer consulta e abrir o tbrowse

Naquela época abria o tbrowse conforme a posição do GET.
Inicial, filtro, pesquisa, escolher ordem dos dados, tá tudo lá.
Uma única rotina de tbrowse, do mesmo jeito que hoje.

Não gosto de olhar programas antigos.
Dá a sensação de que não tive evolução nenhuma em 14 anos.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:23
por JoséQuintas
jpa4.png


jpa5.png


jpa6.png


Só de tabela auxiliar, 1 tonelada.... rs

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:25
por JoséQuintas
jogos.png


Até joguinho.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:28
por JoséQuintas
senhas.png


Uia. A configuração de senhas de acesso.

Caracas.
Se fiz tudo isso há 14 anos atrás... o que fiz de lá pra cá?
Estou sentindo que não evoluí nada...

Pelo menos nessa época não funcionava o mouse em todas as opções de senha.
Viva. Pelo menos encontrei alguma evolução.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:30
por JoséQuintas
backup.png
backup.png (8.74 KiB) Visualizado 3614 vezes


Uia o que fui achar.
Backup pra disquete.

Ah sim, naquela época usava o LHA.EXE pra compactar o backup. (formato LZH).
Isso também mudou.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:35
por JoséQuintas
a1.png


a2.png


Naquela época eu tinha opções de teste.
Esse daí era pra consultar direto o site da Receita Federal.
Processava todo o arquivo de clientes, pra comparar com a Receita Federal.

Desperdício de tempo....

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:40
por JoséQuintas
Estou usando um XP Virtual.
Até agora foi uma versão em Clipper de 2002, de 14 anos atrás.

Só copiar o EXE na pasta.

Para uso, seria mais o LHA.EXE pra backup, e o BLAT.EXE pra emails.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:44
por JoséQuintas
jpa2004.png


A de 2004, ainda em Clipper, já foi abusada.
Já começou perguntando sobre atualizar versão, porque detectou que é velha.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:48
por JoséQuintas
jpa2004.png


Em 2004 melhorou a cor do menu.

Tem mais sub-sistemas no menu

Dá pra ver ali um opções de portaria, RH, armazém.

E a tela de fundo está mais escura, pra despoluir.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:49
por JoséQuintas
Bom. No geral é igual o anterior.
Mas no anterior esqueci de mostrar relatórios.

relatorios.png
relatorios.png (10.17 KiB) Visualizado 3613 vezes


Muitas opções de seleção, incluindo
vídeo ou impressora.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:53
por JoséQuintas
relatorios2.png


O preview de impressão.
Opção de imprimir qualquer intervalo de páginas, ou páginas selecionadas à vontade, não precisava ser uma sequência, e tudo separado por vírgulas.
Ex. página 1, 10, e da 20 até 30, e mais da 40 até 50

Com opção de enviar por email.

Acho que era dessa época, o preview em Visual Basic, com opção de PDF.

Era opcional, se estivesse instalado, ele era usado.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:55
por JoséQuintas
reindexacao.png


Sim, a reindexação geral, mostrando o gráfico de previsão de tempo.
É o gráfico e previsão pra fazer tudo, não apenas um arquivo.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 16:58
por JoséQuintas
Daí pula direto pro 2010/2011, não tenho mais os intermediários.

jpa2010.png


Mostrando atualização de estruturas, adicionando ou removendo campos.
Tinha isso nos anteriores, mas neste aqui está mostrando o que está fazendo, e deu tempo do print-screen.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 17:01
por JoséQuintas
Pulamos pro 2013, já em Harbour.

jpa2013.png


Mudei configuração pra formato XML, é isso que ele está avisando, sobre essa conversão.

Sim, estou rodando uma versão encima da outra.
Uma atualiza a anterior.

Se algum cliente meu tiver arquivos antigos, de qualquer ano, mesmo anteriores a 2002, pode converter tudo pro sistema atual.
Só por isso mantenho essas versões antigas.
Elas fazem as conversões de DBFs anteriores a elas.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 17:08
por JoséQuintas
jpa2013.png


Nessa época o arquivo de clientes já ocupando 3 telas.
Então a opção de clicar nas abas.
Acho que isso eu já usava nos tempos do Clipper.

Nessa época, coloquei as letras de opção em outra cor.

Nada complicado, algo como DestacaLetra( cTexto )
E pegar as letras entre "<" e ">"

Até que a tela está simpática, bem leve.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 17:13
por JoséQuintas
E opção de relatório em PDF.
Nessa época era uma rotina do Visual Basic, que converti pra Harbour.
Nessa época não conhecia a harupdf.

jpa2013.png


Eliminado o uso de LHA.EXE e do BLAT.EXE

Somente um EXE e nada mais.

Também eliminada a necessidade de VBScript pra atualização online, e outras coisas.

Menos mal. Finalmente algum progresso visível em 14 anos.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 17:34
por JoséQuintas
De lá pra cá, classes e harupdf.

Isso vém bem a calhar pra mostrar o que falei sobre classes.
Eu já tinha funções pra muita coisa, eu apenas aproveitei as classes pra agrupar essas funções que eu já tinha.
E acabei tirando mais proveito.

Importante:

Pra quem acha que o que faço tem a ver com classe ou codeblock, fique sabendo que na época do Clipper eu não entendia nada de codeblock.
Codeblock era coisa do outro mundo pra mim na época.

14 anos atrás... Não sabia nem metade do que sei hoje.
Apenas era empolgado em tirar proveito do Clipper e do computador.

E olhe que eu abusava de PUBLIC, PRIVATE, não declarava variáveis desse tipo, não tinha o recurso -w3 -es2.
Acho que pelo menos declarava quando a variável era LOCAL.
Mas os fontes sempre organizados, sabendo de onde vinham e pra onde iam as variáveis.

Tudo isso me facilitou o trabalho.
Pra mim, sinceramente, sempre achei que todo mundo fazia melhor, e que eu estava atrasado por nem conhecer codeblock.
Atualmente ainda vejo alguns que não fazem o que eu já fazia há 14 anos atrás com o Clipper.

Acho que esqueceram de programar não só para o cliente, mas pra si próprios.
Ainda dá tempo.

Por falar nisso....
No final do ano passado, ou começo deste ano, alterei meu tbrowse pra aceitar codeblock ao invés de função de usuário em caractere.
Muito bom.
Era obrigado a inventar nomes únicos pra cada função de usuário, porque tinham que ser funções públicas, visíveis no aplicativo inteiro.

de:
fazbrowse( , , "funcaodeusuario" )


para:
fazbrowse( , , { || FuncaoDeUsuario() } )


Pensando bem, até que ainda tem novidade.
Muitas delas é na parte que não se vê.

Meu modo de trabalho

MensagemEnviado: 18 Jan 2016 18:00
por JoséQuintas
Ah sim, muito tempo perdido foi testando LIBs gráficas.
Só fui em frente quando deixei elas de lado.

O que quero mostrar aqui é o seguinte:

Não é esperar que algo aconteça, é fazer.
Quer que fique fácil, então faça ficar fácil.
Conhece poucos recursos? use os que conhece.

Tá brigando com LIB gráfica?
Tem tanta coisa que pode ser feita além da parte visual.

Com certeza se não fizer nada, nada acontecerá.
Um degrauzinho por dia, vai chegar no topo da escada igual qualquer um.
É só começar a subir, mesmo que seja um pedaço de um degrau de cada vez.
Vai subindo, um degrauzinho de cada vez, sossegado....
Melhor subir devagar e sempre, do que subir correndo, cair e rolar escada abaixo.

De 14 anos atrás pra cá, deu pra ver que continuo subindo mais alguns degraus.

O começo de tudo foi querer simplificar meu trabalho, só isso.
O resto é só consequência disso.

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:32
por JoséQuintas
Depois de tanta tela DOS, só pra melhorar um pouco, um teste antigo com GTWVG: ela não foi feita pra isso, mas dá pra brincar.

testevisual.png

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:34
por JoséQuintas
A rotina de tbrowse com codeblock, tá permitindo facilitar.
A primeira tela com locadores, pessoas que alugam casas, mostrando totais mes a mes.
A função de usuário no codeblock é NavegaLocatario()

STATIC FUNCTION NavegaLocador()

   LOCAL dData, nCont
   LOCAL oTBrowse := { ;
      { "NOME",    { || templocador->Nome } }, ;
      { "LOCADOR", { || templocador->Locador } } }

   dData := UltDia( Ctod( "01/01/" + StrZero( Year( Date() ) - 1, 4 ) ) )
   FOR nCont = 1 TO 36
      AAdd( oTBrowse, { Substr( Dtoc( dData ), 4 ), LocadorColMesBlock( dData, 1 ) } )
      dData := UltDia( dData + 1 )
   NEXT

   Cls()
   SELECT templocador
   GOTO TOP
   DO WHILE .T.
      Mensagem( "ENTER Locatários do locador, ESC Sai" )
      DBView( 2, 0, MaxRow() - 4, MaxCol(), oTBrowse, { | oBrowse, nKey | NavegaLocatario( oBrowse, nKey, templocador->Locador ) }, 1 )
      IF LastKey() == K_ESC
         EXIT
      ENDIF
   ENDDO
   RETURN NIL

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:36
por JoséQuintas
Ao teclar ENTER, abre os locatários, os inquilinos daquele locador.
Também com totais mes a mes.
O codeblock com função de usuário daqui, LocatarioTotaisMes()

STATIC FUNCTION NavegaLocatario( oBrowse, nKey, nLocador )

   LOCAL dData, nCont, nSelect := Select()
   LOCAL oTBrowse := { ;
      { "NOME",      { || templocatario->Nome } }, ;
      { "LOCATÁRIO", { || templocatario->Locatario } } }

   IF nKey != K_ENTER
      RETURN NIL
   ENDIF
   dData := UltDia( Ctod( "01/01/" + StrZero( Year( Date() ) - 1, 4 ) ) )
   FOR nCont = 1 TO 36
      AAdd( oTBrowse, { Substr( Dtoc( dData ), 4 ), LocatarioColMesBlock( dData, 1, nLocador ) } )
      dData := UltDia( dData + 1 )
   NEXT

   Cls()
   @ 2,0 SAY templocador->Locador PICTURE "9999"
   @ Row(), Col() + 2 SAY templocador->Nome
   SELECT templocatario
   SET SCOPE TO Str( nLocador, 4 )
   GOTO TOP
   DO WHILE .T.
      Mensagem( "ENTER detalhes" )
      DBView( 4, 0, MaxRow() - 4, MaxCol(), oTBrowse, { | oBrowse, nKey | LocatarioTotaisMes( oBrowse, nKey, nLocador, templocatario->Locatario ) }, 1 )
      IF LastKey() == K_ESC
         EXIT
      ENDIF
   ENDDO
   SET SCOPE TO
   SELECT ( nSelect )
   HB_SYMBOL_UNUSED( oBrowse )
   RETURN NIL

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:39
por JoséQuintas
Ao teclar ENTER no anterior, abre os totais mes a mes de um único locador.
É meio repetitivo do anterior, mas aqui está na vertical, e mostra valor e comissão mensal.
Aqui o codeblock de função de usuário é Lancamentos()

FUNCTION LocatarioTotaisMes( oBrowse, nKey, nLocador, nLocatario )

   LOCAL nSelect := Select(), dData, nCont, cTmpFile, oTBrowse

   IF nKey != K_ENTER
      RETURN 0
   ENDIF

   wSave()
   cTmpFile := MyTempFile( "DBF" )
   SELECT 0
   dbCreate( cTmpFile, { { "DATA", "D", 8, 0 } } )
   USE ( cTmpFile ) ALIAS tempmes
   dData := UltDia( Ctod( "01/01/" + StrZero( Year( Date() ) - 1, 4 ) ) )
   FOR nCont = 1 TO 36
      SELECT tempmes
      RecAppend()
      REPLACE tempmes->Data WITH dData
      dData := UltDia( dData + 1 )
   NEXT
   oTBrowse := { ;
      { "MES",      { || Substr( Dtoc( tempmes->Data ), 4 ) } }, ;
      { "VALOR",    { || LocatarioTotaisMesColValor( nLocador, nLocatario, tempmes->Data, 1 ) } }, ;
      { "COMISSAO", { || LocatarioTotaisMesColValor( nLocador, nLocatario, tempmes->Data, 2 ) } } }
   Cls()
   @ 2, 0 SAY locador->Locador PICTURE "9999"
   @ Row(), Col() + 2 SAY templocador->Nome
   @ 3, 0 SAY templocatario->Locatario PICTURE "9999999"
   @ Row(), Col() + 2 SAY templocatario->Nome
   GOTO TOP
   dbView( 5, 0, MaxRow() - 4, MaxCol(), oTBrowse, { | oBrowse, nKey | Lancamentos( oBrowse, nKey, nLocador, nLocatario, tempmes->Data ) } )
   USE
   fErase( cTmpFile )
   wRestore()
   SELECT ( nSelect )
   HB_SYMBOL_UNUSED( oBrowse )
   RETURN 0

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:48
por JoséQuintas
Este aqui comecei agora, vai mostrar cada lançamento e permitir alteração.
A intenção é fazer ajustes nas datas. Um lançamento com data errada, pode transformar o que era isento, em 30% de imposto.

STATIC FUNCTION Lancamentos( oBrowse, nKey, nLocador, nLocatario, dData )

   @ 2, 0 SAY templocador->Locador PICTURE "9999"
   @ Row(), Col() + 2 SAY templocador->Nome
   @ 3, 0 SAY templocatario->Locatario PICTURE "9999999"
   @ Row(), Col() + 2 SAY templocatario->Nome
   HB_SYMBOL_UNUSED( oBrowse + nKey + nLocador + nLocatario + dData )
   RETURN NIL


E no meio disso tudo, rotinas auxiliares, mostrar só uma:

STATIC FUNCTION LocadorColMes( dData, nTipoVal, nLocador )

   LOCAL nValor, cnMySql := ADOClass():New( AppcnMySqlLocal() )

   cnMySql:cSql := "SELECT SUM( " + iif( nTipoVal == 1, "VALOR", "VALORCOMIS" ) + " ) AS SOMA FROM INFORME WHERE LOCOD=" + NumberSql( nLocador ) + " AND YEAR( IFDATPAG ) = " + ;
      NumberSql( Year( dData ) ) + " AND MONTH( IFDATPAG ) = " + NumberSql( Month( dData ) )
   cnMySql:Execute()
   nValor := cnMySql:NumberSql( "SOMA" )
   cnMySql:Close()
   RETURN nValor


É um trem legal.
Um tborwse chamando outro, DBF, MySQL, tudo junto e misturado.

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 14:54
por JoséQuintas
Isso me lembrou uma coisa, esqueci de dizer naquela viagem no tempo...

Foi nos tempos do Clipper que criei meu webservice de CEP.
O aplicativo acabava pesquisando endereços diretamente nos correios pela internet, usando VBScript.
Isso há 14 anos atrás, e no Clipper.

E me lembrou outra mudança... o uso de MySQL.

Realmente, teve avanço de lá pra cá, e foi na parte que não se vê.
Por isso eu não estava vendo.... rs

Só o salário mesmo, é que andou pra trás....
Essa parte eu sempre esqueço de melhorar.

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 16:09
por JoséQuintas
Quase lá... Este é o que mostra os lançamentos.

STATIC FUNCTION Lancamentos( oBrowse, nKey, nLocador, nLocatario, dData )

   LOCAL cnMySql := ADOClass():New( AppcnMySqlLocal() ), nSelect := Select(), oTBrowse, cTmpFile

   wSave()
   Cls()
   @ 2, 0 SAY templocador->Locador PICTURE "9999"
   @ Row(), Col() + 2 SAY templocador->Nome
   @ 3, 0 SAY templocatario->Locatario PICTURE "9999999"
   @ Row(), Col() + 2 SAY templocatario->Nome
   @ 4, 0 SAY Substr( Dtoc( dData ), 4 )

   cnMySql:cSql := "SELECT IFID, DATA, VALOR, VALORCOMIS FROM INFORME WHERE LOCOD=" + NumberSql( nLocador ) + " AND COD=" + NumberSql( nLocatario ) + ;
      " AND MONTH( IFDATPAG )=" + NumberSql( Month( dData ) )
   cTmpFile := cnMySql:ExecuteDbf()
   SELECT 0
   USE ( cTmpFile ) ALIAS tempLancto
   oTBrowse := { ;
      { "ID",         { || templancto->ifId } }, ;
      { "DATA",       { || templancto->Data } }, ;
      { "VALOR",      { || templancto->Valor } }, ;
      { "VALORCOMIS", { || templancto->ValorComis } } }
   dbView( 6, 0, MaxRow() -4, MaxCol(), oTBrowse, { | oBrowse, nKey | AlteraData( oBrowse, nKey, templancto->ifId, templancto->Data, templancto->Valor, templancto->ValorComis ) } )
   USE
   SELECT ( nSelect )
   fErase( cTmpFile )
   wRestore()
   HB_SYMBOL_UNUSED( oBrowse + nKey )
   RETURN NIL


Outra função de usuário.
Já passa todos os valores pra função de usuário.
Como é MySQL, vou ter o ID do registro pra atualizar depois.
Vai ser só GET e atualizar.

UFA.

Meu modo de trabalho

MensagemEnviado: 21 Jan 2016 16:53
por microvolution
olá professor...
tô passando aqui, só pra dizer q tô ligado nas suas postagens, tá enriquecendo cada vez mais esse fórum.
parabéns!

Meu modo de trabalho

MensagemEnviado: 22 Jan 2016 07:51
por JoséQuintas
Uma alteração nestes dias do menu:

MenuOption( "Consulta Imóveis vhlal01",       "VHLAL01",    { || vhlal01() } )
MenuOption( "Rel.Imóveis (Fichas) p/alugar",  "LISTAIMO",   { || listaimo() } )
MenuOption( "Rel.Imóveis p/ alugar (Tipo)",   "LISTAIMO2",  { || listaimo2() } )
MenuOption( "Cadastro de Imóveis pra alugar", "PALUGUEL",   { || paluguel() } )


MenuOption( "Consulta Imóveis vhlal01",       "PALUGUELPE",    { || pAluguelPe() } )
MenuOption( "Rel.Imóveis (Fichas) p/alugar",  "PALUGUELL1",    { || pAluguelL1() } )
MenuOption( "Rel.Imóveis p/ alugar (Tipo)",   "PALUGUELL2",    { || pAluguelL2() } )
MenuOption( "Cadastro de Imóveis pra alugar", "PALUGUEL",      { || paluguel() } )


Estou alterando o nome dos fontes, o que implica em configurar senhas,etc.

Fazer isso a mão no cliente? correr o risco de esquecer? de jeito nenhum.

FUNCTION AlteradoEntreVersoes()

   IF .NOT. AbreArquivos( "hlsenha" )
      RETURN NIL
   ENDIF
   NovoAcesso( "PDIMOBCAD", "PDIMOB" )
   NovoAcesso( "PNATALINI", "PININATAL" )
   NovoAcesso( "PNATALETI", "PETINATAL" )
   NovoAcesso( "PNATALLIS", "PLISNATAL" )
   NovoAcesso( "PALUGUELPE", "VHLAL01" )
   NovoAcesso( "PALUGUELL1", "LISTAIMO" )
   NovoAcesso( "PALUGUELL2", "LISTAIMO2" )
   CLOSE DATABASES
   RETURN NIL


Ao trocar o programa, ele vai cadastrar acesso automaticamente, liberando a opção nova pra quem tinha acesso à opção velha.

É o que o nome diz mesmo.
É pra etiqueta de natal.
Só vai ser usado agora no ano que vém.
Já tá resolvido um ano antes, pra que esperar.
Daqui um ano já esqueci tudo, mas melhor esquecer deixando pronto.... rs

Meu modo de trabalho

MensagemEnviado: 22 Jan 2016 18:55
por JoséQuintas
Uia....

Minha mensagem de erro de hoje, recebi por email acessível pelo celular:

Problema não consta da lista de erros conhecidos

Horário : 22/01/2016 13:35:04
--------------------------------------------------------------------------------
Já pode tirar aonde apaga txtdimob2015.txt
Já pode tirar aonde apaga corre13.dbf
Já pode tirar aonde apaga corre14.dbf
Já pode tirar aonde apaga infoant.dbf
Ja pode tirar adição do campo CORRENTE.CRDATPAG
Já pode tirar adição do campo INFORME.IFDATPAG
Já pode tirar adição do campo INFORME.IFDATDIM
Já pode tirar adição do campo INFORME.IFDATDOC
Já pode tirar adição do campo INFORME.IFDATLAN
Já pode tirar remoção do campo INFORME.UFIR
Já pode tirar remoção do campo INFORME.UFM
Já pode tirar remoção do campo INFORME.URV
Já pode tirar remoção do campo INFORME.VALURV
Já pode tirar remoção do campo HLDIMO.UFIR
Já pode tirar remoção do campo HLDIMO.UFM
Já pode tirar remoção do campo HLDIMO.URV
Já pode tirar remoção do campo HLDIMO.VALURV
Já pode tirar remoção do campo HLDIMO1.UFM
Já pode tirar remoção do campo HLDIMO1.URV
Já pode tirar remoção do campo HLDIMO1.VALURV


O aplicativo me avisando que já posso remover essas rotinas!!!!
Os campos se referem a base de dados MySQL.

Isso é bom demais.

Meu modo de trabalho

MensagemEnviado: 22 Jan 2016 20:24
por microvolution
JoséQuintas escreveu:Minha mensagem de erro de hoje, recebi por email acessível pelo celular:

prezado professor, gostei dessa ideia, que por coincidência neste mesmo horário estava acabando de sair de uma cliente onde oferecia meu software (e dizia para ela) que em breve quem estivesse conectado receberia informações sobre as novas atualizações do sistema. Semelhante ao que nós vemos no Mozilla Firefox que quando a gente clica em "about..." e ele atualiza automaticamente...
aí o nobre professor posta uma coisa dessas que dá água na boca... que coisa não?
bom, nesse caso dá pra matar 2 coelhos com um tiro só né? ou seja:
- o cliente recebe atualizações e as instala automaticamente; e,
- a gente recebe (por email, sms ou zap).

muito bacana...
se puder me dar o link onde V.postou os códigos, quero estudar "PáVêSiCõsigoAprêdê" rsrsrs

Meu modo de trabalho

MensagemEnviado: 10 Fev 2016 20:03
por JoséQuintas
A rotina de erros padrão é a errorsys.prg
É ela quem apresenta as mensagens de erro usando @ SAY.
Só alterar pra salvar em disco.

E o envio, é o envio normal de e-mails.

Meu modo de trabalho

MensagemEnviado: 12 Jun 2016 01:15
por fladimir
Poderia compartilhar sua rotina errorsys Zé pra efeito de estudo? é a mesma do all-in-one?

Outro assunto meio q no mesmo...
Na minha eu tenho um comando para pegar o IP_Publico mas esta demorando pra retornar com o IP, antes era rapidinho mas o site q eu usava caiu fora do ar... caso já use isso na tua ou saiba outra forma mais rápida e puder passar o link agradeço, mas essa questão não é principal...

[]´s

Meu modo de trabalho

MensagemEnviado: 12 Jun 2016 09:48
por JoséQuintas
É praticamente a mesma.
A diferença é que a do aplicativo consegue pegar informações do aplicativo, como por exemplo o usuário logado e nome da empresa.

A única coisa que a errorsys faz é gravar no disco, no arquivo HB_OUT.LOG que é padrão do Harbour pra erros "não Clipper".

Erro por email não fica na errorsys.
É relativamente simples: ao abrir o aplicativo, se existe HB_OUT.LOG envia o conteúdo por email.
E acumula o conteúdo em um errogeral.log, apagando hb_out.log no final do email - precaução pra sempre ter o histórico de erros completo.

Vai ver lá, que procuro mostrar variáveis de ambiente, que contém nome de servidor, nome de usuário no Windows, nome do computador, etc.
Ajuda a identificar qual é a máquina.

Se preciso IP externo acabo usando meu site:
http://www.jpatecnologia.com.br/meuip.asp

Meu modo de trabalho

MensagemEnviado: 12 Jun 2016 22:56
por fladimir
Entendi.. obrigado zé...

[]´s

Meu modo de trabalho

MensagemEnviado: 12 Ago 2016 23:24
por JoséQuintas
Mudança de hoje, de:

   IF AppcnMySqlLocal() == NIL
      cnMySql := ADOClass():New( AppcnMySqlJPA() )
   ELSE
      cnMySql := ADOClass():New( AppcnMySqlLocal() )
   ENDIF


para

   IF AppcnMySqlLocal() == NIL
      MsgExclamation( "Módulo não disponível sem base MySQL" )
      RETURN
   ENDIF
      cnMySql := ADOClass():New( AppcnMySqlLocal() )


Continuando o:
- Chega de IFs com opção de base de dados
- tchau meu servidor MySQL remoto
- tchau DBF
- tchau hbnetio
- MySQL local obrigatório
- Harbour ou não Harbour, depois se vê

Meu modo de trabalho

MensagemEnviado: 13 Ago 2016 18:52
por microvolution
prezado professor boa noite!
não entendi o complemento do seu post, onde vc diz: "tchau" várias vezes :%
o que significa as expressões no seu comentário?

Meu modo de trabalho

MensagemEnviado: 13 Ago 2016 18:56
por JoséQuintas
É que começa o rumo pra acabar com o uso daquilo.

Meu modo de trabalho

MensagemEnviado: 14 Ago 2016 11:05
por microvolution
continuei sem entender "nadica de nada" :'(

Meu modo de trabalho

MensagemEnviado: 14 Ago 2016 22:50
por JoséQuintas
Pra mim eu tinha respondido algo mais aqui, mas não apareceu.
Mas vamos lá:

Não lembro aonde instalei ou não MySQL, por isso o aplicativo tem muitos IFs.
Cada arquivo pode ser DBF ou tabela no MySQL ou hbnetio.
E alguns arquivos são no meu servidor MySQL, remoto para o cliente.

É essa salada que vai ter fim, dos IFs.
Comecei o rumo a trabalhar somente com MySQL no cliente, sem MySQL remoto, sem DBF e sem hbnetio.
Os DBFs vão começar a "sumir".

Meu modo de trabalho

MensagemEnviado: 16 Ago 2016 02:54
por JoséQuintas
As alterações pra isso (parciais dos fontes):

   SayScroll( "Verificando atualizações" )
   SAllMySql()
   SAllDbf()
   ConvGeral()


primeira etapa, criar/garantir bases mysql:

FUNCTION SAllMySql()

   LOCAL cnMySql := ADOClass():New( AppcnMySqlLocal() )

   IF AppcnMySqlLocal() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "Verificando tabelas MySql" )
   // JPREGUSO antes de todos
   cnMySql:ExecuteCmd( JPREGUSOCreateMySql() ) // primeiro ref log
   cnMySql:ExecuteCmd( JPCONFICreateMySql() )  // segundo  ref configuração
   cnMySql:ExecuteCmd( JPBARRACreateMySql() )


segunda etapa, criar/garantir bases DBF, incluindo conversões/atualização de estruturas:

FUNCTION SAllDbf()

   IF AppDatabase() != DATABASE_DBF
      RETURN NIL
   ENDIF
   SayScroll( "Verificando DBFs" )
   // JPREGUSO antes de todos
   JPREGUSOCreateDbf()
   JPCONFICreateDbf()


terceira etapa, conversões adicionais, que podem ser DBF ou MySQL.

FUNCTION Conv2016()

   SayScroll()
   SayScroll( "Verificando atualizações 2016" )

   IF AppcnMySqlLocal() == NIL
      JPREGUSOCreateDbf()
      JPBARRACreateDbf()
      JPCOTACACreateDbf()
      JPDECRETCreateDbf()
      JPIBPTCreateDbf()
      JPLICMOVCreateDbf()
      JPORDBARCreateDbf()
      JPORDSERCreateDbf()
      JPPROMIXCreateDbf()
      JPVVDEMCreateDbf()
      JPVVFINCreateDbf()
   ELSE
      IF File( "JPREGUSO.DBF" )
         CopyDbfToMySql( "JPREGUSO", .T. )
         fErase( "JPREGUSO.DBF" )
      ENDIF
      IF File( "JPBARRA.DBF" )
         JPBARRACreateDbf()
         CopyDbfToMySql( "JPBARRA", .T. )
         fErase( "JPBARRA.DBF" )
      ENDIF


Simplificando, a cada nova versão, os DBFs vão sumindo e virando MySQL.
Isso vai acontecer quando o cliente atualizar, seja quando for.

Essa parte de atualizar automático não é novidade, uso há muito tempo.
A parte nova é mesmo eliminar de vez alguns DBFs.

Instalou MySQL, criou base de dados, configurou usuário/senha, o aplicativo faz o resto.

Nenhum cliente usa todos os módulos do aplicativo, inclusive alguns só eu mesmo, ou meu servidor.
Então, esses módulos continuam sendo os que estão deixando de ter opção em DBF.

E pra não amontoar muita conversão antiga, o que faço de vez em quando, é guardar o EXE de determinada versão antes de remover as conversões.
Tenho jpa2002.exe, jpa2004.exe, jpa2010.exe, jpa2013.exe, e jpa2015.exe
Se alguém tiver uma versão do aplicativo de 10 anos atrás, é ir executando uma versão dessas de cada vez, até chegar na atual.
Nem sei se alguém ainda tem alguma versão tão antiga, mas não custa nada deixar os EXEs guardados.
Vai automático de Clipper até Harbour + MySQL, só trocando versão.

O ano do EXE é ano que fiz a cópia, não tem nada a ver com quais conversões estão contidas no EXE.

Meu modo de trabalho

MensagemEnviado: 03 Set 2016 19:04
por JoséQuintas
A evolução do meu uso de comandos SQL.

Antes lembrar que pra inclusão ou alteração, é assim, e quase sempre é com muito mais campos:

INSERT INTO CLIENTES ( CODIGO, NOME, ENDERECO ) VALUES ( 1, "JUCA", "RUA CHICO BENTO" )
UPDATE CLIENTES SET NOME="JUCA", ENDERECO="RUA CHICO BENTO" WHERE CODIGO=1


No início, e quanto mais campos pior:

[INSERT INTO CLIENTES ( CODIGO, NOME, ENDERECO ) VALUES ( ] + Ltrim( Str( nCodigo ) ) + [, "] + cNome + [", "] + cEndereco + ["]
[UPDATE CLIENTES SET NOME="] + cNome + [", ENDERECO="] + cEndereco + [" WHERE CODIGO=] + Ltrim( Str( nCodigo ) )


Com funções de conversão, melhorou, evita alguns erros:

"INSERT INTO CLIENTES ( CODIGO, NOME, ENDERECO ) VALUES ( " + NumberSql( nCodigo ) + ", " + StringSql( cNome ) + ", " + StringSql( cEndereco )
"UPDATE CLIENTES SET NOME=" + StringSql( Nome ) + ", ENDERECO=" + StringSql( cEndereco ) + " WHERE CODIGO=" + NumberSql( nCodigo )


Agora com uma classe, dá pra "enxergar" direito:

:QueryCreate()
:QueryAdd( "CODIGO", nCodigo )
:QueryAdd( "NOME", cNome )
:QueryAdd( "ENDERECO", cEndereco )
:QueryExecuteInsert( "CLIENTES" )

:QueryCreate()
:QueryAdd( "NOME", cNome )
:QueryAdd( "ENDERECO", cEndereco )
:QueryExecuteUpdate( "CLIENTES", "CODIGO=" + NumberSql( nCodigo ) )


Apenas estou procurando encontrar a melhor forma de evitar erros de digitação, ou de facilitar entender o fonte.
Nos primeiros, quando tem muitos campos, é complicado enxergar se a ordem está correta, ou quais os campos estão sendo atualizados e com quais valores.
Agora ficou bem visível isso.
Quase ficou igual o replace, que usa campo e valor.

Meu modo de trabalho

MensagemEnviado: 08 Set 2016 15:22
por JoséQuintas
Não sei se foi pra melhor, mas organizei um pouco diferente:

#include "jpa.ch"

FUNCTION ConverteGeral()

   SayScroll()
   SayScroll( "Verificando se há ajustes adicionais" )
   DelTempFiles()
   IF AppDatabase() != DATABASE_DBF
      RETURN NIL
   ENDIF
   ConverteCriaMySql()
   ConverteCriaDbf()
   Converte20160101() // arquivos opcionais dbf/mysql
   IF AppVersaoDbfAnt() < 20160829; Converte20160829();   ENDIF // bancario renomeado
   IF AppVersaoDbfAnt() < 20160829; Converte20160830();   ENDIF // nome cidades mogi mirim e embu das artes
   IF AppVersaoDbfAnt() < 20160829; Converte20160831();   ENDIF // antigos imgcopy
   IF AppVersaoDbfAnt() < 20160829; Converte20160901();   ENDIF // aumento de campo em jpreguso
   IF AppVersaoDbfAnt() < 20160905; Converte20160905();   ENDIF // JPEMISSOR eliminado
   IF AppVersaoDbfAnt() < 20160907; Converte20160907();   ENDIF // jpnfbase
   IF AppVersaoDbfAnt() < 20160908; Converte20160908();   ENDIF // jpedicfg
   IF AppVersaoDbfAnt() < 20130601; RemoveSenhasDesativadas();     ENDIF
   IF File( "rastrea.dbf" ) ;  fErase( "rastrea.dbf" ) ;  ENDIF
   IF File( "rastrea.cdx" ) ;  fErase( "rastrea.cdx" ) ;  ENDIF
   IF File( "jplicmov.dbf" ) ; fErase( "jplicmov.dbf" ) ; ENDIF
   IF File( "jplicmov.cdx" ) ; fErase( "jplicmov.cdx" ) ; ENDIF

   RETURN NIL


1 - Se for acesso via hbnetio, não tem direto a fazer atualização, mudança de estruturas, etc., entra direto no aplicativo.
2 - Cria as estruturas de MySQL, se precisar
3 - Cria/Atualiza as estruturas de DBF, se precisar
4 - Faz outras atualizações nas bases de dados

A última atualização foi esta:

FUNCTION Converte20160908()

   LOCAL lEof, cnMySql := AdoClass():New( AppcnMySqlLocal() )

   IF File( "jpedicfg.cdx" )
      fErase( "jpedicfg.cdx" )
   ENDIF
   IF AppcnMySqlLocal() != NIL
      cnMySql:ExecuteCmd( "ALTER TABLE JPEDICFG MODIFY EDDESEDI VARCHAR(50) NOT NULL DEFAULT ''" )
   ENDIF
   IF File( "jpedicfg.dbf" )
      SayScroll( "Somente em MySQL - JPEDICFG" )
      JPEDICFGCreateDbf()
      USE jpedicfg
      lEof := ( LastRec() < 5 )
      USE
      IF ! lEof
         CopyDbfToMySql( "JPEDICFG", .T. )
      ENDIF
      fErase( "jpedicfg.dbf" )
   ENDIF

   RETURN NIL

STATIC FUNCTION JPEDICFGCreateDbf()

   LOCAL mStruOk

   SayScroll( "JPEDICFG, verificando atualizações" )
   mStruOk := { ;
      { "EDNUMLAN",  "C", 6 }, ;
      { "EDTIPO",    "C", 6 }, ;
      { "EDCODJPA",  "C", 6 }, ;
      { "EDCODEDI1", "C", 20 }, ;
      { "EDCODEDI2", "C", 20 }, ;
      { "EDDESEDI",  "C", 30 }, ;
      { "EDINFINC",  "C", 80 }, ;
      { "EDINFALT",  "C", 80 } }
   IF ! ValidaStru( "JPEDICFG", mStruOk )
      MsgStop( "JPEDICFG não disponível!" )
      QUIT
   ENDIF

   RETURN NIL


Se tem MySQL, aumenta o campo ref. descrição.
Se tem conteúdo acima de 5 registros nesse arquivo de conversão JPEDICFG, transfere pra MySQL.
A partir desta versão, esse DBF não existe mais.
Quem não usou até agora, só usa se instalar MySQL.

Assim que instalar essa versão em todos os clientes, vou poder apagar a conversão.
O único lado ruim dessas coisas, de remover conversão, é que se algum dia precisar de um backup antigo, pode não vai funcionar mais.
Mas não sei em qual situação isso seria necessário.

Em breve vão acabar os "arquivinhos" de uso exclusivo.
Vai precisar de outro esquema de atualização pros demais.
Talvez eu acabe usando um simultâneo, gravando DBF + MySQL ao mesmo tempo.

A minha intenção é o cliente e eu podermos continuar trocando o programa todo dia e toda hora, sempre que precisar.
Então não dá pra esperar alterar 300 programas que mexem com clientes, por exemplo, pra poder trocar versão.
Acho que a gravação simultânea vai ser a melhor opção.

Meu modo de trabalho

MensagemEnviado: 09 Set 2016 17:22
por JoséQuintas
Troquei no cliente ontem à noite.

Hoje foi só um erro que precisou ser corrigido: esqueci de colocar a string entre aspas no comando MySql.

Ontem uma determinada tarefa usava DBF, e hoje usa MySQL.

Ainda bem, tem sido assim minhas mudanças de DBF pra MySQL.
Se tiver algum erro, é um erro básico, de rápida solução.

Dados pra rastreamento, só esse cliente usa.
Configuração pra importação de XML, vários clientes usam, mas todos eles tem servidor MySQL instalado.
Com isso, essas opções continuam existindo no aplicativo, mas só estarão disponíveis se existir servidor MySQL.
São bases relativamente pequenas, mas de uso contínuo.

Continuo com uma única versão do aplicativo.
Continuo com bases de dados em DBF e em MySQL, o que tem em DBF não tem em MySQL.
Mas alguns arquivos que eu tinha alterado antes pra opcionais, continuam assim, podem ser DBF ou MySQL.
Como exemplo, o que registra tudo que acontece no aplicativo. Está em MySQL aonde tem MySQL, e em DBF aonde não tem MySQL.

É bom trabalhar assim, fazer tranquilo, sem pressão.
O lado ruim é que não estou cobrando isso de ninguém, os clientes vão passar de DBF pra MySQL sem custo nenhum.
Sempre esqueço dessa parte.....
.

Meu modo de trabalho

MensagemEnviado: 12 Set 2016 17:41
por rosalvo rosa
Boa tarde José Quintas.

Poderia disponibilizar o arquivo Jpa.ch ?

Trabalho com HB30 e não tenho na minha pasta Include.

Desde já agradeço

Abraço

Rosalvo

Meu modo de trabalho

MensagemEnviado: 12 Set 2016 18:18
por JoséQuintas
Se isso se refere à classe Sefaz, deixei por esquecimento.
É que uso nos fontes do meu aplicativo, mesmo nem todos precisando do que tem lá dentro.

A título de curiosidade, no momento tem isto:

/*
PROGRAMA...: JPA.CH - HEADER DA JPA
CRIACAO....: 2010.03.22.0900 - JOSE
*/

#xtranslate MemoWrit( ... )  => ChangeMemoWritTohb_MemoWrit( ... )
#xtranslate TempFile( ... )  => ChangeTempFileToMyTempFile( ... )
#xtranslate Descend( ... ] ) => ChangeDescendToMyDescend( ... )
#xtranslate Xtoc( ... )      => ChangeXtocToToString( ... )

#ifndef AD_STATE_CLOSED
   #define AD_STATE_CLOSED  0
#endif
#define DATABASE_DBF      1
#define DATABASE_HBNETIO  2
#define MYSQL_MAX_CMDINSERT   500000
#define MYSQL_MAX_RECBACKUP   25000

#define DOW_DOMINGO   1
#define DOW_SEGUNDA   2
#define DOW_TERCA     3
#define DOW_QUARTA    4
#define DOW_QUINTA    5
#define DOW_SEXTA     6
#define DOW_SABADO    7

#define AUX_BANCO   "BANCO." // bancos
#define AUX_CADCTL  "CADCTL" // Contábil para Cadastro
#define AUX_CARCOR  "CARCOR" // códigos de carta de correção
#define AUX_CCUSTO  "CCUSTO" // centros de custo
#define AUX_CFOP    "CFOP.." // CFOP
#define AUX_CLIGRU  "CLIGRU" // Cliente - Grupos
#define AUX_CNAE    "CNAE.." // CNAE
#define AUX_CTAADM  "CTAADM" // contas administrativas
#define AUX_DEMDOC  "DEMDOC" // documentos do demonstrativo
#define AUX_EDICFG  "EDICFG" // configuracao de edi
#define AUX_FILIAL  "FILIAL" // filiais
#define AUX_FINANC  "FINANC" // financeiras
#define AUX_FINOPE  "FINOPE" // financeiro - operações
#define AUX_FINPOR  "FINPOR" // financeiro - portadores
#define AUX_ICMCST  "ICMCST" // ICMS CST
#define AUX_IPICST  "IPICST" // IPI CST
#define AUX_IPIENQ  "IPIENQ" // IPI Enquadramento
#define AUX_LICTIP  "LICTIP" // Tipo de licença
#define AUX_LICOBJ  "LICOBJ" // Tipo de objeto
#define AUX_MEIAUT  "MEIAUT" // Meio pelo qual foi feita a autorização
#define AUX_MIDIA   "MIDIA." // Midia, forma por onde chegou o cliente
#define AUX_MODFIS  "MODFIS" // Modelo de documento fiscal
#define AUX_MOTIVO  "MOTIVO" // Motivo de cancelamento
#define AUX_ORDCLI  "ORDCLI" // ordem de serviço - status de cliente
#define AUX_ORDSTA  "ORDSTA" // ordem de serviço - status geral
#define AUX_ORDTEC  "ORDTEC" // ordem de serviço - status técnico
#define AUX_ORIMER  "ORIMER" // origem da mercadoria
#define AUX_OSAPAR  "OSAPAR" // ordem de serviço - aparelho
#define AUX_PISCST  "PISCST" // PIS CST
#define AUX_PISENQ  "PISENQ" // PIS Enquadramento
#define AUX_PPRECO  "PPRECO" // percentuais de tabelas de preço
#define AUX_PROGRU  "PROGRU" // Produto Grupo
#define AUX_PRODEP  "PRODEP" // Produto Departamento
#define AUX_PROSEC  "PROSEC" // Produto Seção
#define AUX_PROLOC  "PROLOC" // Produto Localização
#define AUX_PROUNI  "PROUNI" // Produto unidade
#define AUX_QUAASS  "QUAASS" // Qualificação do Assinante
#define AUX_TABAUX  "TABAUX" // Tabelas Auxiliares
#define AUX_TARSTA  "TARSTA" // Tarefas Status
#define AUX_TARTIP  "TARTIP" // Tarefas Tipos
#define AUX_TRICAD  "TRICAD" // Tributação de Cadastros
#define AUX_TRIEMP  "TRIEMP" // Tributação de empresa
#define AUX_TRIPRO  "TRIPRO" // Tributação de produtos
#define AUX_TRIUF   "TRIUF." // Tributação de UFs

#define AUX_CXATIP   "CXATIP"   // Tipo de lançamento no caixa

#define AUX_ECIVIL   "ECIVIL"   // Estado Civil
#define AUX_REAJUS   "REAJUS"   // Tipo de Reajuste
#define AUX_TIPIMO   "TIPIMO"   // Tipo de Imóvel
#define AUX_TIPCTR   "TIPCTR"   // Tipo de Contrato

#define HLCAIXA_MONE_CHEQUE      1
#define HLCAIXA_MONE_DINHEIRO    2
#define HLCAIXA_MONE_CARTAO      3
#define HLCAIXA_MONE_DOLAR       4
#define HLCAIXA_MONE_VALE        5

#define HLCAIXA_DB_ENTRADA       1
#define HLCAIXA_DB_SAIDA         2
#define HLCAIXA_DB_EXTRACAIXA    3

#define HLCAIXA_TIPO_RECIBO               1
#define HLCAIXA_TIPO_RECIBO_ALUGUEL       2
#define HLCAIXA_TIPO_RECIBO_LUZ           3
#define HLCAIXA_TIPO_RECIBO_AGUA          4
#define HLCAIXA_TIPO_RECIBO_PREDIAL       5
#define HLCAIXA_TIPO_RECIBO_CONDOMINIO    6
#define HLCAIXA_TIPO_RECIBO_TELEFONE      7
#define HLCAIXA_TIPO_RECIBO_CONTRATOS     8
#define HLCAIXA_TIPO_DIVERSOS_ENTRADA     9
#define HLCAIXA_TIPO_RECIBO_FIANCA       10
#define HLCAIXA_TIPO_RECIBO_TAXALIXO     11
#define HLCAIXA_TIPO_RECIBO_ADICDIVS     11
#define HLCAIXA_TIPO_RECIBO_IRRF         12
#define HLCAIXA_TIPO_BOLETO_AVULSO       13
#define HLCAIXA_TIPO_HAVER               15
#define HLCAIXA_TIPO_RECIBO_DESCDIVS     17
#define HLCAIXA_TIPO_EXTRATOS            20
#define HLCAIXA_TIPO_ADIANTAMENTO        21
#define HLCAIXA_TIPO_DIVERSOS_SAIDA      22
#define HLCAIXA_TIPO_CHEQUE_DEPOSITO     23
#define HLCAIXA_TIPO_DEVE                26
#define HLCAIXA_TIPO_RETIRADACC          27
#define HLCAIXA_TIPO_BOLETO              28

#define RECIBO10_ENTRADA 1
#define RECIBO10_SAIDA   2

Meu modo de trabalho

MensagemEnviado: 12 Set 2016 18:23
por JoséQuintas
Pensando bem, vou alterar o HBP do meu aplicativo e remover o #include de todos os fontes.

-ijpa.ch

Meu modo de trabalho

MensagemEnviado: 12 Set 2016 19:43
por Poka
Olá Quintas,
É bom saber como os outros trabalham, para ver se estamos fazendo certo, se podemos melhorar, enfim sempre é um aprendizado a mais.

Sobre mudar de dbf para SQL , no meu caso Firebird.

Tenho um sistema grande, com muitas linhas, incluindo compras, almoxarifado, estoque de matéria prima, estoque de acabados, contas a pagar, contas a receber, produção, custo, emissão de notas, pedidos, etc.

Decidi por fazer o sistema inteiro , para depois mudar de uma vez. A parte de passar os dados do dbf para firebird já esta tudo pronto, vou fazendo por partes agora, sempre atualizando os dados no novo, checando os relatórios, mostrando para cada setor como vai ficar, até terminar, queria terminar esse ano, mas com certeza não vai dar.
Quando mudar vai ser assim, encerra no dia com dbf e no outro inicia com firebird.
Vamos ver o que vai dar

um abraço

Poka

Meu modo de trabalho

MensagemEnviado: 13 Set 2016 08:37
por rosalvo rosa
Bom dia!

Na verdade estou precisando do arquivo jpa.ch para uso em uma função com Mouse, e aqui eu preciso dele mesmo.

Obrigado.

Rosalvo

Meu modo de trabalho

MensagemEnviado: 13 Set 2016 08:48
por asimoes
Quintas,

Você tá usando -ijpa.ch tem como incluir mais de 1 .ch com este comando?

Eu utilizo:

-u+hbcompat.ch

Meu modo de trabalho

MensagemEnviado: 13 Set 2016 11:39
por JoséQuintas
Fui usar e não aceitou o -i
Não entendi, devo ter me confundido com isso.

Sobre o jpa.ch
O meu atual é esse que postei.
Não está confundindo? ou será que é um jpa.ch meu antigo?

Faça o seguinte, dependendo da rotina: compile usando -w3 -es2
Vai mostrar quais palavras que estão faltando no #define.

Por exemplo, retirando jpa.ch do meu programa principal:

d:\CDROM\FONTES\INTEGRA>hbmk2 jpa.hbp
hbmk2: Processing environment options: -comp=msvc
Harbour 3.4.0dev (b4aaa05) (2016-09-06 07:30)
Copyright (c) 1999-2016, http://github.com/vszakats/harbour-core/
Compiling 'jpa.prg'...
100
jpa.prg:125: warning W0001  Ambiguous reference 'DATABASE_DBF'
200
No code generated.
hbmk2 [jpa]: Error: Running Harbour compiler (built-in). 1


Esse DATABASE_DBF está definido no jpa.ch, ao retirar #include "jpa.ch" reclamou que falta isso.

Não faço idéia do que pode precisar, deve ser de um jpa.ch anterior talvez.

Meu modo de trabalho

MensagemEnviado: 13 Set 2016 11:46
por JoséQuintas
Voltando ao que o A Simões postou:
Talvez dê pra usar o gtwvg.hbc como exemplo, onde adiciona .ch
Mas acho que pra usar em aplicativo teria que deixar na pasta como .hbm

description=GTWVG (an extension of GTWVT, win-only)

skip={!allwin}

incpaths=.

headers=hbgtwvg.ch wvgparts.ch wvtwin.ch

gt=${_HB_DYNPREF}${hb_name}${_HB_DYNSUFF}

libs=hbwin.hbc

gui=yes
mt=yes

Meu modo de trabalho

MensagemEnviado: 13 Set 2016 13:09
por asimoes
Quintas,

Com -u

Uso assim:

-u+hbcompat.ch
-u+stdsmf.ch //header de uso em toda aplicação
-u+ga.ch //header de uso em toda aplicação

Pode usar no hbp ou hbm

Assim, só no hbc

headers=hbgtwvg.ch wvgparts.ch wvtwin.ch

Meu modo de trabalho

MensagemEnviado: 14 Set 2016 11:43
por JoséQuintas
Isso do mouse, será que não tem a ver com isto que retirei em 2012.
Usava #defines que existem no Harbour, mas não existiam no Clipper 5.2

2012.png


Se puder analisar a compilação do fonte usando -w3 -es2, poderemos identificar fácil quais as variáveis que precisa do jpa.ch, porque o atual não deve servir mais.

Meu modo de trabalho

MensagemEnviado: 14 Set 2016 12:14
por JoséQuintas
Comecei a usar o GIT em junho/2015.
Mas tinha ZIPs de backup antigos.
Com muita paciência, fui registrando um por um no GIT.

Isso me permitiu apagar alguns gigabytes do HD, sem perder os backups antigos.

Até deu trabalho fazer isso pra deixar registrado no GIT.
Mas foi pra fazer uma vez só.
Toda minha história de fontes está contida em 170MB, e não sei se algum dia vai chegar a 200MB.
O GIT economiza muito espaço em disco para controlar isso.
Bem melhor 170MB pra vida inteira, do que 100MB para cada dia de backup.

Recomendo a todos usar o GIT.
Nem que seja só pra usar de backup, enquanto não souber aproveitar mais os recursos.
Não sou expert nele, não conheço nem 1% dos recursos, mas o que conheço já ajuda muito.
Essa tela é da interface Windows, que é a que uso.

gitcontrol.png


Nota: Ao fazer o download do repositório do Harbour, também vém a vida inteira dele, desde a versão ZERO.
Por exemplo, o repositório do Vszakats, por ser um Fork, olha só o início do Harbour, em 1999, pelo menos a parte registrada no github
harbour.png

Meu modo de trabalho

MensagemEnviado: 16 Set 2016 09:44
por JoséQuintas
Só a título de curiosidade.
Geralmente só acrescento mais coisas no EXE, então ele sempre aumenta de tamanho.
Mas de vez em quando simplifico algumas rotinas já existentes.
Então às vezes fica menor.

 Pasta de e:\backupd\cdrom\fontes\integra

08/09/2016  20:37         1.889.472 JPA.exe
               1 arquivo(s)      1.889.472 bytes
               0 pasta(s)   466.481.528.832 bytes disponíveis

d:\cdrom\fontes\integra>dir *.exe
O volume na unidade D é TRABALHO
O Número de Série do Volume é 640D-3E41

Pasta de d:\cdrom\fontes\integra

16/09/2016  09:02         1.883.840 JPA.exe
               1 arquivo(s)      1.883.840 bytes
               0 pasta(s)   469.194.063.872 bytes disponíveis


Depois de uma semana, mesmo tendo acrescentado recursos, o EXE está menor.
Só altero fontes pra ficar mais fácil de mexer neles.
O tamanho do EXE não é o motivo, é apenas uma consequência das alterações.

Eu apenas aproveito ao fazer uma alteração, pra ver se não tem fontes parecidos que poderia fazer a mesma coisa.
Como tudo é o mesmo EXE, vale a pena, mesmo sendo um pouquinho de cada vez.

Meu modo de trabalho

MensagemEnviado: 18 Set 2016 11:23
por JoséQuintas
Uma coisa que acostumei a usar, mas não é obrigatória no Harbour, é o ALIAS no replace.

   REPLACE ;
      jpcadas->cdEndereco WITH mcdEndereco, jpcadas->cdNumero   WITH mcdNumero,   jpcadas->cdCompl    WITH mcdCompl, ;
      jpcadas->cdBairro   WITH mcdBairro,   jpcadas->cdCidade   WITH mcdCidade,   jpcadas->cdUF       WITH mcdUf, ;
      jpcadas->cdCep      WITH mcdCep,      jpcadas->cdContato  WITH mcdContato,  jpcadas->cdTelefone WITH mcdTelefone, ;
      jpcadas->cdFax      WITH mcdFax,      jpcadas->cdEndCob   WITH mcdEndCob,   jpcadas->cdNumCob   WITH mcdNumCob, ;
      jpcadas->cdComCob   WITH mcdComCob,   jpcadas->cdBaiCob   WITH mcdBaiCob,   jpcadas->cdCidCob   WITH mcdCidCob, ;
      jpcadas->cdUFCob    WITH mcdUfCob,    jpcadas->cdCepCob   WITH mcdCepCob,   jpcadas->cdConCob   WITH mcdConCob, ;
      jpcadas->cdTelCob   WITH mcdTelCob,   jpcadas->cdFaxCob   WITH mcdFaxCob,   jpcadas->cdNomEnt   WITH mcdNomEnt, ;
      jpcadas->cdEndEnt   WITH mcdEndEnt,   jpcadas->cdNumEnt   WITH mcdNumEnt,   jpcadas->cdComEnt   WITH mcdComEnt, ;
      jpcadas->cdBaiEnt   WITH mcdBaiEnt,   jpcadas->cdCidEnt   WITH mcdCidEnt,   jpcadas->cdUfEnt    WITH mcdUfEnt, ;
      jpcadas->cdCepEnt   WITH mcdCepEnt,   jpcadas->cdConEnt   WITH mcdConEnt,   jpcadas->cdTelEnt   WITH mcdTelEnt, ;
      jpcadas->cdFaxEnt   WITH mcdFaxEnt


Isso vai me ajudar no MySql !!!
Por exemplo, se eu colocar pra atualizar simultâneo o JPCADAS no DBF e no MySql....
Vou ter que pesquisar todos os fontes que fazem replace no jpcadas.
Como todos tem o ALIAS, mais fácil.

Mas nessas horas faz falta um recurso que não encontrei em nenhum editor de texto: pesquisar no resultado de uma pesquisa, ou pesquisa combinada.
pesquisa combinada: Poderia procurar em todos os fontes a ocorrência das palavras JPCADAS e WITH na mesma linha.
ou pesquisar WITH no resultado da pesquisa de JPCADAS.

Primeiro passo: gravação simultânea DBF e MySQL - só o que for incluso/atualizado ficará no MySql
Segundo passo: gravar tudo dos DBFs no MySQL - tudo duplicado
Terceiro passo: buscar somente do MySQL
Quarto passo: gravar somente no MySql
Último passo: Mais e mais recursos do MySQL

Parece trabalhoso, mas não sei se perceberam:
Vou poder alterar um único fonte, e liberar para o cliente.
Alterar outro fonte, liberar para o cliente.
E por aí vai. Sempre alterando e instalando, nunca vai existir um fonte em espera.

Isso é diferente de: alterar tudo correndo pra tudo funcionar no MySQL, ficar sem relatórios até terminar, ficar sem instalar até terminar, criar duas versões de fonte pra trocar depois, etc.
E isso vai me dar tempo de ir tirando dúvidas do MySql, melhorar rotinas aproveitando novos recursos, etc.

Por enquanto a idéia que achei mais apropriada foi essa.

Meu modo de trabalho

MensagemEnviado: 27 Out 2016 14:12
por JoséQuintas
Tenho um relatório monstruoso de pedidos.
Preciso acrescentar opção de escolher um cliente, matriz e filiais, como fazer?

Simples: (nota: relatório é um dos poucos aonde ainda uso PRIVATE)

Primeiro as variáveis necessárias pra escolha: array de opções, variável com a opção selecionada, código do cliente
E a montagem de seleção, que como é sempre igual, tenho pronta.

MEMVAR acTxtCliente, nOpcCliente, mCliente
...
nOpcCliente := 1
      acTxtCliente := { "Todos", "Específico" }
      mCliente := Space(6)
...
   JPCADAS1Class():Intervalo( nOpcGeral + 3, 25, @nOpcCliente, @mCliente )


antes de emitir o relatório, pego a lista de códigos que corresponde ao mesmo prefixo de CNPJ

   IF nOpcCliente != 1
      acClienteList := PNOT0100FiliaisList( mCliente )
   ENDIF


E na escolha de pedidos, coloco o filtro, que só depende do código de cliente

     CASE nOpcCliente == 2 .AND. AScan( acClienteList, jppedi->pdCliFor ) == 0
         SKIP
         LOOP


E se fosse MySQL, seria só acrescentar esse filtro na cláusula WHERE.

Nem preciso me preocupar com o restante do relatório. Basta mexer aonde interessa.
Lógico, o programa do relatório estando organizado, cada coisa no seu lugar, já se sabe em que lugar mexer.

Nada de gênio, nada de expert, apenas solução simples no lugar correto.
Quer fazer igual? só deixar o fonte organizado, cada coisa no seu lugar.

Ah sim... as variáveis private.
É porque em certos casos quero aproveitar o array de seleção como complemento do titulo.
Apenas como exemplo:

acTitulo := { "RELATORIO X", "Cliente:" + acTxtCliente[ nOpcaoCliente ] + iif( nOpcaoCliente == 1,"",  " " + mCliente ) }


De repente o usuário escolhe um filtro, e esquece que colocou filtro, melhor mostrar pra ele exatamente o que pediu.

Meu modo de trabalho

MensagemEnviado: 04 Nov 2016 12:45
por JoséQuintas
Chegou hoje por email:

DEMO Erro em 2016-11-04 09:25:53

Folder: C:\Users\admin\Desktop\lixo\
MySQL local: NÃO
Windows: Windows 7 6.1 SP1
Computer Name: BARBOSA-DSK
Windows User: admin
Logon Server: \BARBOSA-DSK
User Domain: BARBOSA-DSK
Harbour: Harbour 3.4.0dev (e679c4a) (2016-10-28 20:57)
Compiler: Microsoft Visual C++ 16.0.40219 (32-bit)
GT: WVG

Error BASE/1005 No exported variable: ENUMINDEX
Called from _ENUMINDEX(0)
Called from BOXMENU(770)
Called from BOXMENU(839)
Called from MENUPRINC(707)
Called from SISTEMA(129)
Called from (b)MAIN(69)


Deve ser na máquina de alguém aqui do fórum.
No fonte, coloquei um underline a menos, _EnumIndex ao invés de __EnumIndex.
Curioso é que o erro só aconteceu na máquina dele.
Já fiz a correção.

Meu modo de trabalho

MensagemEnviado: 04 Nov 2016 12:52
por JoséQuintas
Erro executando comando:-2147217900 Duplicate column name 'ATDESCRI'


Este foi em cliente.
Minha rotina de testar campo existente precisou de ajuste.
Motivo: No Linux, a tabela de schemmas é case sensitive, e usei o nome do banco de dados em maiúsculas, quando está em minúsculas.
No Windows isso não é problema.
Como acusou que não existia o campo.... deu erro na hora de acrescentar.

A titulo de curiosidade:

METHOD FieldExists( cField, cTable ) CLASS ADOClass

   LOCAL nQtd

   ::cSql := "SELECT COUNT(*) AS QTD FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=" + StringSql( Lower( AppEmpresaApelido() ) ) + " AND TABLE_NAME=" + StringSql( cTable ) + ;
                   " AND COLUMN_NAME=" + StringSql( cField )
   ::Execute()
   nQtd := ::Value( "QTD" )
   ::CloseRecordset()

   RETURN nQtd > 0


Mas no post lembrei de um método que criei depois, e dá pra simplificar, pelo menos um pouquinho.

METHOD FieldExists( cField, cTable ) CLASS ADOClass

   ::cSql := "SELECT COUNT(*) AS QTD FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=" + StringSql( Lower( AppEmpresaApelido() ) ) + " AND TABLE_NAME=" + StringSql( cTable ) + ;
                   " AND COLUMN_NAME=" + StringSql( cField )
   RETURN ::ReturnValueAndClose(  "QTD"  )

Meu modo de trabalho

MensagemEnviado: 04 Mar 2017 11:09
por Abel
Ola, JoseQuintas

Como voce fez para colocar esta imagem de fundo
com o menu sobreposto ?

pode dar um exemplo...?

Grato,
Abel

Meu modo de trabalho

MensagemEnviado: 04 Mar 2017 12:48
por JoséQuintas
Com a GTWVG e menu normal, trata-se apenas de um quebra-galho.
O que fiz foi o seguinte: desenhar e colocar o menu por cima do desenho.
Não dá pra perceber a repintura, e dá a sensação de que o desenho se mantém.

Exemplo:

DO WHILE .T.
    DrawPicture( ... )
   @ 1, 0 PROMPT ...
   @ 2, 0 PROMPT ...
   # 3. 0 PROMPT ...
   MENU TO nOpc
ENDDO

Meu modo de trabalho

MensagemEnviado: 04 Mar 2017 16:49
por Abel
ola,
não achei nada no forum sobre DrawPicture

Qual a Lib e quais parametros preciso passar ?

Grato,
ABEL

Meu modo de trabalho

MensagemEnviado: 04 Mar 2017 17:13
por JoséQuintas
Desculpe, foi só exemplo.
Cada LIB tem uma opção, a que mencionei foi esta, da GTWVG:

https://github.com/JoseQuintas/AllInOne/blob/master/allinone/menu.prg#L160-L164

Meu modo de trabalho

MensagemEnviado: 08 Mar 2017 10:27
por JoséQuintas
Fiz um teste agora, mas é um controle meu que não existe na GTWVG oficial.
Ainda não coloquei recurso no controle de redimensionar imagem, mas fica igual outras LIBs.

telajpa.png


Parece piada, mas é sério: até criei controles de uso em tela Windows, pra GTWVG, mas eu mesmo não uso.

Meu modo de trabalho

MensagemEnviado: 16 Jul 2017 12:39
por JoséQuintas
E agora José?

josequintas.png
josequintas.png (6.45 KiB) Visualizado 2345 vezes

Meu modo de trabalho

MensagemEnviado: 16 Jul 2017 17:58
por microvolution
JoséQuintas escreveu:Fiz um teste agora, mas é um controle meu que não existe na GTWVG oficial.
Ainda não coloquei recurso no controle de redimensionar imagem, mas fica igual outras LIBs.

telajpa.png


Parece piada, mas é sério: até criei controles de uso em tela Windows, pra GTWVG, mas eu mesmo não uso.

meu professor, você conseguiu resolver o problema dos textos/imagens que sobrepõem aí no menu e textos?

Meu modo de trabalho

MensagemEnviado: 16 Jul 2017 23:53
por JoséQuintas
A saída seria usar menu Windows, e deixar a imagem fixa de fundo, igual outras LIBs fazem.

Meu modo de trabalho

MensagemEnviado: 18 Set 2017 18:41
por JoséQuintas
Como é um uso pessoal, colocar aqui, mas vale pra vocês e pra tudo que usam, se for Harbour.
Vale pra hmg3, hmge, oohg, hwgui, e tudo mais, seja qual for a versão do Harbour ou compilador C.
É o meu jeito, não tem a ver com o uso oficial, e como uso pra teste apenas, não sei num uso pesado.

Cada LIB está em um lugar:

- d:\cdrom\fontes\integra\sefazclass
- d:\cdrom\fontes\integra\josequintas
- d:\cvsfiles\allgui\hmg3
- d:\cvsfiles\allgui\hmge
- d:\cvsfiles\allgui\hwgui
- d:\cvsfiles\allgui\oohg

Agora vamos por partes....

1) Localização de arquivos
No caso das LIBs gráficas juntei todos HBP e HBC numa única pasta d:\cvsfiles\allgui\allgui
E no caso de HBX e LIBs, deixei a saída pra pasta d:\harbour\addons, assim o HBMK2 pega automático

2) Coloquei pro HBMK2 encontrar tudo
Criei um HBMK.HBC com isto, dentro da pasta d:\harbour\bin
É algo como um SET PATH pro HBMK2

harbour\bin\hbmk.hbc

libpaths=d:\cdrom\fontes\integra\sefazclass
libpaths=d:\cdrom\fontes\integra\josequintas
libpaths=d:\cvsfiles\allgui\allgui


3) Na sefazclass, configurei apenas a sefazclass

sefazclass.hbc

Description=any
libs=hbhpdf.hbc hbzebra.hbc hbwin.hbc hbct.hbc sefazclass
libpaths=${hb_install_prefix}\addons\lib\${hb_plat}\${hb_comp}
incpaths=include


sefazclass.hbp

source\ze_capicom.prg
source\ze_SpedAssina.prg
source\ze_spedAssinachk.prg
source\ze_SpedDaCte.prg
source\ze_SpedDaEvento.prg
source\ze_SpedDaMdfe.prg
source\ze_SpedDaNfe.prg
source\ze_SpedDaNFCe.prg
source\ze_SpedDaGeral.prg
source\ze_spedsefazclass.prg
source\ze_spedxmlclass.prg
source\ze_xmlfunc.prg
source\ze_miscfunc.prg
source\ze_xharbour.prg
source\ze_digitodoc.prg
source\ze_extenso.prg
source\ze_inscestadual.prg

-w3
-es2
-m
-n

-o${hb_install_prefix}\addons\lib\${hb_plat}\${hb_comp}\${hb_name}

sefazclass.hbc

-hblib
-hbx=${hb_install_prefix}\addons\${hb_name}


Notem o seguinte:
a única indicação direta de pasta é pra saída da lib: hbx na pasta do Harbour\addons, e lib na pasta por exemplo add-ons\lib\win\mingw
a pasta de include é relativa à pasta aonde está o HBC e/ou HBP, não precisa colocar path completo

4) josequintas
Exatamente a mesma coisa, só mudam os fontes
Mas ela depende da sefazclass.hbc, por isso consta das libs

josequintas.hbc

Description=any
libs=josequintas sefazclass.hbc
libs=sefazclass.hbc hbhpdf.hbc hbwin.hbc hbzebra.hbc gtwvg.hbc hbct.hbc hbtip.hbc hbnetio.hbc hbziparc.hbc hbmisc.hbc
libpaths=${hb_install_prefix}\addons\lib\${hb_plat}\${hb_comp}
incpaths=include
incpaths=..\..\build


josequintas.hbp

source\*.prg
source\particular\*.prg
source\renomear\*.prg
josequintas.hbc
-workdir=c:\temp
-hblib
-o${hb_install_prefix}\addons\lib\${hb_plat}\${hb_comp}\${hb_name}
-hbx=${hb_install_prefix}\addons\${hb_name}
-w3
-es2
-m
-n
-inc


5) As LIBs gráficas.....
Como todas as LIBs gráficas tem algo em comum, e antes testava misturar todas, criei um HBC com o que tem de comum nelas.

allgui.hbc

Description=allgui.hbc
incpaths=.
libpaths=${HB_INSTALL_PREFIX}\addons\lib\win\${hb_comp}
libs=msvfw32 vfw32 hbmisc.hbc hbziparc.hbc hbhpdf.hbc hbct.hbc gtwvg hbwin
gui=yes
mt=yes


Nada demais, só a pasta fixa de todas harbour\add-ons\lib\win\mingw (exemplo), e algumas libs padrão

6) HWGUI. Na verdade pra todas as demais LIBs, tudo igual, só altera a lista de fontes, e talvez um parâmetro a mais específico.

Description=hwgui.hbc
incpaths=..\hwgui\include
libs=allgui.hbc hwgui


hwgui.hbp

..\hwgui\source\winapi\*.prg
..\hwgui\source\winapi\*.c
..\hwgui\source\common\procmisc\*.prg
..\hwgui\source\common\procmisc\*.c
..\hwgui\contrib\ext_controls\*.prg
..\hwgui\contrib\ext_controls\*.c
{!msvc}..\hwgui\contrib\activex\*.prg
{!msvc}..\hwgui\contrib\activex\*.c
{!msvc}..\hwgui\source\common\xml\*.prg
{!msvc}..\hwgui\source\common\xml\*.c

-cflag=-DHWG_USE_POINTER_ITEM

-o${hb_install_prefix}\addons\lib\win\${hb_comp}\hwgui

hwgui.hbc

-hblib
-quiet
-w1
-es2

-hbx=${hb_install_prefix}\addons\${hb_name}


No HBC indicando aonde buscar os arquivos INCLUDE e LIB
No HBP a lista de fontes, e a saída pra harbour\add-ons

7) OOHG
oohg.hbc

Description=oohg.hbc
incpaths=..\oohg\include
libs=allgui.hbc oohg


../oohg/source/*.c
../oohg/source/*.prg
-hblib
oohg.hbc
-inc
-o${HB_INSTALL_PREFIX}/addons/lib/win/%{h_comp}/${hb_name}
-hbx=${HB_INSTALL_PREFIX}/addons/${hb_name}


8) HMG3

hmg3.hbc

Description=hmg3.hbc
incpaths=..\hmg3\include
libs=allgui.hbc hmg3


hmg3.hbp

..\hmg3\source\*.prg
..\hmg3\source\*.c
..\hmg3\source\bostaurus\*.prg
..\hmg3\source\bostaurus\*.c
..\hmg3\source\class\*.prg
..\hmg3\source\class\*.c
..\hmg3\source\crypt\*.prg
..\hmg3\source\crypt\*.c
..\hmg3\source\edit\*.prg
..\hmg3\source\edit\*.c
..\hmg3\source\editex\*.prg
..\hmg3\source\editex\*.c
..\hmg3\source\graph\*.prg
..\hmg3\source\graph\*.c
..\hmg3\source\hbvpdf\*.prg
..\hmg3\source\hbvpdf\*.c
..\hmg3\source\ini\*.prg
..\hmg3\source\ini\*.c
..\hmg3\source\report\*.prg
..\hmg3\source\report\*.c
..\hmg3\hfcl\source\*.prg
..\hmg3\hfcl\source\*.c

-stop{msvc}

-o${hb_install_prefix}\addons\lib\win\${hb_comp}\hmg3

-hblib

hmg3.hbc

-dflag="pthread -static -lpthread"

-quiet
-w0
-es0

-hbx=${hb_install_prefix}\addons\${hb_name}


9) HMG Extended

hmge.hbc

Description=hmge.hbc
incpaths=..\hmge\include
libs=allgui.hbc hmge


hmge.hbp

..\hmge\source\*.prg
..\hmge\source\*.c
..\hmge\source\bostaurus\bostaurus.prg
..\hmge\source\calldll\*.prg
#..\hmge\source\hbprinter\*.prg
#..\hmge\source\hbprinter\*.c
#..\hmge\source\miniprint\*.prg
#..\hmge\source\miniprint\*.c
#..\hmge\source\hbole\*.prg
#..\hmge\source\hbole\*.c
..\hmge\source\propgrid\*.prg
..\hmge\source\propgrid\*.c
..\hmge\source\propsheet\*.prg
..\hmge\source\propsheet\*.c
..\hmge\source\qhtm\*.prg
..\hmge\source\qhtm\*.c
..\hmge\source\socket\*.prg
..\hmge\source\socket\*.c
..\hmge\source\tget\*.prg
..\hmge\source\tsbrowse\*.prg
..\hmge\source\tsbrowse\*.c
..\hmge\source\winreport\*.prg
..\hmge\source\winreport\*.c

-o${hb_install_prefix}\addons\lib\win\${hb_comp}\hmge

hmge.hbc

hbhpdf.hbc
hbzebra.hbc
hbwin.hbc

-hblib
-w1
-es2

-hbx=${hb_install_prefix}\addons\${hb_name}


pronto.
Cada lib tem seu HBP, configurado de acordo com a "pasta atual"
Cada lib tem seu HBC, configurado de acordo com a "pasta atual"
A única pasta fixa é harbour\add-ons, que é obtida pela variável HB_INSTALL_PREFIX
As LIBs vão ser geradas com os nomes hmg3, hmge, hwgui, oohg, sefazclass, josequintas
A pasta da SAÍDA da lib vai depender do compilador C: pode ser add-ons\lib\win\mingw, add-ons\lib\win\msvc, add-ons\lib\win\bcc, add-ons\lib\win\mingw64, etc.

A partir daí é só usar.
Compilar normal: hbmk2 test.prg
Compilar com sefaz: hbmk2 test.prg sefazclass.hbc
Compilar com hwgui: hbmk2 test hwgui.hbc
Compilar com hmg3: hbmk2 test hmg3.hbc

NÃO FUNCIONA, NÃO SÃO COMPATÍVEIS, mas se fosse compilar com todas de uma vez:
hbmk2 test sefazclass.hbc josequintas.hbc hwgui.hbc hmg3.hbc hmge.hbc oohg.hbc gtwvg.hbc

Também poderia ser cada lib (.lib,.a) numa pasta da lib, junto com o hbx, mas aí o hbx não teria o mesmo efeito.

Achei legal o resultado.
Máquina com Harbour funcionando, com ou sem as LIBs, só vão ser usadas se indicar o HBC delas.
Tanto faz a pasta das LIBs, o HBP e HBC delas sempre vai funcionar.
Tanto faz o compilador C, um não mistura com o outro, pode usar todos ao mesmo tempo
Tanto faz o Harbour, mas depende de primeiro criar a variável HB_INSTALL_PREFIX, pra não misturar um com o outro (e paths do Harbour)

Se tiver mais de um Harbour na máquina, só configurar PATH e HB_INSTALL_PREFIX pro Harbour, e colocar o HBMK.HBC na pasta harbour\bin
Nem precisa mexer em nada das LIBs, só ir na pasta allgui e digitar HBMK2 *.HBP (aliás faltou dizer isto antes).

Meu modo de trabalho

MensagemEnviado: 18 Set 2017 18:47
por JoséQuintas
Faltou dizer:
Não precisa, e NEM DEVEM copiar daqui.
É bom tentar comentar com os desenvolvedores das LIBs, porque eles devem fazer os arquivos mais adequados, os meus são só para meus testes, e podem estar errados.

Mesmo assim, estão todos aqui.

https://github.com/JoseQuintas/AllGui

Os fontes das LIBs são originais, mas os HBP e HBC não vieram com as LIBs, então é só pra teste mesmo.
Pra produção é melhor usar os arquivos originais, porque são eles que vão ser usados para suporte.

Mas fica aí a dica, pra cobrar dos autores de suas LIBs.

E podem criar pra fivewin também, no mesmo estilo.

E numa IDE, bastaria a IDE ter arquivos iguais.....

Meu modo de trabalho

MensagemEnviado: 31 Jan 2018 18:50
por microvolution
JoséQuintas escreveu:Mas fica aí a dica, pra cobrar dos autores de suas LIBs.

prezado professor, por incrível que pareça, estava num outro tópico onde eu tenho "umas muitas dúvidas" e alguém havia me dado uma pasta (eu perguntando sobre HMG) e que eu não achava... até que o MSDN disse que era referente à HMG-E.

Se o Sr. que é "ninja" está com este montão de "trecos/troços", imagina "euzinho" que estou ainda de fraldas...

Eu, disse no outro tópico, que deveria haver uma união e homogenia entre as LIBs, pois são tantas: HMG, HMGE, OOHG, HWGUI, WVW, VWG, etc... e o que a gente aprende numa - apesar de tudo ser HARBOUR, não é do mesmo jeito na outra...
deveria ser padronizado como são o ABNT e ABNT2, ou ASC e ASCII.

#ficaAdica

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 07:35
por juniorcamilo

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 09:51
por JoséQuintas
juniorcamilo escreveu:como pós imagem em modo console?


DrawImage(), e depois o texto por cima.
Mas a imagem some se mover tela, minimizar, etc... por isso só usei em teste.

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 10:57
por juniorcamilo
estou atrás disso!!!
viewtopic.php?f=4&t=19031#p122172

como será?

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 16:37
por JoséQuintas
Vixe.....

HB_FUNC_TRANSLATE( WVG_DRAWTEXT , WAPI_DRAWTEXT )


Nem precisa da GTWVG !!!
No Harbour 3.4 o Viktor transferiu a função, que é da API Windows pra hbwin.

Ta aí uma coisa interessante:
No 3.2, a função está na GTWVG, mas era pura API Windows
No 3.4, o Viktor padronizou e moveu a função pra HBWIN, que contém rotinas do Windows.

Diferença?
É API Windows... nem precisa GTWVG.
IGUAL EM QUALQUER LIB GRÁFICA.

Tão entendendo o que digo sobre padronizar LIBs?
O que era GTWVG... que era API Windows... foi para o lugar certo.
Igual todas as LIBs poderiam fazer, e ficaria muito mais padronizado !!!!

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 16:39
por JoséQuintas
/* mappings to hbwin */
HB_FUNC_TRANSLATE( WVG_APPENDMENU , WAPI_APPENDMENU )
HB_FUNC_TRANSLATE( WVG_BRINGWINDOWTOTOP , WAPI_BRINGWINDOWTOTOP )
HB_FUNC_TRANSLATE( WVG_CALLWINDOWPROC , WAPI_CALLWINDOWPROC )
HB_FUNC_TRANSLATE( WVG_CHECKDLGBUTTON , WAPI_CHECKDLGBUTTON )
HB_FUNC_TRANSLATE( WVG_CHECKMENUITEM , WAPI_CHECKMENUITEM )
HB_FUNC_TRANSLATE( WVG_CHECKRADIOBUTTON , WAPI_CHECKRADIOBUTTON )
HB_FUNC_TRANSLATE( WVG_CLIENTTOSCREEN , WAPI_CLIENTTOSCREEN )
HB_FUNC_TRANSLATE( WVG_CREATEMENU , WAPI_CREATEMENU )
HB_FUNC_TRANSLATE( WVG_CREATEPOPUPMENU , WAPI_CREATEPOPUPMENU )
HB_FUNC_TRANSLATE( WVG_CREATEWINDOWEX , WAPI_CREATEWINDOWEX )
HB_FUNC_TRANSLATE( WVG_DEFWINDOWPROC , WAPI_DEFWINDOWPROC )
HB_FUNC_TRANSLATE( WVG_DELETEMENU , WAPI_DELETEMENU )
HB_FUNC_TRANSLATE( WVG_DESTROYMENU , WAPI_DESTROYMENU )
HB_FUNC_TRANSLATE( WVG_DESTROYWINDOW , WAPI_DESTROYWINDOW )
HB_FUNC_TRANSLATE( WVG_DRAWMENUBAR , WAPI_DRAWMENUBAR )
HB_FUNC_TRANSLATE( WVG_DRAWTEXT , WAPI_DRAWTEXT )
HB_FUNC_TRANSLATE( WVG_ENABLEMENUITEM , WAPI_ENABLEMENUITEM )
HB_FUNC_TRANSLATE( WVG_ENABLEWINDOW , WAPI_ENABLEWINDOW )
HB_FUNC_TRANSLATE( WVG_FILLRECT , WAPI_FILLRECT )
HB_FUNC_TRANSLATE( WVG_GETDESKTOPWINDOW , WAPI_GETDESKTOPWINDOW )
HB_FUNC_TRANSLATE( WVG_GETDIALOGBASEUNITS , WAPI_GETDIALOGBASEUNITS )
HB_FUNC_TRANSLATE( WVG_GETDLGITEM , WAPI_GETDLGITEM )
HB_FUNC_TRANSLATE( WVG_GETDLGITEMTEXT , WAPI_GETDLGITEMTEXT )
HB_FUNC_TRANSLATE( WVG_GETFOCUS , WAPI_GETFOCUS )
HB_FUNC_TRANSLATE( WVG_GETSTOCKOBJECT , WAPI_GETSTOCKOBJECT )
HB_FUNC_TRANSLATE( WVG_HINSTANCE , WIN_HINSTANCE )
HB_FUNC_TRANSLATE( WVG_HIWORD , WAPI_HIWORD )
HB_FUNC_TRANSLATE( WVG_INSERTMENU , WAPI_INSERTMENU )
HB_FUNC_TRANSLATE( WVG_ISDLGBUTTONCHECKED , WAPI_ISDLGBUTTONCHECKED )
HB_FUNC_TRANSLATE( WVG_ISICONIC , WAPI_ISICONIC )
HB_FUNC_TRANSLATE( WVG_ISWINDOW , WAPI_ISWINDOW )
HB_FUNC_TRANSLATE( WVG_ISZOOMED , WAPI_ISZOOMED )
HB_FUNC_TRANSLATE( WVG_LOWORD , WAPI_LOWORD )
HB_FUNC_TRANSLATE( WVG_MAKELPARAM , WAPI_MAKELPARAM )
HB_FUNC_TRANSLATE( WVG_MESSAGEBOX , WAPI_MESSAGEBOX )
HB_FUNC_TRANSLATE( WVG_MOVEWINDOW , WAPI_MOVEWINDOW )
HB_FUNC_TRANSLATE( WVG_POSTMESSAGE , WAPI_POSTMESSAGE )
HB_FUNC_TRANSLATE( WVG_SCREENTOCLIENT , WAPI_SCREENTOCLIENT )
HB_FUNC_TRANSLATE( WVG_SENDDLGITEMMESSAGE , WAPI_SENDDLGITEMMESSAGE )
HB_FUNC_TRANSLATE( WVG_SENDMESSAGE , WAPI_SENDMESSAGE )
HB_FUNC_TRANSLATE( WVG_SENDMESSAGETEXT , WAPI_SENDMESSAGE )
HB_FUNC_TRANSLATE( WVG_SETBKCOLOR , WAPI_SETBKCOLOR )
HB_FUNC_TRANSLATE( WVG_SETBKMODE , WAPI_SETBKMODE )
HB_FUNC_TRANSLATE( WVG_SETDLGITEMTEXT , WAPI_SETDLGITEMTEXT )
HB_FUNC_TRANSLATE( WVG_SETFOCUS , WAPI_SETFOCUS )
HB_FUNC_TRANSLATE( WVG_SETFOREGROUNDWINDOW , WAPI_BRINGWINDOWTOTOP )
HB_FUNC_TRANSLATE( WVG_SETPARENT , WAPI_SETPARENT )
HB_FUNC_TRANSLATE( WVG_SETTEXTCOLOR , WAPI_SETTEXTCOLOR )
HB_FUNC_TRANSLATE( WVG_SETWINDOWLONG , WAPI_SETWINDOWLONGPTR )
HB_FUNC_TRANSLATE( WVG_SETWINDOWTEXT , WAPI_SETWINDOWTEXT )
HB_FUNC_TRANSLATE( WVG_SHOWWINDOW , WAPI_SHOWWINDOW )
HB_FUNC_TRANSLATE( WVG_SLEEP , WAPI_SLEEP )
HB_FUNC_TRANSLATE( WVG_TREEVIEW_SELECTITEM , WAPI_TREEVIEW_SELECTITEM )
HB_FUNC_TRANSLATE( WVG_TREEVIEW_SETBKCOLOR , WAPI_TREEVIEW_SETBKCOLOR )
HB_FUNC_TRANSLATE( WVG_TREEVIEW_SETLINECOLOR , WAPI_TREEVIEW_SETLINECOLOR )
HB_FUNC_TRANSLATE( WVG_TREEVIEW_SETTEXTCOLOR , WAPI_TREEVIEW_SETTEXTCOLOR )
HB_FUNC_TRANSLATE( WVG_UPDATEWINDOW , WAPI_UPDATEWINDOW )
HB_FUNC_TRANSLATE( WVT__MAKEDLGTEMPLATE , __WAPI_DLGTEMPLATE_RAW_NEW )

#ifdef HB_LEGACY_LEVEL5

/* local synonyms (deprecated) */
HB_FUNC_TRANSLATE( WVT_APPENDMENU , WAPI_APPENDMENU )
HB_FUNC_TRANSLATE( WVT_CREATEMENU , WAPI_CREATEMENU )
HB_FUNC_TRANSLATE( WVT_CREATEPOPUPMENU , WAPI_CREATEPOPUPMENU )
HB_FUNC_TRANSLATE( WVT_DELETEMENU , WAPI_DELETEMENU )
HB_FUNC_TRANSLATE( WVT_DESTROYMENU , WAPI_DESTROYMENU )
HB_FUNC_TRANSLATE( WVT_ENABLEMENUITEM , WAPI_ENABLEMENUITEM )

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 17:13
por JoséQuintas
hazael escreveu:você pode (sempre pôde) fazer isso


Em parte...
O Viktor padronizou mais, movendo e padronizando as chamadas à API Windows.
Talvez no 3.2 não existam todas essas funções, e precise mais código fonte pra fazer a mesma coisa.

Foi nessa padronização que acabei tendo o recurso do Windows de redimensionar imagem, que faz parte do Windows !!!
Acho que no 3.2 ainda não tem isso, já que não atualizaram a GTWVG com as atualizações do 3.4, e nem a hbwin.

Meu modo de trabalho

MensagemEnviado: 24 Set 2019 17:15
por JoséQuintas
Mas voltando à pergunta sobre letra maior na GTWVG

Provavelmente a próxima vai ser: e como uso isso?

Se é função da API Windows.... tem manual detalhado na Microsoft, não precisa manual no Harbour, e nem na GTWVG !!!

Meu modo de trabalho

MensagemEnviado: 18 Out 2019 23:43
por JoséQuintas
Continuando....

Em posts anteriores, de 2016, eu havia comentado de acabar com DBFs, mas parei de mexer por alguns anos.
Faltaram telas em GTWVG.
Agora usando ícones multiresolução, que ficam bons em qualquer resolução de monitor.
E o EXE... que tinha cerca de 2MB na época das postagens, agora tem 6.5MB por conta das imagens.

jpa1.png


jpa2.png

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 01:18
por JoséQuintas
Então....
Na continuação da migração pra MySQL, comecei a mexer em outras coisas ao mesmo tempo...
Então anulei tudo, e fazer uma coisa de cada vez.

Uma coisa é o seguinte: tenho um único cadastro pra clientes/fornecedores e transportadoras.
clientes/fornecedores usa cdtipo="1", e transportadoras usa cdtipo="3".
Vou separar as transportadoras e acabar com esse cdtipo.

Primeiro passo: confirmar índices
   IndexDbf( "jpcadas", "Cadastros (Cli/Forn/Transp)" )
   IndexInd( "jpcadas1", "cdTipo + cdCodigo" )
   IndexInd( "jpcadas2", "cdTipo + cdNome + cdCodigo" )
   IndexInd( "jpcadas3", "cdTipo + cdCnpj + cdDivisao + cdCodigo" )
   IndexInd( "jpcadas4", "cdTipo + cdApelido + cdCodigo" )
   IndexInd( "telefone", "cdTipo + cdTelefone + cdCodigo" )
   IndexInd( "numlan",   "cdCodigo" )
   IndexInd( "nome",     "cdNome + cdCodigo" )
   IndexInd( "cnpj",     "cdCnpj+cdCodigo" )


Já tenho índices com e sem o cdtipo, e já faço muito uso desses índices sem cdtipo, isso vai facilitar.

Só lembrando:

usando NTX, temos lá trocentos arquivos de índice
SET INDEX TO ntx1, ntx2, ntx3, ntx4, ntx5, ntx6, ntx7, ntx8, ntx9, ntx10, ntx11

usando CDX, é um único arquivo, e os índices são internos
SET INDEX TO jpcadas

Além disso, ao invés de SET ORDER TO 1, SET ORDER TO 2, etc...., podemos usar o nome: OrdSetFocus( "numlan" )
Com isso, vou poder apagar os índices jpcadas1 a 4 tranquilamente (após retirar o uso nos fontes, lógico).
Se usasse SET ORDER por número... isso não seria possível.

Primeiro rotina de criar/atualizar estrutura do arquivo:
STATIC FUNCTION JPTRANSPCreateDbf()

   LOCAL mStruOk := { ;
      { "TPCODIGO",   "C", 6 }, ;
      { "TPNOME",     "C", 50 }, ;
      { "TPAPELIDO",  "C", 20 }, ;
      { "TPCNPJ",     "C", 18 }, ;
      { "TPENDERECO", "C", 40 }, ;
      { "TPNUMERO",   "C", 10 }, ;
      { "TPCOMPL",    "C", 20 }, ;
      { "TPBAIRRO",   "C", 20 }, ;
      { "TPCIDADE",   "C", 21 }, ;
      { "TPUF",       "C", 2 }, ;
      { "TPCEP",      "C", 9 }, ;
      { "TPTELEFONE", "C", 30 }, ;
      { "TPINSEST",   "C", 18 }, ;
      { "TPCONTATO",  "C", 30 }, ;
      { "TPTELEF2",   "C", 15 }, ;
      { "TPTELEF3",   "C", 15 }, ;
      { "TPFAX",      "C", 30 }, ;
      { "TPEMAIL",    "C", 250 }, ;
      { "TPHOMEPAGE", "C", 100 }, ;
      { "TPOBS",      "C", 100 }, ;
      { "TPSTATUS",   "C", 6 }, ;
      { "TPINFINC",   "C", 80 }, ;
      { "TPINFALT",   "C", 80 } }

   SayScroll( "JPTRANSP, verificando atualizações" )

   IF ! ValidaStru( "jptransp", mStruOk )
      MsgStop( "jptransp não disponível!" )
      QUIT
   ENDIF

   RETURN NIL


Depois rotina de reindexar:

   IndexDbf( "jptransp", "Transportadoras" )
   IndexInd( "numlan",   "tpCodigo" )
   IndexInd( "nome",     "tpNome + tpCodigo" )
   IndexInd( "cnpj",     "tpCnpj + tpCodigo" )


Pronto, na próxima atualização do sistema, arquivo será criado automaticamente.

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 01:47
por JoséQuintas
Agora a transferência.
Já tenho a rotina de atualização automática, então é só acrescentar mais essa.

// ze_update2019.prg
FUNCTION ze_Update2019()

   IF AppVersaoDbfAnt() < 20191011; Update1011(); ENDIF
   IF AppVersaoDbfAnt() < 20191019; Update1019(); ENDIF

   RETURN NIL


E agora criar a Update1019() (19/10) fazendo a transferência de cdtipo="3" para o novo jptransp.

Na próxima vez que o cliente atualizar versão, terá um arquivo novo só com transportadoras.

Agora é alterar os fontes, para ao invés de usar jpcadas, passar a usar jptransp.

Neste ponto não posso liberar pra cliente, só quando terminar a alteração pra usar jptransp ao invés de jpcadas, senão a conversão vai ficar desatualizada.

Terminado isto, já posso liberar pra clientes.

Depois, mexer nos fontes pra eliminar de vez o campo cdtipo.

Começar eliminando o uso dos índices jpcadas1 a jpcadas4.
Primeiro eliminar as pesquisas por esses índices.
Nesta etapa, tanto faz se atualizar ou não nos clientes durante o processo.

Depois, ajustar os fontes de clientes/fornecedores pra não usarem o cdtipo, deixando a remoção definitiva por último.
Mesma coisa: vou poder atualizar nos clientes a qualquer momento.

Por último: eliminar de vez, mas....antes de eliminar o campo, preciso pegar as transportadoras, lembram? cdtipo="3"
Então, cdtipo só vai deixar de existir no arquivo depois, quando realmente não precisar mais.
Uso isto na fase intermediária:

IF AppVersaoDbfAnt() < 20191019
   AAdd( mStruOk, { "CDTIPO", "C", 1 }  )
ENDIF


Ou seja, defino até quando o campo vai existir.
Quem atualizar versão depois desta, após a transferência o campo vai ser eliminado.

Quando terminar por definitivo, altero aqui também:

FUNCTION AppVersaoDbf()

   RETURN 20191014


Esse é o controle de versão de DBF.
Ao alterar o número de versão, obrigatoriamente o aplicativo faz backup de tudo.
Só depois do backup é que o aplicativo vai alterar estruturas, e transferir transportadoras.

Isso já venho usando desde a versão de 2002, desde o Clipper, que já mostrei neste post.
Apenas coloquei no aplicativo pra ele fazer automático, o que muito antigamente eu fazia manualmente.
É criar uma rotina que atualiza, e com o tempo vai melhorando, conforme as necessidades que forem aparecendo.

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 06:27
por JoséQuintas
A atualização... tudo rotina comum.

STATIC FUNCTION Update1019()

   IF ! AbreArquivos( "jptransp", "jpcadas" )
      QUIT
   ENDIF
   SELECT jpcadas
   SET ORDER TO 0
   GOTO TOP
   DO WHILE ! Eof()
      IF jpcadas->cdTipo != "3"
         SKIP
         LOOP
      ENDIF
      SELECT jptransp
      SEEK jpcadas->cdCodigo
      IF Eof()
         RecAppend()
         REPLACE jptransp->tpCodigo WITH jpcadas->cdCodigo, ;
      ENDIF
      RecLock()
      REPLACE ;
         jptransp->tpNome     WITH jpcadas->cdNome, ;
         jptransp->tpApelido  WITH jpcadas->cdApelido ;
...
         jptransp->tpInfAlt   WITH jpcadas->cdInfAlt
      SELECT jpcadas
      //RecLock()
      //DELETE
      //RecUnlock()
      SKIP
   ENDDO
   CLOSE DATABASES

   RETURN NIL


A checagem é precaução.
Não excluir é precaução.
Durante os testes, posso ficar convertendo toda hora.
Mas terminando as alterações, antes de instalar nos clientes, aí apago de vez.

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 09:52
por JoséQuintas
Costumo usar o nome do arquivo como parte do nome do módulo.
Como transportadoras não é mais jpcadas....

STATIC FUNCTION Update1019B()

   IF ! AbreArquivos( "jpsenha" )
      QUIT
   ENDIF
   pw_AddModule( "PJPTRANSP", "PJPCADAS3" )
   pw_AddModule( "LJPTRANSP", "LJPCADAS3" )
   CLOSE DATABASES

   RETURN NIL


Na próxima atualização, o aplicativo vai liberar os novos módulos, para os usuários que tinham acesso aos antigos.

Primeira parte ok, novo arquivo de transportadoras, pronto pra ser atualizado nos clientes.
Agora retirar as precauções que tinha deixado na atualização.

Próxima etapa: eliminar os índices que se tornaram inúteis, e todo uso de CDTIPO, que se tornou um campo inútil.

E o MySQL?
Isso fazia parte da conversão pra MySQL anterior.
NÃO tenho problema com DBF, então MySQL é importante mas não é urgente.
Uma coisa de cada vez agora.
Quando chegar a hora de converter pra MySQL, vai ser só isso, e não ficar organizando arquivos/tabelas.

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 11:40
por JoséQuintas
Antes da liberação final, definir a nova versão de DBFs:

FUNCTION AppVersaoDbf()

   RETURN 20191019


As conversões vão ser feitas se a versão instalada for anterior a esta.

FUNCTION ze_Update2019()

   IF AppVersaoDbfAnt() < 20191011; Update1011(); ENDIF
   IF AppVersaoDbfAnt() < 20191019; Update1019A(); ENDIF
   IF AppVersaoDbfAnt() < 20191019; Update1019B(); ENDIF

   RETURN NIL


Conclusão:
Agora deixo essa versão na internet, e o cliente pode atualizar na hora que quiser.
TODAS as atualizações pendentes serão feitas.
E eu prossigo com minhas alterações de fonte, não dependo do cliente instalar versão pra poder prosseguir.

E o MySQL?
Ainda tem a limpeza geral, que nem todos os clientes atualizaram.
Quando chegar a vez do MySQL, aí obrigo todos a atualizarem antes de começar.
Por enquanto continuar com a padronização dos DBFs que restam.
Isso não depende da limpeza, é necessário, e vai facilitar depois.

Meu modo de trabalho

MensagemEnviado: 19 Out 2019 18:59
por JoséQuintas
O perigo de alterações é que uma "chama" outra....
O perigo é fugir muito do que estava fazendo antes, e se complicar.
Como a parte anterior tava pronta, tudo bem.
Antes era PJPCADAS1 pra clientes, e PJPCADAS3. Como agora é um só.... lá vamos nós alterar o nome...

STATIC FUNCTION Update1019B()

   IF ! AbreArquivos( "jpsenha" )
      QUIT
   ENDIF
   pw_AddModule( "PJPTRANSP", "PJPCADAS3" )
   pw_AddModule( "LJPTRANSP", "LJPCADAS3" )
   pw_AddModule( "PJPCADAS",  "PJPCADAS1" )
   pw_AddModule( "PJPCADASB", "PJPCADAS1B" )
   pw_AddModule( "LJPCADAS",  "LJPCADAS1" )
   CLOSE DATABASES

   RETURN NIL


Aproveitei a rotina anterior, então apenas alterei a versão pra repetir a atualização.
Se fizer mais de uma vez, sem problema, apenas vai estar liberando mesmos acessos pra mesmas pessoas.

FUNCTION ze_Update2019()

   IF AppVersaoDbfAnt() < 20191011; Update1011(); ENDIF
   IF AppVersaoDbfAnt() < 20191019.1; Update1019A(); ENDIF
   IF AppVersaoDbfAnt() < 20191019.2; Update1019B(); ENDIF

   RETURN NIL


Agora vou pra eliminação dos índices jpcadas1 a jpcadas4, e o campo CDTIPO.
É até bom fazer agora, porque serve de conferência adicional pra mudança anterior, de separar transportadora.

Meu modo de trabalho

MensagemEnviado: 20 Out 2019 22:02
por Fernando queiroz
Boa parte do que você falou já venho fazendo a algum tempo, melhorando fontes, usando w3 , estou modificando os fontes para adaptar para o LETODBF mas já estou enxergando a possiblidade de MYSQL, tá ficando interessante , tem muita coisa ainda, como fazer atualização automática, atualização dos DBF, entre outras "COISAS" :)) :)) :))

Meu modo de trabalho

MensagemEnviado: 21 Out 2019 09:13
por syslink
letodbf, não seria letodb?

Meu modo de trabalho

MensagemEnviado: 21 Out 2019 09:48
por JoséQuintas
Fernando queiroz escreveu:Boa parte do que você falou já venho fazendo a algum tempo, melhorando fontes, usando w3 , estou modificando os fontes para adaptar para o LETODBF mas já estou enxergando a possiblidade de MYSQL, tá ficando interessante , tem muita coisa ainda, como fazer atualização automática, atualização dos DBF, entre outras "COISAS


Pois é.. isso de dar uma geral, ao mesmo tempo que organiza, abre a visão pra mais possibilidades.

Sobre isso que eu comentava:
não é melhoria só dos fontes, o próprio programador começa a querer cada vez mais.
Os fontes melhoram e o programador melhora junto.

syslink escreveu:letodbf, não seria letodb?


Confunde porque tem pelo menos dois, um original mais simples, e um mais "aperfeiçoado".
Não sei dizer os nomes exatos.

Meu modo de trabalho

MensagemEnviado: 21 Out 2019 15:01
por JoséQuintas
Vou deixar o campo por um prazo limitado

   IF AppVersaoDbfAnt() < 20191101
      AAdd( aStruList, { "CDTIPO", "C", 1 } )
   ENDIF


Precaução, caso misturem versões.
01/11/2019

Isso tem a ver com o controle dos dbfs, e não com a versão do EXE.
Cada um tem sua própria versão.

Se colocarem versão velha... tudo bem... tem lá o campo. (mas não vai ter mais as transportadoras).
Se colocarem versão nova... por enquanto o campo é mantido.

Numa outra versão tiro o campo fora, mas vai ter que ser antes da versão MySQL, pra não tentar salvar no MySQL esse campo.

Meu modo de trabalho

MensagemEnviado: 21 Out 2019 15:26
por JoséQuintas
Decidindo as próximas alterações:

1. Alterar JPPEDI pra JPPEDIDO

2. Campo chave = "ID" + nome do arquivo/tabela, e numérico, mas sem o "JP" lógico, para JPPEDIDO, a chave ser IdPedido.

Nos pedidos hoje o campo é pdPedido, caractere de 6. Vai virar idPedido, numérico de 9 ou 11

Pra essa alteração, muito fonte a ser mexido, nos dois casos.

Ainda decidindo, mas é a reta final pra MySQL.

Talvez o maior problema seja no Harbour/DBF: Como alterar um campo de caractere para numérico, sem o erro de TypeMismatch?

Ou talvez dê pra usar aquela descoberta recente, de que pro MySQL, número é número, não importa se for string.
Vou ter que testar isso.

Meu modo de trabalho

MensagemEnviado: 21 Out 2019 15:29
por JoséQuintas
Bingo!!!!
Funciona !!!
O campo CDID é numérico, incremental

INSERT INTO JPAGENDA ( CDID ) VALUES ( "100" )


Isso vai facilitar muuuito.

Agora só preciso renomear os campos, e tá feito.

Meu modo de trabalho

MensagemEnviado: 22 Out 2019 19:12
por JoséQuintas
Agora alterações delicadas.... nome de arquivo e nome de campo CHAVE.
Tive até que mexer nas atualizações anteriores.
O arquivo de pedidos, passa a ser JPPEDIDO ao invés de JPPEDI.
E o campo chave passa a ser IdPedido, ao invés de PdPedido.

STATIC FUNCTION JPPEDIDOCreateDbf()

   LOCAL nCont, cCampo
   LOCAL aStruList := { ;
      { "IDPEDIDO",  "C", 6 }, ;
...

      { "PDINFALT",  "C", 80 } }

   IF AppVersaoDbfAnt() < 99999999
      AAdd( aStruList, { "PDPEDIDO",  "C", 6 } )
   ENDIF

   SayScroll( "JPPEDIDO, verificando atualizações" )

   IF ! ValidaStru( "JPPEDIDO", aStruList )
      MsgStop( "JPPEDIDO não disponível!" )
      QUIT
   ENDIF


Acima: Notem que o campo pdPedido vai existir até determinada versão... ainda em testes, deixei como eterna 99/99/9999

   IF ! File( "jppedi.dbf" )
      RETURN NIL
   ENDIF
   IF ! ValidaStru( "JPPEDI", aStruList )
      MsgStop( "JPPEDI não dispnível!" )
      QUIT
   ENDIF


Acima: Como vou importar o arquivo antigo, com certeza preciso garantir que a estrutura está atualizada.

   IF ! UseSoDbf( "jppedi", .T. )
      QUIT
   ENDIF
   IF ! AbreArquivos( "jppedido" )
      QUIT
   ENDIF
   SELECT jppedi
   GOTO TOP
   DO WHILE ! Eof()
      SELECT jppedido
      IF Empty( jppedi->idPedido )
         SEEK jppedi->pdPedido
      ELSE
         SEEK jppedi->idPedido
      ENDIF
      IF Eof()
         RecAppend()
         REPLACE ;
            jppedido->pdPedido WITH iif( Empty( jppedi->idPedido ), jppedi->pdPedido, jppedi->idPedido ), ;
            jppedido->idPedido WITH jppedi->idPedido
      ENDIF
      RecLock()
      FOR nCont = 1 TO FCount()
         cCampo := FieldName( nCont )
         FieldPut( nCont, jppedi->( FieldGet( FieldNum( cCampo ) ) ) )
      NEXT
      RecUnlock()
      SELECT jppedi
      RecLock()
      DELETE
      RecUnlock()
      SKIP
   ENDDO
   CLOSE DATABASES
   fErase( "jppedi.dbf" )

   RETURN NIL


Acima:
Detalhe 1:
      IF Empty( jppedi->idPedido )
         SEEK jppedi->pdPedido
      ELSE
         SEEK jppedi->idPedido
      ENDIF


Porque isso?
Vai que algum cliente já rodou a atualização que gravou idPedido.... pra garantir se tiver conteúdo pego idPedido.
Ou seja... se a atualização já foi feita, não faz de novo, ou não corre o risco de gravar um número em branco no lugar dele.

Detalhe 2:
      RecLock()
      FOR nCont = 1 TO FCount()
         cCampo := FieldName( nCont )
         FieldPut( nCont, jppedi->( FieldGet( FieldNum( cCampo ) ) ) )
      NEXT
      RecUnlock()


Se os arquivos são iguais, porque preciso do FieldNum() ?
Minha atualização de estrutura NÃO mexe com ordem de campos.
Isso significa que os arquivos podem conter os mesmos campos, mas podem estar em outra ordem.

Bom a parte acima conclui a atualização de estrutura, de trocar o nome do arquivo, de importar o arquivo anterior.
Mesma coisa, precaução: vai incluindo e apagando. Se por algum motivo não conseguir excluir, na próxima vez não vai importar duplicado. (apesar que o SEEK resolve isso).

Meu modo de trabalho

MensagemEnviado: 22 Out 2019 19:25
por JoséQuintas
A atualização do log no MySQL, porque alterou o nome do arquivo....

   SayScroll( "Atualizando mysql" )
   WITH OBJECT cnMySql
      :ExecuteCmd( "UPDATE JPREGUSO SET RUARQUIVO='JPPEDIDO' WHERE RUARQUIVO='JPPEDI'" )
      :ExecuteCmd( "UPDATE JPREGUSO SET RUCODIGO=CONCAT( '1', SUBSTR( RUCODIGO, 2, 5 ) ) WHERE RUARQUIVO='JPPEDIDO' AND SUBSTR( RUCODIGO, 1, 1 ) = '7'" )
      :ExecuteCmd( "UPDATE JPREGUSO SET RUCODIGO=CONCAT( '2', SUBSTR( RUCODIGO, 2, 5 ) ) WHERE RUARQUIVO='JPPEDIDO' AND SUBSTR( RUCODIGO, 1, 1 ) = '8'" )
      :ExecuteCmd( "UPDATE JPREGUSO SET RUCODIGO=CONCAT( '3', SUBSTR( RUCODIGO, 2, 5 ) ) WHERE RUARQUIVO='JPPEDIDO' AND SUBSTR( RUCODIGO, 1, 1 ) = '9'" )
   ENDWITH


E a transferência de PdPedido para IdPedido

STATIC FUNCTION Update1022E()

   IF ! UseSoDbf( "jppedido", .T. )
      QUIT
   ENDIF
   GOTO TOP
   DO WHILE ! Eof()
      IF Empty( jppedi->idPedido )
         RecLock()
         REPLACE jppedi->idPedido WITH jppedi->pdPedido
         RecUnlock()
      ENDIF
      SKIP
   ENDDO
   CLOSE DATABASES

   RETURN NIL


Com a precaução de não gravar encima de campo já convertido.
Vai que o número de versão se perde.... e considera versão errada....
Apesar que o número de versão fica no MySQL, não é tão perigoso como em DBF.

O que acontece agora?
Deixo na internet, quem atualizar, vai ter tudo convertido.

Por enquanto MySQL parado, os clientes precisam rodar a limpeza antes que eu comece a transferência pra MySQL.
Enquanto isso... vou organizando os DBFs, preparando pra transferir depois.

Em pedidos... tudo resolvido, pronto do jeito que eu quero pra MySQL.
Quero usar ID seguido do nome do arquivo. JPPEDIDO, tirando o JP fica PEDIDO, a ID dele é IdPedido

Agora vamos aos próximos.
Vou fazer isso com cada um deles.

Lembrando: NÃO é exigência do MySQL, eu é que decidi fazer assim.

Meu modo de trabalho

MensagemEnviado: 22 Out 2019 19:59
por JoséQuintas
Uia...
Faz tempo não recebia um email deste:

Modulo: PDFECTECANCEL faltou abrir: jpclista  
Called from ENCONTRA(16) 
Called from DOCSALVAEMAIL(323) 
Called from DOCENVIAEMAIL(661) 
Called from SALVAXMLMYSQL(655) 
Called from PDFECTECANCEL(67) 
Called from DO(0) 
Called from RUNMODULE(94) 
Called from BOXMENU(757) 
Called from BOXMENU(744) 
Called from MENUPRINC(592) 
Called from SISTEMA(87) 
Called from (b)MAIN(48)
[/quote]

Não é erro, o aplicativo já resolveu.

[code]
IF ! Encontra( "chave", "arquivo", "ordem" )
[/code]

Podemos dizer que minha função Encontra() equivale ao dbSeek().
Mas ao usar um arquivo que não está aberto, ela abre automático.
Coloquei lá pra me avisar quando ela faz isso.

Agora, no PDfeCteCancel(), eu acrescento a abertura do arquivo jpCliSta

[code]
   IF ! AbreArquivos( "jpempre", "jpcadas", "jpclista" )
      RETURN
   ENDIF


Assim tenho controle sobre quais arquivos cada módulo usa/precisa.

Só pra curiosidade: JP.CLI.STA, JP é meu prefixo padrão, CLI=Clientes, STA=Status
É a tabela aonde tenho os possíveis status de cliente, e a configuração sobre o que cada status afeta no uso daquele cliente.
Pode ser bloqueio total, parcial, etc.

Tava aqui pensando....

Estou mostrando rotinas de atualizar estrutura, a Encontra(), Browse(), AbreArquivos(), e outras mais...
VÃO TODAS PRO LIXO.
Estranho? Pra que vai servir rotina de DBF se não existir DBF?

Vai ver é por isso que estou acertando tudo de uma vez.... vai ser a última vez pra muita coisa...

Só vai restar o aplicativo em Flagship usando DBF, que talvez passe diretamente pra MySQL...

Meu modo de trabalho

MensagemEnviado: 23 Out 2019 13:36
por JoséQuintas
vb.png


Como eu comentei, estou atualizando até mesmo os fontes em Visual Basic 6.

Aí vocês podem pensar: já usava MySQL

ERA DBF MESMO.
Visual Basic 6, com ADS Local, acessando DBFs totalmente por comandos SQL !!!!
Compatível com Clipper SIXCDX.

É até interessante....
Seria uma alternativa, caso eu queira manter DBF pra algum uso local sem servidor....

Meu modo de trabalho

MensagemEnviado: 24 Out 2019 12:39
por JoséQuintas
Bom...
Eu disse que ia fazer uma coisa de cada vez....
De certa forma eu fiz isso mas....
Mexi em pedidos, clientes, estoque, produtos, financeiro....
Não vou colocar as rotinas porque fica repetitivo.
É minha reta final com DBFs, talvez a última vez que mexo com eles.

A atualização em funcionamento, apesar que não tem muito o que ver...

update.png

Meu modo de trabalho

MensagemEnviado: 24 Out 2019 13:18
por JoséQuintas
Primeiro problema.... é grave... mas não é grave...

cli.png


Em clientes não existe mais CDTIPO que fazia parte da chave.
Esqueci de retirar isso do específico do cadastro de clientes.

É só apagar esse "1" + , então não é grave, mas.... o cliente não poder consultar clientes é grave.

Mas tudo bem, já corrigi, e o cliente já clicou em atualizar.

Nota:
O susto foi que a moça ligou aqui e falou que tinha sumido o cliente.
Já pensei no pior...
Mas não, apenas a pesquisa no cadastro que deu problema.
Todo restante, outras telas que pesquisam cliente, tudo ok.

É a vantagem da atualização on-line, prático pra resolver problema.
Lógico... melhor teria sido testar tudo direito antes de instalar...

Meu modo de trabalho

MensagemEnviado: 24 Out 2019 14:25
por JoséQuintas
Mais um erro

Tentativa 1: Erro executando comando:-2147217900 Unknown column 'RUID' in 'field list'
Called from ADOCLASS:EXECUTECMD(226)
Called from ADOCLASS:EXECUTE(199)
Called from ADOCLASS:SQLTODBF(378)
Called from PJPREGUSO(60)
Called from LOCAT00CLASS:USERFUNCTION(490)
Called from LOCAT00CLASS:EXECUTE(331)
Called from PLOCATARIO(62)
Called from PLOCAT00(30)
Called from DO(0)
Called from BOXMENU(509)
Called from MAINMENU(356)
Called from MAIN(70)


Isso tem a ver com aquela história de mais de um aplicativo acessando a mesma base.
E de voltar fontes, e não prestar atenção, ficou pela metade.
Acabei deixando um aplicativo atualizado e o outro não.

RUID agora é IDREGUSO.

Foi na consulta de ocorrências.
Não é grave, porque não causa perda de dados, mas atrapalha a consulta de ocorrências.

Este erro foi no aplicativo da imobiliária
No aplicativo principal já digitaram pedido, emitiram nota, e tudo bem.

Meu modo de trabalho

MensagemEnviado: 25 Out 2019 23:07
por JoséQuintas
O processo inteiro de atualização.
Seria o mesmo que fazemos, se fôssemos fazer manual.

Primeiro um backup, afinal vamos mexer em DBFs...

atual1.png


Depois a atualização de estruturas.
Ela acaba acontecendo DUAS vezes: ANTES e DEPOIS das ATUALIZAÇÕES

atual2.png


Com estruturas ok, toda parte de conversão e ajustes de DBF

atual3.png


Após as conversões/atualizações, novamente a atualização de estrutura.
Porque: Porque posso estar adicionando um campo que vai conter o conteúdo de outro campo que vai ser eliminado.
Então... adiciono o novo campo ANTES de todas conversões, e removo o campo velho no final.

E no fim de tudo, uma reindexação de tudo que precisar ser reindexado.
As mudanças podem, e desta vez é certeza, necessitar criar novos índices.
A atualização de estrutura já apaga índices anteriores, pra evitar problemas.

E pronto.
Agora está pronto pra uso.

Agora instalar em TODOS os clientes antes de mexer em mais alguma coisa.
Na verdade era pra ter feito isso antes de todas as mexidas.... mas foi difícil resistir...

O ruim de muita atualização junta, é que fica demorada.
Por ser demorada.... corre muito o risco de um usuário entrar no aplicativo por outro terminal durante a atualização.
Isso pode ser problema....

Mas daqui pra frente... todas as atualizações vão ser assim: demoradas e perigosas se outro usuário entrar no aplicativo.

Vém mais mudanças de estrutura por aí... e a transferência pra MySQL.

Meu modo de trabalho

MensagemEnviado: 28 Out 2019 14:15
por JoséQuintas
Nem eu acredito no que eu fiz....

Bom...
É a preparação pra deixar DBF pronto pra MySQL...

Arquivo com 8 caracteres???? Porque??? não é mais DOS há muito tempo...

CTDIARI.DBF virou JPCONTABIL.DBF
JPCADAS.DBF virou JPCADASTRO.DBF e JPTRANSP.DBF (foi dividido)
JPCOMISS.DBF virou JPCOMISSAO.DBF
JPEMPRE.DBF virou JPEMPRESA.DBF
JPESTOQ.DBF virou JPESTOQUE.DBF
JPIMPOS.DBF virou JPIMPOSTO.DBF
JPLFISC.DBF virou JPFISCAL.DBF
JPNOTA.DBF virou JPNOTFIS.DBF
JPPEDI.DBF virou JPPEDIDO.DBF
JPVEICUL.DBF virou JPVEICULO.DBF
JPVENDED.DBF virou JPVENDEDOR.DBF

E campos chave:

JPCADASTRO, chave cdCodigo, virou IdCadas e depois idCadastro
JPTRANSP, chave cdCodigo, virou tpCodigo, depois idTransp
JPFINAN, chave fiNumLan, virou idFinan
JPITEM, chave era ieItem, virou idItem
JPFISCAL, chave era lfNumLan, virou idFiscal
JPNOTFIS, chave era nfNumLan virou idNotFis
JPFORPAG, chave era fpNumLan virou idForPag
JPCIDADE, chave era ciNumLan virou idCidade
JPCLISTA, chave era csNumLan virou IdCliSta
JPTRANSA, chave era trTransa virou idTransa
JPVEICULO, chave era veNumLan virou idVeiculo
JPVENDEDOR, chave era vdVendedor virou idVendedor
JPMOTORI, chave era moMotori virou idMotori
JPESTOQUE, chave era esNumLan virou idEstoque
JPPEDIDO, chave era pdPedido virou idPedido
JPIMPOSTO, chave era imNumLan virou idImposto
JPEMPRESA, chave era emNumLan virou IdEmpresa

E já estava em MySQL
Em JPREGUSO, chave era ruId virou IdRegUso

São alterações relativamente simples, mas alteram arquivos, estruturas, e todo fonte que faz uso dos arquivos e campos.
Ainda fiquei na dúvida, porque os campos dos arquivos começam com duas letras indicando o arquivo, e agora a chave ficou diferente do resto.
A minha chave dos arquivos passou a ser ID seguida do nome do arquivo.

Era isso que tinha misturado na migração pra MySQL.
Desta vez fui fazendo uma coisa de cada vez, do ponto de vista de programação/alteração de fontes.
Nos clientes, ao instalar, vai tudo de uma vez.

Por enquanto instalei em 5 clientes, e tudo ok.
instalar = clicar na opção de atualizar aplicativo

Daqui pra frente, só atualizações sem volta... rumo ao MySQL e pronto.

Nota: durante o texto, outro cliente fez atualização.

Meu modo de trabalho

MensagemEnviado: 31 Out 2019 20:06
por JoséQuintas
Hoje liguei pra um cliente que tem movimentação pesada, pra ele atualizar o aplicativo...
Mas ele já tinha atualizado kkkk

Só ficou um sem atualizar, mas é praticamente monousuário, então nem vou me preocupar.
Quando atualizar... vai direto pra próxima.

A hora do MySQL chegou.

Meu modo de trabalho

MensagemEnviado: 05 Nov 2019 10:50
por JoséQuintas
Hoje começou a gravação dupla.
Como era de se esperar.... muita correção, mas tá indo.

Lembram da minha errorsys exagerada?
Pois é... tá ajudando....

OK OPTION DEFAULT: Error DBFCDX/1021  Data width error: NFPESLIQ  


No DBF pode até aceitar campo estourado, mas no MySQL não.

Só não me perguntem porque o usuário anda emitindo nota com mais de 99 toneladas....
Talvez porque o aplicativo permite... kkkkk

Meu modo de trabalho

MensagemEnviado: 05 Nov 2019 11:17
por JoséQuintas
A título de curiosidade:
Joguei todo meu controle de numeração no lixo.

   ...
   :QueryAdd( "NFFILIAL", mObs2Filial )
   :QueryAdd( "NFINFINC", LogInfo() )

   midNotFis := StrZero( :QueryExecuteInsert( "JPNOTFIS" ), 6 )

   :QueryAdd( "IDNOTFIS", midNotFis )
   :DBFQueryExecuteInsert( "JPNOTFIS" )


Ao incluir no MySQL, o próprio MySQL numera.
Então... adiciono a chave retornada antes de gravar no DBF.

Como a lista de campos e valores é igual para os dois, certeza de conteúdo igual.

Agora é continuar acompanhando, pra ver se errei em algum lugar.

Próxima etapa: se tá gravado no MySQL, pra que DBF?

Meu modo de trabalho

MensagemEnviado: 11 Nov 2019 09:36
por JoséQuintas
Aqui convém destacar uma coisa, TBROWSE:

Além de mostrar meu tbrowse em DBF, mostra meu início do MySQL, convertendo o resultado em DBF.

METHOD GridSelection() CLASS ClassiClass

   LOCAL nSelect := Select(), oTBrowse
   LOCAL cnMySql := ADOClass():New( AppConexao() )
   LOCAL cTmpFile, cCdxFile

   cnMySql:cSql := "SELECT DESCRICAO, CLASSI, TIPO, TIPO1, GRUPO, GERAL, NIVEL FROM CLASSI ORDER BY CLASSI"
   cTmpFile     := cnMySql:SqlToDbf()
   SELECT 0
   USE ( cTmpFile ) ALIAS temp
   cCdxFile := MyTempFile( "CDX" )
   INDEX ON temp->classi TO ( cCdxFile )
   GOTO TOP

   oTBrowse := { ;
      { "Descricao",   {|| temp->Descricao } }, ;
      { "Classi",      {|| temp->Classi } }, ;
      { "Tipo",        {|| temp->Tipo } }, ;
      { "Tipo1",       {|| temp->Tipo1 } }, ;
      { "Grupo",       {|| temp->Grupo } }, ;
      { "Geral",       {|| temp->Geral } }, ;
      { "Nivel",       {|| temp->Nivel } } }
   FazBrowse( oTBrowse )
   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD temp->Classi + Chr( K_ENTER )
   ENDIF
   USE
   fErase( cTmpFile )
   fErase( cCdxFile )
   SELECT ( nSelect )

   RETURN NIL


E o tbrowse agora direto em ADO:

METHOD GridSelection() CLASS JPAGENDAClass

   LOCAL oTBrowse, cnMySql := ADOClass():new( AppConexao() )

   WITH OBJECT cnMySql
      :cSql := "SELECT AGNOME, AGTELEFONE, AGTELEF2, AGTELEF3, AGCIDADE, AGUF, IDAGENDA FROM JPAGENDA ORDER BY AGNOME"
      :Execute()
      oTBrowse := { ;
         { "NOME",     { || :String( "AGNOME", 30 ) } }, ;
         { "TELEFONE", { || :String( "AGTELEFONE", 13 ) } }, ;
         { "TELEF2",   { || :String( "AGTELEF2", 13 ) } }, ;
         { "TELEF3",   { || :String( "AGTELEF3", 13 ) } }, ;
         { "CIDADE",   { || :String( "AGCIDADE", 15 ) } }, ;
         { "UF",       { || :String( "AGUF", 2 ) } } }
      BrowseADO( cnMySql, oTBrowse, "AGNOME", { || StrZero( :Number( "IDAGENDA" ), 6 ) } )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


O destaque nisso é o seguinte: ao mesmo tempo que estou adaptando meus fontes para o ADO, também estou adaptando o uso do ADO aos meus fontes, ao meu estilo.
Seja DBF ou ADO, crio um array com o conteúdo do tbrowse.

Já comentei por aqui:
Deixar fontes simples/limpos/padronizados, antes de complicar.
Tenho meu estilo/padrão de trabalho criado, então vou seguindo esse estilo/padrão.
É diferente de sair remendando qualquer fonte de qualquer jeito.
Bastam pequenos ajustes no estilo/padrão, e estou usando uma base de dados totalmente diferente, quase do mesmo jeito que fazia antes.

Tem uma coisa interessante: no MySQL meus códigos são numéricos, já no DBF/aplicativo sempre foram caractere.
Na hora de montar a string para o MySQL, basta eu somar o campo, sem precisar nenhuma conversão.
"SELECT * FROM JPCADASTRO WHERE IdCadastro=" + midCadastro

Foi sem querer... Apenas queria usar o incremental no MySQL para os campos que no DBF hoje são string.
Parecia que isso ia ser problema, afinal são tipos diferentes, mas acabou facilitando as coisas.

Meu modo de trabalho

MensagemEnviado: 11 Nov 2019 13:27
por JoséQuintas
Transportadoras... lembram?
separei do cadastro, ficava junto com clientes.
JPCADAS virou JPCADASTRO e JPTRANSP
Hoje deixei somente MySQL.
JPTRANSP.DBF é o primeiro DBF eliminado da temporada - e junto JPTRANSP.CDX
Não deu tempo nem de completar um mês de uso.

Transportadoras é pouco usado, por isso foi rápido.
Basicamente entra no cadastro de clientes, e na nota/nota eletrônica.
Listagem... só uma.

Meu modo de trabalho

MensagemEnviado: 11 Nov 2019 13:43
por JoséQuintas
As atualizações de versão....
22 de outubro, transportadoras virou arquivo separado

STATIC FUNCTION Update1022B()

   SayScroll( "Transferindo transportadoras" )
   IF ! AbreArquivos( "jptransp", "jpcadastro" )
      QUIT
   ENDIF
   SELECT jpcadastro
   SET ORDER TO 0
   GOTO TOP
   GrafTempo( "JPTRANSP" )
   DO WHILE ! Eof()
      GrafTempo( RecNo(), LastRec() )
      IF jpcadastro->cdTipo != "3"
         SKIP
         LOOP
      ENDIF
      SELECT jptransp
      RecAppend()
      REPLACE ;
         jptransp->tpCodigo   WITH jpcadastro->cdCodigo, ;
         jptransp->idTransp   WITH iif( Empty( jpcadastro->idCadas ), jpcadastro->cdCodigo, jpcadastro->idCadas ), ;
         jptransp->tpNome     WITH jpcadastro->cdNome, ;
         jptransp->tpApelido  WITH jpcadastro->cdApelido, ;
         jptransp->tpCnpj     WITH jpcadastro->cdCnpj, ;
         jptransp->tpEndereco WITH jpcadastro->cdEndereco, ;
         jptransp->tpNumero   WITH jpcadastro->cdNumero, ;
         jptransp->tpCompl    WITH jpcadastro->cdCompl, ;
         jptransp->tpBairro   WITH jpcadastro->cdBairro, ;
         jptransp->tpCidade   WITH jpcadastro->cdCidade, ;
         jptransp->tpUF       WITH jpcadastro->cdUF, ;
         jptransp->tpCep      WITH jpcadastro->cdCep, ;
         jptransp->tpTelefone WITH jpcadastro->cdTelefone, ;
         jptransp->tpInsEst   WITH jpcadastro->cdInsEst, ;
         jptransp->tpContato  WITH jpcadastro->cdContato, ;
         jptransp->tpTelef2   WITH jpcadastro->cdTelef2, ;
         jptransp->tpTelef3   WITH jpcadastro->cdTelef3, ;
         jptransp->tpTelef4   WITH jpcadastro->cdFax, ;
         jptransp->tpEmail    WITH jpcadastro->cdEmail, ;
         jptransp->tpHomePage WITH jpcadastro->cdHomePage, ;
         jptransp->tpObs      WITH jpcadastro->cdObs, ;
         jptransp->tpStatus   WITH jpcadastro->cdStatus, ;
         jptransp->tpInfInc   WITH jpcadastro->cdInfInc, ;
         jptransp->tpInfAlt   WITH jpcadastro->cdInfAlt
      SELECT jpcadastro
      RecLock()
      DELETE
      RecUnlock()
      SKIP
   ENDDO
   CLOSE DATABASES
   Mensagem()

   RETURN NIL


23/outubro, teve o campo chave alterado

STATIC FUNCTION Update1023D()

   IF ! AbreArquivos( "jptransp" )
      QUIT
   ENDIF
   SayScroll( "Atualizando JPTRANSP" )
   SET ORDER TO 0
   GOTO TOP
   GrafTempo( "JPTRANSP" )
   DO WHILE ! Eof()
      GrafTempo( RecNo(), LastRec() )
      IF Empty( jptransp->idTransp )
         RecLock()
         REPLACE jptransp->idTransp WITH jptransp->tpCodigo
         RecUnlock()
      ENDIF
      SKIP
   ENDDO
   CLOSE DATABASES
   Mensagem()

   RETURN NIL


03/novembro.... checagem de códigos repetidos - em dbf pode acontecer, e gravação dupla mysql

STATIC FUNCTION Update1103()

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   VerificaNumeracao( "JPTRANSP", "IDTRANSP", "TPOBS" )
   CopyDbfToMySql( "JPTRANSP", .T., .F., .T. )

   RETURN NIL


E hoje.... deixou de existir em DBF.

Foram 20 dias, mas não exclusivo nesse arquivo, foi reorganizando o aplicativo, preparando rotinas, etc.

Como eu disse antes, sem pressa.
Quem precisa se acostumar a não existir DBF sou eu, e não o aplicativo kkkk
Devagar chega lá.

Meu modo de trabalho

MensagemEnviado: 13 Nov 2019 11:42
por JoséQuintas
O browse de clientes anterior - DBF

METHOD GridSelection() CLASS JPCADASTROClass

   LOCAL oElement, nSelect := Select(), cOrdSetFocus, oTBrowse

   IF Select( "jpclista" ) == 0
      SELECT 0
      AbreArquivos( "jpclista" )
      Errorsys_WriteErrorLog( "Faltou abrir jpclista", 3 )
   ENDIF
   SELECT jpcadastro
   oTBrowse := { ;
      { "Nome",        {|| jpcadastro->cdNome } }, ;
      { "Apelido",     {|| jpcadastro->cdApelido } }, ;
      { "Código",      {|| jpcadastro->idCadastro } }, ;
      { "UF",          {|| jpcadastro->cdUf } }, ;
      { "Cidade",      {|| jpcadastro->cdCidade } }, ;
      { "Ref.Mapa",    {|| jpcadastro->cdMapa } },  ;
      { "Endereço",    {|| jpcadastro->cdEndereco } }, ;
      { "Número",      {|| jpcadastro->cdNumero } }, ;
      { "Complemento", {|| jpcadastro->cdCompl } }, ;
      { "Cnpj",        {|| jpcadastro->cdCnpj } } }
   FOR EACH  oElement IN oTBrowse
      AAdd( oElement, {|| iif( ! Encontra( jpcadastro->cdStatus, "jpclista", "numlan" ) .OR. Val( jpcadastro->cdStatus ) < 2, { 1, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "0", { 3, 2 }, ;
         iif( Trim( jpclista->csBloqueio ) == "1", { 4, 2 }, { 7, 2 } ) ) ) } )
   NEXT
   cOrdSetFocus := ordSetFocus()
   ordSetFocus( "nome" )
   FazBrowse( oTBrowse )
   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD jpcadastro->idCadastro + Chr( K_ENTER )
   ENDIF
   ordSetFocus( cOrdSetFocus )
   SELECT ( nSelect )

   RETURN NIL


o browse atual - ADO/MySQL

METHOD GridSelection() CLASS JPCADASTROClass

   LOCAL oElement, oTBrowse, cnMySql := ADOClass():New( AppConexao() )

   WITH OBJECT cnMySql
      :cSql := "SELECT IDCADASTRO, CDNOME, CDAPELIDO, CDUF, CDCIDADE, CDMAPA, CDENDERECO, " + ;
         "CDNUMERO, CDCOMPL, CDCNPJ, JPCLISTA.CSBLOQUEIO AS STATUS FROM JPCADASTRO " + ;
         "LEFT JOIN JPCLISTA ON JPCLISTA.IDCLISTA = JPCADASTRO.CDSTATUS " + ;
         "ORDER BY CDNOME"
      :Execute()
      oTBrowse := { ;
         { "NOME",        { || :String( "CDNOME", 40 ) } }, ;
         { "APELIDO",     { || :String( "CDAPELIDO", 20 ) } }, ;
         { "CÓDIGO",      { || StrZero( :Number( "IDCADASTRO" ), 6 ) } }, ;
         { "UF",          { || :String( "CDUF", 2 ) } }, ;
         { "CIDADE",      { || :String( "CDCIDADE", 21 ) } }, ;
         { "REF.MAPA",    { || :String( "CDMAPA", 30 ) } }, ;
         { "ENDEREÇO",    { || :String( "CDENDERECO", 40 ) } }, ;
         { "NÚMERO",      { || :String( "CDNUMERO", 10 ) } }, ;
         { "COMPLEMENTO", { || :String( "CDCOMPL", 20 ) } }, ;
         { "CNPJ",        { || :String( "CDCNPJ", 18 ) } } }
      FOR EACH oElement IN oTBrowse
         AAdd( oElement, { || iif( :String( "STATUS", 1 ) == "0", { 3,2 }, ;
                              iif( :String( "STATUS", 1 ) == "1", { 4,2 }, { 7,2 } ) ) } )
      NEXT
      BrowseADO( cnMySql, oTBrowse, "CDNOME", { || StrZero( :String( "IDCADASTRO" ), 6 ) } )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


Nada de testar arquivo aberto, nada de salvar área atual, nada de arquivos.
Apenas pede informação pro servidor e faz o browse.

Lembrando:
No MySQL estou usando VARCHAR como tipo de campo, então as strings não tem tamanho fixo.
Aproveitei na classe a função :String() pra já definir o tamanho que eu quero, dispensando conversões extras, e simplificando muito

Meu modo de trabalho

MensagemEnviado: 18 Nov 2019 19:39
por JoséQuintas
Curiosidade, um erro de hoje

SYSTEM ERROR
Error BASE/1004 Message not found: ADOCLASS:QUERYEXECUTEINSER
Called from __ERRRT_SBASE(0)
Called from ADOCLASS:ERROR(0)
Called from (b)HBOBJECT(0)
Called from ADOCLASS:MSGNOTFOUND(0)
Called from ADOCLASS:QUERYEXECUTEINSER(0)
Called from HLCOFRECLASS:TELADADOS(163)
Called from HLCOFRECLASS:EXECUTE(403)
Called from PHLCOFRE(20)
Called from DO(0)
Called from BOXMENU(509)
Called from MAINMENU(356)
Called from MAIN(70)


O correto é QueryExecuteInsert(), falhou a digitação e faltou o T.
NENHUM erro com ADO ou MYSQL, apenas erro de programador mesmo... kkkkk

Mas alterando sem parar, eliminando tudo que é DBF temporário, que usei como forma quebra-galho no começo do uso do ADO.
Tudo ficando mais rápido sem precisar de DBF.

Meu modo de trabalho

MensagemEnviado: 20 Nov 2019 21:17
por JoséQuintas
Até já coloquei em outro post, mas aqui é a mudança com o tempo, não faria sentido colocar junto ao outro post.

Aqui o tbrowse era somente DBF:

METHOD GridSelection() CLASS JPPEDIDOClass

   LOCAL nCont, oTBrowse, nSelect := Select()

   IF Select( "jptransa" ) == 0
      SELECT 0
      AbreArquivos( "jptransa" )
      Errorsys_WriteErrorLog( "Faltou abrir jptransa", 3 )
   ENDIF
   IF Select( "jpnotfis" ) == 0
      SELECT 0
      AbreArquivos( "jpnotfis" )
      Errorsys_WriteErrorLog( "Faltou abrir jpnotfis", 3 )
   ENDIF
   IF Select( "jpcadastro" ) == 0
      SELECT 0
      AbreArquivos( "jpcadastro" )
      Errorsys_WriteErrorLog( "Faltou abrir jpcadas", 3 )
   ENDIF
   SELECT jppedido
   oTBrowse := { ;
      { "PEDIDO",      {|| jppedido->idPedido } }, ;
      { "DT.EMIS.",    {|| jppedido->pdDatEmi } }, ;
      { "CLIENTE",     {|| jppedido->pdCliFor + " " + ReturnValue( "", Encontra( jppedido->pdCliFor, "jpcadastro", "numlan" ) ) + Pad( jpcadastro->cdNome, 30 ) } }, ;
      { "NF",          {|| ReturnValue( "", Encontra( jppedido->idPedido, "jpnotfis", "pedido" ) ) + Right( jpnotfis->nfFilial, 2 ) + "." + jpnotfis->nfNotFis } }, ;
      { "SITUAÇÃO",    {|| Pad( Pedido():Status(), 10 ) } }, ;
      { "TRANSAÇÃO",   {|| ReturnValue( "", Encontra( jppedido->pdTransa, "jptransa", "numlan" ) ) + Pad( jptransa->trDescri, 12 ) } }, ;
      { "VAL.NF",      {|| Transform( jppedido->pdValNot, "999,999,999.99" ) } } }
   FOR nCont = 1 TO Len( oTBrowse )
      AAdd( oTBrowse[ nCont ], {|| ;
         iif( "NF." $ Pedido():Status() .OR. "CANCELADO" $ Pedido():Status() .OR. "DEM." $ Pedido():Status(), { 1, 2 }, ;
         iif( "FATURAR" $ Pedido():Status(), { 7, 2 }, { 6, 2 } ) ) } )
   NEXT
   FazBrowse( oTbrowse )
   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD jppedido->idPedido + Chr( K_ENTER )
   ENDIF
   SELECT ( nSelect )

   RETURN NIL


Aqui ficou parcial DBF/MySQL

WITH OBJECT cnMySql
   :cSql := "SELECT IDPEDIDO, PDDATEMI, PDCLIFOR, LEFT( JPCADASTRO.CDNOME, 30 ) AS NOME, " + ;
    "PDTRANSA, PDVALNOT, PDCONF, PDSTATUS " + ;
    "FROM JPPEDIDO " + ;
    "LEFT JOIN JPCADASTRO ON JPPEDIDO.PDCLIFOR = JPCADASTRO.IDCADASTRO " + ;
    "WHERE PDDATEMI > " + DateSql( Date() - 180 )
   :Execute()
   oTBrowse := { ;
    { "PEDIDO",  { || StrZero( :Number( "IDPEDIDO" ), 6 ) } }, ;
    { "DT.EMIS.", { || :Date( "PDDATEMI" ) } }, ;
    { "CLIENTE", { || :String( "PDCLIFOR", 6 ) + " " + :String( "NOME", 30 ) } }, ;
    { "NF",    { || ReturnValue( "", Encontra( StrZero( :Number( "IDPEDIDO" ), 6 ), "jpnotfis", "pedido" ) ) + Right( jpnotfis->nfFilial, 2 ) + "." + jpnotfis->nfNotFis } }, ;
    { "SITUAÇÃO", { || Pad( PedidoStatus( StrZero( :Number( "IDPEDIDO" ), 6 ), :String( "PDSTATUS", 2 ), :String( "PDCONF", 2 ) ), 10 ) } }, ;
    { "TRANSAÇÃO", { || ReturnValue( "", Encontra( :String( "PDTRANSA", 6 ), "jptransa", "numlan" ) ) + Pad( jptransa->trDescri, 12 ) } }, ;
    { "VAL.NF",  { || Transform( :Number( "PDVALNOT" ), "999,999,999.99" ) } } }
   FOR nCont = 1 TO Len( oTBrowse )
    AAdd( oTBrowse[ nCont ], { | cStatus | cStatus := PedidoStatus( StrZero( :Number( "IDPEDIDO" ), 6 ), ;
      :String( "PDSTATUS", 2 ), :String( "PDCONF", 2 ) ), ;
      iif( "NF." $ cStatus .OR. "CANCELADO" $ cStatus, { 1, 2 }, ;
      iif( "FATURAR" $ cStatus, { 7, 2 }, { 6, 2 } ) ) } )
   NEXT
   BrowseADO( cnMySql, oTBrowse, "NOME", { || StrZero( :Number( "IDPEDIDO" ), 6 ) } )
   :CloseRecordset()
ENDWITH


Aqui ficou mais MySQL ainda, pegando informação do MySQLJPNOTFIS ao invés de DBF.JPNOTFIS

METHOD GridSelection() CLASS JPPEDIDOClass

   LOCAL nCont, oTBrowse, nSelect := Select(), cnMySql := ADOClass():New( AppConexao() )

   IF Select( "jptransa" ) == 0
      SELECT 0
      AbreArquivos( "jptransa" )
      Errorsys_WriteErrorLog( "Faltou abrir jptransa", 3 )
   ENDIF
   WITH OBJECT cnMySql
      :cSql := "SELECT IDPEDIDO, PDDATEMI, PDCLIFOR, LEFT( JPCADASTRO.CDNOME, 30 ) AS NOME, " + ;
         "PDTRANSA, PDVALNOT, PDCONF, PDSTATUS, JPNOTFIS.IDNOTFIS AS NOTFIS, RIGHT( JPNOTFIS.NFFILIAL, 2 ) AS FILIAL " + ;
         "FROM JPPEDIDO " + ;
         "LEFT JOIN JPCADASTRO ON JPPEDIDO.PDCLIFOR = JPCADASTRO.IDCADASTRO " + ;
         "LEFT JOIN JPNOTFIS ON JPPEDIDO.IDPEDIDO = JPNOTFIS.NFPEDIDO " + ;
         "WHERE PDDATEMI > " + DateSql( Date() - 180 )
      :Execute()
      oTBrowse := { ;
         { "PEDIDO",    { || StrZero( :Number( "IDPEDIDO" ), 6 ) } }, ;
         { "DT.EMIS.",  { || :Date( "PDDATEMI" ) } }, ;
         { "CLIENTE",   { || :String( "PDCLIFOR", 6 ) + " " + :String( "NOME", 30 ) } }, ;
         { "NF",        { || :String( "FILIAL", 2 ) + "." + StrZero( :Number( "NOTFIS" ), 6 ) } }, ;
         { "SITUAÇÃO",  { || Pad( PedidoStatus( StrZero( :Number( "IDPEDIDO" ), 6 ), :String( "PDSTATUS", 2 ), :String( "PDCONF", 2 ) ), 10 ) } }, ;
         { "TRANSAÇÃO", { || ReturnValue( "", Encontra( :String( "PDTRANSA", 6 ), "jptransa", "numlan" ) ) + Pad( jptransa->trDescri, 12 ) } }, ;
         { "VAL.NF",    { || Transform( :Number( "PDVALNOT" ), "999,999,999.99" ) } } }
      FOR nCont = 1 TO Len( oTBrowse )
         AAdd( oTBrowse[ nCont ], { | cStatus | cStatus := PedidoStatus( StrZero( :Number( "IDPEDIDO" ), 6 ), ;
            :String( "PDSTATUS", 2 ), :String( "PDCONF", 2 ) ), ;
            iif( "NF." $ cStatus .OR. "CANCELADO" $ cStatus, { 1, 2 }, ;
            iif( "FATURAR" $ cStatus, { 7, 2 }, { 6, 2 } ) ) } )
      NEXT
      BrowseADO( cnMySql, oTBrowse, "NOME", { || StrZero( :Number( "IDPEDIDO" ), 6 ) } )
      :CloseRecordset()
   ENDWITH
   SELECT ( nSelect )

   RETURN NIL


O próximo será JPTRANSA e pronto, nenhum DBF para esse tbrowse.

Tive que modificar o campo de pedido em JPNOTFIS, porque nem tinha paciência de esperar o tbrowse, de tão demorado que tinha ficado.
Depois de alterar o campo pra numérico, e criar índice... aí sim ficou instantâneo.

Alterar de string para numérico no MySQL é simples... ou quase.
Se o conteúdo for numérico, a alteração é instantânea, mas se contiver algo diferente... aí precisa ajustar o conteúdo diferente primeiro.

Quanto à migração pra MySQL:
Por enquanto eliminando a necessidade de DBFs, nos TBROWSEs, e módulos sem complicação.

Lembrando:
É sempre no esquema do cliente clicar e já atualizar.
Deu certo já fica disponível pra quem quiser.
Sempre em frente, e sempre funcionando.

Meu modo de trabalho

MensagemEnviado: 21 Nov 2019 21:21
por JoséQuintas
Erro executando comando:-2147467259 [MySQL][ODBC 5.3(a) Driver][mysqld-5.7.12-log]Incorrect integer value: '' for column 'NFPEDIDO' at row 15
Called from ADOCLASS:EXECUTECMD(254)
Called from COPYDBFTOMYSQL(144)
Called from UPDATE1103(648)
Called from ZE_UPDATE2019(34)
Called from ZE_UPDATE0000(20)
Called from ZE_UPDATE(123)
Called from SISTEMA(65)
Called from (b)MAIN(51)


Esse erro é aqui nos testes, e eu não contava com isso, mas tudo bem.

O que acontece?

No MySQL, alterar um campo string pra numérico pode ser feito, se a string for "0" vai alterar pro número zero.
Mas se o campo contiver espaços.... vai dar erro.
O mesmo na hora de gravar... algo equivalente a REPLACE NUMERO WITH '0'

Clientes convertidos, coloquei lá os comandos pra ajustar o MySQL antes de fazer a modificação, tudo certo.

Mas.... tem os clientes atrasadinhos... onde ainda não tem nada no MySQL.
Ao criar a estrutura nova, com campos numéricos... a conversão do DBF não funciona mais, e eles ainda precisam dela.

Tudo bem, a solução foi alterar a antiga conversão - que os outros já fizeram - pra gravar esse ZERO aonde necessário.

Agora os atrasadinhos vão poder atualizar normalmente.

Mas agora estou pensando em forçar os atrasados a atualizarem, pra não ter nenhum outro imprevisto desse depois.
Se todos atualizarem, posso até apagar toda conversão do aplicativo.

Também poderia converter os DBFs pra numérico antes de converter pra MySQL... mas aí vai complicar muito mais, porque o Harbour não aceita alterar campo de caractere pra numérico....

Pois é...
atualizar estrutura de DBF ok.
atualizar estrutura de MySQL ok.
Agora... trabalhar com os dois juntos, com coisa convertida e sem converter.... isso fica complicado e perigoso...

Meu modo de trabalho

MensagemEnviado: 21 Nov 2019 21:43
por JoséQuintas
A solução foi acrescentar na conversão isto aqui:

      IF Val( jpnotfis->nfPedido ) == 0
         RecLock()
         REPLACE jpnotfis->nfPedido WITH "0"
         RecUnlock()
      ENDIF


É conversão que JÁ FOI feita em vários clientes.
Tive que alterar a conversão "antiga", que vai ser usada somente nos atrasadinhos.

Meu modo de trabalho

MensagemEnviado: 22 Nov 2019 18:27
por sygecom
Zé,
Nesses casos não seria melhor criar um campo novo(outro nome),migrar os dados para o campo novo e alterar o sistema para passar a usar esse campo novo, depois por fim delete no campo velho.

Meu modo de trabalho

MensagemEnviado: 22 Nov 2019 19:45
por JoséQuintas
sygecom escreveu:Zé,Nesses casos não seria melhor criar um campo novo(outro nome),migrar os dados para o campo novo e alterar o sistema para passar a usar esse campo novo, depois por fim delete no campo velho.


Acho que não entendeu.

Coloquei no aplicativo toda conversão/migração automática.
O problema é que num certo momento, fiz isso valendo como continuação... e esqueci dos que estavam na fase anterior.

Com o ajuste que fiz, o aplicativo vai fazer tudo para os atrasados.
Para os que já atualizaram, isso nem vai fazer diferença.

Então... mudar estrutura de DBF agora seria mexer até com quem já fez atualização.
O próximo passo é mesmo eliminar leitura de DBFs, pra depois eliminar os DBFs.

Meu modo de trabalho

MensagemEnviado: 25 Nov 2019 13:40
por JoséQuintas
Erro de agora

Error WINOLE/1007 O item não pode ser encontrado na coleção correspondente ao nome ou ao ordinal solicitado. (0x800A0CC1): ADODB.Recordset;(DOS Error -2147352567)
Called from WIN_OLEAUTO:FIELDS(0)
Called from ADOCLASS:STRING(358)
Called from (b)JPPEDIDOCLASS_GRIDSELECTION(161)
Called from BROWSEADORC(70)
Called from BROWSEADO(148)
Called from JPPEDIDOCLASS:GRIDSELECTION(161)
Called from PESQUISA(339)


Normal... se usar arquivo->CampoNaoExiste também daria erro, no ADO não é diferente....

É que alterei isto:
SELECT LPAD( IDPEDIDO, 6, '0' ) AS PEDIDO, 


e esqueci de alterar aonde usava IDPEDIDO para o novo nome PEDIDO.
Tudo correndo bem, só erro de programador mesmo.

Meu modo de trabalho

MensagemEnviado: 25 Nov 2019 15:53
por asimoes
Olá Quintas,

Vi que você usa a função LPAD é a mesma função PadL() ? os parâmetros são parecidos

Meu modo de trabalho

MensagemEnviado: 25 Nov 2019 15:56
por JoséQuintas
asimoes escreveu:Vi que você usa a função LPAD é a mesma função PadL() ? os parâmetros são parecidos


Sim, com a diferença de que no MySQL pode ser campo numérico.
Ou seja, substitui Padl() e StrZero().

Meu modo de trabalho

MensagemEnviado: 25 Nov 2019 19:55
por asimoes
JoséQuintas escreveu:Sim, com a diferença de que no MySQL pode ser campo numérico.
Ou seja, substitui Padl() e StrZero().

Na documentação da PadL diz que ela aceita tipo Caracter , Data e Número

PadC(), PadL(), and PadR() are conversion functions that convert the value of an expression to a character string, padded with a fill character. The functions accept for <expression> values of data type Character, Date and Numeric. 

Meu modo de trabalho

MensagemEnviado: 27 Nov 2019 05:32
por JoséQuintas
Só recapitulando como estou fazendo.
Neste momento migrando o arquivo de transações para o MySQL.

1. Alterei a parte do MySQL pra criar a tabela no MySQL
2. Criei a rotina de atualização, que grava o arquivo JPTRANSA.DBF na tabela JPTRANSA
3. Alterei o cadastro de transações, pra atualizar no DBF e no MySQL ao mesmo tempo
Então existe DBF e MySQL e os dois estão sendo atualizados.
Pronto pra instalação nos clientes.

Vantagem desse método:
Tô alterando e colocando pra funcionar.
Pra gravar duplicado, praticamente só alterei o fonte do cadastro, o que foi rápido.

E agora?
O que usa informação do DBF funciona, e o que usar informação do MySQL também funciona.
Então, posso ir alterando e trocando no cliente CADA fonte.
É teste prático de CADA alteração assim que for feita.
Se não precisar mais do DBF, só apagar.

Nota:
Aonde usa notas fiscais e pedidos, ainda não terminei de alterar, é muuuito fonte.
Mas tudo está funcionando, seja DBF ou MySQL.

Uma opção disponível, não que eu vá usar:
Se apagar agora dos DBFs informação anterior a 2018, relatórios em DBF não terão a informação, mas em MySQL sim.
Seria perigoso se fosse reutilizada a numeração de lançamentos, mas se é o MySQL que passou a fornecer o número... nunca vai repetir.
A vantagem seria menos DBF pra processar, deixando a parte de DBF mais rápida.

Meu modo de trabalho

MensagemEnviado: 27 Nov 2019 11:07
por JoséQuintas
   WITH OBJECT cnMySql
      :cSql := "SELECT ..."
...
      oTBrowse := { ;
         { "PEDIDO",    { || :String( "PEDIDO", 6 ) } }, ;
         { "DT.EMIS.",  { || :Date( "PDDATEMI" ) } }, ;
         { "TRANSAÇÃO", { || ReturnValue( "", Encontra( :String( "PDTRANSA", 6 ), "jptransa", "numlan" ) ) + Pad( jptransa->trDescri, 12 ) } }, ;
...


Nesse browse, o JPTRANSA está em DBF... sem problemas.
Mas com a mudança do JPTRANSA disponível em MySQL... fim dos DBFs pra esse browse e fim de pesquisas adicionais no programa pro browse.

Meu modo de trabalho

MensagemEnviado: 01 Dez 2019 23:19
por JoséQuintas
UMA das mudanças de hoje:

STATIC FUNCTION MostraUltimaCompra( cItem )

   LOCAL nSelect, cOrdSetFocus, cSetColor

   IF AppUserLevel() != 0
      RETURN NIL
   ENDIF
   nSelect := Select()
   SELECT jpestoque
   cOrdSetFocus := ordSetFocus( "jpestoq3" )
   SEEK cItem + "X" SOFTSEEK
   SKIP -1
   DO WHILE jpestoque->esItem == cItem .AND. ! Bof()
      IF Val( jpestoque->esPedido ) == 0
         SKIP -1
         LOOP
      ENDIF
      Encontra( jpestoque->esTransa, "jptransa", "numlan" )
      IF "COMPRA" $ jptransa->trReacao
         EXIT
      ENDIF
      SKIP -1
   ENDDO

   cSetColor := SetColor()
   IF Date() - jpestoque->esDatLan > 90 .OR. jpestoque->esItem != cItem .OR. jpestoque->esTipLan != "2"
      SetColor( SetColorAlerta() )
   ENDIF
   @ Row() + 2, 5     SAY Space( 80 )
   IF jpestoque->esItem == cItem .AND. jpestoque->esTipLan == "2"
      Encontra( jpestoque->esCliFor, "jpcadastro", "numlan" )
      @ Row(), 5         SAY "Última Entrada:" + DToC( jpestoque->esDatLan )
      @ Row(), Col() + 2 SAY "Qtd:" + LTrim( Str( jpestoque->esQtde ) )
      @ Row(), Col() + 2 SAY "Vl:" + LTrim( Transform( jpestoque->esValor, PicVal( 14, 2 ) ) )
      @ Row() + 1, 5     SAY Space( 80 )
      @ Row(), 5         SAY "Pedido:" + jpestoque->esPedido
      @ Row(), Col() + 2 SAY "Forn:" + jpcadastro->cdNome
   ENDIF
   @ Row() + 1, 5 SAY "Custo (Tabela): " + LTrim( Transform( CustoItem( cItem ), PicVal( 14, 2 ) ) ) + " Qt.Disp: " + Str( jpitem->ieQtd1 + jpitem->ieQtd2 + jpitem->ieQtd3 + jpitem->ieQtd4 - jpitem->ieRes1, 8 )
   SetColor( cSetColor )
   ordSetFocus( cOrdSetFocus )
   SELECT ( nSelect )

   RETURN NIL


STATIC FUNCTION MostraUltimaCompra( cItem )

   LOCAL cSetColor, cnMySql := ADOClass():New( AppConexao() )

   IF AppUserLevel() != 0
      RETURN NIL
   ENDIF
   WITH OBJECT cnMySql
      :cSql := "SELECT ESDATLAN, ESCLIFOR, ESQTDE, ESVALOR, ESPEDIDO, JPCADASTRO.CDNOME AS NOME, " + ;
         "JPITEM.IEQTD1 + JPITEM.IEQTD2 + JPITEM.IEQTD3 + JPITEM.IEQTD4 - JPITEM.IERES1 AS QTDE " + ;
         "FROM JPESTOQUE " + ;
         "LEFT JOIN JPTRANSA ON JPESTOQUE.ESTRANSA=JPTRANSA.IDTRANSA " + ;
         "LEFT JOIN JPCADASTRO ON JPESTOQUE.ESCLIFOR=JPCADASTRO.IDCADASTRO " + ;
         "LEFT JOIN JPITEM ON JPESTOQUE.ESITEM=JPITEM.IDITEM " + ;
         "WHERE ESITEM=" + StringSql( cItem ) + " AND ESTIPLAN='2' AND JPTRANSA.TRREACAO LIKE '%COMPRA%' " + ;
         "ORDER BY ESDATLAN DESC LIMIT 1"
      :Execute()
      cSetColor := SetColor()
      IF Date() - :Date( "ESDATLAN" ) > 90
         SetColor( SetColorAlerta() )
      ENDIF
      @ Row() + 2, 5     SAY Space( 80 )
      @ Row(), 5         SAY "Última Entrada:" + DToC( :Date( "ESDATLAN" ) )
      @ Row(), Col() + 2 SAY "Qtd:" + LTrim( Str( :Number( "ESQTDE" ) ) )
      @ Row(), Col() + 2 SAY "Vl:" + LTrim( Transform( :Number( "ESVALOR" ), PicVal( 14, 2 ) ) )
      @ Row() + 1, 5     SAY Space( 80 )
      @ Row(), 5         SAY "Pedido:" + :String( "ESPEDIDO", 6 )
      @ Row(), Col() + 2 SAY "Forn:" + :String( "NOME" )
      @ Row() + 1, 5 SAY "Custo (Tabela): " + LTrim( Transform( CustoItem( cItem ), PicVal( 14, 5 ) ) ) + " Qt.Disp: " + Str( :Number( "QTDE" ), 8 )
      SetColor( cSetColor )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


Deixando de lado o que mais poderia ser melhorado....

1. Antes utilizava os DBFs jpitem, jptransa, jpestoque, jpcadastro agora NENHUM
2. Antes o processamento era local, agora no servidor
3. No tráfego de rede, ao invés de 4 DBFs + 4 CDXs + vários registros de movimentação... agora é apenas a informação que interessa
4. Nada de área em uso, escolher índice, etc. etc. essas coisas de DBF
5. A rotina pode ser chamada de qualquer lugar, não depende mais de arquivo em uso, nem de qualquer coisa externa

E.... no futuro... no uso de alguma GUI... menos tranqueira pra se preocupar.

Essa rotina é um bom exemplo de que SQL NÃO TEM NADA A VER COM DBF. E que aproveitar fonte de DBF é apenas temporário.

Novamente.... chamando a atenção:

Usar RDDSQL, SQLMIX, etc, deixa aproveitar todo fonte de DBF.
Não é por trabalhar igual DBF que não precisa mexer nos fontes: se usar SQL igual DBF... não vai estar tirando nenhum proveito do SQL.
Essas RDDs são pra manter compatibilidade, NÃO há problema nisso, só não pode esquecer de usar os recursos do servidor.

Se não me engano elas deixam usar USE ( "SELECT * FROM SERVIDOR..." )...
Então dá pra tirar proveito de SQL mesmo usando as RDDs, elas permitiriam alterar pra um único comando.

Nota:

Com certeza nada é automático.
Apanhei muito nesse comando SQL hoje, porque estava usando ".AND.", e isso com pontos é do Harbour não do SQL ... kkkk
Estou dizendo isso pra destacar que tem problemas igual com DBF, se colocarmos errado no fonte também não funciona, mas... igual fazemos com DBF, é ir ajustando até resolver.

Graças à gravação dupla, DBF + MySQL, continuo podendo alterar os fontes aos poucos, e melhorando a cada dia.
Ainda não eliminei nenhum dos DBFs com gravação duplicada no MySQL, porque tem muito fonte que faz leitura deles.
Mas os clientes continuam notando aumento de velocidade em diversas rotinas.

Destaque pra isso:
Mesmo quem usa via terminal service, também tá ficando mais rápido, e nesse caso é comparando DBF LOCAL com MySQL.
Para os que usam via rede então.... vixe

Meu modo de trabalho

MensagemEnviado: 03 Dez 2019 13:25
por Itamar M. Lins Jr.
Ola!
Usar RDDSQL, SQLMIX, etc, deixa aproveitar todo fonte de DBF.
Não é por trabalhar igual DBF que não precisa mexer nos fontes: se usar SQL igual DBF... não vai estar tirando nenhum proveito do SQL.

Essa afirmação, não está de acordo com uso do SQLMIX, pois não é a mesma abordagem.

SQLMIX = 100% comandos SQL, para ler e gravar os dados.

Os comandos DBF são apenas para manipular os resultados.
É a mesma coisa de quem usa ADO e para quem usa acesso nativo, precisa manipular arrays...

Saudações,
Itamar M. Lins Jr.

Meu modo de trabalho

MensagemEnviado: 03 Dez 2019 14:07
por JoséQuintas
Itamar M. Lins Jr. escreveu:Essa afirmação, não está de acordo com uso do SQLMIX, pois não é a mesma abordagem.
SQLMIX = 100% comandos SQL, para ler e gravar os dados.


Não lembro se é o SQLMIX que dá essa opção: USE (ctable from conexão)
Ou... USE ( "SELECT * FROM FINANCEIRO" )

É sério:
Tem usuário que tá fazendo isso, ou quase isso.
Tá trazendo tudo pra selecionar apenas uma informação.
Acha que SQL é igual DBF e precisa continuar fazendo processamento local.

É para isso que estou chamando atenção, e não depende do que foi escolhido pra conectar, e sim com aproveitar o que o servidor pode fazer.

Meu modo de trabalho

MensagemEnviado: 03 Dez 2019 16:13
por JoséQuintas
Pensei que aquele era um exemplo.... até chegar neste:
TODO esse fonte vai pro lixo, vai virar um único comando SQL.

   wSave()
   oStru := {  ;
      { "LOCADOR",     "N", 4, 0 }, ;
      { "LOCATARIO",   "N", 7, 0 }, ;
      { "ENDERECO",    "C", 35, 0 }, ;
      { "RECIBO",      "N", 6, 0 }, ;
      { "DATA",        "D", 8, 0 }, ;
      { "CIC",         "C", 18, 0 }, ;
      { "NOME",        "C", 35, 0 }, ;
      { "VALOR",       "N", 12, 2 }, ;
      { "VALORCOMIS",  "N", 12, 2 }, ;
      { "RATEIO",      "N", 6, 2 }, ;
      { "MES",         "C", 6, 0 } }

   cTmpFile  := MyTempFile( "DBF" )
   cTmpFile2 := MyTempFile( "CDX" )
   SELECT 0
   dbCreate( cTmpFile, oStru )
   USE ( cTmpFile ) EXCLUSIVE ALIAS simulado

   SELECT templocador
   nTotal := LastRec()
   GrafTempo( "Gerando dados para relatório" )
   GOTO TOP
   DO WHILE ! Eof()
      GrafTempo( nAtual++, nTotal )
      WITH OBJECT cnMySql
         cnMySql:cSql := "SELECT COD, LOCOD, IFDATPAG, IFDATDIM, CIC, RECIBO, VALOR, VALORCOMIS FROM INFORME WHERE LOCOD=" + ;
            NumberSql( templocador->Locador ) + " AND ANOBASE = " + StringSql( Str( Year( dDataReferencia ), 4 ) ) + ;
            " ORDER BY LOCOD, COD"
         cnMySql:Execute()
         DO WHILE :Number( "LOCOD" ) == templocador->Locador .AND. ! :Eof()
            DO CASE
            CASE Year( :Date( "IFDATDIM" ) ) != Year( dDataReferencia )
               :MoveNext()
               LOOP
            CASE :Number( "COD" ) == 0
               :MoveNext()
               LOOP
            CASE Month( :Date( "IFDATDIM" ) ) != Month( dDataReferencia )
               :MoveNext()
               LOOP
            ENDCASE
            cnMySqlHLDIMRAT:cSql := "SELECT * FROM HLDIMRAT WHERE LOCADOR=" + NumberSql( templocador->Locador ) + " AND LOCATARIO=" + NumberSql( :Number( "COD" ) )
            cnMySqlHLDIMRAT:Execute()
            DO WHILE ! cnMySqlHLDIMRAT:Eof()
               SELECT simulado
               APPEND BLANK
               REPLACE ;
                  simulado->Locador     WITH templocador->Locador, ;
                  simulado->Locatario   WITH :Number( "LOCOD" ), ;
                  simulado->Endereco    WITH "", ;
                  simulado->DATA        WITH :Date( "IFDATPAG" ), ;
                  simulado->Cic         WITH cnMySqlHLDIMRAT:String( "CIC" ), ;
                  simulado->Nome        WITH cnMySqlHLDIMRAT:String( "NOME" ), ;
                  simulado->Rateio      WITH cnMySqlHLDIMRAT:Number( "RATEIO" ), ;
                  simulado->Recibo      WITH :Number( "RECIBO" ), ;
                  simulado->Valor       WITH :Number( "VALOR" ) * cnMySqlHLDIMRAT:Number( "RATEIO" ) / 100, ;
                  simulado->ValorComis  WITH :Number( "VALORCOMIS" ) * cnMySqlHLDIMRAT:Number( "RATEIO" ) / 100, ;
                  simulado->Mes         WITH Left( Dtos( :Date( "IFDATPAG" ) ), 6 )
               cnMySqlHLDIMRAT:MoveNext()
            ENDDO
            cnMySqlHLDIMRAT:CloseRecordset()
            :MoveNext()
         ENDDO
         :CloseRecordset()
         SELECT templocador
      ENDWITH
      SKIP
   ENDDO

Meu modo de trabalho

MensagemEnviado: 04 Dez 2019 12:17
por JoséQuintas
Atualização de hoje interessante:

FUNCTION ze_Update2019()

   SayScroll( "Verificando atualizações 2019" )
   IF AppVersaoDbfAnt() < 20191203;   Update1203();  ENDIF // antes das demais, arquivo desativado
   IF AppVersaoDbfAnt() < 20191022.1; Update1022A(); ENDIF
...
   IF AppVersaoDbfAnt() < 20191127;   Update1127(); ENDIF
   IF AppVersaoDbfAnt() < 20191204.1; Update1204A(); ENDIF
...
STATIC FUNCTION Update1203() // Antes das demais

   LOCAL aStruList := { ;
      { "IDCLISTA",   "C", 6 }, ;
      { "CSDESCRI",   "C", 80 }, ;
      { "CSBLOQUEIO", "C", 1 }, ;
      { "CSINFINC",   "C", 80 }, ;
      { "CSINFALT",   "C", 80 } }

   IF AppVersaoDbfAnt() < 20191101
      AAdd( aStruList, { "CSNUMLAN",   "C", 6 } )
   ENDIF

   SayScroll( "JPCLISTA, verificando atualizações" )

   IF ! ValidaStru( "jpclista", aStruList )
      MsgStop( "JPCLISTA não disponível!" )
      QUIT
   ENDIF
   IF AppVersaoDbfAnt() >= 20130201
      RETURN NIL
   ENDIF
   IF ! UseSoDbf( "jpclista", .T. )
      QUIT
   ENDIF
   IF Eof()
      RecAppend()
      REPLACE ;
         jpclista->idCliSta WITH StrZero( 1, 6 ), ;
         jpclista->csDescri WITH "GERAL"
      RecUnlock()
   ENDIF
   CLOSE DATABASES

   RETURN NIL

STATIC FUNCTION Update1204A()

   fErase( "jpclista.dbf" )
   fErase( "jpclista.cdx" )

   RETURN NIL


Atualização fora de ordem?
Criar pra apagar?

NÃO vai precisar mais do DBF a partir de hoje.
Mas... até a versão de ontem precisava.
Então... clientes atrasados vão atualizar versão, vai checar estrutura do DBF, atualizar para o MySQL e... no final apagar.
É que pras conversões de datas anteriores funcionarem precisa da estrutura correta, então precisa testar estrutura antes das datas anteriores.
Clientes com versão em dia... até vai testar estrutura, mas só precisa apagar.
Seja como for, depois da versão atual vai ser apagado, e não vai mais ser testado.

É o primeiro DBF apagado depois da gravação dupla.
É o status de cliente, converti pra facilitar o browse de clientes.
Como era usado em poucos fontes, foi rápido pra eliminar de vez.

Meu modo de trabalho

MensagemEnviado: 05 Dez 2019 16:44
por JoséQuintas
Ainda bem que estou atualizando aos poucos, porque estou cometendo muitos erros....

o de hoje:

mysql.png


usar alias de DBF em MySQL... aí não dá kkkkk

Meu modo de trabalho

MensagemEnviado: 05 Dez 2019 17:23
por JoséQuintas
enquanto isso...

   WITH OBJECT cnMySql
      :cSql := "SELECT LPAD( IDPEDIDO, 6, '0' ) AS PEDIDO, PDDATEMI, PDCLIFOR," + ;
         " LEFT( JPCADASTRO.CDNOME, 30 ) AS NOME, LEFT( JPTRANSA.TRDESCRI, 12 ) AS TRANSACAO, " + ;
         " PDVALNOT, PDCONF, PDSTATUS, JPNOTFIS.NFNOTFIS, RIGHT( JPNOTFIS.NFFILIAL, 2 ) AS FILIAL " + ;
         " FROM JPPEDIDO " + ;
         " LEFT JOIN JPCADASTRO ON JPPEDIDO.PDCLIFOR = JPCADASTRO.IDCADASTRO " + ;
         " LEFT JOIN JPNOTFIS ON JPPEDIDO.IDPEDIDO = JPNOTFIS.NFPEDIDO " + ;
         " LEFT JOIN JPTRANSA ON JPPEDIDO.PDTRANSA = JPTRANSA.IDTRANSA " + ;
         " WHERE 1=1 " + cFiltro + " ORDER BY PEDIDO DESC LIMIT 2000"
      :Execute()


Deu certo, mas não implementei ainda o seguinte:
Os arquivos de NFE, de certa forma, não tem vínculo com o aplicativo.
Mas... guardo com CNPJ emitente/destinatário e nota fiscal.

Não ficou demorado, porque já tem índice, acrescentar nesse trem:

"LEFT JOIN JPNFEKEY ON KKEMINFE=" + StringSql( jpempresa->emCnpj ) + ;
"AND KKDESNFE=JPCADASTRO.CDCNPJ AND KKNOTFIS=JPNOTFIS.NFNOTFIS"


O trem é doido.
consulta de pedidos...
... que busca os 2.000 pedidos mais recentes
... que busca o cadastro do cliente a partir do código nos pedidos
... que busca a transação a partir do código nos pedidos
... que busca a nota fiscal a partir do número de pedido
... que busca a nota eletrônica a partir do CNPJ no cadastro + nota em notas fiscais + CNPJ da empresa
Em 1 segundo o browse tá na tela.
A partir daí, qualquer filtro é instantâneo.

Feliz com o resultado das mudanças.

Se pensar que isso é recurso básico, de muitos anos atrás....
Só resta recuperar o tempo perdido com DBFs...

Meu modo de trabalho

MensagemEnviado: 07 Dez 2019 23:47
por JoséQuintas
O SQL cada vez mais tomando conta...

SELECT SUM( IF( ESTIPLAN = '1', -ESQTDE, ESQTDE ) ) AS SALDO,
ESITEM AS ITEM, JPITEM.IEDESCRI AS DESCRICAO, JPITEM.IEUNID AS UNIDADE,
JPITEM.IEPROGRU AS GRUPO, JPITEM.IENCM AS NCM, JPITEM.IECEST AS CEST
FROM JPESTOQUE
LEFT JOIN JPITEM ON JPESTOQUE.ESITEM=JPITEM.IDITEM
WHERE ESDATLAN <= '2019-01-01'
GROUP BY ITEM
HAVING SALDO > 0
ORDER BY ITEM

Meu modo de trabalho

MensagemEnviado: 16 Dez 2019 04:14
por JoséQuintas
Primeiro relatório mais.... "sofisticado".
Ainda ajustando...
Caminhando pra apagar o estoque em DBF...

         IF ConfirmaImpressao()
            WITH OBJECT cnMySql
               :cSql := "SELECT IDESTOQUE, ESITEM, ESCLIFOR, ESDATLAN, ESPEDIDO, ESNUMDOC, ESOBS, ESTIPLAN, ESNUMDEP, " + ;
                  iif( nOpcEmbalagem == 1, "ESQTDE", "ESQTDE / IF( JPITEM.IEQTDCOM < 1, 1, JPITEM.IEQTDCOM )" ) + " AS QTDE, " + ;
                  " JPAUXILIAR.AXDESCRI AS LOCDESCRICAO," + ;
                  " JPITEM.IEDESCRI AS ITEDESCRICAO, " + ;
                  " IF( JPITEM.IEQTDCOM < 1, 1, JPITEM.IEQTDCOM ) AS CONVERSAO" + ;
                  " FROM JPESTOQUE" + ;
                  " LEFT JOIN JPITEM ON ESITEM=JPITEM.IDITEM" + ;
                  " LEFT JOIN JPAUXILIAR ON AXTABELA=" + StringSql( AUX_PROLOC ) + " AND AXCODIGO=JPITEM.IEPROLOC" + ;
                  " WHERE 1=1"
               IF nOpcData == 2
                  :cSql += " AND ESDATLAN < " + DateSql( m_Dataf )
               ENDIF
               IF nOpcCliente == 2
                  :cSql += " AND ESCLIFOR != " + StringSql( mClientei )
               ENDIF
               IF nOpcItem == 2
                  :cSql += " AND ESITEM >= " + StringSql( cItemi )  + " AND ESITEM <= " + StringSql( cItemf )
               ENDIF
               IF nOpcTipoEst != 1
                  :cSql += " AND JPITEM.IETIPO = " + StringSql( Substr( "*SNIAU", nOpcTipoEst, 1 ) )
               ENDIF
               IF nOpcProDep != 1
                  :cSql += " AND JPITEM.IEPRODEP = " + StringSql( mieProDep )
               ENDIF
               IF nOpcProSec != 1
                  :cSql += " AND JPITEM.IEPROSEC = " + StringSql( mieProSec )
               ENDIF
               IF nOpcProGru != 1
                  :cSql += " AND JPITEM.IEPROGRU = " + StringSql( mieProGru )
               ENDIF
               IF nOpcAnp == 3
                  :cSql += " AND JPITEM.IEANP = " + StringSql( cieAnp )
               ELSEIF nOpcAnp == 2
                  :cSql += " AND JPITEM.IEANP IN ( " + ProdutoAnp( "", "", .T. ) + " )"
               ENDIF
               IF nOpcDeposito != 1
                  :cSql += " AND ESNUMDEP=" + StringSql( Str( nOpcDeposito - 1, 1 ) )
               ENDIF
               IF nOpcTransf == 2 .OR. nOpcTransf == 3
                  :cSql += " AND"
                  IF nOpcTransf == 3
                     :cSql += " NOT"
                  ENDIF
                  :cSql += "( ESCFOP LIKE '%905' OR ESCFOP LIKE '%663' OR ESCFOP LIKE '%664' OR ESCFOP LIKE '%906' )"
               ENDIF
               IF nOpcOrdem == 1
                  :cSql += " ORDER BY ESITEM, ESDATLAN, ESTIPLAN DESC"
               ELSE
                  :cSql += " ORDER BY LOCDESCRICAO, ITEDESCRICAO, ESITEM, ESDATLAN, ESTIPLAN DESC"
               ENDIF
               :Execute()
            ENDWITH

Meu modo de trabalho

MensagemEnviado: 16 Dez 2019 12:27
por JoséQuintas
Esta parte é legal, aqui já com correção:

   iif( nOpcEmbalagem == 1, "ESQTDE", "ESQTDE * IF( JPITEM.IEQTDCOM < 1, 1, JPITEM.IEQTDCOM )" ) + " AS QTDE, " + ;


O que acontece: pode ser caixa com 5 latas de um litro, caixa com 24 latas de meio litro, etc.
Caso o usuário queira em litros ao invés de caixas, esta parte faz a conversão.
Só sobra mesmo para o relatório fazer a impressão.

Pois é... gostando cada vez mais desse troço... que existe há dezenas de anos e eu não usava...

Meu modo de trabalho

MensagemEnviado: 16 Dez 2019 14:08
por JoséQuintas
Convém destacar pontos importantes nisso:

Pra quem estranha, seria igual um SET FILTER, mas que no DBF é lento e no SQL é rápido, o WHERE equivale ao SET FILTER TO

E da mesma forma, o ponto interessante é o seguinte:

É comum encontrar

SET FILTER TO CODCLI=&variável .AND. DEPTO=&depto


Isso deixa preso à variável.

Pode ser interessante fazer:

cFiltro := [CODCLI=] + Str( variável ) + [ .AND. DEPTO=] + Str( Depto )
SET FILTER TO &cFiltro


Qual a diferença?
No primeiro caso, a macro no meio do filtro deixa dependente das variáveis existirem.
No segundo caso, a macro é diretamente no texto, e não depende mais nem da própria variável.
Por não depender de variável, o filtro vai continuar funcionando mesmo que troque de módulo.

Poderia não ser num SET FILTER, mas em algo como:

DO WHILE ! Eof()
   IF ! &( cCondicao )
      SKIP
     LOOP
   ENDIF


Isso permitiria um relatório genérico onde só precisaria passar um texto de filtro.
Se for igual mostrei acima, o filtro não depende de variável nenhuma, e vai funcionar em qualquer lugar.
No SQL somos obrigados a usar assim, acabamos usando "esse jeito", mas ele vale pra qualquer situação.

É como eu sempre digo:
Em tudo dá pra aprender alguma coisa.
Na correria, às vezes não percebemos essas "coisinhas".

Também é bom pra mostrar que no final tudo é a mesma coisa.
Não se trata de ser diferente, se trata de acostumarmos a fazer de um jeito, mesmo podendo fazer de outro.
Às vezes porque queremos deixar mais rápido, mas às vezes só porque virou mania mesmo.

É só questão de ir trocando as manias antigas por manias novas...

Também uma coisa que comento sempre: EVITAR USAR MACRO
Os dois casos acima usam macro, mas um deles de certa forma usa mais do que deveria.

Meu modo de trabalho

MensagemEnviado: 21 Dez 2019 17:09
por JoséQuintas
Outro SELECT grande.

   WITH OBJECT cnMySql
      :cSql := "SELECT LPAD( IDIMPOSTO, 6, '0' ) AS ID, IMTRANSA, IMTRIUF, IMTRICAD, IMTRIPRO, IMCFOP, " + ;
         " IMCFOP, IMIIALI, IMIPIALI, IMIPIICM, IMIPSALI, IMICMCST, IMICMRED, " + ;
         " IMICMALI, IMICMCST, IMICMRED, IMICMALI, IMICSALI, IMFCPALI, IMSUBIVA, " + ;
         " IMSUBRED, IMSUBALI, IMISSALI, IMPISCST, IMPISALI, IMPISENQ, IMCOFCST, " + ;
         " IMCOFALI, IMCOFENQ, IMLEIS" + ;
         " LEFT( AUXTRIUF.AXDESCRI, 15 )  AS TRIUFDES, " + ;
         " LEFT( AUXTRICAD.AXDESCRI, 15 ) AS TRICADDES, " + ;
         " LEFT( AUXTRIPRO.AXDESCRI, 15 ) AS TRIPRODES, " + ;
         " LEFT( AUXIPICST.AXDESCRI, 15 ) AS IPICSTDES, " + ;
         " LEFT( AUXPISCST.AXDESCRI, 15 ) AS PISCSTDES, " + ;
         " LEFT( AUXCOFCST.AXDESCRI, 15 ) AS COFCSTDES, " + ;
         " LEFT( JPTRANSA.TRDESCRI, 15 ) AS TRANSADES, " + ;
         " FROM JPIMPOSTO" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA=JPIMPOSTO.IMTRANSA" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRIUF  ON AUXTRIUF.AXTABELA="  + StringSql( AUX_TRIUF ) + " AND AUXTRIUF.AXCODIGO=JPIMPOSTO.IMTRIUF" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRICAD ON AUXTRICAD.AXTABELA=" + StringSql( AUX_TRICAD ) + " AND AUXTRICAD.AXCODIGO=JPIMPOSTO.IMTRICAD" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRIPRO ON AUXTRIPRO.AXTABELA=" + StringSql( AUX_TRIPRO ) + " AND AUXTRIPRO.AXCODIGO=JPIMPOSTO.IMTRIPRO" + ;
         " LEFT JOIN JPAUXILIAR AS AUXIPICST ON AUXIPICST.AXTABELA=" + StringSql( AUX_IPICST ) + " AND AUXIPICST.AXCODIGO=JPIMPOSTO.IMIPICST" + ;
         " LEFT JOIN JPAUXILIAS AS AUXICMCST ON AUXICMCST.AXTABELA=" + StringSql( AUX_ICMCST ) + " AND AUXICMCST.AXCODIGO=JPIMPOSTO.IMICMCST" + ;
         " LEFT JOIN JPAUXILIAR AS AUXPISCST ON AUXPISCST.AXTABELA=" + StringSql( AUX_PISCST ) + " AND AUXPISCST.AXCODIGO=JPIMPOSTO.IMPISCST" + ;
         " LEFT JOIN JPAUXILIAR AS AUXCOFCST ON AUXCOFCST.AXTABELA=" + StringSql( AUX_PISCST ) + " AND AUXCOFCST.AXCODIGO=JPIMPOSTO.IMCOFCST" + ;
         " ORDER BY TRANSADES"
      :Execute()


É um SELECT simples, apenas pegar códigos e descrições, mas de várias tabelas, e até de uma mesma tabela várias vezes.
A única coisa diferente, neste momento, é que JPAUXILIAR atende várias tabelas diferentes, e precisa ser indicada de forma especial.
Ainda não testei, preciso terminar o fonte, que ainda depende do DBF, mas já apaguei antes de terminar os testes... rs

É direto na versão em uso: só volta a funcionar depois que eu terminar a eliminação de JPIMPOSTO.DBF.

Meu modo de trabalho

MensagemEnviado: 21 Dez 2019 21:42
por JoséQuintas
versão final:

METHOD GridSelection() CLASS JPIMPOSTOClass

   LOCAL oTBrowse, cnMySql := ADOClass():New( AppConexao() )

   WITH OBJECT cnMySql
      :cSql := "SELECT LPAD( IDIMPOSTO, 6, '0' ) AS ID, IMTRANSA, IMTRIUF, IMTRICAD, IMTRIPRO, IMCFOP, " + ;
         " IMCFOP, IMIIALI, IMIPICST, IMIPIALI, IMIPIICM, IMIPSALI, IMICMCST, IMICMRED, " + ;
         " IMICMALI, IMICMCST, IMICMRED, IMICMALI, IMICSALI, IMFCPALI, IMSUBIVA, " + ;
         " IMSUBRED, IMSUBALI, IMISSALI, IMPISCST, IMPISALI, IMPISENQ, IMCOFCST, " + ;
         " IMCOFALI, IMCOFENQ, IMLEIS, " + ;
         " LEFT( AUXTRIUF.AXDESCRI, 15 )  AS TRIUFDES, " + ;
         " LEFT( AUXTRICAD.AXDESCRI, 15 ) AS TRICADDES, " + ;
         " LEFT( AUXTRIPRO.AXDESCRI, 15 ) AS TRIPRODES, " + ;
         " LEFT( AUXIPICST.AXDESCRI, 15 ) AS IPICSTDES, " + ;
         " LEFT( AUXICMCST.AXDESCRI, 15 ) AS ICMCSTDES, " + ;
         " LEFT( AUXPISCST.AXDESCRI, 15 ) AS PISCSTDES, " + ;
         " LEFT( AUXCOFCST.AXDESCRI, 15 ) AS COFCSTDES, " + ;
         " LEFT( JPTRANSA.TRDESCRI, 15 ) AS TRANSADES " + ;
         " FROM JPIMPOSTO" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA=JPIMPOSTO.IMTRANSA" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRIUF  ON AUXTRIUF.AXTABELA="  + StringSql( AUX_TRIUF ) + " AND AUXTRIUF.AXCODIGO=JPIMPOSTO.IMTRIUF" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRICAD ON AUXTRICAD.AXTABELA=" + StringSql( AUX_TRICAD ) + " AND AUXTRICAD.AXCODIGO=JPIMPOSTO.IMTRICAD" + ;
         " LEFT JOIN JPAUXILIAR AS AUXTRIPRO ON AUXTRIPRO.AXTABELA=" + StringSql( AUX_TRIPRO ) + " AND AUXTRIPRO.AXCODIGO=JPIMPOSTO.IMTRIPRO" + ;
         " LEFT JOIN JPAUXILIAR AS AUXIPICST ON AUXIPICST.AXTABELA=" + StringSql( AUX_IPICST ) + " AND AUXIPICST.AXCODIGO=JPIMPOSTO.IMIPICST" + ;
         " LEFT JOIN JPAUXILIAR AS AUXICMCST ON AUXICMCST.AXTABELA=" + StringSql( AUX_ICMCST ) + " AND AUXICMCST.AXCODIGO=SUBSTR( JPIMPOSTO.IMICMCST, 2 )" + ;
         " LEFT JOIN JPAUXILIAR AS AUXPISCST ON AUXPISCST.AXTABELA=" + StringSql( AUX_PISCST ) + " AND AUXPISCST.AXCODIGO=JPIMPOSTO.IMPISCST" + ;
         " LEFT JOIN JPAUXILIAR AS AUXCOFCST ON AUXCOFCST.AXTABELA=" + StringSql( AUX_PISCST ) + " AND AUXCOFCST.AXCODIGO=JPIMPOSTO.IMCOFCST" + ;
         " ORDER BY TRANSADES"
      :Execute()
      oTBrowse := { ;
         { "N.Lanç",     { || :String( "ID", 6 ) } }, ;
         { "Transação",  { || :String( "IMTRANSA", 6 ) + " " + :String( "TRANSADES", 15 ) } }, ;
         { "Trib.UF",    { || :String( "IMTRIUF", 6 )  + " " + :String( "TRIUFDES", 15 ) } }, ;
         { "Trib.Cad",   { || :String( "IMTRICAD", 6 ) + " " + :String( "TRICADDES", , 15 ) } }, ;
         { "Trib.Prod",  { || :String( "IMTRIPRO", 6 ) + " " + :String( "TRIPRODES", 15 ) } }, ;
         { "CFOP",       { || :String( "IMCFOP", 6 ) } }, ;
         { "II.Alíq",    { || Str( :Number( "IMIIALI" ), 6, 2 ) } }, ;
         { "IPI CST",    { || :String( "IMIPICST", 2 ) + " "  + :String( "IPICSTDES", 15 ) } }, ;
         { "IPI Alíq",   { || Str( :Number( "IMIPIALI" ), 6, 2 ) } }, ;
         { "IPI ICM",    { || Str( :Number( "IMIPIICM" ), 6, 2 ) } }, ;
         { "IPI Simp",   { || Str( :Number( "IMIPSALI" ), 6, 2 ) } }, ;
         { "ICMS CST",   { || :String( "IMICMCST", 4 ) + " " + :String( "ICMCSTDES", 15 ) } }, ;
         { "ICMS Red",   { || Str( :Number( "IMICMRED" ), 6, 2 ) } }, ;
         { "ICMS Alíq",  { || Str( :Number( "IMICMALI" ), 6, 2 ) } }, ;
         { "ICMS Simp",  { || Str( :Number( "IMICSALI" ), 9, 5 ) } }, ;
         { "FCP Aliq",   { || Str( :Number( "IMFCPALI" ), 6, 2 ) } }, ;
         { "ST IVA",     { || Str( :Number( "IMSUBIVA" ), 6, 2 ) } }, ;
         { "ST Red",     { || Str( :Number( "IMSUBRED" ), 6, 2 ) } }, ;
         { "ST Alíq",    { || Str( :Number( "IMSUBALI" ), 6, 2 ) } }, ;
         { "ISS Alíq",   { || Str( :Number( "IMISSALI" ), 6, 2 ) } }, ;
         { "PIS CST",    { || :String( "IMPISCST", 2 ) + " " + :String( "PISCSTDES", 15 ) } }, ;
         { "PIS Alíq",   { || Str( :Number( "IMPISALI" ), 6, 2 ) } }, ;
         { "PIS Enq",    { || :String( "IMPISENQ", 3 ) } }, ;
         { "Cofins CST", { || :String( "IMCOFCST", 2 ) + " " + :String( "COFCSTDES", , 15 ) } }, ;
         { "Cof Alíq",   { || Str( :Number( "IMCOFALI" ), 6, 2 ) } }, ;
         { "Cof Enq",    { || :String( "IMCOFENQ", 3 ) } }, ;
         { "Leis",       { || :String( "IMLEIS", 70 ) } } }
      BrowseADO( cnMySql, oTBrowse, "TRANSADES", { || :String( "ID", 6 ) } )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


o browse, apenas 3 partes dele

parte1.png


parte2.png


parte3.png


Trouxe tudo de todas as tabelas.

Meu modo de trabalho

MensagemEnviado: 21 Dez 2019 21:48
por JoséQuintas
O fonte pra DBF era menor, não tinha o comando SQL, mas dependia de todos os arquivos abertos.

METHOD GridSelection() CLASS JPIMPOSTOClass

   LOCAL nSelect := Select(), oTBrowse

   SELECT jpimposto
   oTBrowse := { ;
      { "N.Lanç",     { || jpimposto->idImposto } }, ;
      { "Transação",  { || jpimposto->imTransa + iif( Encontra( jpimposto->imTransa, "jptransa", "numlan" ), "", "" ) + " " + Left( jptransa->trDescri, 15 ) } }, ;
      { "Trib.UF",    { || jpimposto->imTriUf + " " + Left( AUXTRIUFClass():Descricao( jpimposto->imTriUf ), 15 ) } }, ;
      { "Trib.Cad",   { || jpimposto->imTriCad + " " + Left( AUXTRICADClass():Descricao( jpimposto->imTriCad ), 15 ) } }, ;
      { "Trib.Prod",  { || jpimposto->imTriPro + " " + Left( AUXTRIPROClass():Descricao( jpimposto->imTriPro ), 15 ) } }, ;
      { "CFOP",       { || jpimposto->imCfOp } }, ;
      { "II.Alíq",    { || jpimposto->imIIAli } }, ;
      { "IPI CST",    { || jpimposto->imIpiCst + " "  + Left( AUXIPICSTClass():Descricao( jpimposto->imIpiCst ), 15 ) } }, ;
      { "IPI Alíq",   { || jpimposto->imIpiAli } }, ;
      { "IPI ICM",    { || jpimposto->imIpiIcm } }, ;
      { "IPI Simp",   { || jpimposto->imIpSAli } }, ;
      { "ICMS CST",   { || jpimposto->imIcmCst + " " + Left( AUXICMCSTClass():Descricao( Pad( Substr( jpimposto->imIcmCst, 2 ), 3 ) ), 15 ) } }, ;
      { "ICMS Red",   { || jpimposto->imIcmRed } }, ;
      { "ICMS Alíq",  { || jpimposto->imIcmAli } }, ;
      { "ICMS Simp",  { || jpimposto->imIcsAli } }, ;
      { "FCP Aliq",   { || jpimposto->imFcpAli } }, ;
      { "ST IVA",     { || jpimposto->imSubIva } }, ;
      { "ST Red",     { || jpimposto->imSubRed } }, ;
      { "ST Alíq",    { || jpimposto->imSubAli } }, ;
      { "ISS Alíq",   { || jpimposto->imIssAli } }, ;
      { "PIS CST",    { || jpimposto->imPisCst + " " + Left( AUXPISCSTClass():Descricao( jpimposto->imPisCst ),15 ) } }, ;
      { "PIS Alíq",   { || jpimposto->imPisAli } }, ;
      { "PIS Enq",    { || jpimposto->imPisEnq } }, ;
      { "Cofins CST", { || jpimposto->imCofCst + " " + Left( AUXPISCSTClass():Descricao( jpimposto->imCofCst ), 15 ) } }, ;
      { "Cof Alíq",   { || jpimposto->imCofAli } }, ;
      { "Cof Enq",    { || jpimposto->imCofEnq } }, ;
      { "Leis",       { || jpimposto->imLeis } } }
   FazBrowse( oTBrowse )
   IF LastKey() != K_ESC .AND. ! Eof()
      KEYBOARD jpimposto->idImposto + Chr( K_ENTER )
   ENDIF
   SELECT ( nSelect )

   RETURN NIL


Apesar de tudo, o que dá pra notar entre os dois?
O comando SQL coloca o trabalho pesado sendo feito no servidor, nas bases locais para o servidor.
O aplicativo apenas mostra o que veio do servidor e nada mais.

Meu modo de trabalho

MensagemEnviado: 21 Dez 2019 22:08
por JoséQuintas
Tem mais um ponto importantíssimo nisso aí:

é um comando SQL, e um browse...
Qualquer linguagem de programação tem isso.

Sabem como era no Visual Basic 6, de 20 anos atrás?
Exatamente isso.
Um texto digitado, que é o comando SQL, e o GRID.

Entenderam? não está preso a DBF, e nem mesmo ao Harbour !!!

Estou confirmando isso na prática agora.
Abandonar o mecanismo DBF é o primeiro passo pra evolução e pra liberdade !!!!
Pode ser com ADO, a Microsoft deixou o ADO disponível pra todos, DE GRAÇA, há 20 anos.

Meu modo de trabalho

MensagemEnviado: 22 Dez 2019 18:03
por JoséQuintas
Situação em 20/11/2019: 37 DBFs
Situação em 22/12/2019: 30 DBFs, e 48 no MySQL.
Usando cliente com mais opções em uso como referência 813MB em DBF, e 3.7GB em MySQL.
Não anotei outras referências.

Meu modo de trabalho

MensagemEnviado: 23 Dez 2019 18:12
por JoséQuintas
Nos meus fontes eu já usava classe, em muita coisa, então pra muita coisa foi só ajustar as classes.

O cadastro de veículos:

PROCEDURE PJPVEICULO

   LOCAL oFrm := JPVEICULOClass():New()

   IF ! AbreArquivos( "jpcadastro", "jpcidade", "jpconfi", "jpempresa", "jpitem", "jpitped", ;
      "jpnotfis", "jpnumero", "jppedido", "jppreco", "jpsenha", "jptabel", "jpuf" )
      RETURN
   ENDIF
   oFrm:Execute()

   RETURN


E a classe aplicada herança:

CREATE CLASS JPVEICULOClass INHERIT FrmCadastroClass

   VAR    cDataTable INIT "JPVEICULO"
   VAR    cDataField INIT "IDVEICULO"
   VAR    axKeyField INIT { Space(6) }
   VAR    cnMySql    INIT ADOClass():New( AppConexao() )
   METHOD GridSelection()
   METHOD Especifico( lExiste )
   METHOD TelaDados( lEdit )
   METHOD Valida( cCodigo, lMostra, cCampo )

   ENDCLASS


No que o cadastro de veículso é diferente dos demais?
o browse, que pode ser usado em qualquer parte do aplicativo

METHOD GridSelection() CLASS JPVEICULOClass

   LOCAL oTBrowse, cnMySql := ADOClass():New( AppConexao() )

   WITH OBJECT cnMySql
      :cSql := "SELECT * FROM JPVEICULO ORDER BY VEPLACA"
      :Execute()
      oTBrowse := { ;
         { "CÓDIGO",    { || StrZero( :Number( "IDVEICULO" ), 6 ) } }, ;
         { "PLACA",     { || :String( "VEPLACA", 10 ) } }, ;
         { "MOTORISTA", { || :String( "VEMOTORI", 30 ) } }, ;
         { "TELEFONE",  { || :String( "VETELEFONE", 20 ) } }, ;
         { "CAP.TOT",   { || Transform( :Number( "VECAPACTOT" ), PicVal(6) ) } }, ;
         { "CAP.1",     { || Transform( :Number( "VECAPAC1" ), PicVal(6) ) } }, ;
         { "CAP.2",     { || Transform( :Number( "VECAPAC2" ), PicVal(6) ) } }, ;
         { "CAP.3",     { || Transform( :Number( "VECAPAC3" ), PicVal(6) ) } }, ;
         { "CAP.4",     { || Transform( :Number( "VECAPAC4" ), PicVal(6) ) } }, ;
         { "CAP.5",     { || Transform( :Number( "VECAPAC5" ), PicVal(6) ) } }, ;
         { "CAP.6",     { || Transform( :Number( "VECAPAC6" ), PicVal(6) ) } }, ;
         { "CAP.7",     { || Transform( :Number( "VECAPAC7" ), PicVal(6) ) } }, ;
         { "CAP.8",     { || Transform( :Number( "VECAPAC8" ), PicVal(6) ) } }, ;
         { "CAP.9",     { || Transform( :Number( "VECAPAC9" ), PicVal(6) ) } } }
      BrowseADO( cnMySql, oTBrowse, "VEPLACA,VEMOTORI,VETELEFONE", { || StrZero( :Number( "IDVEICULO" ), 6 ) } )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


escolher um código específico:

METHOD Especifico( lExiste ) CLASS JPVEICULOClass

   LOCAL GetList := {}, midVeiculo

   midVeiculo := ::axKeyValue[ 1 ]
   @ Row()+1, 20 GET midVeiculo PICTURE "@K 999999"  VALID NovoMaiorZero( @midVeiculo )
   Mensagem( "Digite código, F9 pesquisa, ESC sai" )
   READ
   Mensagem()
   IF LastKey() == K_ESC .OR. ( Val( midVeiculo ) == 0 .AND. ::cOpc != "I" )
      RETURN .F.
   ENDIF
   IF ! ::EspecificoExiste( lExiste, ADORecCount( "JPVEICULO", "IDVEICULO=" + StringSql( midVeiculo ) ) == 0 )
      RETURN .F.
   ENDIF
   ::axKeyValue := { midVeiculo }

   RETURN .T.


A tela de edição, que tem muito lixo por sinal, e não parei pra retirar a parte inútil...

METHOD TelaDados( lEdit ) CLASS JPVEICULOClass

   LOCAL GetList := {}
   LOCAL midVeiculo, mvePlaca, mveMotori, mveTelefone, mveCapacTot, mveCapac1, mveCapac2, mveCapac3
   LOCAL mveCapac4, mveCapac5, mveCapac6, mveCapac7, mveCapac8, mveCapac9, mvePeso, mveInfInc, mveInfAlt

   midVeiculo := ::axKeyValue[1]
   WITH OBJECT ::cnMySql
      mvePlaca    := :String( "VEPLACA", 8 )
      mveMotori   := :String( "VEMOTORI", 30 )
      mveTelefone := :String( "VETELEFONE", 20 )
      mvePeso     := :Number( "VEPESO" )
      mveCapac1   := :Number( "VECAPAC1" )
      mveCapac2   := :Number( "VECAPAC2" )
      mveCapac3   := :Number( "VECAPAC3" )
      mveCapac4   := :Number( "VECAPAC4" )
      mveCapac5   := :Number( "VECAPAC5" )
      mveCapac6   := :Number( "VECAPAC6" )
      mveCapac7   := :Number( "VECAPAC7" )
      mveCapac8   := :Number( "VECAPAC8" )
      mveCapac9   := :Number( "VECAPAC9" )
      mveInfInc   := :String( "VEINFINC" )
      mveInfAlt   := :String( "VEINFALT" )
      :CloseRecordset()
   ENDWITH

   hb_Default( @lEdit, .F. )
   ::nNumTab := 1
   DO WHILE .T.
      ::ShowTabs()
      DO CASE
      CASE ::nNumTab == 1
         @ Row() + 1, 1 SAY "Núm. Lançto......:" GET midVeiculo WHEN .F.
         @ Row() + 2, 1 SAY "Placa............:" GET mvePlaca    PICTURE "!!!-9999" VALID ValidaPlaca( @mvePlaca )
//         @ Row() + 2, 1 SAY "CPF..............:" GET mveCPF      PICTURE "@9"       VALID OkGetCnpjCpf( @mvecpf, .T. )
         @ Row() + 2, 1 SAY "Motorista........:" GET mveMotori   PICTURE "@!"       VALID ! Empty( mveMotori )
         @ Row() + 1, 1 SAY "Telefone.........:" GET mveTelefone PICTURE "@!"
         @ Row() + 1, 1 SAY "Capacidade Total.:" GET mveCapacTot PICTURE "999,999"  VALID mveCapacTot > 0
         @ Row() + 1, 1 SAY "Peso do Veículo..:" GET mvePeso     PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 1...:" GET mveCapac1   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 2...:" GET mveCapac2   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 3...:" GET mveCapac3   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 4...:" GET mveCapac4   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 5...:" GET mveCapac5   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 6...:" GET mveCapac6   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 7...:" GET mveCapac7   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 8...:" GET mveCapac8   PICTURE "999,999"
         @ Row() + 1, 1 SAY "Capac.Tanque 9...:" GET mveCapac9   PICTURE "999,999"
         @ Row() + 2, 1 SAY "Inf.Inclusão.....:" GET mveInfInc   WHEN .F.
         @ Row() + 1, 1 SAY "Inf.Alteração....:" GET mveInfAlt   WHEN .F.
      ENDCASE
      //SetPaintGetList( GetList )
      IF ! lEdit
         CLEAR GETS
         EXIT
      ENDIF
      Mensagem( "Digite campos, F9 Pesquisa, ESC Sai" )
      READ
      Mensagem()
      ::nNumTab += 1
      IF LastKey() == K_ESC
         EXIT
      ENDIF
      IF ::nNumTab > Len( ::acTabName )
         EXIT
      ENDIF
   ENDDO
   IF ! lEdit
      RETURN NIL
   ENDIF
   ::nNumTab := 1
   IF LastKey() == K_ESC
      RETURN NIL
   ENDIF
   WITH OBJECT ::cnMySql
      :QueryCreate()
      :QueryAdd( "VEPLACA", mvePlaca )
      :QueryAdd( "VEMOTORI", mveMotori )
      :QueryAdd( "VEPESO", mvePeso )
      :QueryAdd( "VECAPACTOT", mveCapacTot )
      :QueryAdd( "VECAPAC1", mveCapac1 )
      :QueryAdd( "VECAPAC2", mveCapac2 )
      :QueryAdd( "VECAPAC3", mveCapac3 )
      :QueryAdd( "VECAPAC4", mveCapac4 )
      :QueryAdd( "VECAPAC5", mveCapac5 )
      :QueryAdd( "VECAPAC6", mveCapac6 )
      :QueryAdd( "VECAPAC7", mveCapac7 )
      :QueryAdd( "VECAPAC8", mveCapac8 )
      :QueryAdd( "VECAPAC9", mveCapac9 )
      IF ::cOpc == "I"
         :QueryAdd( "VEINFINC", LogInfo() )
         IF Val( midVeiculo ) == 0
            midVeiculo := StrZero( :QueryExecuteInsert( "JPVEICULO" ), 6 )
         ELSE
            :QueryAdd( "IDVEICULO", midVeiculo )
            :QueryExecuteInsert( "JPVEICULO" )
         ENDIF
      ELSE
         :QueryAdd( "VEINFALT", LogInfo() )
         :QueryExecuteUpdate( "JPVEICULO", "IDVEICULO=" + StringSql( midVeiculo ) )
      ENDIF
   ENDWITH

   RETURN NIL


A validação de veículo e/ou placa que pode ser usada no aplicativo inteiro

METHOD Valida( cCodigo, lMostra, cCampo ) CLASS JPVEICULOClass

   LOCAL cnMySql := ADOClass():New( AppConexao() )

   hb_Default( @lMostra, .T. )
   hb_Default( @cCampo, "codigo" )
   IF cCampo == "placa"
      IF Len( AllTrim( cCodigo ) ) < 2
         RETURN .T.
      ENDIF
      IF ! ValidaPlaca( cCodigo )
         RETURN .F.
      ENDIF
      IF ADORecCount( "JPVEICULO", "VEPLACA=" + StringSql( cCodigo ) ) == 0
         IF ! MsgYesNo( "Placa não utilizada antes. Continua?" )
            RETURN .F.
         ENDIF
         WITH OBJECT cnMySql
            :QueryCreate()
            :QueryAdd( "VEPLACA", cCodigo )
            :QueryExecuteInsert( "JPVEICULO" )
         ENDWITH
      ENDIF
   ELSE
      IF lMostra
         @ Row(), 32 SAY Space(10)
      ENDIF
      cCodigo := StrZero( Val( cCodigo ), 6 )
      IF ADORecCount( "JPVEICULO", "IDVEICULO=" + StringSql( cCodigo ) ) == 0
         MsgExclamation( "Veículo não cadastrado" )
         RETURN .F.
      ENDIF
      IF lMostra
         @ Row(), 32 SAY ADOField( "VEPLACA", "C", "JPVEICULO", "IDVEICULO=" + StringSql( cCodigo ) )
      ENDIF
   ENDIF

   RETURN .T.


Apenas ajustei o estilo DBF pra comportar o estilo ADO.
Mantendo o código caractere, apesar de salvar como numérico no MySQL, pra compatibilidade, até a completa migração pra MySQL.

aplicativo.png


Dizem que a GTWVG é complicada, enche de código fonte estranho dentro do fonte....
Tão vendo alguma coisa de GTWVG no fonte?
Pois é... uma IDE pra desenhar telas pra que? pra qual tela?

Meu modo de trabalho

MensagemEnviado: 25 Dez 2019 17:24
por JoséQuintas
O meu aplicativo também tem uns fontes ridículos....
Algum dia devo ter criado, não sei se alguém usa, mas tá lá até hoje.
Vou alterar neste instante, porque estou querendo eliminar o JPESTOQUE.DBF
Este vai ser um bom exemplo de mudança pra SQL !!!

/*
PESTOENTFOR - Consulta Entradas de Fornecedor
2000.12.05 José Quintas
*/

#include "inkey.ch"

PROCEDURE pEstoEntFor

   LOCAL m_TxtTmp, mFornec, GetList := {}, mTexto, mDocto

   IF ! AbreArquivos( "jpcadastro", "jpcidade", "jpconfi", "jpempresa", ;
      "jpestoque", "jpitem", "jpitped", "jpfiscal", "jpnotfis", "jpnumero", "jppedido", ;
      "jppreco", "jpsenha", "jptabel", "jpuf" )
      RETURN
   ENDIF
   m_TxtTmp  := MyTempFile( "DBF" )
   dbCreate( m_TxtTmp, { { "TEXTO", "C", 80, 0 } } )
   SELECT 0
   USE ( m_TxtTmp ) EXCLUSIVE ALIAS TEMP
   mFornec := Space(6)
   DO WHILE .T.
      @ 4, 0 SAY "Fornecedor.." GET mFornec PICTURE "@K 999999" VALID JPCADASTROClass():Valida( @mFornec )
      Mensagem( "Digite fornecedor, F9 pesquisa, ESC sai" )
      READ
      Mensagem()
      IF lastkey() == K_ESC
         EXIT
      ENDIF
      WSave()
      SELECT temp
      ZAP
      RecAppend()
      REPLACE temp->Texto WITH "FORNECEDOR (" + jpcadastro->idCadastro + ") " + jpcadastro->cdNome
      RecAppend()
      RecUnlock()
      SELECT jpestoque
      OrdSetFocus( "jpestoq5" )
      SEEK "2" + mFornec
      DO WHILE jpestoque->esTipLan == "2" .AND. jpestoque->esCliFor == mFornec .AND. ! Eof()
         GrafProc()
         mTexto := "Docto " + jpestoque->esNumDoc
         mTexto += " Emissao " + Dtoc( jpestoque->esDatLan )
         SELECT temp
         RecAppend()
         REPLACE temp->Texto WITH mTexto
         RecUnlock()
         mDocto := jpestoque->esNumDoc
         SELECT jpestoque
         DO WHILE mDocto == jpestoque->esNumDoc .AND. mFornec == jpestoque->esCliFor .AND. jpestoque->esTipLan == "2" .AND. ! Eof()
            GrafProc()
            Encontra( jpestoque->esItem, "jpitem","item" )
            mTexto := "   Qtde " + str( jpestoque->esQtde )
            mTexto += " Preco " + str( jpestoque->esValor )
            mTexto += " Item " + Left( jpitem->ieDescri, 70 )
            SELECT Temp
            RecAppend()
            REPLACE temp->Texto WITH mTexto
            RecUnlock()
            SELECT jpestoque
            SKIP
         ENDDO
      ENDDO
      SELECT temp
      RecAppend()
      REPLACE temp->Texto WITH "*** FIM ***"
      RecUnlock()
      Mensagem( "Selecione e tecle ENTER, ESC sai" )
      SELECT temp
      GOTO TOP
      FazBrowse()
      WRestore()
   ENDDO
   SELECT ( Select( "temp" ) )
   USE
   fErase( m_TxtTmp )

   RETURN

Meu modo de trabalho

MensagemEnviado: 25 Dez 2019 18:12
por JoséQuintas
Pronto.
Pede pro servidor e mostra.
Não precisa de nenhum DBF agora, nem nada em disco.

#include "inkey.ch"

PROCEDURE pEstoEntFor

   LOCAL mFornec := Space(6), GetList := {}, oTBrowse
   LOCAL cnMySql := ADOClass():New( AppConexao() )

   DO WHILE .T.
      @ 4, 0 SAY "Fornecedor.." GET mFornec PICTURE "@K 999999" VALID JPCADASTROClass():Valida( @mFornec )
      Mensagem( "Digite fornecedor, F9 pesquisa, ESC sai" )
      READ
      Mensagem()
      IF lastkey() == K_ESC
         EXIT
      ENDIF
      WITH OBJECT cnMySql
         :cSql := "SELECT LPAD( IDESTOQUE, 6, '0' ) AS ID, ESCLIFOR, ESDATLAN, ESNUMDOC, " + ;
            " ESQTDE, ESVALOR, ESQTDE * ESVALOR AS VALTOT, " + ;
            " LEFT( JPITEM.IEDESCRI, 50 ) AS ITENOME" + ;
            " FROM JPESTOQUE" + ;
            " LEFT JOIN JPITEM ON ESITEM=JPITEM.IDITEM" + ;
            " WHERE ESTIPLAN='2'" + ;
            " AND ESCLIFOR=" + StringSql( mFornec ) + ;
            " ORDER BY ESDATLAN DESC, ITENOME"
         :Execute()
         oTBrowse := { ;
            { "ID",      { || :String( "ID", 6 ) } }, ;
            { "DOCTO",   { || :String( "ESNUMDOC", 9 ) } }, ;
            { "DATA",    { || :Date( "ESDATLAN" ) } }, ;
            { "QTDE",    { || Transform( :Number( "ESQTDE" ), "@E 999,999,999.99" ) } }, ;
            { "VALOR",   { || Transform( :Number( "ESVALOR" ), "@E 999,999,999.9999" ) } }, ;
            { "TOTAL",   { || Transform( :Number( "VALTOT" ), "@E 999,999,999.99" ) } }, ;
            { "PRODUTO", { || :String( "ITENOME", 30 ) } } }
         BrowseADO( cnMySql, oTBrowse, "ESNUMDOC,ITENOME" )
         :CloseRecordset()
      ENDWITH
   ENDDO

   RETURN


Aproveitar fonte anterior?
De certa forma aproveitou a lógica, mas o fonte, quem se importa.

O que tem no fonte?
Pede pro servidor e faz um browse, nenhum processamento no terminal ou no aplicativo.

Trazer total pronto, ou multiplicar no browse? talvez... pode economizar alguns milésimos de segundo...

Meu modo de trabalho

MensagemEnviado: 25 Dez 2019 19:04
por JoséQuintas
Agora pensando numa coisa:

Se não depende de arquivo aberto....
Nada impede de embutir/chamar essa rotina direto no cadastro do fornecedor, por exemplo.

É disso que o usuário gosta: recursos na mão (ou no click).

Meu modo de trabalho

MensagemEnviado: 28 Dez 2019 13:45
por JoséQuintas
Quase acabando com o uso do dbf jpestoque.
O alias aparece 76 vezes em todos os fontes, incluindo rotinas de conversão/atualização.
Falta pouco....

jpestoque.png

Meu modo de trabalho

MensagemEnviado: 28 Dez 2019 22:56
por asimoes
Quintas,

Nas minhas consultas, percebi que criar indices temporários é muito mais rápido que set filter to...

A vantagem disso é que que informo uma chave de ordenação e um filtro usando a clausula "for" que é equivalente ao "where" do sql

ainda de quebra posso usar OrdScope, OrdKeyCount...

Meu modo de trabalho

MensagemEnviado: 28 Dez 2019 23:12
por JoséQuintas
asimoes escreveu:Nas minhas consultas, percebi que criar indices temporários é muito mais rápido que set filter to...
A vantagem disso é que que informo uma chave de ordenação e um filtro usando a clausula "for" que é equivalente ao "where" do sql ainda de quebra posso usar OrdScope, OrdKeyCount...


Também tinha o sub-index, usava em SIXCDX.

INDEX ON .... FOR ... WHILE ...

Um índice criado a partir do índice atual, que pode servir pra algum tipo de filtro, pra ganhar tempo.

Mas estou encerrando com DBF.

Repetir o que já disse antes:
Trazer do MySQL é 1 segundo.
Gravar isso em DBF LOCAL, demora minutos.
Usar DBF deixou totalmente de fazer sentido.

Sinceramente...
Quer velocidade com DBF?
Use ADS LOCAL e comando SQL.
SEM ÍNDICE vai ser mais rápido do que qualquer outra alternativa.

Meu modo de trabalho

MensagemEnviado: 28 Dez 2019 23:19
por JoséQuintas
Um exemplo de uso do sub-índice:

tem lá o movimento por data:

OrdSetFocus( "ordemdata" )
SEEK Dtos( "20191201" )
INDEX ON produto WHILE Data <= Ctod("31/12/2019")

Nesse caso vai usar o índice por data, usando só os lançamentos do mês, pra criar um índice por produto.

Mais fácil e rápido no MySQL... rs...
SELECT * FROM MOVIMENTO WHERE DATA BETWEEN ... ORDER BY PRODUTO

Meu modo de trabalho

MensagemEnviado: 29 Dez 2019 10:47
por asimoes
Trabalhamos com Oracle desde 2001, sei do que você está falando.
Mas ainda existe sistema legado ( dbf ) ai optamos por manter do jeito que está, até converter para c#, enquanto isso, tem esse recurso do temporário.

Meu modo de trabalho

MensagemEnviado: 31 Dez 2019 17:15
por JoséQuintas
Última alteração de 2019:
Toda movimentação de estoque agora é exclusiva em MySQL.
Isso inclui relatórios, consultas, arquivo de envio pra ANP, etc.
Sem mais leitura ou gravação em DBF pra estoque.

Meu modo de trabalho

MensagemEnviado: 26 Jan 2020 22:01
por JoséQuintas
Ainda estou eliminando JPNOTFIS.DBF, esse é o foco central, porque é o maior DBF de todos.
De um modo geral, em certos fontes é mais fácil eliminar vários DBFs de uma vez do que um só.
E também é interessante já ir testando um uso "mais avançado", pra ver se o comportamento é o esperado.

jpnotfis.png


Lembrando:
As gravações já estão duplicadas em DBF e MySQL.
Estou alterando agora as leituras, pra usar somente o MySQL.

Não tinha pensado nisso antes:

Mesmo que fosse alguma LIB do Harbour, até mesmo se fosse uma RDDSQL da vida, eu teria que fazer essa parte, de alterar ao máximo pra comandos SQL, pra ter velocidade.
Nem dá pra comparar o MEU tempo de migração com o de alguma RDDSQL, porque acabaria tendo que fazer isso do mesmo jeito.
Parece que na prática deu no mesmo, só alterei a ordem das coisas, fui direto pros finalmentes, antes de eliminar os DBFs.

Só uma coisa é certa:
Ver tudo funcionando mais rápido, deixa mais empolgado pra terminar tudo mais rápido.
E todos já devem ter percebido como estou empolgado.... rs
E ainda tenho DBFs, ainda nem me livrei de todos eles !!!

Meu modo de trabalho

MensagemEnviado: 26 Jan 2020 22:17
por JoséQuintas
jpnotfis.png


O jpnotfis.dbf é o que estou eliminando, mas todos os NÃO marcados com X estão dentro do MySQL.
Acabei acrescentando os outros pra facilitar.

Pensando bem... acho que dava pra eliminar o jpestoque primeiro...
Se é que ainda precisa dele... rs

Meu modo de trabalho

MensagemEnviado: 26 Jan 2020 22:19
por JoséQuintas
Vixe... não precisa mais dele....
Eliminei a necessidade de um DBF e nem percebi kkkk
Não usa em mais lugar nenhum.

jpestoque.png

Meu modo de trabalho

MensagemEnviado: 26 Jan 2020 22:47
por JoséQuintas
Ok, erro de programador.

   IF AppVersaoDbfAnt() < 20200101;   Update0100A(); ENDIF
   IF AppVersaoDbfAnt() > 20200101;   Update0100B(); ENDIF // apagar apos ok
   IF AppVersaoDbfAnt() < 20200102.1; Update0102A(); ENDIF
   IF AppVersaoDbfAnt() < 20200102.2; Update0102B(); ENDIF
   IF AppVersaoDbfAnt() < 20200110.1; Update0110A(); ENDIF
   IF AppVersaoDbfAnt() < 20200113.1; Update0113A(); ENDIF
   IF AppVersaoDbfAnt() < 20200116.1; Update0116A(); ENDIF

   RETURN NIL


No segundo IF, por precaução, é sinal de maior, mas deixei nas atualizações de 2019.
Como já passou.... nunca era executado.

Porque diferente:

Entra a versão 2020, digamos que no cliente seja 2018, faz toda conversão, mas não vai apagar nada.
Na próxima entrada, que tudo está convertido e a versão mudou pra 2020, sinal de que tudo correu bem, então apaga.
Fiz isso como precaução.

Mas... deixei em 2019... e se a versão mudou pra 2020, nem processa nenhuma conversão de 2019, nem essa.

jpestoque.png


Agora sim, 2 DBFs a menos, e 100MB a menos em DBF.

Pois é... perdendo tempo e espaço à toa fazendo backup desses 100MB.... rs
Ainda bem que fui postar aqui, e reparei no jpestoque.dbf...

Meu modo de trabalho

MensagemEnviado: 26 Jan 2020 23:08
por JoséQuintas
Pra quem não entendeu:

Toda migração de DBF pra MySQL está sendo automática.
O cliente clica em atualizar versão, e o que era DBF vira MySQL.
Está sendo tudo automático.
Não precisa mais desse jpestoque.dbf, faltava apagar o DBF.
Na próxima atualização que o cliente fizer, o arquivo será apagado.

Está sendo assim desde o começo.
Daqui a pouco, não tem mais nenhum DBF na pasta, e o cliente nem vai perceber.

Meu modo de trabalho

MensagemEnviado: 29 Jan 2020 17:11
por JoséQuintas
jpnotfis.png


Tá baixando o uso do jpnotfis.dbf.
Faltam menos de 187 lugares.
Se comecei pelos mais simples, sinal de que faltam os que precisam de mais atenção.

Os mais simples serviram pra ir adquirindo experiência, talvez esses tenham se tornado simples agora.

Meu modo de trabalho

MensagemEnviado: 30 Jan 2020 18:46
por JoséQuintas
O que tenho feito nos fontes mais "delicados":

Recapitulando:
Um fonte pode depender de DBFs abertos e posicionados.
E este fonte pode chamar outros fontes, e outros, e mais outros, e sempre dependendo dos DBFs posicionados.

Estou pegando o mais interno, e alterando pra depender apenas de um código.
Por exemplo, na geração do XML de NFE, o bloco de cobrança:

NfeBlocoCobranca( @cXml, jpnotfis->idNotFis )
...
STATIC FUNCTION NfeBlocoCobranca( cXml, midNotFis )

Alterei pra receber a ID da nota fiscal, e com essa ID gera o bloco.
O anterior dependia disso, e continua dependendo, então nada mudou na rotina anterior.
Mas, para o bloco de cobrança, passou a depender somente de um valor.

Qual a diferença?
Depois altero o anterior pra também não depender do DBF.

Preciso retirar a dependência do DBF aberto, e é o que estou fazendo, uma rotina por vez.
Se fosse fazer diferente.... vixe... muitos problemas até conseguir finalizar
Retirando uma rotina por vez: pode ser instalado imediatamente nos clientes.

Isso é legal: poder deixar sempre em uso o fonte mais atualizado.

Lembram do princípio da programação: dividir um problema grande em problemas pequenos, porque os pequenos a gente resolve fácil, e acaba resolvendo o problema grande no final.
Pois é... é o que estou fazendo, pequenas conversões mais simples, que no final vão converter tudo.

Na postagem anterior faltavam 187 lugares pra eliminar jpnotfis.dbf, agora 107
Sendo que, além de eliminar o uso de jpnotfis.dbf desses fontes, já estou eliminando de outros DBFs também.

meutrabalho.png

Meu modo de trabalho

MensagemEnviado: 30 Jan 2020 23:19
por JoséQuintas
Como curiosidade, vou alterar este bloco agora.
Já acrescentei mIdNotFis como parâmetro, agora alterar pra não precisar DBF.
Já tinha alterado a transportadora, mas vou aproveitar pra fazer um único comando SQL pegando tudo.
Talvez acrescente algo mais no SQL, só pra ter menos fonte PRG.

STATIC FUNCTION NfeBlocoTransporte( cXml, midNotFis )

   LOCAL mPlaca, cnMySql := ADOClass():New( AppConexao() )
   LOCAL mtpNome, mtpCnpj, mtpInsEst, mtpEndereco, mtpCidade, mtpUF

   WITH OBJECT cnMySql
      :cSql := "SELECT TPNOME, TPCNPJ, TPINSEST, TPENDERECO, TPNUMERO, TPCOMPL, TPBAIRRO, TPCIDADE, TPUF " + ;
         "FROM JPTRANSP WHERE IDTRANSP=" + StringSql( jpnotfis->nfCadTra )
      :Execute()
      mtpNome     := :String( "TPNOME" )
      mtpCnpj     := :String( "TPCNPJ" )
      mtpInsEst   := :String( "TPINSEST" )
      mtpEndereco := :String( "TPENDERECO" ) + " " + :String( "TPNUMERO" ) + " " + :String( "TPCOMPL" ) + ;
         " " + :String( "TPBAIRRO" )
      mtpCidade   := :String( "TPCIDADE" )
      mtpUF       := :String( "TPUF" )
      :CloseRecordset()
   ENDWITH

   cXml += [<transp>]
   DO CASE
   CASE jpnotfis->nfPagFre $ "R0" .AND. ! Empty( mtpNome )
      cXml += XmlTag( "modFrete", "0" )
   CASE jpnotfis->nfPagFre $ "D1" .AND. ! Empty( mtpNome )
      cXml += XmlTag( "modFrete", "1" )
   CASE jpnotfis->nfPagFre $ "T2" .AND. ! Empty( mtpNome )
      cXml += XmlTag( "modFrete", "2" )
   CASE jpnotfis->nfPagFre $ "3" .AND. ! Empty( mtpNome )
      cXml += XmlTag( "modFrete", "3" )
   CASE jpnotfis->nfPagFre $ "4" .AND. ! Empty( mtpNome )
      cXml += XmlTag( "modFrete", "4" )
   OTHERWISE // CASE jpnotfis->nfPagFre == "N"
      cXml += XmlTag( "modFrete", "9" )
   ENDCASE

   IF ! Empty( mtpNome )
      cXml += [<transporta>]
      IF Len( SoNumeros( mtpCnpj ) ) != 0
         IF Len( SoNumeros( mtpCnpj ) ) == 14
            cXml += XmlTag( "CNPJ", SoNumeros( mtpCnpj ) )
         ELSE
            cXml += XmlTag( "CPF", SoNumeros( mtpCnpj ) )
         ENDIF
      ENDIF
      cXml += XmlTag( "xNome", mtpNome )
      IF Len( SoNumeros( mtpInsEst ) ) != 0
         cXml += XmlTag( "IE", SoNumeros( mtpInsEst ) )
         cXml += XmlTag( "xEnder", Trim( Pad( mtpEndereco, 60 ) ) )
         cXml += XmlTag( "xMun", mtpCidade )
         cXml += XmlTag( "UF", mtpUF )
      ENDIF
      cXml += [</transporta>]
   ENDIF
   mPlaca := AllTrim( jpnotfis->nfVeiculo )
   mPlaca := StrTran( mPlaca, "-", "" )
   IF Len( mPlaca ) > 5
      cXml += [<veicTransp>]
      cXml += XmlTag( "placa", mPlaca )
      cXml += XmlTag( "UF", "SP" )
      cXml += [</veicTransp>]
   ENDIF
   cXml += [<vol>]
   cXml += XmlTag( "qVol", jpnotfis->nfQtdVol, 0 )
   cXml += XmlTag( "esp", iif( Len( Trim( jpnotfis->nfEspecie ) ) == 0, "UNID", jpnotfis->nfEspecie ) )
   cXml += XmlTag( "marca", "." )
   cXml += XmlTag( "nVol", "." )
   cXml += XmlTag( "pesoL", jpnotfis->nfPesLiq, 3 )
   cXml += XmlTag( "pesoB", jpnotfis->nfPesBru, 3 )
   cXml += [</vol>]
   cXml += [</transp>]

   RETURN NIL

Meu modo de trabalho

MensagemEnviado: 30 Jan 2020 23:52
por JoséQuintas
Pronto, sem mais DBF na rotina.

STATIC FUNCTION NfeBlocoTransporte( cXml, midNotFis )

   LOCAL mPlaca, cnMySql := ADOClass():New( AppConexao() )

   WITH OBJECT cnMySql
      :cSql := "SELECT NFVEICULO, NFQTDVOL, NFPESLIQ, NFPESBRU," + ;
         " IF( JPTRANSP.IDTRANSP IS NULL, '9', IF( NFPAGFRE IN ( '0', '1', '2', '3', '4', '9' ), " + ;
         " NFPAGFRE, IF( NFPAGFRE = 'R', '0', IF( NFPAGFRE = 'D', '1', IF( NFPAGFRE = 'T', '2'," + ;
         " '9' ) ) ) ) ) AS PAGFRE," + ;
         " IF( LENGTH( NFESPECIE ) < 1, 'UNID', NFESPECIE ) AS ESPECIE," + ;
         " JPTRANSP.TPNOME AS NOME, JPTRANSP.TPCNPJ AS CNPJ, JPTRANSP.TPINSEST AS INSEST," + ;
         " TRIM( LEFT( CONCAT( JPTRANSP.TPENDERECO, ' ', JPTRANSP.TPNUMERO, ' ', JPTRANSP.TPCOMPL, ' ', JPTRANSP.TPBAIRRO ), 60 ) ) AS ENDERECO," + ;
         " JPTRANSP.TPCIDADE AS CIDADE, JPTRANSP.TPUF AS UF" + ;
         " FROM JPNOTFIS" + ;
         " LEFT JOIN JPTRANSP ON JPTRANSP.IDTRANSP = JPNOTFIS.NFCADTRA" + ;
         " WHERE IDNOTFIS = " + StringSql( midNotFis )
      :Execute()

      cXml += [<transp>]
      cXml += XmlTag( "modFrete", :String( "PAGFRE", 1  ) )
      IF ! Empty( :String( "NOME" ) )
         cXml += [<transporta>]
         IF Len( SoNumeros( :String( "CNPJ" ) ) ) != 0
            IF Len( SoNumeros( :String( "CNPJ" ) ) ) == 14
               cXml += XmlTag( "CNPJ", SoNumeros( :String( "CNPJ" ) ) )
            ELSE
               cXml += XmlTag( "CPF", SoNumeros( :String( "CNPJ" ) ) )
            ENDIF
         ENDIF
         cXml += XmlTag( "xNome", :String( "NOME" ) )
         IF Len( SoNumeros( :String( "INSEST" ) ) ) != 0
            cXml += XmlTag( "IE", SoNumeros( :String( "INSEST" ) ) )
            cXml += XmlTag( "xEnder", :String( "ENDERECO" ) )
            cXml += XmlTag( "xMun", :String( "CIDADE" ) )
            cXml += XmlTag( "UF", :String( "UF" ) )
         ENDIF
         cXml += [</transporta>]
      ENDIF
      mPlaca := AllTrim( :String( "NFVEICULO" ) )
      mPlaca := StrTran( mPlaca, "-", "" )
      IF Len( mPlaca ) > 5
         cXml += [<veicTransp>]
         cXml += XmlTag( "placa", mPlaca )
         cXml += XmlTag( "UF", :String( "UF" ) )
         cXml += [</veicTransp>]
      ENDIF
      cXml += [<vol>]
      cXml += XmlTag( "qVol", :Number( "NFQTDVOL" ), 0 )
      cXml += XmlTag( "esp", :String( "ESPECIE" ) )
      cXml += XmlTag( "marca", "." )
      cXml += XmlTag( "nVol", "." )
      cXml += XmlTag( "pesoL", :Number( "NFPESLIQ" ), 3 )
      cXml += XmlTag( "pesoB", :Number( "NFPESBRU" ), 3 )
      cXml += [</vol>]
      cXml += [</transp>]
      :CloseRecordset()
   ENDWITH

   RETURN NIL


e baixou um pouco mais

jpnotfis.png

Meu modo de trabalho

MensagemEnviado: 31 Jan 2020 01:36
por JoséQuintas
Mas uma possibilidade poderia ser:

SELECT CONCAT( '<xEnder>', ENDERECO, '</xEnder><xMun>', CIDADE, '</xMun><UF>', UF, '</UF>' ) AS XML


Poderia ser uma Stored Procedure, um VIEW, ou outra coisa dentro do banco de dados.
E aí, teremos a geração de XML dentro do MySQL, pra qualquer linguagem de programação.

Como eu disse, apenas mostrando possibilidades.

Me veio na cabeça a briga por escolher linguagem de programação, ou pra portar de Desktop pra mobile, etc.

No final, colocar no banco de dados talvez seja a melhor escolha. kkkkkk

Meu modo de trabalho

MensagemEnviado: 13 Fev 2020 11:53
por JoséQuintas
Só sobrou o uso nas atualizações de versão.
Esses fontes depois serão apagados, porque a única utilidade deles passa a ser caso precise usar um backup anterior, que precise conversão.

jpnotfis.png


Nota:
Não usa mais o DBF, mas ainda não retirei a gravação.
Deixar isso pra próxima.
Eliminar depois a gravação, e também o JPNOTFIS.DBF, porque não precisa mais dele.
Notas fiscais agora só em MySQL.
O DBF, vai ser só pra garantia de que tudo está ok, uma última conferência antes de apagar de vez.

Meu modo de trabalho

MensagemEnviado: 13 Fev 2020 12:04
por JoséQuintas
Acho que não cheguei a comentar isto:

O controle de versão, aonde tem MySQL está no MySQL.
Lógico, MySQL é mais seguro pra isso.
Aonde ainda é só DBF - que não vai mais ter emissão de nota fiscal - o controle de versão ainda é no DBF.

O aplicativo é único pra todos, quando chegar a hora, esses últimos só com contábil vão para o MySQL também.
Como eu já disse, o contábil libera empresas à vontade, deixar pra pensar nisso depois.

Meu modo de trabalho

MensagemEnviado: 18 Fev 2020 16:33
por JoséQuintas
Dica pra quem estiver pensando em MySQL:

Primeiro troque as chaves pra numérico, e só depois migre.
Comecei a fazer isso no meio do caminho.... vixe... vários bugs.

Não, MySQL não é problema, mas ajustar os campos no aplicativo.... são muitos... acaba sempre passando coisa...
E gravando duplicado em DBF então... aí piorou.... porque no DBF tá caractere e no MySQL está numérico...

Mas está indo....
Trabalhar com campo numérico é muito mais prático.
Só não estou alterando nos DBFs... porque infelizmente se alterar isso na estrutura vai dar erro de type mismatch.
Só mesmo no dBASE/FoxPro era permitido alterar campo de caractere pra numérico, no Clipper e Harbour não dá.
No MySQL ok, mas no DBF não.

Até que não é complicado, porque basta alterar no REPLACE e usar StrZero() e tudo bem.
Isso vale inclusive para o MySQL/MariaDB.

Isto é válido no MySQL:

UPDATE tabela SET CampoNumerico = '0'

Mas isto não:

UPDATE TABELA SET CampoNumerico = ' '

Inicialmente usava a própria variável, porque facilitou a montagem do comando, mas quando não tem número.... ERRO

cVar := "000000"
"UPDATE TABELA SET CampoNumero = " + ['] + cVar + [']

Então, passando pra numérico de vez, e alterando todo o aplicativo.
E StrZero() antes de salvar em DBF/MYSQL, assim funciona nos dois, DBF como caractere e MySQL como numérico.

Entrando nas partes que dão mais trabalho pra ajustar.
Ainda trocando nomes de campos pra padronizar, assim evita ficar errando nome.
Quem mandou deixar despadronizado kkkkk

Tipo... cadastro IdCadastro, no financeiro fiCliFor, no estoque esCliFor, na nota nfCadDes, no pedido pdCliFor
Agora vai virar idCadastro, fiCadastro, esCadastro, nfCadastro e pdCadastro
iniciais do arquivo seguido do nome do campo, exceto no cadastro principal, onde começa com ID.
De onde vém os campos xxCadastro? do arquivo dbf/tabela JPCADASTRO.
Agora sim... ficando padronizado.

Isso me lembra aquela questão: E se você morrer?
Mais fácil que isso impossível, qualquer um vai poder continuar a manutenção, pelo menos no que se refere a encontrar as informações.

Meu modo de trabalho

MensagemEnviado: 24 Fev 2020 21:12
por JoséQuintas

Erro executando comando:-2147217900 [ma-3.1.6][10.4.11-MariaDB]Unknown column 'ESITEM' in 'where clause'
Called from ADOCLASS:EXECUTECMD(254)
Called from ADOCLASS:EXECUTE(227)
Called from MOSTRAULTIMACOMPRA(1444)
Called from INCALTITEM(1345)
Called from DIGPED(940)
Called from (b)JPPEDIDOCLASS_TELADADOS(816)
Called from DBVIEW(638)
Called from JPPEDIDOCLASS:TELADADOS(816)
Called from JPPEDIDOCLASS:EXECUTE(426)
Called from P0600PED(70)
Called from DO(0)
Called from DOPRG(126)
Called from (b)RUNMODULE(96)


É erro, mas por enquanto é normal...

Estou padronizando nomes de campos, e esse ESITEM agora é ESPRODUTO, e falta ajustar TODOS os fontes ainda, para o novo nome, desse campo e de muitos outros.....

E isso com gravação dupla DBF + MySQL....
Mais divertido do que ficar vendo carnaval na TV kkkk

E tenho que aproveitar agora o carnaval, porque quarta feira já precisa estar instalado em cliente....
Aproveitei pra mexer em TUDO novamente kkkkk

E desta vez sim: fechar a versão, obrigar todos a atualizarem, e remover tudo que é conversão.
Esse negócio de manter conversões anteriores pra cliente atualizar quando quiser, neste momento complicou, porque estou tendo até que refazer conversões anteriores, porque o MYSQL vai estar totalmente atualizado, e qualquer DBF ainda não atualizado vai precisar ser atualizado antes de transferir para o MySQL... coisa que em vários clientes já foi feito e nem precisa mais disso.

No MySQL é moleza, só alterar o nome do campo e pronto.
Mas em DBF é mais chato, precisa processamento porque não dá pra fazer direto.

Pois é... perdendo tempo com DBFs que vão pro lixo kkkk
Mas faz parte, fazer o que....
E gravando duplicado serve como garantia pra algum problema eventual.

Pensando bem.... se agora tá direcionado primariamente pra MySQL, poderia até apagar coisas antigas do DBF, que não vão fazer falta...
Mas não agora, deixa primeiro eu arrumar a bagunça que comecei, porque nada mais funciona, por enquanto kkkk

Nota:
Na prática, nem sei se algum cliente ainda precisa dessas conversões antigas, talvez todos já tenham atualizado.
Mas na dúvida tenho que atualizar, pra não ter imprevisto.
Por isso obrigar a atualizarem, inclusive já criei controle disso no meu website, assim fica registrado que versão cada um está usando.
LIMPEZA GERAL EM MARÇO !!!

Meu modo de trabalho

MensagemEnviado: 26 Fev 2020 10:06
por JoséQuintas
Esta é apenas uma das mudanças do carnaval:

STATIC FUNCTION JPEDICFGCreateMySql()

RETURN ;
"CREATE TABLE IF NOT EXISTS JPEDICFG ( " + ;
"EDID INT(11) NOT NULL AUTO_INCREMENT, " + ;
"EDNUMLAN VARCHAR(6) NOT NULL DEFAULT '', " + ;
"EDTIPO VARCHAR(6) NOT NULL DEFAULT '', " + ;
"EDCODJPA VARCHAR(6) NOT NULL DEFAULT '', " + ;
"EDCODEDI1 VARCHAR(20) NOT NULL DEFAULT '', " + ;
"EDCODEDI2 VARCHAR(20) NOT NULL DEFAULT '', " + ;
"EDDESEDI VARCHAR(50) NOT NULL DEFAULT '', " + ;
"EDINFINC VARCHAR(80) NOT NULL DEFAULT '', " + ;
"EDINFALT VARCHAR(80) NOT NULL DEFAULT '', " + ;
"PRIMARY KEY ( EDID ), " + ;
"INDEX IDXJPEDICFG ( EDNUMLAN, EDID ), " + ;
"INDEX IDXEDI ( EDTIPO, EDCODEDI1, EDCODEDI2, EDNUMLAN ), " + ;
"INDEX IDXJPA ( EDTIPO, EDCODJPA, EDCODEDI1, EDNUMLAN ) " + ;
") COLLATE=latin1_swedish_ci ENGINE=InnoDB"



STATIC FUNCTION JPEDICFGCreateMySql()

RETURN ;
"CREATE TABLE IF NOT EXISTS JPEDICFG ( " + ;
"IDEDICFG INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + ;
"EDTIPO VARCHAR(6) NOT NULL DEFAULT '', " + ;
"EDEMPRESA VARCHAR(20) NOT NULL DEFAULT '', " + ;
"EDEXTERNO VARCHAR(20) NOT NULL DEFAULT '', " + ;
"EDINTERNO VARCHAR(6) NOT NULL DEFAULT '', " + ;
"EDDESCRICAO VARCHAR(50) NOT NULL DEFAULT '', " + ;
"EDINFINC VARCHAR(80) NOT NULL DEFAULT '', " + ;
"EDINFALT VARCHAR(80) NOT NULL DEFAULT '', " + ;
"PRIMARY KEY ( IDEDICFG ), " + ;
"INDEX IDXEXTERNO ( EDTIPO, EDEMPRESA, EDEXTERNO ), " + ;
"INDEX IDXINTERNO ( EDTIPO, EDEMPRESA, EDINTERNO ) " + ;
") COLLATE=latin1_swedish_ci ENGINE=InnoDB"


É o "conversor" de notas, ficou "menos ruim".

EDCODJPA => EDINTERNO = código interno do aplicativo
EDCODEDI1 => EDEMPRESA = empresa sendo integrada
EDCODEDI2 => EDEXTERNO = código usado pela empresa, portanto externo
EDDESEDI => EDDESCRICAO = descrição usada pela empresa/nota da empresa

Se minha migração pra MySQL não é rápida, é porque estou dando uma geral ainda durante a migração.

Meu modo de trabalho

MensagemEnviado: 26 Fev 2020 13:54
por JoséQuintas
Uia...

Erro executando comando:-2147217900 [ma-3.1.6][10.4.11-MariaDB]Unknown column 'FICLIFOR' in 'field list'


IF :FieldExists( "FICLIFOR", "JPFINAN" )
:ExecuteCmd( "ALTER TABLE JPFINAN CHANGE COLUMN FICLIFOR FICADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
ENDIF


Normal, se alterei o campo FICLIFOR pra FICADASTRO... FICLIFOR não existe mais kkkk

Pois é... é o que dá atender telefone no meio de alterações kkkkk

Aí tem um ponto importante:

Minha migração e alteração de campos se torna menos problemática porque tenho padrão no nome dos campos.
TODO campo começando com FI... é do arquivo do financeiro.
FICADASTRO só pode ser o código de cadastro no arquivo/tabela do financeiro.
Se estou mexendo nele, fica fácil localizar em TODOS os fontes aonde preciso alterar, basta o FIND IN FILES.

Então... meu modo de trabalho é facilitado para... o modo que eu trabalho.
Fica mais seguro mexer em muitas coisas, porque está fácil identifica-las nos fontes.
O que vale pra mim pode não valer pra outros fontes, com modo de trabalho diferente.
Se em todos os arquivos fosse IDCadastro... aí ficaria impossível identificar quais os fontes que usam aquele campo daquela tabela, a não ser olhando um a um...

Meu modo de trabalho

MensagemEnviado: 03 Mar 2020 20:37
por JoséQuintas
Uma das alterações deste final de semana:

antes:

METHOD MostraReserva() CLASS JPITEMClass

   LOCAL nSelect := Select()
   LOCAL nOrdSetFocus, nKey := 0, aaPedidoList := {}, acList, oElement

   Mensagem( "Pesquisando nos pedidos, ESC cancela" )
   SELECT jpitped
   nOrdSetFocus := OrdSetFocus()
   OrdSetFocus( "jpitped2" )
   SEEK jpitem->idItem
   DO WHILE nKey != K_ESC .AND. jpitped->ipItem == jpitem->idItem .AND. ! Eof()
      nKey := Inkey()
      Encontra( jpitped->ipPedido, "jppedido", "pedido" )
      IF jppedido->pdConf != "S"
         SKIP
         LOOP
      ENDIF
      IF Left( jpitped->ipCfOp, 1 ) < "5"
         SKIP
         LOOP
      ENDIF
      IF Encontra( jppedido->idPedido, "jpnotfis", "pedido" )
         SKIP
         LOOP
      ENDIF
      IF ! "+R" $ ReacaoJPTRANSA( jppedido->pdTransa )
         SKIP
         LOOP
      ENDIF
      AAdd( aaPedidoList, { jppedido->idPedido, jppedido->pdDatEmi, jpitped->ipQtde } )
      SKIP
   ENDDO
   OrdSetFocus( nOrdSetFocus )
   SELECT( nSelect )
   Mensagem()
   IF Len( aaPedidoList ) == 0
      MsgExclamation( "Não há reserva deste produto" )
   ELSE
      acList := {}
      FOR EACH oElement IN aaPedidoList
         AAdd( acList, oElement[ 1 ] + " " + Dtoc( oElement[ 2 ] ) + " " + Str( oElement[ 3 ], 10, 2 ) )
      NEXT
      Atail( AppForms() ):GUIHide()
      wAChoice( 10, 2, acList, 1, "RESERVA " + jpitem->idItem + " " + Trim( jpitem->ieDescri ) )
      Atail( AppForms() ):GUIShow()
   ENDIF

   RETURN NIL


depois:

METHOD MostraReserva() CLASS JPITEMClass

   LOCAL oTBrowse, mIdProduto
   LOCAL cnMySql := ADOClass():New( AppConexao() )

   mIdProduto := Val( jpitem->idProduto )

   WITH OBJECT cnMySql
      :cSql := "SELECT JPPEDIDO.IDPEDIDO, JPPEDIDO.PDDATEMI, IPQTDE" + ;
         " FROM JPITPED" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPITPED.IPPEDIDO" + ;
         " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPTRANSA ON JPPEDIDO.PDTRANSA = JPTRANSA.IDTRANSA" + ;
         " WHERE JPPEDIDO.PDCONF = 'S'" + ;
         " AND LEFT( JPITPED.IPCFOP, 1 ) >= '5'" + ;
         " AND JPNOTFIS.IDNOTFIS IS NULL" + ;
         " AND JPTRANSA.TRREACAO LIKE '%+R%'" + ;
         " AND JPITPED.IPPRODUTO = " + NumberSql( mIdProduto )
      :Execute()
      OTBrowse := { ;
         { "PEDIDO",   { || StrZero( :Number( "IDPEDIDO" ), 6 ) } }, ;
         { "EMISSAO",  { || :Date( "PDDATEMI" ) } }, ;
         { "QTDE",     { || Str( :Number( "IPQTDE" ), 16, 5 ) } } }
      BrowseADO( cnMySql, oTBrowse )
      :CloseRecordset()
   ENDWITH

   RETURN NIL


Por enquanto ficou preso a JPITEM estar aberto pra pegar o código do produto.
Depois altero pra receber o código por parâmetro e altero a(s) rotina(s) que faz(em) chamada pra ela.
E assim vai indo, eliminando o uso de DBFs em uma rotina por vez.

Detalhe que pode ter passado desapercebido:

mIdProduto := Val( jpitem->idProduto )

Pois é... nem terminei a conversão, e já alterei de novo.
O nome do campo era IEITEM, depois idItem, e agora idProduto
E pra complicar... decidi alterar o aplicativo pra trabalhar com numérico, antes de terminar a conversão.
No DBF ainda é caractere....

Instalei hoje, com certeza vários bugs pra resolver.
E com certeza isso fica facilitado por estar usando compilação -w3 -es2, e por ter o recurso de find-in-files, procurar em todos os fontes, o nome do campo.

Tá valendo a pena o trabalho.

Não tem mais arquivo aberto, não tem mais arquivo temporário/array temporário, é só pedir pro servidor e mostrar.
SELECT, nAnterior := Select(), SEEK, etc. isso não existe mais.

Meu modo de trabalho

MensagemEnviado: 05 Mar 2020 12:13
por JoséQuintas
Alteração do momento:

   Encontra( StrZero( mIdCadastro, 6 ), "jpcadastro", "numlan" )
   oBoleto:Calcula()
   oBoleto:cBeneficNome := Trim( jpempresa->emNome )
   oBoleto:cBeneficEnd1 := Trim( jpempresa->emEndereco )
   oBoleto:cBeneficEnd2 := Trim( jpempresa->emCep ) + " " + Trim( jpempresa->emBairro ) + " " + Trim( jpempresa->emCidade ) + " " + jpempresa->emUf
   oBoleto:cPagadorNome := Trim( jpcadastro->cdNome ) + " " + jpcadastro->cdCnpj
   oBoleto:cPagadorEnd1 := Trim( jpcadastro->cdEndereco ) + " " + Trim( jpcadastro->cdNumero ) + " " + Trim( jpcadastro->cdCompl )
   oBoleto:cPagadorEnd2 := jpcadastro->cdCep + " " + Trim( jpcadastro->cdBairro ) + " " + Trim( jpcadastro->cdCidade ) + " " + jpcadastro->cdUf
   oBoleto:cAvalista    := Trim( jpcadastro->cdNome ) + " " + jpcadastro->cdCnpj


   WITH OBJECT cnMySql
      :cSql := "SELECT CONCAT_WS( ' ', CDNOME, CDCNPJ ) AS PAGADORNOME, " + ;
         " CONCAT_WS( ' ', CDENDERECO, CDNUMERO, CDCOMPL ) AS PAGADOREND1," + ;
         " CONCAT_WS( ' ', CDCEP, CDBAIRRO, CDCIDADE, CDUF ) AS PAGADOREND2," + ;
         " CONCAT_WS( ' ', CDNOME, CDCNPJ ) AS AVALISTA" + ;
         " FROM JPCADASTRO WHERE IDCADASTRO = " + NumberSql( mIdCadastro )
      :Execute()
      oBoleto:Calcula()
      oBoleto:cBeneficNome := Trim( jpempresa->emNome )
      oBoleto:cBeneficEnd1 := Trim( jpempresa->emEndereco )
      oBoleto:cBeneficEnd2 := Trim( jpempresa->emCep ) + " " + Trim( jpempresa->emBairro ) + " " + Trim( jpempresa->emCidade ) + " " + jpempresa->emUf
      oBoleto:cPagadorNome := :String( "PAGADORNOME" )
      oBoleto:cPagadorEnd1 := :String( "PAGADOREND1" )
      oBoleto:cPagadorEnd2 := :String( "PAGADOREND2" )
      oBoleto:cAvalista    := :String( "AVALISTA" )
      :CloseRecordset()
   ENDWITH


Menos código Harbour, mais código SQL.
À primeira vista: o fonte Harbour fica mais simples

Cada vez mais eu estou pensando no seguinte:
Código fonte XBASE... uma eterna busca de substituto Clipper
Código SQL... pronto pra tudo
Eternamente esperar XBASE ou.... deixar pronto pra qualquer coisa que exista?
Então... menos fonte Harbour significa cada vez mais pronto pra qualquer coisa.

O que vém depois disso?
Se pode ser qualquer coisa... também pode ser o Harbour... não tenho nenhum motivo pra quebrar a cabeça procurando alternativa pro Harbour, talvez algum dia no futuro.

Meu modo de trabalho

MensagemEnviado: 11 Mar 2020 10:34
por Itamar M. Lins Jr.
Ola!
Eternamente esperar XBASE ou.... deixar pronto pra qualquer coisa que exista?
Então... menos fonte Harbour significa cada vez mais pronto pra qualquer coisa.


Pois é! vc está vendo isso mudando para SQL. "Back-end"
A mesma coisa se for usar HTML+CSS para "Front-end"

Quanto mais aprendemos, OOP, SQL, HTML, CSS, Javascript, vamos nos tornando mais exigentes, e aumentando a nossa visão do Harbour por exemplo.
Muitos programas, frameworks por exemplo são feitos com HTML+CSS, não usam nem windows(api) ou alguma coisa grafica do Linux(KDE/Gnome...) roda pelo browse. HeidiSQL só windows(api), PHPMyAdmin via navegador Web.

Saudações,
Itamar M. LIns Jr.

Meu modo de trabalho

MensagemEnviado: 12 Mar 2020 13:36
por JoséQuintas
Ontem aconteceu um problema grave:
Alterar um produto, e salvar encima de outro - NO DBF

Fiz a correção do fonte, troquei versão, e a solução foi:
Entrar nos dois produtos, pedir pra alterar, e salvar sem mexer em nada.
No MySQL estava certo, a alteração toma como base o MySQL, então só precisava salvar no DBF pra corrigir.

Comentei em outro post:

Tenho ID única para todos os registros, uso no MySQL, e isso deixa o MySQL sempre certo.
No DBF os fontes "estão acostumados" a usar o registro atual, então o fonte está preso a primeiro posicionar no DBF antes de salvar.

Mas é como eu disse no começo: fui eliminando as partes simples, e estou chegando nas que precisam de muito mais atenção.
E é engraçado: o problema não está no uso do MySQL, mas sim de continuar usando DBF kkkk

A parte mais legal é que isso não desanima, pelo contrário, não vejo a hora de acabar com esses DBFs !!!!

Mesmo esquema de sempre: vou alterando e instalando, ou melhor, os clientes vão atualizando pra nova versão.
Só obriguei a última, porque depois dessa começa o..... apagar coisas do DBF...

Só pra recapitular, acho que é importante:

Pra mim DBF não é problema, sempre funcionou direito.
Comecei a alterar pra MySQL porque eu queria.
Confiei no DBF pra salvar simultâneo no MySQL, e agora confiando no MySQL pra salvar no DBF.
A diferença está sendo mesmo em VELOCIDADE.
Por isso continuo alterando tranquilo, e tranquilo tudo dá certo, ou quase kkkk

Meu modo de trabalho

MensagemEnviado: 13 Mar 2020 10:53
por JoséQuintas
Bati o recorde de erro agora:

Comecei a remover o uso de um DBF... produtos de pedido.
Seria normal.... se ele já estivesse no MySQL, mas ainda não está kkkk
É comecei e não terminei e aí achei que já tinha terminado...

Ainda bem que deu tempo de retornar versão anterior no cliente, após problemas pra emitir nota.....

Aproveitar pra mostrar como estou fazendo:

1. Criei a tabela/DBF no MySQL
Aqui, ao trocar versão no cliente, só aparece uma tabela nova no MySQL

2. Criei o campo que vai ser o equivalente ao incremental do MySQL, não existia antes
Aqui, ao trocar versão no cliente, é um campo a mais sem uso.

3. Comecei/continuei a alterar a gravação simultânea DBF/MySQL, usando o incremental do MySQL como referência para o DBF
Aqui, ao trocar versão no cliente, pode até ficar errado no MySQL, não importa por enquanto, mas vou testando as rotinas alteradas, e os produtos de novos pedidos vão sendo gravados no MySQL

4. Terminadas as alterações de fonte em inclusão/alteração/exclusão, começo a conferir o conteúdo da tabela do MySQL comparando com DBF
Aqui, já com versão instalada em cliente

5. Posso também já transferir tudo do DBF para o MySQL, assim as IDs ficam idênticas, e mais prático comparar TODO conteúdo de DBF e MySQL pra ver se sempre estarão 100% iguais.
Aqui, já com versão instalada em cliente

6. Se tudo ok, começo a eliminar a leitura do DBF e passando a fazer no MySQL
Aqui, instalando e acompanhando no cliente

7. Se tudo ok, apago toda gravação de DBF.

8. Na última etapa, só por precaução, vou conferindo o DBF com o MySQL e apagando o conteúdo, porque não precisa mais
Se sobrar alguma coisa, verifico porque está diferente

E fim.

No momento estão em gravação dupla: pedidos, financeiro, cadastros, e produtos de pedido

O de notas fiscais entrou na fase de apagar DBF.
O aplicativo vai conferindo/apagando 100 notas por vez, assim fica um processo rápido, sem atrapalhar o cliente.
A cada 5.000 notas apagadas, o aplicativo vai fazer um PACK, pra evitar que fique lento o processo de checagem.
Cismei de fazer assim, em último caso só o computador que vai perder tempo, então que seja pouco tempo por vez.

09/03/2020  12:04        10.946.384 jpcadastro.DBF
04/03/2020  17:38        16.037.090 jpbancario.dbf
09/03/2020  17:47       114.975.659 jpfinan.DBF
09/03/2020  22:08       162.660.754 JPPEDIDO.DBF
09/03/2020  22:08       176.127.995 jpitped.DBF
13/03/2020  07:03       236.190.407 JPNOTFIS.DBF
              38 arquivo(s)    730.188.657 bytes


Olhem lá, total 730MB em DBF.
Sendo apagado jpnotfis.dbf, 236MB, significa que praticamente já reduziu pra 494MB em DBFs

Por enquanto o objetivo é apagar JPITPED.DBF, 176MB, vai reduzir pra 318MB em DBFs
Parece que vai ser o mais fácil de apagar.... rs

Mas também estão sendo eliminados JPPEDIDO.DBF e JPFINAN.DBF, mais 276MB, vai reduzir pra 46MB em DBF
Já reduzi muito o uso desses DBF, mas ainda não totalmente.
Acho que faltavam os produtos de pedido em MySQL, pra fazer uma eliminação mais radical.

Aí vém aquela bost. de atualização do Windows 10 derrubando conexões, pra atrapalhar uso de MySQL...
Coisas de Microsoft....

Meu modo de trabalho

MensagemEnviado: 15 Mar 2020 14:31
por JoséQuintas
JoséQuintas escreveu:Olhem lá, total 730MB em DBF.
Sendo apagado jpnotfis.dbf, 236MB, significa que praticamente já reduziu pra 494MB em DBFs


15/03/2020  10:36         1.246.217 jppretab.DBF
03/03/2020  02:33         1.254.539 jpcidade.dbf
11/03/2020  17:44         1.370.469 jpitem.DBF
11/03/2020  14:58         2.247.920 jpmdfcab.dbf
11/03/2020  14:33         5.887.930 jpmdfdet.dbf
11/03/2020  23:50        10.946.384 jpcadastro.DBF
04/03/2020  17:38        16.037.090 jpbancario.dbf
11/03/2020  18:09       115.022.587 jpfinan.DBF
11/03/2020  17:44       162.752.554 JPPEDIDO.DBF
15/03/2020  14:21       176.209.043 jpitped.DBF
              27 arquivo(s)    494.157.038 bytes


E não é que reduziu pra 494MB mesmo... rs
E de 38 arquivos DBF, reduziu pra 27, porque tinha deixado alguns lá só pra efeito de conversão.
Comecei a apagar conversões, porque "acho" que não vou precisar mais.
E começando novas conversões kkkk

Meu modo de trabalho

MensagemEnviado: 15 Mar 2020 19:28
por asimoes
Quintas,

Você não trabalha com controle de transação: BeginTrans, CommitTrans e RollBackTrans ?

Meu modo de trabalho

MensagemEnviado: 16 Mar 2020 02:25
por JoséQuintas
asimoes escreveu:Você não trabalha com controle de transação: BeginTrans, CommitTrans e RollBackTrans ?


Não se esqueça que sou principiante no SQL, ainda estou passando de DBF pra SQL.
Só vou pensar em usar algo mais em SQL depois que a migração estiver concluída.
E depois que aprender a usar kkkkk

Pelos seus comentários, já está acostumado a usar SQL com Oracle, e já faz parte do seu dia a dia.
Eu não quero começar a usar alguma coisa que tenha que voltar atrás depois, por não ter planejado direito.

Por enquanto melhor eu terminar a migração, e não arrumar nada que atrapalhe.

Meu modo de trabalho

MensagemEnviado: 16 Mar 2020 09:09
por asimoes
JoséQuintas escreveu:Não se esqueça que sou principiante no SQL, ainda estou passando de DBF pra SQL.
Só vou pensar em usar algo mais em SQL depois que a migração estiver concluída.
E depois que aprender a usar kkkkk


Se você ainda está no mix sql/dbf não convém usar controle de transação.

   
   oConexao:BeginTrans // inicia o controle de transação
   BEGIN SEQUENCE WITH __BREAKBLOCK()
      oConexao:CommitTrans // efetiva a transação de update, insert ou delete
   RECOVER USING oErro
      oConexao:RollBackTrans // Deu erro, anula update, insert ou delete se foi chamado
   END   


O committrans e rollbacktrans finalizam o begintrans

Meu modo de trabalho

MensagemEnviado: 17 Mar 2020 14:45
por JoséQuintas
Erro de hoje:

Não deveria indicar decimal
Called from ADOCLASS:NUMBER(388)
Called from PJPIMPOSTO(39)
Called from DO(0)
Called from RUNMODULE(104)
Called from BOXMENU(758)
Called from BOXMENU(745)
Called from MENUPRINC(593)
Called from SISTEMA(87)
Called from (b)MAIN(58)


É proposital, é que coloquei errado em certos fontes.
Então.... coloquei pro aplicativo me avisar aonde está errado kkkk

   IF ValType( nLen ) == "N" .AND. ValType( nDec ) != "N"
      Errorsys_WriteErrorLog( "Não deveria indicar decimal", 3 )
   ENDIF

Meu modo de trabalho

MensagemEnviado: 23 Mar 2020 19:44
por JoséQuintas
Mexi na minha atualização de DBFs.

STATIC FUNCTION JPFORPAGCreateDbf( nVersaoDBF )

   LOCAL aStruList := { ;
      { "IDFORPAG",   "C", 6 }, ;
      { "FPDESCRI",   "C", 80 } }

   hb_Default( @nVersaoDbf, AppVersaoDbfAnt() )
   IF nVersaoDBF < 20200315
      AAdd( aStruList, { "FPNUMLAN",   "C", 6 } )
   ENDIF

   SayScroll( "JPFORPAG, verificando atualizações" )

   IF ! ValidaStru( "jpforpag", aStruList )
      MsgStop( "jpforpag não disponível!" )
      QUIT
   ENDIF
   CLOSE DATABASES
   IF nVersaoDBF >= 20200315
      RETURN NIL
   ENDIF
   SayScroll( "Atualizando JPFORPAG.DBF" )
   IF ! UseSoDbf( "jpforpag", , .T. )
      QUIT
   ENDIF
   GOTO TOP
   GrafTempo( "JPFORPAG" )
   DO WHILE ! Eof()
      GrafTempo( RecNo(), LastRec() )
      IF Val( jpforpag->idForPag ) == 0
         RecLock()
         REPLACE jpforpag->idForPag WITH jpforpag->fpNumLan
      ENDIF
      SKIP
   ENDDO
   CLOSE DATABASES
   JPFORPAGCreateDbf( 20200315 )

   RETURN NIL


Notem que a atualização chama ela mesma novamente no final.

Então: dividi a atualização em duas partes.

A primeira é essa:

Ela faz tudo que precisa no DBF.
Acrescenta campos novos (e/ou antigos), se precisar.
Faz a conversão, se precisar.
E no final chama ela mesma, pra retirar o lixo antigo da estrutura.
Ou seja, entrou aí do jeito velho, sai do jeito novo, e fim.

A segunda é com atualizações por data.

Antes:

- acrescentava campos novos em todos os arquivos
- rodava atualizações especiais por data, em todos os arquivos
- no final, apagava os campos antigos "lixo" de todos os arquivos

Problema que surgiu:
Pra gravar no MySQL, que ficava nas atualizações por data (no meio), não podia ter campo "lixo".
Isso complicou, acabei fazendo novas atualizações antes de salvar, mas tava quase perdendo o controle.

Nova atualização:
- acrescenta campos novos num arquivo
- faz o que tem que fazer com o arquivo
- assim que acabou com esse arquivo, já remove campos "lixo".
- Pode processar normalmente as atualizações por data depois, e pronto.

Essas por data, pode ser por exemplo: isso de salvar no MySQL, ou outras coisas não relacionadas a apenas estrutura, ou que dependem de mais de um arquivo ao mesmo tempo.

É o jeito... pra tudo se atualizar sozinho, a qualquer data, só assim mesmo.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 14:44
por JoséQuintas
Este é o meu esquema, com minha classe, com meus arquivos.
Pode não servir pra outros, mas pode dar uma idéia.
Mudando pra gravação dupla, por enquanto.

Tô mexendo também na estrutura, então atualização do DBF
Vai alterar o campo MCNUMLAN pra IDMDFCAB
MDF porque é o MDFE, e CAB porque é o cabeçalho do MDFE.

STATIC FUNCTION JPMDFCABCreateDbf( nVersaoDBF )

   LOCAL aStruList := { ;
      { "IDMDFCAB",  "C", 6 }, ;
      { "MCDATEMI",  "D", 8 }, ;
      { "MCCHAVE",   "C", 44 }, ;
      { "MCUFORI",   "C", 2 }, ;
      { "MCUFDES",   "C", 2 }, ;
      { "MCVEICULO", "C", 6 }, ;
      { "MCMOTORI",  "C", 6 }, ;
      { "MCSTATUS",  "C", 6 }, ;
      { "MCINFINC",  "C", 80 }, ;
      { "MCINFALT",  "C", 80 } }

   hb_Default( @nVersaoDbf, AppVersaoDbfAnt() )
   IF nVersaoDBF < 20200324
      AAdd( aStruList, { "MCNUMLAN", "C", 6 } )
   ENDIF
   SayScroll( "JPMDFCAB.DBF, Verificando atualizações" )

   IF ! ValidaStru( "jpmdfcab", aStruList )
      MsgStop( "jpmdfcab não disponível!" )
      QUIT
   ENDIF
   IF nVersaoDBF >= 20200324
      RETURN NIL
   ENDIF
   IF ! UseSoDbf( "jpmdfcab", .T. )
      QUIT
   ENDIF
   GOTO TOP
   DO WHILE ! Eof()
      IF Empty( jpmdfcab->IdMdfCab )
         RecLock()
         REPLACE jpmdfcab->idMdfCab WITH StrZero( Val( jpmdfcab->mcNumLan ), 6 )
      ENDIF
      SKIP
   ENDDO
   CLOSE DATABASES
   JPMDFCABCreateDBF( 20200324 )

   RETURN NIL


E alterei o respectivo fonte, já alterando a variável mIdMdfCab pra numérica.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 14:46
por JoséQuintas
Próximo passo, criar estrutura no MySQL
Meio compatível ainda.

STATIC FUNCTION JPMDFCABCreateSQL()

   RETURN ;
      "CREATE TABLE IF NOT EXISTS JPMDFCAB ( " + ;
      "IDMDFCAB INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, " + ;
      "MCDATEMI DATE NULL, " + ;
      "MCCHAVE VARCHAR(44) NOT NULL DEFAULT '', " + ;
      "MCUFORI VARCHAR(2) NOT NULL DEFAULT '', " + ;
      "MCUFDES VARCHAR(2) NOT NULL DEFAULT '', " + ;
      "MCVEICULO VARCHAR(6) NOT NULL DEFAULT '', " + ;
      "MCMOTORI VARCHAR(6) NOT NULL DEFAULT ''," + ;
      "MCSTATUS VARCHAR(6) NOT NULL DEFAULT ''," + ;
      "MCINFINC VARCHAR(80) NOT NULL DEFAULT ''," + ;
      "MCINFALT VARCHAR(80) NOT NULL DEFAULT ''," + ;
      "PRIMARY KEY ( IDMDFCAB ) " + ;
      ") COLLATE=latin1_swedish_ci ENGINE=InnoDB"

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 14:47
por JoséQuintas
E a transferência dos dados atuais para o MySQL
Só transferir, normal....

STATIC FUNCTION Update0324()

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF

   SayScroll( "2020/03/24 Copiando JPMDFCAB.DBF para JPMDFCAB.SQL" )
   CopyDBFToMySQL( "JPMDFCAB", .T., .F., .T. )

   RETURN NIL

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 14:50
por JoséQuintas
Alterar a parte de gravação:

   WITH OBJECT ::cnSQL
      :QUeryAdd( "MCDATEMI", mmcDatEmi )
      :QueryAdd( "MCUFORI",  mmcUfOri )
      :QueryAdd( "MCUFDES",  mmcUfDes )
      :QueryAdd( "MCVEICULO", StrZero( mIdVeiculo, 6 ) )
      :QueryAdd( "MCMOTORI",  StrZero( mIdMotori, 6 ) )
      :QueryAdd( "MCSTATUS",  mmcStatus )
      IF ::cOpc == "I"
         mIdMdfCab := ::axKeyValue[ 1 ]
         :QueryAdd( "MCINFINC", LogInfo() )
         IF mIdMdfCab == 0
            mIdMdfCab := :QueryExecuteInsert( "JPMDFCAB" )
            :QueryAdd( "IDMDFCAB", StrZero( mIdMdfCab, 6 ) )
         ELSE
            :QueryAdd( "IDMDFCAB", StrZero( mIdMdfCab, 6 ) )
            :QueryExecuteInsert( "JPMDFCAB" )
         ENDIF
         :DBFQueryExecuteInsert()
         @ ::RowIni() + 1, 22 SAY mIdMdfCab PICTURE "999999"
      ELSE
         :QueryAdd( "MCINFALT", LogInfo() )
         :DBFQueryExecuteUpdate( "JPMDFCAB" )
         :QueryExecuteUpdate( "JPMDFCAB", "IDMDFCAB = " + NumberSQL( mIdMdfCab ) )
      ENDIF
   ENDWITH


É um array com campo/conteúdo.
:DBFQueryExecuteInsert() grava no DBF
:QueryExecuteInsert() grava no MySQL
:DBFQueryExecuteUpdate() grava no DBF
:QueryExecuteUpdate() grava no MySQL

Aqui, já pegando o retorno do insert no MySQL, porque o campo é incremental, e o MySQL vai numerar.
Opcionalmente, o usuário pode escolher um número (sempre tem exceções)
Obrigatoriamente o DBF precisa estar posicionado.
Ok, trabalhando com a dupla DBF + MySQL, nada fora do comum os dois "andarem sincronizados".

A partir deste momento, DBF e MySQL tem o mesmo conteúdo.
Falta a parte de exclusão.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 15:01
por JoséQuintas
E não é que a exclusão tava errada.
Só estava excluindo manifesto, mas não os detalhes do manifesto (as notas)

METHOD Delete() CLASS JPMDFCABClass

   LOCAL mIdMdfCab

   IF AppUserLevel() > 1
      MsgExclamation( "Não autorizado a excluir lançamento" )
      RETURN NIL
   ENDIF
   mIdMdfCab := ::axKeyValue[ 1 ]
   IF ! MsgYesNo( "Confirma a exclusão?" )
      RETURN NIL
   ENDIF
   WITH OBJECT ::cnSQL
      :ExecuteCmd( "DELETE FROM JPMDFDET WHERE MDMDFNUM = " + NumberSQL( mIdMDFCab ) )
      :ExecuteCmd( "DELETE FROM JPMDFCAB WHERE IDMDFCAB = " + NumberSQL( mIdMDFCab ) )
      DO WHILE .T.
         IF ! Encontra( StrZero( mIdMdfCab, 6 ), "jpmdfdet", "mdf" )
            EXIT
         ENDIF
         jpmdfdet->( RecDelete() )
      ENDDO
      Encontra( StrZero( mIdMdfCab, 6 ), "jpmdfcab", "primary" )
      RecDelete()
   ENDWITH

   RETURN NIL


Agora sim, os dois vão ficar totalmente sincronizados, DBF e MySQL, seja inclusão, alteração ou exclusão.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 15:05
por JoséQuintas
Já que posso usar qualquer um dos dois, DBF ou MySQL, porque não já agilizar o browse?

METHOD GridSelection() CLASS JPMDFCABClass

   LOCAL oTBrowse, cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT IDMDFCAB, MCDATEMI, MCUFORI, MCOFDES, MCSTATUS, MCCHAVE" + ;
         " FROM JPMDFCAB" + ;
         " ORDER BY IDMDFCAB DESC"
      :Execute()
      oTBrowse := { ;
         { "NÚMERO",  { || Str( :Number( "IDMDFCAB" ), 6 ) } }, ;
         { "EMISSÃO", { || :Date( "MCDATEMI" ) } }, ;
         { "ORIGEM",  { || :String( "MCUFORI", 2 ) } }, ;
         { "DESTINO", { || :String( "MCUFDES", 2 ) } }, ;
         { "STATUS",  { || jpmdfStatus( :String( "MCSTATUS", 2 ) ) } }, ;
         { "CHAVE",   { || :String( "MCCHAVE", 44 ) } } }
      BrowseADO( cnSQL, oTBrowse, "", { || StrZero( :Number( "IDMDFCAB" ), 6 ) } )
   ENDWITH

   RETURN NIL


Opcionalmente, posso deixar só os últimos 1000, pra ficar mais rápido. Vejo depois que testar.
Nem sei pra que serve guardar os antigos, muito menos ficar consultando ... rs

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 15:18
por JoséQuintas
mdf.png


Estou com muita vontade de mudar pelo menos esses browses pra GUI, mas me segurando pra não mexer nisso antes de terminar tudo.

Quem usa DBF... vai dizer que o cara do SQL é louco em trazer 10.000 registros
Já quem usa SQL... vai dizer que o cara do DBF é louco kkkk

MEIO SEGUNDO.
O cara do SQL não é louco, mas o cara do DBF precisa ver para acreditar.
Manter a rede ocupada no browse pra que? se posso trazer tudo em meio segundo
Vários anos de manifesto em MEIO SEGUNDO !!!!

Pensei que ia demorar mais, baseado em outros, mas é porque este não tem relacionamento com nada, é só ele mesmo.
Mesmo assim, limitar aos 1.000 últimos.

Exagerei um pouco, não são 10.000 registros em meio segundo, são só 9.381.... rs

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 15:28
por JoséQuintas
Agora começar a eliminar o uso do DBF.
Alterar rotinas que trabalham com ele em uso, e posicionado, pra receber a ID do manifesto e usar o MySQL.

Alterar isso antes de mexer no detalhe do manifesto, pra não correr riscos.
Mas eliminar os dois de uma vez depois.

Acabei desviando minha migração, pra fazer esses dois primeiros.
Eles tem pouco uso, praticamente uma única tela, vai dar pra eliminar rápido os dois DBFs.

Diferente de trabalhar com DBF:
Trabalhar com DBF: é trabalhar SEMPRE com o DBF aberto e posicionado, os fontes seguem isso.
Trabalhar com SQL: é praticamente trabalhar com NADA aberto, sempre com uma variável de ID, pra permitir buscar informações sobre o registro atual.

Parece uma diferença simples, mas sabemos que nossos fontes sempre foram direcionados pra DBF aberto, posicionado, e usando os campos de DBF.
Pra relatórios... sem problema... sempre buscamos tudo.
Mas pra telas de digitação.... precisa muito cuidado nas alterações, porque não vai mais estar posicionado no registro.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 15:37
por JoséQuintas
mdfe.png


Ainda no MDF, vejam lá:
- Exclui
- Altera
- Emite
- Encerra
- VerPDF
- Cancela

Todas essas rotinas trabalham encima do registro atual do DBF.

Então, vou alterar uma de cada vez, pra usar o ID do manifesto como referência.
Assim que terminar, só apagar o DBF.

Mas como MDFCAB e MDFDET são ligados, vou procurar eliminar os dois de uma vez, até pra simplificar os comandos SQL.
Só questão de tempo e paciência agora.
Só tomar cuidado.

Mas.... já posso instalar nos clientes, mesmo não tendo terminado !!!!

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 16:05
por JoséQuintas
JoséQuintas escreveu:Mas.... já posso instalar nos clientes, mesmo não tendo terminado !!!!


Esta parte é a interessante:
Se eu alterar os fontes daqui pra frente, pra usar só MySQL, só preciso trocar o EXE nos clientes.
E quando terminar, só preciso apagar o DBF.

Como eu digo por aqui, NÃO tenho problema com DBF.
Então, fazer isso é tranquilo.

Quem tá com problema em DBF, seria interessante procurar resolver, antes de fazer isso.
Porque se tiver problema.... pode ser o mesmo que tinha com DBF, ou pode ser um novo... não vai saber.

Mas é mais tranquilo desse jeito, porque tem todo tempo daqui pra frente pra testar/trocar.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 16:24
por JoséQuintas
Uma parte interessante:

São 5 linhas juntas, TODAS usando o DBF só por causa do número.

      hb_MemoWrit( "NFE\MDFE-" + jpmdfcab->idMdfCab + "-02-SemAssinatura.xml",  cXml )
      hb_MemoWrit( "NFE\MDFE-" + jpmdfcab->idMdfCab + "-03-Assinado.xml",  oSefaz:cXmlDocumento )
      hb_MemoWrit( "NFE\MDFE-" + jpmdfcab->idMdfCab + "-04-Recibo.xml",    oSefaz:cXmlRecibo )
      hb_MemoWrit( "NFE\MDFE-" + jpmdfcab->idMdfCab + "-05-Protocolo.xml", oSefaz:cXmlProtocolo )
      hb_MemoWrit( "NFE\MDFE-" + jpmdfcab->idMdfCab + "-06-Retorno.xml",   oSefaz:cXmlRetorno )


Agora, mesmo que ainda não esteja pra MySQL, só resta uma linha pra resolver

     mIdMdfCab := jpmdfcab->idMdfCab
      hb_MemoWrit( "NFE\MDFE-" + midMdfCab + "-02-SemAssinatura.xml",  cXml )
      hb_MemoWrit( "NFE\MDFE-" + midMdfCab + "-03-Assinado.xml",  oSefaz:cXmlDocumento )
      hb_MemoWrit( "NFE\MDFE-" + midMdfCab + "-04-Recibo.xml",    oSefaz:cXmlRecibo )
      hb_MemoWrit( "NFE\MDFE-" + midMdfCab + "-05-Protocolo.xml", oSefaz:cXmlProtocolo )
      hb_MemoWrit( "NFE\MDFE-" + midMdfCab + "-06-Retorno.xml",   oSefaz:cXmlRetorno )


Preciso reduzir o uso NOS FONTES.
É assim que vou fazendo, reduzindo aos poucos a necessidade do DBF.
Desse jeito fica mais "sob controle".
Quanto menos coisa pra resolver, mais fácil.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 18:44
por JoséQuintas
Um erro interessante:

Esqueci que a autorização, o encerramento, o cancelamento, etc. causam alteração do manifesto.
Então... o MySQL já ficaria errado, diferente do correto que tá no DBF.

Até aí, tudo bem, corrijo os que faltaram, e posso transferir novamente o DBF que está correto.

Então... a gravação dupla serve até pra corrigir gravação errada no MySQL.
Por isso é bom que DBF funcione direito, porque é a base do MySQL.

Por enquanto o importante é:
DBF e MySQL precisam estar exatamente iguais, como prova de funcionamento.

E ir eliminando o DBF, confirmando/revisando se tudo ok mesmo, e se não vai fazer falta.

Terminado isso sim, vai ser só apagar o DBF.

A vantagem de poder instalar antecipado no cliente é que o uso vai ser mais pesado, e vai ter mais informação pra conferir/validar, pra ver se realmente tudo está ok.
Nesse caso, por exemplo, esqueci de algumas situações importantes que atualizam informação.
Mas não causa nenhum problema, porque o DBF ainda está lá.

Nota: descobri ainda mexendo nos fontes, ainda não instalei em lugar nenhum.

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 24 Mar 2020 19:19
por JoséQuintas
Mais ajustes.
Se vou alterar o campo pra numérico, é melhor garantir que só tenha número antes de enviar pro MySQL.
Assim NÃO tenho imprevistos depois.

   REPLACE ;
            jpmdfcab->idMdfCab WITH StrZero( Val( jpmdfcab->mcNumLan ), 6 ), ;
            jpmdfcab->mcMotori WITH StrZero( Val( jpmdfcab->mcMotori ), 6 ), ;
            jpmdfcab->mcVeiculo WITH StrZero( Val( jpmdfcab->mcVeiculo ), 6 )

Convertendo manifesto pra ADO-SQL

MensagemEnviado: 25 Mar 2020 09:16
por JoséQuintas
Já fiz do outro relacionado, o detalhe do manifesto.
Neste aqui criei um campo que não existia o ID
Gravar o default como RecNo()
Esse campo vai ser a chave pra atualizar MySQL

STATIC FUNCTION JPMDFDETCreateDbf( nVersaoDBF )

   LOCAL aStruList := { ;
      { "IDMDFDET",  "C", 6 }, ;
      { "MDMDFNUM",  "C", 6 }, ;
      { "MDNUMDOC",  "C", 9 }, ;
      { "MDDATEMI",  "D", 8 }, ;
      { "MDUF",      "C", 2 }, ;
      { "MDCIDADE",  "C", 21 }, ;
      { "MDCLIENTE", "C", 6 }, ;
      { "MDPESO",    "N", 6 }, ;
      { "MDVALMER",  "N", 12, 2 }, ;
      { "MDCHAVE",   "C", 44 }, ;
      { "MDINFINC",  "C", 80 }, ;
      { "MDINFALT",  "C", 80 } }

   hb_Default( @nVersaoDBF, AppVersaoDbfAnt() )
   SayScroll( "JPMDFDET.DBF, Verificando atualizações" )

   IF ! ValidaStru( "jpmdfdet", aStruList )
      MsgStop( "jpmdfdet não disponível!" )
      QUIT
   ENDIF

   IF nVersaoDBF >= 20200324
      RETURN NIL
   ENDIF
   IF ! UseSoDbf( "jpmdfdet", .T. )
      QUIT
   ENDIF
   GrafTempo( "JPMDFDET" )
   DO WHILE ! Eof()
      GrafTempo( RecNo(), LastRec() )
      RecLock()
      REPLACE ;
         jpmdfdet->mdCliente WITH StrZero( Val( jpmdfdet->mdCliente ), 6 ), ;
         jpmdfdet->IdMdfDet WITH StrZero( RecNo(), 6 ), ;
         jpmdfdet->mdmdfnum WITH StrZero( Val( jpmdfdet->mdMdfNum ), 6 ), ;
         jpmdfdet->mdNumDoc WITH StrZero( Val( jpmdfdet->mdNumDoc ), 9 )
      SKIP
   ENDDO
   CLOSE DATABASES
   JPMDFDETCreateDbf( 20200324 )

   RETURN NIL


Quase que preciso trocar no cliente agora há pouco pra resolver outro problema.
Por isso vou fazendo em partes, mesmo que não instale no cliente, porque nunca se sabe.

Aqui, como NÃO alterei o número de versão, essas rotinas são executadas SEMPRE, a cada carga do aplicativo.
Assim que ok, altero o controle de versão pra 2020.03.25, assim converte e transfere uma vez só.
Enquanto está em testes.... pode acontecer qualquer coisa de errado, então vou repetindo como teste.

E antes de instalar nos clientes, é onde rodo em todos os backups, pra ver se a conversão vai gerar algum problema por causa de algum detalhe em algum cliente que posso não ter previsto.

Pelo backup, assim como nos clientes, cada aplicativo de cada cliente está em uma versão diferente.
Vai que tem diferença de conversão conforme a versão... já vou ficar sabendo...

Meu modo de trabalho

MensagemEnviado: 25 Mar 2020 18:46
por JoséQuintas
Finalmente o Windows parece normal.
Hoje foi complicado... o dia inteiro perdido por causa do Windows.
Só agora testando a atualização nos backups.

Meu modo de trabalho

MensagemEnviado: 30 Mar 2020 09:49
por JoséQuintas
mdfe.png


Menos dois.

Agora MDFE é totalmente SQL.
Esses JPMDFCAB e JPMDFDET não são mais usados.

Os demais maiores, continuo eliminando o uso do DBF.

Meu modo de trabalho

MensagemEnviado: 02 Abr 2020 12:40
por JoséQuintas
Um relatório simples, alterado pra MySQL.

A parte de escolha de opções... sem alteração.

#include "inkey.ch"
#include "josequintas.ch"

MEMVAR nOpcOrdem, acTxtOrdem, midProdutoi, midProdutof, nOpcProDep, acTxtProDep, nOpcProduto
MEMVAR acTxtProduto, nOpcTipoEstoque, acTxtTipoEstoque, mIdProDep
MEMVAR nOpcProSec, acTxtProSec, nOpcProGru, acTxtProGru, mIdProSec, mIdProGru, nOpcPrinterType

PROCEDURE LJPITEM

   LOCAL nOpcGeral := 1, acTxtGeral := Array(8), nOpcTemp

   PRIVATE nOpcOrdem   := 1, acTxtOrdem := { "Código", "Alfabética", "Depto+Seção+Grupo", "Tributação" }
   PRIVATE nOpcProduto := 1, mIdProdutoi := 0, midProdutof := 0, acTxtProduto := { "Todos", "Intervalo" }
   PRIVATE nOpcProDep  := 1, mIdProDep := 0, acTxtProDep := { "Todos", "Específico" }
   PRIVATE nOpcProSec  := 1, mIdProSec := 0, acTxtProSec := { "Todos", "Específico" }
   PRIVATE nOpcProGru  := 1, mIdProGru := 0, acTxtProGru := { "Todos", "Específico" }
   PRIVATE nOpcTipoEstoque := 1, acTxtTipoEstoque := { "Todos", "Sim (Vendas)", "Não (Vendas)", "Inativo", "Ativo (Bens)", "Uso" }
   PRIVATE nOpcPrinterType := AppPrinterType()

   IF ! AbreArquivos( "jpempresa" )
      RETURN
   ENDIF

   WOpen( 5, 4, 7 + Len( acTxtGeral ), 45, "Opções disponíveis" )

   DO WHILE .T.

      acTxtGeral := { ;
         TxtImprime(), ;
         "Ordem.....: " + acTxtOrdem[ nOpcOrdem ], ;
         "Intervalo.: " + iif( nOpcProduto == 1, acTxtProduto[ 1 ], StrZero( midProdutoi, 6 ) + " A " + StrZero( midProdutof, 6 ) ), ;
         "Tipo Estoq: " + acTxtTipoEstoque[ nOpcTipoEstoque ], ;
         "Depto.....: " + Iif( nOpcProDep == 1, acTxtProDep[ nOpcProDep ], Str( mIdProDep, 6 ) ), ;
         "Seção.....: " + iif( nOpcProSec == 1, acTxtProSec[ nOpcProSec ], Str( mIdProSec, 6 ) ), ;
         "Grupo.....: " + iif( nOpcProGru == 1, acTxtProGru[ nOpcProGru ], Str( mIdProGru, 6 ) ), ;
         "Saída.....: " + TxtSaida()[ nOpcPrinterType ] }

      FazAchoice( 7, 5, 6 + Len( acTxtGeral ), 44, acTxtGeral, @nOpcGeral )

      nOpcTemp := 1
      DO CASE
      CASE LastKey() == K_ESC
         EXIT

      CASE nOpcGeral == nOpcTemp++
         IF ConfirmaImpressao()
            imprime()
         ENDIF

      CASE nOpcGeral == nOpcTemp++
         WAchoice( nOpcGeral + 6, 25, acTxtOrdem, @nOpcOrdem, "Ordem" )

      CASE nOpcGeral == nOpcTemp++
         JPPRODUTOClass():Intervalo( nOpcGeral + 6, 25, @nOpcProduto, @midProdutoi, @midProdutof )

      CASE nOpcGeral == nOpcTemp++
         WAchoice( nOpcGeral+6, 25, acTxtTipoEstoque, @nOpcTipoEstoque, "Tipo de Estoque" )

      CASE nOpcGeral == nOpcTemp++
         AUXPRODEPClass():Intervalo( nOpcGeral + 6, 25, @nOpcProDep, @mIdProDep )

      CASE nOpcGeral == nOpcTemp++
         AUXPROSECClass():Intervalo( nOpcGeral + 6, 25, @nOpcProSec, @mIdProSec )

      CASE nOpcGeral == nOpcTemp++
         AUXPROGRUClass():Intervalo( nOpcGeral + 6, 25, @nOpcProGru, @mIdProGru )

      CASE nOpcGeral == nOpcTemp
         WAchoice( nOpcGeral+6, 25, TxtSaida(), @nOpcPrinterType, "Saída" )
         AppPrinterType( nOpcPrinterType )

      ENDCASE
   ENDDO
   WClose()

   RETURN


A configuração do relatório, cabeçalho, etc. relativamente sem alterações

STATIC FUNCTION imprime()

   LOCAL oPDF, mProDep, mProSec, mProGru, mTripro, nKey
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey = 0

   oPDF:acHeader := {"","",""}
   oPDF:acHeader[ 1 ] = "LISTAGEM DO CADASTRO DE PRODUTOS"
   oPDF:acHeader[ 3 ] = Space(15) + "-PRODUTO/" + Pad( "DESCRICAO", 65, "-" ) + "  UN     PESO_LIQ     PESO_BRU     MEDIDAS"
   oPDF:acHeader[ 2 ] := "Ordem:" + acTxtOrdem[ nOpcOrdem ]
   IF nOpcProduto != 1
      oPDF:acHeader[ 2 ] := oPDF:acHeader[ 2 ] + ", de: "   + StrZero( midProdutoi, 6 ) + " ate: " + StrZero( midProdutof, 6 )
   ENDIF
   oPDF:acHeader[ 2 ] += ", Est:"+iif( nOpcTipoEstoque == 1, "Todos", acTxtTipoEstoque[ nOpcTipoEstoque ] )
   IF nOpcProDep == 2
      oPDF:acHeader[ 2 ] += ", Depto " + NumberSQL( mIdProDep )
   ENDIF
   IF nOpcProSec == 2
      oPDF:acHeader[ 2 ] += ", Secao " + NumberSQL( mIdProSec )
   ENDIF
   IF nOpcProGru == 2
      oPDF:acHeader[ 2 ] += ", Grupo " + NumberSQL( mIdProGru )
   ENDIF


A preparação do que vai imprimir, com certeza mudou.


   WITH OBJECT cnSQL
      :cSQL := "SELECT" + ;
            " IDPRODUTO, IEPRODEP, IEPROSEC, IEPROGRU, IETIPO, IETRIPRO, IEDESCRI," + ;
            " IELARGURA, IEALTURA, IEPROFUND, IEUNID, IEPESLIQ, IEPESBRU," + ;
            " PRODEP.AXDESCRI AS PRODEPDESCRI," + ;
            " PROSEC.AXDESCRI AS PROSECDESCRI," + ;
            " PROGRU.AXDESCRI AS PROGRUDESCRI," + ;
            " TRIPRO.AXDESCRI AS TRIPRODESCRI" + ;
         " FROM JPITEM" + ;
         " LEFT JOIN JPAUXILIAR AS PRODEP ON PRODEP.AXTABELA = " + StringSQL( AUX_PRODEP ) + ;
            " AND PRODEP.AXCODIGO = JPITEM.IEPRODEP" + ;
         " LEFT JOIN JPAUXILIAR AS PROSEC ON PROSEC.AXTABELA = " + StringSQL( AUX_PROSEC ) + ;
            " AND PROSEC.AXCODIGO = JPITEM.IEPROSEC" + ;
         " LEFT JOIN JPAUXILIAR AS PROGRU ON PROGRU.AXTABELA = " + StringSQL( AUX_PROGRU ) + ;
            " AND PROGRU.AXCODIGO = JPITEM.IEPROGRU" + ;
         " LEFT JOIN JPAUXILIAR AS TRIPRO ON TRIPRO.AXTABELA = " + StringSQL( AUX_TRIPRO ) + ;
            " AND TRIPRO.AXCODIGO = JPITEM.IETRIPRO" + ;
         " WHERE 1 = 1"
      IF nOpcProduto == 2
         :cSQL += " AND IDPRODUTO BETWEEN " + NumberSQL( mIdProdutoi ) + " AND " + NumberSql( mIdProdutof )
      ENDIF
      IF nOpcProDep == 2
         :cSQL += " AND IEPRODEP = " + NumberSQL( mIdProDep )
      ENDIF
      IF nOpcProSec == 2
         :cSQL += " AND IEPROSEC = " + NumberSQL( mIdProSec )
      ENDIF
      IF nOpcProGru == 2
         :cSQL += " AND IEPROGRU = " + NumberSQL( mIdProGru )
      ENDIF
      IF nOpcTipoEstoque != 1
         :cSQL += " AND IETIPO = " + StringSQL( Substr( "XSNIAU", nOpcTipoEstoque, 1 ) )
      ENDIF
      DO CASE
      CASE nOpcOrdem == 1; :cSQL += " ORDER BY IDPRODUTO"
      CASE nOpcOrdem == 2; :cSQL += " ORDER BY IEDESCRI"
      CASE nOpcOrdem == 3; :cSQL += " ORDER BY PRODEPDESCRI, PROSECDESCRI, PROGRUDESCRI, IEDESCRI"
      CASE nOpcOrdem == 4; :cSQL += " ORDER BY IETRIPRO"
      ENDCASE
      :Execute()


E a impressão, agora pegando do MYSQL

      mProDep := 0
      mProSec := 0
      mProGru := 0
      mTriPro := 0
      DO WHILE nKey != K_ESC .AND. ! :Eof()
         nKey = Inkey()
         oPDF:MaxRowTest()
         IF nOpcOrdem == 3 .AND. :Number( "IEPRODEP" ) != mProDep
            oPDF:DrawText( oPDF:nRow + 1, 0, "D:" + Str( :Number( "IEPRODEP" ), 6 ) + " - " + :String( "PRODEPDESCRI" ) )
            mProDep := :Number( "IEPRODEP" )
            mProSec := 0
            mProGru := 0
            oPDF:nRow += 3
            oPDF:MaxRowTest()
         ENDIF
         IF nOpcOrdem == 3 .AND. :Number( "IEPROSEC" ) != mProSec
            oPDF:DrawText( oPDF:nRow+1, 5, "S:" + Str( :Number( "IEPROSEC" ), 6 ) + " - " + :String( "PROSECDESCRI" ) )
            mProSec := :Number( "IEPROSEC" )
            mProGru := 0
            oPDF:nRow += 2
            oPDF:MaxRowTest()
         ENDIF
         IF nOpcOrdem == 3 .AND. :Number( "IEPROGRU" ) != mProGru
            oPDF:DrawText( oPDF:nRow+1, 10, "G:" + Str( :Number( "IEPROGRU" ), 6 ) + " - " + :String( "PROGRUDESCRI" ) )
            mProGru := :Number( "IEPROGRU" )
            oPDF:nRow += 2
            oPDF:MaxRowTest()
         ENDIF
         IF nOpcOrdem == 4 .AND. :Number( "IETRIPRO" ) != mTriPro
            oPDF:DrawText( oPDF:nRow+1, 15, "T:" + Str( :Number( "IETRIPRO" ), 6 ) + " - " + :String( "TRIPRODESCRI" ) )
            mTriPro := :Number( "IETRIPRO" )
            oPDF:nRow += 3
            oPDF:MaxRowTest()
         ENDIF
         oPDF:DrawText( oPDF:nRow, 20, Str( :Number( "IDPRODUTO" ), 6 ) )
         oPDF:DrawText( oPDF:nRow, oPDF:nCol + 2, :String( "IEDESCRI", 60 ) )
         oPDF:DrawText( oPDF:nRow, oPDF:nCol + 2, :String( "IEUNID", 6 ) )
         oPDF:DrawText( oPDF:nRow, oPDF:nCol + 2, :Number( "IEPESLIQ" ), PicVal( 9, 3 ) )
         oPDF:DrawText( oPDF:nRow, oPDF:nCol + 2, :Number( "IEPESBRU" ), PicVal( 9, 3 ) )
         oPDF:DrawText( oPDF:nRow, oPDF:nCol + 2, Ltrim( Str( :Number( "IEALTURA" ) ) ) + "x" + ;
            Ltrim( Str( :Number( "IELARGURA" ) ) ) + "x" + Ltrim( Str( :Number( "IEPROFUND" ) ) ) )
         oPDF:nRow += 1
         :MoveNext()
      ENDDO
      :CloseRecordset()
   ENDWITH
   oPDF:End()

   RETURN NIL


relatorio.png

Meu modo de trabalho

MensagemEnviado: 02 Abr 2020 13:01
por JoséQuintas
Comentários sobre o relatório anterior:

Em DBF, abria produtos (JPITEM) e JPAUXILIAR pras tabelas auxiliares.
Acho que nem precisa dizer, mas precisava posicionar departamento, seção, grupo, tributação. (SEEK com o índice correto).
Também precisava indexar, conforme a ordem.
E mais os IFs. conforme a seleção, para os filtros.

Em SQL... os IFs continuam, mas pra montagem do comando SQL.
E o comando SQL substituiu tudo.

Nenhum arquivo aberto, nenhuma preocupação com índice, apenas um comando SQL que pede tudo e vém pronto.
É praticamente um comando SQL, e imprimir.
Dá até pra pensar em expandir as opções, porque vai ser mexer no comando SQL, e mexer na impressão.

No final, relatório de cadastro, ou um mais "sofisticado", podem ficar relativamente iguais.
Lógico, sempre tem exceções, mas na regra geral seria isso.

Acaba se tornando relativamente mais fácil fazer manutenção em um relatório. (ou pra apresentação em tela com um browse).

Está aí, não tinha pensado nisso, mas.....
Agora poderia dar a opção de fazer um browse ao invés do relatório, usando o resultado do comando SQL !!!
Ou pra gerar em Excel !!!

Vixe... ainda migrando e novas possibilidades aparecendo....

Pois é... chamar a atenção de novo pra isso: só trocando DBF pra MySQL, e facilidades começando a aparecer.
De repente, qualquer relatório pra Excel ou outra coisa...

Meu modo de trabalho

MensagemEnviado: 04 Abr 2020 01:00
por JoséQuintas
Um outro fonte alterado.
Apesar de relativamente simples, é mais interessante.

FUNCTION MsgSemRegra( cModulo )

   LOCAL cTexto := ""

   hb_Default( @cModulo, m_Prog )

   cTexto += "REGRA DE TRIBUTAÇÃO:" + hb_eol()
   cTexto += hb_eol()
   cTexto += "Transação..........: "
   IF Empty( jppedido->pdTransa )
      cTexto += MSG_DESCONHECIDO
   ELSE
      cTexto += jppedido->pdTransa + " " + DescricaoJPTRANSA( jppedido->pdTransa )
   ENDIF
   cTexto += hb_eol() + hb_eol()
   cTexto += "Tributação UF......: "
   IF Empty( jpuf->ufTriUf )
      cTexto += MSG_DESCONHECIDO
   ELSE
      cTexto += jpuf->ufTriUf + " " + Trim( AUXTRIUFClass():Descricao( jpuf->ufTriUf ) )
   ENDIF
   cTexto += hb_eol()
   cTexto += Space(10) + jpcadastro->cdUf
   cTexto += hb_eol() + hb_eol()
   cTexto += "Tributação Cadastro: "
   IF Empty( jpcadastro->cdTriCad )
      cTexto += MSG_DESCONHECIDO
   ELSE
      cTexto += jpcadastro->cdTriCad + " " + Trim( AUXTRICADClass():Descricao( jpcadastro->cdTriCad ) )
   ENDIF
   cTexto += hb_eol()
   cTexto += Space(10) + jpcadastro->idCadastro + " " + jpcadastro->cdNome
   cTexto += hb_eol() + hb_eol()
   Encontra( jpcadastro->cdUf, "jpuf", "numlan" )
   cTexto += "Tributação Produto.: "
   IF Empty( jpitem->ieTriPro )
      cTexto += MSG_DESCONHECIDO
   ELSE
      cTexto += jpitem->ieTriPro + " " + Trim( AUXTRIPROClass():Descricao( jpitem->ieTriPro ) )
   ENDIF
   cTexto += hb_eol()
   cTexto += Space(10) + jpitem->idProduto + " " + jpitem->ieDescri
   cTexto += hb_eol() + hb_eol()
   IF Empty( jpitped->ipPisCst )
      cTexto += "*** produto sem CST PIS ***" + hb_eol()
   ENDIF
   cTexto += "Posição:" + cModulo + hb_eol()
   cTexto += hb_eol()
   cTexto += "Não foi encontrada uma regra de tributação para essa combinação de informações" + hb_eol()
   cTexto += "Pode ser alguma informação errada, ou falta cadastrar uma regra de tributação com esta combinação" + hb_eol()
   cTexto += "Se for na confirmação do pedido, pode faltar passar na digitação de produto pra atualizar pedido" + hb_eol()

   MsgWarning( cTexto )

   RETURN NIL


O que ele usa:
- JPITPED - produtos dos pedidos
- JPPEDIDO - pedidos
- JPCADASTRO - cadastros
- JPTRANSA - transação
- JPITEM - produtos
- JPUF - pra UFs
- JPAUXILIAR - para descrição de tributação de UF
- JPAUXILIAR - pra descrição de tributação de cadastro
- JPAUXILIAR - pra descrição de tributação de produto
Por isso é interessante: são 7 DBFs, mas equivalente a 9 DBFs.

Como ficou em SQL: o de sempre, o comando e o uso do resultado.

FUNCTION MsgSemRegra( mIdItPed, cModulo )

   LOCAL cTexto := ""
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   hb_Default( @cModulo, m_Prog )

   WITH OBJECT cnSQL
      :cSQL := "SELECT" + ;
         " JPPEDIDO.PDTRANSA, JPTRANSA.TRDESCRI," + ;
         " JPPEDIDO.PDCADASTRO, JPCADASTRO.CDNOME, JPCADASTRO.CDTRICAD, TRICAD.AXDESCRI AS TRICADDESCRI," + ;
         " JPCADASTRO.CDUF, JPUF.UFTRIUF, TRIUF.AXDESCRI AS TRIUFDESCRI," + ;
         " JPITPED.IPPRODUTO, JPITEM.IEDESCRI, JPITEM.IETRIPRO, TRIPRO.AXDESCRI AS TRIPRODESCRI" + ;
         " FROM JPITPED" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPITPED.IPPEDIDO" + ;
         " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
         " LEFT JOIN JPUF ON JPUF.UFUF = JPCADASTRO.CDUF" + ;
         " LEFT JOIN JPAUXILIAR AS TRICAD ON TRICAD.AXTABELA = " + StringSQL( AUX_TRICAD ) + ;
            " AND TRICAD.AXCODIGO = JPCADASTRO.CDTRICAD" + ;
         " LEFT JOIN JPAUXILIAR AS TRIPRO ON TRIPRO.AXTABELA = " + StringSQL( AUX_TRIPRO ) + ;
            " AND TRIPRO.AXCODIGO = JPITEM.IETRIPRO" + ;
         " LEFT JOIN JPAUXILIAR AS TRIUF ON TRIUF.AXTABELA = " + StringSQL( AUX_TRIUF ) + ;
            " AND TRIUF.AXCODIGO = JPUF.UFTRIUF" + ;
         " WHERE JPITPED.IDITPED = " + NumberSQL( midItPed )
      :Execute()
      cTexto += "REGRA DE TRIBUTAÇÃO:" + hb_eol()
      cTexto += hb_eol()
      cTexto += "Transação..........: "
      IF :Number( "PDTRANSA" ) == 0
         cTexto += MSG_DESCONHECIDO
      ELSE
         cTexto += Str( :Number( "PDTRANSA" ), 6 ) + " " + :String( "TRDESCRI" )
      ENDIF
      cTexto += hb_eol() + hb_eol()
      cTexto += "Tributação UF......: "
      IF :Number( "UFTRIUF" ) = 0
         cTexto += MSG_DESCONHECIDO
      ELSE
         cTexto += Str( :Number( "UFTRIUF" ), 6 ) + " " + :String( "TRIUFDESCRI" )
      ENDIF
      cTexto += hb_eol()
      cTexto += Space(10) + :String( "CDUF", 2 )
      cTexto += hb_eol() + hb_eol()
      cTexto += "Tributação Cadastro: "
      IF :Number( "CDTRICAD" ) == 0
         cTexto += MSG_DESCONHECIDO
      ELSE
         cTexto += Str( :Number( "CDTRICAD" ), 6 )+ " " + :String( "TRICADDESCRI" )
      ENDIF
      cTexto += hb_eol()
      cTexto += Space(10) + Str( :Number( "PDCADASTRO" ), 6 ) + " " + :String( "CDNOME" )
      cTexto += hb_eol() + hb_eol()
      cTexto += "Tributação Produto.: "
      IF :Number( "IETRIPRO" ) == 0
         cTexto += MSG_DESCONHECIDO
      ELSE
         cTexto += Str( :Number( "IETRIPRO" ), 6 ) + " " + :String( "TRIPRODESCRI" )
      ENDIF
      cTexto += hb_eol()
      cTexto += Space(10) + Str( :Number( "IPPRODUTO" ), 6 ) + " " + :String( "IEDESCRI" )
      cTexto += hb_eol() + hb_eol()
      cTexto += "Posição:" + cModulo + hb_eol()
      cTexto += hb_eol()
      cTexto += "Não foi encontrada uma regra de tributação para essa combinação de informações" + hb_eol()
      cTexto += "Pode ser alguma informação errada, ou falta cadastrar uma regra de tributação com esta combinação" + hb_eol()
      cTexto += "Se for na confirmação do pedido, pode faltar passar na digitação de produto pra atualizar pedido" + hb_eol()
      :CloseRecordset()
   ENDWITH
   MsgWarning( cTexto )

   RETURN NIL


Parece complicado, mas é o de sempre.

SELECT com a seleção dos campos que interessam

" JPPEDIDO.PDTRANSA, JPTRANSA.TRDESCRI," + ;
" JPPEDIDO.PDCADASTRO, JPCADASTRO.CDNOME, JPCADASTRO.CDTRICAD, TRICAD.AXDESCRI AS TRICADDESCRI," + ;
" JPCADASTRO.CDUF, JPUF.UFTRIUF, TRIUF.AXDESCRI AS TRIUFDESCRI," + ;
" JPITPED.IPPRODUTO, JPITEM.IEDESCRI, JPITEM.IETRIPRO, TRIPRO.AXDESCRI AS TRIPRODESCRI" + ;

se quiser comparar com DBF, seria só trocar o ponto pelo ->
por exemplo, jpcadastro.cdnome, equivale a jpcadastro->cdnome, seria o campo CDNOME que está na tabela JPCADASTRO

FROM com a indicação de onde vém

FROM JPITPED - vém dos produtos dos pedidos (itens do pedido)

LEFT JOIN é o relacionamento, pra "ensinar" o MySQL qual a ligação/relacionamento entre os arquivos

" LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPITPED.IPPEDIDO" + ;
vai usar o campo jpitped->ipPedido pra procurar nos pedidos o jppedido->idPedido com mesmo número

WHERE pra indicar o filtro

" WHERE JPITPED.IDITPED = " + NumberSQL( midItPed )
Vai procurar o lançamento de produto para um pedido, aonde a identificação desse lançamento é esse número

Em termos práticos: a partir do lançamento no pedido, vai pesquisar todas as informações relacionadas a ele. (as que interessam neste fonte)
E isso é feito usando um único comando SQL.
Então, o comando diz qual a informação, e como o servidor vai fazer pra pegar a informação.

O comando grande não significa que vai demorar mais.
Por último: o retorno, NESSE CASO, é apenas um registro com todo conteúdo indicado.
A partir daí, só usar o registro.

Nota:
Só lembrando que eu decidi fazer desse jeito, não significa que é a única forma de fazer isso.
E esses relacionamentos poderiam já ter sido definidos previamente na própria base de dados, e nem precisar colocar no comando.
É só uma das muitas possibilidades de se fazer isso.
Ainda estou saindo dos DBFs... vou fazendo do jeito que conheço...

Meu modo de trabalho

MensagemEnviado: 05 Abr 2020 18:27
por JoséQuintas
Este tive que apelar pra algum método de debug, porque estava falhando.
Agora resolvido.
Acho que o nome diz tudo.

METHOD CalculaImpostosProduto( mIdItPed ) CLASS SubPedidoClass

   LOCAL mipIcmBas, mipIcmAli, mipIcmRed, mipIcmVal, mipCfop, mipTribut, mipIpiCst, mipIpiEnq
   LOCAL mipFcpAli, mipFcpVal, mipIcmCst, mipPisCst, mipPisEnq, mipCofCst, mipCofEnq
   LOCAL mipIpiBas, mipIpiAli, mipIpiVal, mipImpAli, mipImpVal
   LOCAL mipIssBas, mipIssAli, mipIssVal
   LOCAL mipPisBas, mipPisAli, mipPisVal
   LOCAL mipCofBas, mipCofAli, mipCofVal
   LOCAL mipSubBas, mipSubIva, mipSubAli, mipSubRed, mipSubVal
   LOCAL mipValNot, mipIIBas, mipIIVal
   LOCAL mipIcsBas, mipIcsVal, mipIcsAli, mipIpiIcm
   LOCAL mipValPro, mipValFre, mipValSeg, mipValOut, nCont, mipLeis, mTmpLei, mipValExt
   LOCAL mipValDes, mipIIAli, mipDifAlii, mipDifAliu, mipDifAlif, mipDifVali, mipDifValf, mipDifBas, mipDifCal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF mIdItPed == 0
      RETURN NIL
   ENDIF
   WITH OBJECT cnSQL
      :cSQL := ;
         " SELECT JPITPED.*," + ;
         " JPPEDIDO.PDTRANSA," + ;
         " JPCADASTRO.CDUFENT, JPCADASTRO.CDTRICAD," + ;
         " JPITEM.IETRIPRO," + ;
         " JPUF.UFTRIUF," + ;
         " JPTRANSA.TRREACAO," + ;
         " JPIBPT.IBNACALI," + ;
         " JPIBPT.IBIMPALI," + ;
         " JPIMPOSTO.*" + ;
         " FROM JPITPED" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPITPED.IPPEDIDO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
         " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
         " LEFT JOIN JPUF ON JPUF.UFUF = JPCADASTRO.CDUFENT" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
         " LEFT JOIN JPIMPOSTO" + ;
         " ON  JPIMPOSTO.IMTRANSA = JPPEDIDO.PDTRANSA" + ;
         " AND JPIMPOSTO.IMTRIUF = JPUF.UFTRIUF" + ;
         " AND JPIMPOSTO.IMTRICAD = JPCADASTRO.CDTRICAD" + ;
         " AND JPIMPOSTO.IMTRIPRO = JPITEM.IETRIPRO" + ;
         " LEFT JOIN JPIBPT ON JPIBPT.IBCODIGO = JPITEM.IENCM" + ;
         " WHERE IDITPED = " + NumberSQL( midItPed )
      :Execute()
      MsgExclamation( ;
         "pdtransa: " + :String( "PDTRANSA" ) + hb_Eol() + ;
         "triuf: " + :String( "UFTRIUF" ) + hb_Eol() + ;
         "tricad:" + :String( "CDTRICAD" ) + hb_Eol() + ;
         "tripro:" + :String( "IETRIPRO" ) + hb_Eol() + ;
         "tribut:" + :String( "IDIMPOSTO" ) + hb_Eol() + ;
         "ok" )
...


É o uso do anterior, regra de tributação, e algo mais.

Por enquanto me limitando a converter de DBF pra MySQL.
Provavelmente não precisaria fazer um produto por vez, mas preciso alterar com cuidado, e por enquanto ainda tem a gravação em DBF compatível.
Depois.... quando não precisar mais do DBF, e apagar o DBF, aí vou repassar pra fazer mais coisas por vez.

Meu modo de trabalho

MensagemEnviado: 06 Abr 2020 15:34
por JoséQuintas
Só a título de curiosidade:

   IF ! Encontra( jppedido->pdCadastro, "jpcadastro", "numlan" ) .AND. jppedido->pdConf != "S"
      MsgWarning( "INVÁLIDO! Cliente sem cadastro!" )
      RETURN .F.
   ENDIF
   IF ! jpcadastro->cdSitFaz $ " 1"
      MsgStop( "INVÁLIDO! Irregularidade do cadastro na SEFAZ!" )
      RETURN .F.
   ENDIF


Seria fácil alterar o primeiro IF pra ADO.
Mas o segundo IF, e provavelmente mais coisas no fonte, dependem do posicionamento do cliente, que é feito no primeiro IF.
É por isso que as alterações precisam ser feitas devagar.
Lógico... um SELECT detalhado no MySQL pode acabar atendendo o fonte inteiro.

Por isso, às vezes é melhor reorganizar o fonte primeiro, pra facilitar o que vém depois.
De repente até ajeitar o fonte para pegar tudo do dbf logo no começo, e depois só substituir para um comando SQL.
Cada caso é um caso.... cada fonte é um fonte, que pode ter solução igual ou diferente do que já foi feito antes.

Lembrando: no meu caso, tem tudo no DBF ou no MySQL, então estou eliminando a necessidade de pegar do DBF - mantendo posicionamento do DBF apenas aonde grava - enquanto precisa dele.
Devagar tá indo...

Meu modo de trabalho

MensagemEnviado: 06 Abr 2020 16:42
por JoséQuintas
Também a título de curiosidade....

Tive que trocar a versão no cliente, durante as alterações....
Ok, sem problemas, apenas confirmei se era melhor cancelar o que estava pela metade, ou terminar e instalar.

E surgiu um problema: o produto estava no MYSQL mas NÃO no DBF.

Cadastrei o código no DBF, entrei na alteração do cadastro, e fui até o final.
Ao terminar, atualizou também esse código equivalente no DBF.
É... se não fosse o DBF não teria problema kkkk
Mas é lógico, NÃO é problema em usar DBF, é porque cometi algum erro, e não gravou no DBF.

Esse problema aconteceu com versão anterior, que estava em uso.
Até revisei na versão atual, mas parece tudo ok.
Por enquanto é aguardar se vai acontecer de novo.

Pois é... trata-se de uma alteração geral, alterar devagar e com cuidado não significa que não acontecem problemas...

Meu modo de trabalho

MensagemEnviado: 20 Abr 2020 18:37
por JoséQuintas
NÃO parei com as alterações.
É que agora é tudo com cuidado.
Pedidos, financeiro, boletos, bancos, faturamento, etc. essas coisas.
Usando ao máximo números nos campos chave, e deixando de usar DBF como referência principal.

Meu modo de trabalho

MensagemEnviado: 27 Abr 2020 19:30
por JoséQuintas
Mais um relatório simples.
É só um total por fornecedor, do contas a pagar.
Vou começar a alterar ainda, aqui é o fonte atual usando DBF, que está rodando no cliente (se é que algum usa).

/*
PFINANRELMAIFOR - RELATORIO DE MAIORES FORNECEDORES
1997.04 José Quintas
*/

#include "inkey.ch"

MEMVAR m_DeAte, m_Datai, m_Dataf, m_Ordem, m_DbfTmp, m_NtxTmp, m_TxtOrdem, m_TxtDeAte, nOpcPrinterType

PROCEDURE pFinanRelMaiFor

   LOCAL  nOpcGeral := 1, m_TxtMenu, m_Temp, mDefault, m_Conf := 2, m_Stru

   PRIVATE m_txtordem := { "Valor (Decresc)", "Fornecedor" }
   PRIVATE m_deate    := 1
   PRIVATE m_datai    := Ctod( "" )
   PRIVATE m_dataf    := Ctod( "" )
   PRIVATE m_txtdeate := { "Todas", "Intervalo" }

   nOpcPrinterType := AppPrinterType()

   IF ! AbreArquivos( "jpcadastro", "jpconfi", "jpempresa", "jpfinan", "jpsenha", "jptabel" )
      RETURN
   ENDIF
   SELECT jpfinan

   m_DbfTmp := MyTempFile( "DBF" )
   m_NtxTmp := MyTempFile( "CDX" )

   m_Stru := { ;
      { "FORNEC",  "C",  6, 0 }, ;
      { "NOME",    "C", 30, 0 }, ;
      { "XXCNPJ",  "C", 18, 0 }, ;
      { "VALOR",   "N", 17, 2 } }
   SELECT 0
   dbCreate( m_DbfTmp, m_Stru )
   USE (m_DbfTmp) EXCLUSIVE NEW ALIAS temp

   mDefault  := LeCnfRel()
   m_ordem   := iif( mDefault[ 1 ] > 2, 1, mDefault[ 1 ] )
   m_txtmenu := Array(5)

   WOpen( 5, 4, 7 + Len( m_txtmenu ), 45, "Opções disponíveis" )

   DO WHILE .T.

      m_txtmenu := { ;
         TxtImprime(), ;
         TxtSalva(), ;
         "Ordem.....: " + m_txtordem[ m_ordem ], ;
         "Datas.....: " + iif( m_DeAte == 1, m_txtdeate[ 1 ], ;
         Dtoc( m_datai ) + " a " + Dtoc( m_dataf ) ), ;
         "Saída.....: " + TxtSaida()[ nOpcPrinterType ] }

      FazAchoice( 7, 5, 6 + Len( m_txtmenu ), 44, m_txtmenu, @nOpcGeral )

      m_Temp := 1
      DO CASE
      CASE LastKey() == K_ESC
         EXIT

      CASE nOpcGeral == m_Temp++
         IF ConfirmaImpressao()
            imprime()
         ENDIF

      CASE nOpcGeral == m_Temp++
         m_conf = 2
         WAchoice( nOpcGeral+6, 25, TxtConf(), @m_conf, TxtSalva() )
         IF m_conf == 1 .AND. LastKey() != K_ESC
            GravaCnfRel( { m_Ordem } )
         ENDIF

      CASE nOpcGeral == m_Temp++
         WAchoice( nOpcGeral + 6, 25, m_txtordem, @m_ordem, "Ordem de Impressão" )

      CASE nOpcGeral == m_Temp++
         DataIntervalo( nOpcGeral + 6, 25, @m_DeAte, @m_Datai, @m_Dataf )

      CASE nOpcGeral == m_Temp
         WAchoice( nOpcGeral + 6, 25, TxtSaida(), @nOpcPrinterType, "Saída" )
         AppPrinterType( nOpcPrinterType )

      ENDCASE
   ENDDO
   WClose()
   CLOSE DATABASES
   fErase(m_DbfTmp)
   fErase(m_NtxTmp)

   RETURN

STATIC FUNCTION imprime()

   LOCAL oPDF, nKey, m_Soma, m_Total

   SELECT Temp
   ZAP
   INDEX ON temp->Fornec TO ( m_NtxTmp )

   SELECT jpfinan
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      IF jpfinan->fiTipLan != "2"
         SKIP
         LOOP
      ENDIF
      IF m_DeAte==2
         IF jpfinan->fiDatEmi < m_Datai .OR. jpfinan->fiDatEmi > m_Dataf
            SKIP
            LOOP
         ENDIF
      ENDIF
      encontra( jpfinan->fiCadastro, "jpcadastro", "numlan" )
      IF ! encontra( jpfinan->fiCadastro, "temp" )
         SELECT temp
         RecAppend()
         REPLACE ;
            temp->Fornec WITH jpfinan->fiCadastro, ;
            Temp->xxCnpj WITH jpcadastro->cdCnpj, ;
            Temp->Nome WITH jpcadastro->cdNome
         RecUnlock()
      ENDIF
      SELECT temp
      RecLock()
      REPLACE Temp->Valor WITH temp->Valor + jpfinan->fiValor
      RecUnlock()
      SELECT jpfinan
      SKIP
   ENDDO

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()

   nKey = 0

   oPDF:acHeader := { "", "", "" }
   oPDF:acHeader[ 1 ] := "CONTAS A PAGAR"
   oPDF:acHeader[ 2 ] := "MAIORES FORNECEDORES - Ordem: " + m_txtordem[ m_ordem ]
   IF m_deate == 2
      oPDF:acHeader[ 2 ] += " - Data: " + Dtoc( m_datai ) + " a " + Dtoc( m_dataf )
   ENDIF
   oPDF:acHeader[ 3 ] := "-----------NOME DO FORNECEDOR-----------  -------C.G.C.-------  -----FATURAMENTO----  %TOTAL"

   SELECT Temp
   IF m_Ordem == 1
      INDEX ON -temp->valor TO ( m_NtxTmp )
   ELSE
      INDEX ON temp->nome TO ( m_NtxTmp )
   ENDIF
   SUM temp->Valor TO m_Total
   m_Soma := 0
   GOTO TOP
   DO WHILE nKey != K_ESC .AND. ! Eof()
      grafproc()
      nKey = Inkey()
      oPDF:MaxRowTest()
      oPDF:DrawText( oPDF:nRow, 0, Temp->Nome )
      oPDF:DrawText( oPDF:nRow, 42, Temp->xxCnpj )
      oPDF:DrawText( oPDF:nRow, 64, Temp->Valor, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow, 86, ( Temp->Valor * 100 / m_Total ), "@E 999.99" )
      m_Soma += temp->Valor
      oPDF:nRow  += 1
      SKIP
   ENDDO
   IF oPDF:nPageNumber != 0
      oPDF:MaxRowTest()
      oPDF:DrawText( oPDF:nRow, 0, "TOTAL IMPRESSO....:" )
      oPDF:DrawText( oPDF:nRow, 64, m_Soma, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow, 86, ( m_Soma * 100 / m_Total ), "@E 999.99" )
      oPDF:nRow += 1
      oPDF:MaxRowTest()
   ENDIF
   oPDF:End()

   RETURN NIL

Meu modo de trabalho

MensagemEnviado: 27 Abr 2020 19:48
por JoséQuintas
pronto.

/*
PFINANRELMAIFOR - RELATORIO DE MAIORES FORNECEDORES
1997.04 José Quintas
*/

#include "inkey.ch"

MEMVAR nOpcData, m_Datai, m_Dataf, nOpcOrdem, acTxtOrdem, acTxtData, nOpcPrinterType

PROCEDURE pFinanRelMaiFor

   LOCAL  nOpcGeral := 1, acTxtGeral, m_Temp, mDefault, m_Conf := 2

   PRIVATE acTxtOrdem := { "Valor (Decresc)", "Fornecedor" }
   PRIVATE nOpcData    := 1
   PRIVATE m_datai    := Ctod( "" )
   PRIVATE m_dataf    := Ctod( "" )
   PRIVATE acTxtData := { "Todas", "Intervalo" }

   nOpcPrinterType := AppPrinterType()

   IF ! AbreArquivos( "jpcadastro", "jpconfi", "jpempresa", "jpfinan", "jpsenha", "jptabel" )
      RETURN
   ENDIF
   SELECT jpfinan

   mDefault  := LeCnfRel()
   nOpcOrdem   := iif( mDefault[ 1 ] > 2, 1, mDefault[ 1 ] )
   acTxtGeral := Array(5)

   WOpen( 5, 4, 7 + Len( acTxtGeral ), 45, "Opções disponíveis" )

   DO WHILE .T.

      acTxtGeral := { ;
         TxtImprime(), ;
         TxtSalva(), ;
         "Ordem.....: " + acTxtOrdem[ nOpcOrdem ], ;
         "Datas.....: " + iif( nOpcData == 1, acTxtData[ 1 ], ;
         Dtoc( m_datai ) + " a " + Dtoc( m_dataf ) ), ;
         "Saída.....: " + TxtSaida()[ nOpcPrinterType ] }

      FazAchoice( 7, 5, 6 + Len( acTxtGeral ), 44, acTxtGeral, @nOpcGeral )

      m_Temp := 1
      DO CASE
      CASE LastKey() == K_ESC
         EXIT

      CASE nOpcGeral == m_Temp++
         IF ConfirmaImpressao()
            imprime()
         ENDIF

      CASE nOpcGeral == m_Temp++
         m_conf = 2
         WAchoice( nOpcGeral+6, 25, TxtConf(), @m_conf, TxtSalva() )
         IF m_conf == 1 .AND. LastKey() != K_ESC
            GravaCnfRel( { nOpcOrdem } )
         ENDIF

      CASE nOpcGeral == m_Temp++
         WAchoice( nOpcGeral + 6, 25, acTxtOrdem, @nOpcOrdem, "Ordem de Impressão" )

      CASE nOpcGeral == m_Temp++
         DataIntervalo( nOpcGeral + 6, 25, @nOpcData, @m_Datai, @m_Dataf )

      CASE nOpcGeral == m_Temp
         WAchoice( nOpcGeral + 6, 25, TxtSaida(), @nOpcPrinterType, "Saída" )
         AppPrinterType( nOpcPrinterType )

      ENDCASE
   ENDDO
   WClose()
   CLOSE DATABASES

   RETURN

STATIC FUNCTION imprime()

   LOCAL oPDF, nKey, m_Total
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey = 0
   oPDF:acHeader := { "", "", "" }
   oPDF:acHeader[ 1 ] := "CONTAS A PAGAR"
   oPDF:acHeader[ 2 ] := "MAIORES FORNECEDORES - Ordem: " + acTxtOrdem[ nOpcOrdem ]
   IF nOpcData == 2
      oPDF:acHeader[ 2 ] += " - Data: " + Dtoc( m_datai ) + " a " + Dtoc( m_dataf )
   ENDIF
   oPDF:acHeader[ 3 ] := "-----------NOME DO FORNECEDOR-----------  -------C.G.C.-------  -----FATURAMENTO----  %TOTAL"
   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( FIVALOR ) AS TOTAL, FICADASTRO," + ;
         " JPCADASTRO.CDNOME, JPCADASTRO.CDCNPJ" + ;
         " FROM JPFINAN" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPFINAN.FICADASTRO" + ;
         " WHERE FITIPLAN = '2'"
      IF nOpcData == 2
         :cSQL += " AND FIDATEMI BETWEEN CAST( " + DateSQL( m_Datai ) + " AS DATE )" + ;
            " AND CAST( " + DateSQL( m_Dataf ) + " AS DATE )"
      ENDIF
      :cSQL += " GROUP BY FICADASTRO"
      IF nOpcOrdem == 1
         :cSQL += " ORDER BY TOTAL DESC"
      ELSE
         :cSQL += " ORDER BY CDNOME"
      ENDIF
      :Execute()
      m_Total := 0
      DO WHILE ! :Eof()
         m_Total += :Number( "TOTAL" )
         :MoveNext()
      ENDDO
      :MoveFirst()
      DO WHILE nKey != K_ESC .AND. ! :Eof()
         grafproc()
         nKey = Inkey()
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow, 0, :String( "CDNOME", 30 ) )
         oPDF:DrawText( oPDF:nRow, 42, :String( "CDCNPJ", 18 ) )
         oPDF:DrawText( oPDF:nRow, 64, :Number( "TOTAL" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 86, ( :Number( "TOTAL" ) * 100 / m_Total ), "@E 999.99" )
         oPDF:nRow  += 1
         :MoveNext()
      ENDDO
      IF oPDF:nPageNumber != 0
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow, 0, "TOTAL IMPRESSO....:" )
         oPDF:DrawText( oPDF:nRow, 64, m_Total, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 86, 100, "@E 999.99" )
         oPDF:nRow += 1
         oPDF:MaxRowTest()
      ENDIF
      :CloseRecordset()
   ENDWITH
   oPDF:End()

   RETURN NIL


18 minutos, incluindo dar uma geral no fonte até nos nomes de variáveis.
Acho que tá bom pra um simples.

Meu modo de trabalho

MensagemEnviado: 06 Mai 2020 16:01
por JoséQuintas
Uma alteração mais simples, lembrando que é usando minha classe pra ADO.
Na tela do financeiro, mostrando todos os lançamentos referentes ao mesmo pedido do lançamento atual.

   IF mIdPedido != 0
      Encontra( StrZero( mIdPedido, 6 ), "jppedido", "pedido" )
      @ mRow+2, 0 SAY "Valor do pedido:" + Transform( jppedido->pdValNot, PicVal(14,2) )
      @ mRow+3, 0 SAY ""
      mRecNo  := RecNo()
      OrdSetFocus( "pedido" )
      SEEK StrZero( mIdPedido, 6 )
      mCol := 1
      DO WHILE Val( jpfinan->fiPedido ) == mIdPedido .AND. Row() < MaxRow()-3 .AND. ! Eof()
         @ Row(), mCol SAY "Lanc." + jpfinan->idFinan + " " + Dtoc( jpfinan->fiDatVen ) + Transform( jpfinan->fiValor, PicVal(14,2) ) + iif( Empty( jpfinan->fiDatPag ), "AB", "PG" )
         IF mCol == 1
            mCol := 55
         ELSE
            mCol := 1
            @ Row()+1, 0 SAY ""
         ENDIF
         SKIP
      ENDDO
      OrdSetFocus( "jpfinan1" )
      GOTO mRecNo
   ENDIF


agora alterado

   IF mIdPedido != 0
      WITH OBJECT cnSQL
         :cSQL := "SELECT IDPEDIDO, PDVALNOT, JPFINAN.IDFINAN, JPFINAN.FIDATVEN," + ;
            " JPFINAN.FIDATPAG, JPFINAN.FIVALOR" + ;
            " FROM JPPEDIDO" + ;
            " LEFT JOIN JPFINAN ON JPFINAN.FIPEDIDO = JPPEDIDO.IDPEDIDO" + ;
            " WHERE IDPEDIDO = " + NumberSQL( mIdPedido )
         :Execute()
         @ mRow+2, 0 SAY "Valor do pedido:" + Transform( :Number( "PDVALNOT" ), PicVal(14,2) )
         @ mRow+3, 0 SAY ""
         mCol := 1
         DO WHILE Row() < MaxRow()-3 .AND. ! :Eof()
            @ Row(), mCol SAY "Lanc." + Str( :Number( "IDFINAN" ), 6 ) + " " + ;
               Dtoc( :Date( "FIDATVEN" ) ) + Transform( :Number( "FIVALOR" ), PicVal(14,2) ) + ;
               iif( Empty( :Date( "FIDATPAG" ) ), "AB", "PG" )
            IF mCol == 1
               mCol := 55
            ELSE
               mCol := 1
               @ Row()+1, 0 SAY ""
            ENDIF
            :MoveNext()
         ENDDO
         :CloseRecordset()
      ENDWITH
   ENDIF


Até que o fonte não alterou muito a lógica, mas agora sem DBF.

Meu modo de trabalho

MensagemEnviado: 06 Mai 2020 16:26
por JoséQuintas
Aproveitando pra mostrar a mudança em variáveis.
Ficou muito legal.

   mIdFinanceiro := ::axKeyValue[ 1 ]
   Encontra( StrZero( mIdFinanceiro, 6 ), "jpfinan", "numlan" )
   WITH OBJECT cnSQL
      :cSQL := "SELECT * FROM JPFINAN WHERE IDFINAN = " + NumberSQL( mIdFinanceiro )
      :Execute()
      mfiDatPag   := :Date( "FIDATPAG" )
      mVlBaixa    := :Number( "FIVALOR" )
      mfiNumDoc   := :String( "FINUMDOC", 9 )
      mfiParcela  := :String( "FIPARCELA", 3 )
      mfiTipDoc   := :String( "FITIPDOC", 6 )
      mIdCadastro := :Number( "FICADASTRO" )
      mfiDatEmi   := :Date( "FIDATEMI" )
      mfiDatVen   := :Date( "FIDATVEN" )
      mfiValor    := :Number( "FIVALOR" )
      mIdPortador := :Number( "FIPORTADOR" )
      mfiDatCan   := :Date( "FIDATCAN" )
      mIdCCusto   := :Number( "FICCUSTO" )
      mIdVendedor := :Number( "FIVENDEDOR" )
      mfiObs      := :String( "FIOBS", 100 )
      mfiNumBan   := :Number( "FINUMBAN" )
      mIdPedido   := :Number( "FIPEDIDO" )
      mIdOperacao := :Number( "FIOPERACAO" )
   ENDWITH


No arquivo/tabela do financeiro, é tudo começando com FI.
Pra variáveis acrescente o prefixo m, no caso desse financeiro mFI.
Mas....
Nas mudanças durante a conversão, alterei tudo que é campo como o campo chave de cada tabela.
Isso facilitou muito no aplicativo em geral.

FICADASTRO, FIPORTADOR, FICCUSTO, FIVENDEDOR, FIPEDIDO, FIOPERACAO
esses agora mudaram para
mIdCadastro, mIdPortador, mIdCCusto, mIdVendedor, mIdPedido, mIdOperacao

Isso no aplicativo, durante o uso.
Nas tabelas seguem como eram antes, exceto terem mudado de caractere para numérico NO MYSQL.

Meu modo de trabalho

MensagemEnviado: 07 Mai 2020 01:40
por JoséQuintas
Tava convertendo esta parte:

   Encontra( StrZero( mIdFinanceiro, 6 ), "jpfinan", "numlan" )
   IF ! Empty( jpfinan->fiDatPag ) .OR. ! Empty( jpfinan->fiDatCan )
      MsgWarning( "Documento já possui baixa" )
      RETURN NIL
   ENDIF


Aí pensei... porque não...

   IF ADORecCount( "JPFINAN", "IDFINAN = " + NumberSQL( midFinanceiro ) + ;
      " AND FIDATPAG IS NULL AND FIDATCAN IS NULL" ) == 0
      MsgWarning( "Documento já possui baixa" )
      RETURN NIL
   ENDIF


Não vou ter o registro atual no MySQL, mas pra não encher de fonte, simplifiquei apenas testando se existe o lançamento sem baixa.
Convém lembrar que na tela vai estar o lançamento, então ele existe.
É uma solução interessante, pra não buscar o lançamento só pra pegar esses dois campos e testar.

Isso não é uma coisa que uma conversão automática, ou RDD faria pra mim.
Devagarzinho, sem pressa, o uso do DBF vai sumindo...

E importante:
A criação da função ADORecCount() foi justamente pra economizar fonte.
São essas "coisinhas" que facilitam, e não o uso de um ou outro tipo de conexão.

Lembram: somos programadores, precisamos considerar que somos nosso cliente e trabalhar mais pra nós... a fim de trabalharmos menos.... rs

E .... fazendo devagar dá pra pensar mais e trabalhar menos...
Se estivesse com pressa de converter tudo, talvez não tivesse pensado nisso.

Meu modo de trabalho

MensagemEnviado: 07 Mai 2020 11:44
por JoséQuintas
Mais outra função que criei como quebra-galho, e é interessante.
É uma função simples, pra retornar um campo do SQL.
Aqui um exemplo de uso, antes e depois:

         DO CASE
         CASE mfiTipLan == "D"
            OrdSetFocus("jpfinan2") // fiTipLan + fiNumDoc
         CASE mfiTipLan == "B"
            OrdSetFocus("numbanco") // fiNumBan
         OTHERWISE
            OrdSetFocus("numlan") // idFinan
         ENDCASE
         @ 5, 0 SAY "Num. Docto...........:" GET mfiNumDoc PICTURE "@K 999999999" VALID FillZeros( @mfiNumDoc ) WHEN mfiTipLan == "D"
         @ Row(), 35 SAY "Parcela.." GET mfiParcela PICTURE "@K 999" WHEN mfiTipLan == "D" VALID FillZeros( @mfiParcela )
         @ 6, 0 SAY "Num.Bancário.........:" GET mfiNumBan PICTURE "@K 999999" WHEN mfiTipLan == "B"
         @ 7, 0 SAY "Num.Lançamento.......:" GET midFinanceiro   PICTURE "@K 999999" VALID OkNumLanFin( @midFinanceiro ) WHEN ! mfiTipLan $ "DB"
         @ 8, 0 SAY "Valor Original.......:" GET mfiValor    PICTURE PicVal(14,2)
         @ 9, 0 SAY "Juros(+) Descto(-)...:" GET mfiJurDes   PICTURE PicVal(14,2)
         Mensagem( "Digite campos, F9 Pesquisa ESC Sai" )
         READ
         Mensagem()
         IF LastKey() == K_ESC
            EXIT
         ENDIF
         DO CASE
         CASE mfiTipLan == "D"
            SEEK "1" + mfiNumDoc + mfiParcela
            nRecFound := RecNo()
            DO WHILE jpfinan->fiTipLan == "1" .AND. jpfinan->fiNumDoc == mfiNumDoc .AND. jpfinan->fiParcela == mfiParcela .AND. ! Eof()
               nRecFound := RecNo()
               SKIP
            ENDDO
            GOTO (nRecFound)
         CASE mfiTipLan == "B"
            SEEK StrZero( mfiNumBan, 6 )
         OTHERWISE
            SEEK StrZero( midFinanceiro, 6 )
         ENDCASE
         IF Eof()
            MsgWarning( "Documento não cadastrado!" )
            LOOP
         ENDIF
         Encontra( jpfinan->fiCadastro, "jpcadastro", "numlan" )


Primeiro pensar...
o objetivo é encontrar um lançamento no financeiro, podendo ser 3 critérios diferentes.
O que identifica a pesquisa pode variar, mas o resultado é um lançamento, então.... IdFinanceiro

         @ 5, 0 SAY "Num. Docto...........:" GET mfiNumDoc PICTURE "@K 999999999" VALID FillZeros( @mfiNumDoc ) WHEN mfiTipLan == "D"
         @ Row(), 35 SAY "Parcela.." GET mfiParcela PICTURE "@K 999" WHEN mfiTipLan == "D" VALID FillZeros( @mfiParcela )
         @ 6, 0 SAY "Num.Bancário.........:" GET mfiNumBan PICTURE "@K 999999" WHEN mfiTipLan == "B"
         @ 7, 0 SAY "Num.Lançamento.......:" GET midFinanceiro   PICTURE "@K 999999" VALID OkNumLanFin( @midFinanceiro ) WHEN ! mfiTipLan $ "DB"
         @ 8, 0 SAY "Valor Original.......:" GET mfiValor    PICTURE PicVal(14,2)
         @ 9, 0 SAY "Juros(+) Descto(-)...:" GET mfiJurDes   PICTURE PicVal(14,2)
         Mensagem( "Digite campos, F9 Pesquisa ESC Sai" )
         READ
         Mensagem()
         IF LastKey() == K_ESC
            EXIT
         ENDIF
         DO CASE
         CASE mfiTipLan == "D"
            nIdFinanceiro := ADOField( "IDFINAN", "N", "JPFINAN", "FITIPLAN = '1'" + ;
               " AND FINUMDOC = " + StringSql( mfiNumDoc ) + " AND FIPARCELA = " + StringSql( mfiParcela ) + ;
               " ORDER BY IDFINAN DESC" )
         CASE mfiTipLan == "B"
            mIdFinanceiro := ADOField( "IDFINAN", "N", "JPFINAN", "FINUMBAN = " + StringSQL( mfiNumBan ) )
         OTHERWISE
            midFinanceiro := ADOField( "IDFINAN", "N", "JPFINAN", "IDFINAN = " + NumberSQL( mIdFinanceiro ) )
         ENDCASE
         IF mIdFinanceiro == 0
            MsgWarning( "Documento não cadastrado!" )
            LOOP
         ENDIF
         Encontra( StrZero( mIdFinanceiro, 6 ), "jpfinan", "numlan" )
         Encontra( jpfinan->fiCadastro, "jpcadastro", "numlan" )


Relativamente simples: seja qual for a pesquisa, retorna a ID do lançamento.
E cada pesquisa usando o que entra na pesquisa.

A função usa o nome do campo, "N" pra identificar que é retorno numérico, a tabela, e o comando SQL.
A identificação de tipo é porque no SQL poderia conter um NULL no campo, então o tipo garante que o retorno vai ser numérico, porque vai converter NULL pra número 0.

A função só precisa combinar os parâmetros.
Numa forma resumida:

ADOField( cCampo, cTipo, cTabela, cCriterio )

"SELECT " + cCampo + " FROM " + cTabela + " WHERE " + cCriterio

É até pouco pra usar função mas... considerando fazer consulta, testar resultado, e até poder usar em IF, a torna interessante.
Comparando com DBF, é como abrir arquivo, posicionar com SEEK, e retornar o campo.

Porque entrou esta parte?
Encontra( StrZero( mIdFinanceiro, 6 ), "jpfinan", "numlan" )

Não dependo mais do DBF pra ESSA pesquisa, mas falta alterar o resto do fonte.
Tudo bem, por enquanto o que importa é reduzir o uso de DBF mas que o fonte continue funcionando.
Melhor alterar aos poucos, pra evitar perder o controle.
Essa parte garante que não vai faltar nada pro resto do fonte.

De quebra, eu poderia acabar com outros índices, e manter somente o índice por ID no DBF !!!
Apenas uma possibilidade, com certeza continuar caminhando pra eliminar o dbf de vez.
A gravação dupla está sendo legal nisso: no mesmo fonte, tanto faz se pego do MySQL ou DBF.
Basta ir eliminando a necessidade do DBF e pronto, depois só apagar tudo do DBF.

Meu modo de trabalho

MensagemEnviado: 07 Mai 2020 12:00
por JoséQuintas
Só complementando:

Tá demorando pra terminar a conversão?
E daí?
Nenhum cliente pediu MySQL, está tudo ficando mais rápido e continua tudo funcionando.

Detalhe: antes o campo de número bancário era de 10 posições ou mais, por enquanto limitando a 6 posições, e depois (ou durante) trocar pra numérico no MySQL, e gravar StrZero(x,6) no DBF.
Melhor até alterar a pesquisa pelo número bancário pra já considerar numérico.
Esse é mais um motivo pra eu fazer devagar, porque estou ajustando campos.

E esse negócio de ter ID único numérico pra tudo que é arquivo/tabela é fantástico, simplifica muuuito, não importa se usa SQL ou DBF.

Em todo caso: no MySQL usando INT(11) significa que estou aumentando os limites pra BILHÕES de tudo que é tabela. Nunca mais me preocupar sobre estourar limites.

Meu modo de trabalho

MensagemEnviado: 11 Mai 2020 14:59
por JoséQuintas
Erro de hoje:

SYSTEM ERROR
Error BASE/1081 Argument error: +
Called from IMPRIME(395)
Called from PFINANRELPAGAR(101)
Called from DO(0)
Called from RUNMODULE(106)
Called from BOXMENU(753)
Called from BOXMENU(740)
Called from MENUPRINC(588)
Called from SISTEMA(87)
Called from (b)MAIN(58)


Tem a ver, ao mesmo tempo que não tem a ver com a migração SQL.

               oPDF:DrawText( oPDF:nRow, 0, "***PORTADOR " + midPortador + ", " + Pad( AUXFINPORClass():Descricao( midPortador ), 30 ) + " ...:" )


Já comentei aqui: uma das mudanças que tenho feito é alterar campo chave de caractere pra numérico.

mIdPortador agora é numérico.

É por isso que eu disse que tem e não tem a ver... porque não é erro de migração SQL, e sim de mudança de estrutura pra aproveitar melhor recursos SQL.

É um erro DA MINHA MIGRAÇÃO, e não do MySQL... aliás, é uma variável.
E só acontece quando escolhe um determinado portador, na montagem de um sub-título que mostra opções selecionadas.

Dá pra considerar normal, porque aconteceria o mesmo com DBF. (apesar que no dbf continua como caractere, até sua extinção).

Meu modo de trabalho

MensagemEnviado: 11 Mai 2020 15:13
por JoséQuintas
A correção, apenas o uso de Str():

oPDF:DrawText( oPDF:nRow, 0, "***PORTADOR " + Str( midPortador. 6 ) + ", " + Pad( AUXFINPORClass():Descricao( midPortador ), 30 ) + " ...:" )


O que leva a outra coisa: pra que continuar pesquisando o portador, se isso pode ser feito no próprio comando SQL?
Pois é... como eu já disse por aqui, é ir migrando devagar, e durante a migração vamos aprendendo e tirando cada vez mais proveito do SQL.

Usar SQLMIX é uma boa opção pra migração rápida, mas.... se até usando ADO diretamente essas coisas podem passar desapercebidas, imagem usando estilo DBF....

Convém destacar:

Isso NÃO tem a ver com escolher ADO pra conectar.
Isso está diretamente relacionado a tirar mais proveito do servidor MySQL e comandos SQL.
Talvez eu não tenha feito isso antes porque estava migrando uma tabela de cada vez, mas a partir da gravação dupla passou a ser possível tirar mais proveito de tudo, já que todas as tabelas ficaram disponíveis no MySQL.

E assim vai prosseguindo a minha conversão: devagar, até refazendo coisa que tinha feito antes, mas sempre em frente.

Meu modo de trabalho

MensagemEnviado: 11 Mai 2020 15:20
por JoséQuintas
JoséQuintas escreveu:mIdPortador agora é numérico.


Faltou acrescentar:
Durante a fase de migração, alterei essa função que trás a descrição pra aceitar tanto chave caractere como numérica.
Desta forma ela continuou valendo pra fontes novos e velhos.

Nada complicado, algo do tipo:

FUNCTION Mostra( mIdPortador )

IF ValType( mIdPortador ) == "C"
   mIdPortador := Val( mIdPortador )
ENDIF


Desse jeito, tanto faz se ele é passado como caractere ou numérico, vale pro DBF que é caractere e pro MySQL que é numérico.
E quando terminar tudo, será remover esse IF (e outros que existirem).

Corrigindo: vale pra fonte novo e fonte velho, porque nos novos é sempre numérico, porque o foco principal é no tipo final.
No caso de DBF, leio como Val( arquivo->portador ) e gravo como StrZero( nIdPortador, 6 ), assim o aplicativo passa a trabalhar somente com numérico.
E quando remover o DBF, já remove a conversão junto.

Meu modo de trabalho

MensagemEnviado: 25 Mai 2020 13:16
por JoséQuintas
Vou começar a alterar esta parte agora.
É uma demonstração interessante de como "pensar em modo MySQL" é diferente de "pensar em modo DBF".

METHOD UserFunction( lProcessou ) CLASS FinanEdReceberClass

   LOCAL oXmlPdf, nIdFinanceiro := ::axKeyValue[ 1 ]

   Encontra( StrZero( nIdFinanceiro, 6 ), "jpfinan", "numlan" )
   Encontra( jpfinan->fiCadastro, "jpcadastro", "numlan" )
   DO CASE
   CASE ::cOpc == "F3"
      ::GeraFatura()
   CASE ::cOpc == "O"
      PJPREGUSO( "JPFINAN", Val( jpfinan->idFinan ) )
   CASE ::cOpc == "W"
      IF Val( jpfinan->fiPedido ) != 0 .AND. ADORecCount( "JPNOTFIS", "NFPEDIDO = " + NumberSQL( jpfinan->fiPedido ) ) != 0
         PosicionaEmpresa( Val( jpfinan->nfFilial ) )
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", Val( jpfinan->fiNumDoc ), "55" )
         oXmlPdf:GeraPDF()
         PosicionaEmpresa()
      ELSE
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", Val( jpfinan->fiNumDoc ), "55", jpcadastro->cdCnpj )
         oXmlPdf:GeraPDF()
      ENDIF
   CASE ::cOpc == "Q"
      ::PesquisaDocto()
   CASE ::cOpc == "T"
      ::GuiHide()
      ::TrocaCheque()
      ::GuiShow()
   CASE ::cOpc == "B"
      ::BaixaDocto()
   OTHERWISE
      lProcessou := .F.
   ENDCASE

   RETURN lProcessou


Como já comentei por aqui, comecei eliminando um arquivo por vez, mas depois mudei de idéia no meio do caminho com a gravação dupla.
usa o JPFINAN - financeiro em DBF
pesquisa JPCADASTRO, em DBF, pra encontrar o cliente
pesquisa JPNOTFIS, em MYSQL, pra verificar se existe nota fiscal emitida

Usava o financeiro em DBF, posicionado em DBF, o registro atual.
Com base no ID ainda posiciono o DBF.

Dá pra perceber aí também que padronizei nIdFinanceiro, nIdFilial, nIdPedido, como numéricos, porque são campos chave, mesmo no DBF sendo caractere, porque no MySQL já são numéricos.

Meu modo de trabalho

MensagemEnviado: 25 Mai 2020 13:24
por JoséQuintas
Fazer em duas etapas pra facilitar, apesar que é pequeno e nem precisava.
Substitui o acesso aos campos por variáveis, porque isso virá do MySQL.

METHOD UserFunction( lProcessou ) CLASS FinanEdReceberClass

   LOCAL oXmlPdf, nIdFinanceiro, nIdPedido, nIdFilial, nNumDoc, cCnpj

   nIdFinanceiro := ::axKeyValue[ 1 ]

   Encontra( StrZero( nIdFinanceiro, 6 ), "jpfinan", "numlan" )
   Encontra( jpfinan->fiCadastro, "jpcadastro", "numlan" )
   nIdPedido := Val( jpfinan->fiPedido )
   nIdFilial := Val( jpfinan->nfFilial )
   nNumDoc   := Val( jpfinan->fiNumDoc )
   cCnpj     := jpcadastro->cdCnpj

   DO CASE
   CASE ::cOpc == "F3"
      ::GeraFatura()
   CASE ::cOpc == "O"
      PJPREGUSO( "JPFINAN", nIdFinanceiro )
   CASE ::cOpc == "W"
      IF Val( jpfinan->fiPedido ) != 0 .AND. ADORecCount( "JPNOTFIS", "NFPEDIDO = " + NumberSQL( nIdPedido ) ) != 0
         PosicionaEmpresa( nIdFilial )
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", nNumDoc, "55" )
         oXmlPdf:GeraPDF()
         PosicionaEmpresa()
      ELSE
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", nNumDoc, "55", cCnpj )
         oXmlPdf:GeraPDF()
      ENDIF
   CASE ::cOpc == "Q"
      ::PesquisaDocto()
   CASE ::cOpc == "T"
      ::GuiHide()
      ::TrocaCheque()
      ::GuiShow()
   CASE ::cOpc == "B"
      ::BaixaDocto()
   OTHERWISE
      lProcessou := .F.
   ENDCASE

   RETURN lProcessou


Agora é substituir a origem das variáveis para o MySQL.

Meu modo de trabalho

MensagemEnviado: 25 Mai 2020 13:31
por JoséQuintas
Pronto.
Um único comando resolveu tudo, até a parte de fonte anterior de MySQL.

METHOD UserFunction( lProcessou ) CLASS FinanEdReceberClass

   LOCAL oXmlPdf, nIdFinanceiro, nIdPedido, nIdFilial, nNumDoc, cCnpj
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   nIdFinanceiro := ::axKeyValue[ 1 ]
   WITH OBJECT cnSQL
      :cSQL := "SELECT FIPEDIDO, NFFILIAL, FINUMDOC, FICADASTRO," + ;
         " JPNOTFIS.IDNOTFIS, JPNOTFIS.NFFILIAL, " + ;
         " JPCADASTRO.CDCNPJ " + ;
         " FROM JPFINAN " + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPFINAN.FIPEDIDO" + ;
         " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " WHERE IDFINAN = " + NumberSQL( nIdFinanceiro )
      :Execute()
      nIdPedido := :Number( "FIPEDIDO" )
      nIdFilial := :Number( "NFFILIAL" )
      nNumDoc   := :Number( "FINUMDOC" )
      cCnpj     := :String( "CDCNPJ" )
      :CloseRecordset()
   ENDWITH

   DO CASE
   CASE ::cOpc == "F3"
      ::GeraFatura()
   CASE ::cOpc == "O"
      PJPREGUSO( "JPFINAN", nIdFinanceiro )
   CASE ::cOpc == "W"
      IF nIdPedido != 0 .AND. nIdFilial != 0
         PosicionaEmpresa( nIdFilial )
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", nNumDoc, "55" )
         oXmlPdf:GeraPDF()
         PosicionaEmpresa()
      ELSE
         oXmlPdf := XmlPdfClass():New()
         oXmlPdf:GetFromMySql( "", nNumDoc, "55", cCnpj )
         oXmlPdf:GeraPDF()
      ENDIF
   CASE ::cOpc == "Q"
      ::PesquisaDocto()
   CASE ::cOpc == "T"
      ::GuiHide()
      ::TrocaCheque()
      ::GuiShow()
   CASE ::cOpc == "B"
      ::BaixaDocto()
   OTHERWISE
      lProcessou := .F.
   ENDCASE

   RETURN lProcessou


Nota: já conferi as outras sub-rotinas chamadas, e não dependem mais do posicionamento do DBF.

Este trecho é interessante pelo seguinte:
Mostra que pode ser mais interessante mover vários arquivos de uma vez, ao invés de um por um.
E é justamente aí que a gravação dupla foi vantagem: ao invés de alterar o fonte pra um arquivo MySQL por vez, já altero o fonte em definitivo.

Por enquanto fica assim, depois se aprender algo mais prático, ou redesenhar as bases de dados, faço de outro jeito.
Isso também mostra porque já padronizei os fontes pra usar campos numéricos, diferentes do DBF, porque já estão usando o formato final do MySQL.

E mais uma "não visível":

NÃO precisa de arquivo aberto, ou arquivo posicionado, bastam as variáveis contendo a informação.
Se o usuário largar o aplicativo aberto.... trata-se somente do aplicativo aberto, e uma única conexão pra troca de mensagens com MySQL, e nada mais.

Se fosse DBF: arquivos abertos, índices abertos, no terminal e no servidor, mais tempo do Windows pra manter tudo conectado, etc. etc. etc.

Como eu já disse por aqui: pensar "igual SQL", e esquecer como o DBF funciona. Não ter o DBF aberto e posicionado e trazer tudo pronto são as principais diferenças.
E não foi usado disco pra nada, apenas troca de mensagens entre aplicativo e servidor.

Meu modo de trabalho

MensagemEnviado: 25 Mai 2020 14:31
por JoséQuintas
Ah sim, devem se perguntar: porque tive que usar o arquivo de PEDIDOS?

Tenho financeiro com número de pedido ZERO, e tenho nota fiscal com número de pedido ZERO.
Se relacionar os dois diretamente, pelo número de pedido, os zerados vão estar relacionados.
A saída que usei foi essa: não existe pedido ZERO, então ao relacionar com pedido, deixa de existir nota fiscal relacionada.

Como eu comentei antes, sou principiante, é meu primeiro ano de uso de MySQL pra valer.
Tenho muito que aprender, mas com certeza já sei que usando DBF + MySQL vou ficar limitado.
Então... é terminar usando o que já aprendi, e depois ver o que vou poder melhorar.

Já quem acha que já sei muito....
Lembram que já comentei: é usar o básico, e ficar forte no básico, com o básico forte, a gente se vira pra fazer o resto.
Praticamente até agora só tenho usado o básico.
Seria o equivalente do DBF pra fazer SEEK e SET RELATION e nada mais.

Acho que a parte que mencionei no início seria igual em DBF.
Algo do tipo:

SELECT 0
USE NOTAS INDEX NPEDIDOS
SELECT 0
USE PEDIDOS INDEX FPEDIDOS
SET RELATION TO PEDIDOS->IDPEDIDO INTO NOTAS
SELECT 0
USE FINANCEIRO
SET RELATION TO FINANCEIRO->PEDIDO INTO PEDIDOS; TO FINANCEIRO->FICADASTRO INTO CADASTRO


É até interessante isso: apesar de filtros parecidos, o que no DBF é a parte mais lenta, no MySQL é a parte mais rápida.

Mas então... estou usando bastante o básico, e ficando craque no básico.
É como se eu estivesse num "treinamento intensivo" do básico.
Depois... quando tudo estiver em MySQL... aí vou poder aprender e usar mais coisas.

O importante é: estou aprendendo, estou convertendo, está em uso nos clientes, está funcionando perfeito, os DBFs estão deixando de ser usados.
Se vou terminar este mês, este ano, ano que vém, no outro ano,.... tanto faz... estou indo sempre em frente.
Acho que deste ano não passa.

Lembrando:
já eliminei o uso de estoque.dbf e notafiscal.dbf, agora é o financeiro.dbf.
Mas conforme altero rotinas, aproveito pra eliminar o uso de outros DBFs também, como na rotina acima, que eliminei o uso do cadastro.dbf.
E em relatórios, que pegam informação de tudo que é lugar ao mesmo tempo.
Por isso acho que deste ano não vai passar, mesmo fazendo tudo devagar.

Meu modo de trabalho

MensagemEnviado: 26 Mai 2020 11:46
por JoséQuintas
Só pra corrigir: o comando SQL anterior deu erro.

É que coloquei pra pegar JPCADASTRO.CDCNPJ, mas não indiquei essa tabela como parte do comando, por isso deu erro de não encontrar o campo.

O MySQL poderia pegar automaticamente?
Sim, mas como o cadastro tem muitos registros, faltou indicar o relacionamento, pra ele saber qual registro pegar.
Ainda bem que ele exige isso, senão iria pegar todos os cadastros.

Meu modo de trabalho

MensagemEnviado: 29 Mai 2020 13:43
por JoséQuintas
Eba !!!!
Menos um.

sql.png


Terminei de eliminar o uso de DBF do financeiro, o JPFINAN.DBF.

Nesse cliente, como dá pra ver na imagem, baixando de 500MB pra 400MB em DBF.
estoque, financeiro, e outros, agora somente em MySQL.

E o jpfinan.dbf?
Se venho fazendo gravação dupla DBF + MySQL, e está funcionando com as informações do MySQL... esse DBF virou lixo.
Vou apenas manter por mais um tempo, só por precaução, mas já pode ir pro lixo.
O DBF deixou de ser atualizado, não usa pra mais nada mesmo.

Os próximos são pedidos e produtos de pedido, que representam 350MB dos 400MB restantes em DBF.
Lembrando que eles já estão em gravação dupla há tempos, e já alterei muitos fontes pra não usarem mais os DBFs desses dois.

Recomendo a gravação dupla pra todos.
Mudar de DBF pra MySQL... aos poucos.... sem o cliente perceber... é fantástico...
Erros acontecem, mas são poucos a cada mudança, e fica fácil resolver.

No meu caso continua sendo ADO puro.
Se por um acaso trocar de linguagem de programação - NÃO é essa a intenção - tá tudo no jeito.

Meu modo de trabalho

MensagemEnviado: 11 Jun 2020 10:01
por JoséQuintas
Tava olhando aonde falta alterar o uso do produtos de pedidos: JPITPED.
Procurei por jpitped-> nos fontes.

falta1.png


falta2.png


falat3.png


Lembrando que no meio disso tem a gravação dupla, que se trata apenas de apagar.
A conversão ainda vai ser mantida, apenas pra transferir pra MySQL quem ainda não transferiu.
Tá quase lá....
Mas com certeza, as partes mais "complicadas" ficaram para o final.
Restam praticamente 8 (oito) fontes.

Nota: omiti pelo menos uma página, porque é o mesmo fonte JPPEDIDO.

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 10:44
por JoséQuintas
Acho que ficou aceitável, com minha classe.

FUNCTION ClonarPedido( nIdPedido )

   LOCAL nIdItPed, cField
   LOCAL cnSQL := ADOCLass():New( AppConexao() )

   IF nIdPedido == 0
      RETURN NIL
   ENDIF
   IF ! MsgYesNo( "Criar um novo pedido igual ao atual?" )
      RETURN NIL
   ENDIF
   WITH OBJECT cnSQL
      :cSQL := "SELECT JPPEDIDO.*, JPITPED.*" + ;
         " FROM JPPEDIDO" + ;
         " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " WHERE IDPEDIDO = " + NumberSQL( nIdPedido )
      :Execute()
      :QueryCreate()
      :QueryAdd( "PDCONF", "N" )
      :QueryAdd( "PDDATEMI", Date() )
      :QueryAdd( "PDINFINC", LogInfo() )
      FOR EACH cField IN { "PDFILIAL", "PDTRANSA", "PDCADASTRO", "PDVENDEDOR", "PDTRANSP", "PDFORPAG" }
         :QueryAdd( cField, StrZero( :Number( cField ), 6 ) )
      NEXT
      FOR EACH cField IN { "PDPEDCLI" }
         :QueryAdd( cField, StrZero( :Number( cField ), 9 ) )
      NEXT
      FOR EACH cField IN { "PDCONTATO", "PDDIFCAL", "PDEMAIL", "PDOBS", "PDLEIS" }
         :QueryAdd( cField, :String( cField ) )
      NEXT
      FOR EACH cField IN { "PDPERDES", "PDPERADI", "PDVALCUS", "PDVALPRO", "PDVALNOT", "PDVALFRE", ;
         "PDVALSEG", "PDVALOUT", "PDVALEXT", "PDVALDES", "PDVALADI", "PDVALADU", "PDVALIOF", "PDIIBAS", ;
         "PDIIVAL", "PDIPIBAS", "PDICMVAL", "PDFCPVAL", "PDSUBBAS", "PDSUBVAL", "PDDIFVALI", "PDDIFVALF", ;
         "PDISSBAS", "PDISSVAL", "PDIPIVAL", "PDICMBAS", "PDPISBAS", "PDPISVAL", "PDCOFBAS", "PDCOFVAL", ;
         "PDICSBAS", "PDICSVAL", "PDIMPVAL" }
         :QueryAdd( cField, :Number( cField ) )
      NEXT
      nIdPedido := :QueryExecuteInsert( "JPPEDIDO" )
      :QueryAdd( "IDPEDIDO", StrZero( nIdPedido, 6 ) )
      jppedido->( :DBFQueryExecuteInsert( "JPPEDIDO" ) )
      DO WHILE ! :Eof()
         :QueryCreate()
         :QueryAdd( "IPPEDIDO", StrZero( nIdPedido, 6 ) )
         :QueryAdd( "IPINFINC", LogInfo() )
         FOR EACH cField IN { "IPFILIAL", "IPPRODUTO", "IPTRIBUT" }
            :QueryAdd( cField, StrZero( :Number( cField ), 6 ) )
         NEXT
         FOR EACH cField IN { "IPPEDCOM", "IPCFOP", "IPLEIS", "IPORIMER", "IPIPIICM", "IPIPICST", ;
            "IPIPIENQ", "IPICMCST", "IPDIFCAL", "IPPISCST", "IPPISENQ", "IPCOFCST", "IPCOFENQ" }
            :QueryAdd( cField, :String( cField ) )
         NEXT
         FOR EACH cField IN { "IPPRECUS", "IPPREPED", "IPQTDE", "IPVALCUS", "IPGARANTIA", "IPPRENOT", ;
            "IPVALADI", "IPVALFRE", "IPVALSEG", "IPVALOUT", "IPVALEXT", "IPVALADU", "IPVALIOF", "IPVALDES", ;
            "IPVALPRO", "IPVALNOT", "IPIIBAS", "IPIIALI", "IPIPIBAS", "IPIPIALI", "IPIPIVAL", "IPICMBAS", ;
            "IPICMALI", "IPICMRED", "IPICMVAL", "IPFCPALI", "IPFCPVAL", "IPICSBAS", "IPICSALI", "IPICSVAL", ;
            "IPSUBIVA", "IPSUBBAS", "IPSUBRED", "IPSUBALI", "IPSUBVAL", "IPDIFBAS", "IPDIFALIF", "IPDIFALIU", ;
            "IPDIFALII", "IPDIFVALI", "IPDIFVALF", "IPPISBAS", "IPPISALI", "IPPISVAL", "IPCOFBAS", "IPCOFALI", ;
            "IPCOFVAL", "IPISSBAS", "IPISSALI", "IPISSVAL", "IPIMPALI", "IPIMPVAL" }
            :QueryAdd( cField, :Number( cField ) )
         NEXT
         nIdItPed := :QueryExecuteInsert( "JPITPED" )
         :QueryAdd( "IDITPED", StrZero( nIdItPed, 6 ) )
         jpitped->( :DbfQueryExecuteInsert( "JPITPED" ) )
         :MoveNext()
      ENDDO
      :CloseRecordset()
   ENDWITH
   SubPedidoClass():CalculaValores( nIdPedido )

   RETURN NIL


Acabei separando em blocos.
Detalhe deste:
      FOR EACH cField IN { "PDFILIAL", "PDTRANSA", "PDCADASTRO", "PDVENDEDOR", "PDTRANSP", "PDFORPAG" }
         :QueryAdd( cField, StrZero( :Number( cField ), 6 ) )
      NEXT


Esses campos, no DBF são tipo caractere, e no MySQL são tipo numérico.
Depois, quando eliminar o DBF, talvez troque por INSERT ... ( SELECT )
Mas não vai ser muita vantagem, porque vai precisar indicar os campos do mesmo jeito.

Ou talvez seja, se eu cadastrar como uma Stored Procedure, porque aí não precisa nenhum fonte no aplicativo !!!

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 12:40
por JoséQuintas
Uia.
Estou trabalhando na tela de pedidos.
É um bom exemplo pra comparar DBF com SQL.

telaped.png


Tem informação até demais:

- JPPEDIDO: pedidos
- JPITPED: produtos do pedido
- JPITEM: descrição dos produtos
- JPVENDEDOR: nome do vendedor
- JPCADASTRO: dados do cliente
- JPFORPAG: descrição da forma de pagamento
- JPTRANSA: descrição da transação
- JPNOTFIS: dados de nota fiscal, se existir

Em DBF, 8 arquivos + 8 índices, selecionar área, índice, pesquisar, do while nos produtos de pedido pesquisando os nomes dos produtos, etc.
Em SQL, um único comando

Em SQL fica complicado?
Depende do ponto de vista.
Se comparar com um único SEEK, parece complicado.
Se comparar com no mínimo 8 SEEKs, aí parece muito mais simples

      :cSQL := "SELECT JPPEDIDO.*, JPITPED.*, JPCADASTRO.CDVENDEDOR, JPCADASTRO.CDNOME, JPCADASTRO.CDUF," + ;
      " JPCADASTRO.CDCNPJ, JPCADASTRO.CDENDENT, JPITEM.IEDESCRI, JPFORPAG.FPDESCRI, " + ;
      " JPNOTFIS.NFFILIAL, JPNOTFIS.NFNOTFIS, JPNOTFIS.NFDATEMI, JPTRANSA.TRDESCRI," + ;
      " JPVENDEDOR.VDDESCRI" + ;
      " FROM JPPEDIDO" + ;
      " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
      " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
      " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
      " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
      " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
      " LEFT JOIN JPVENDEDOR ON JPVENDEDOR.IDVENDEDOR = JPPEDIDO.PDVENDEDOR" + ;
      " LEFT JOIN JPFORPAG ON JPFORPAG.IDFORPAG = JPPEDIDO.PDFORPAG" + ;
      " WHERE IDPEDIDO = " + NumberSQL( nIdPedido )


Aproveitar pra explicar, porque acaba sendo relativamente simples.

os campos que eu quero
      :cSQL := "SELECT JPPEDIDO.*, JPITPED.*, JPCADASTRO.CDVENDEDOR, JPCADASTRO.CDNOME, JPCADASTRO.CDUF," + ;
      " JPCADASTRO.CDCNPJ, JPCADASTRO.CDENDENT, JPITEM.IEDESCRI, JPFORPAG.FPDESCRI, " + ;
      " JPNOTFIS.NFFILIAL, JPNOTFIS.NFNOTFIS, JPNOTFIS.NFDATEMI, JPTRANSA.TRDESCRI," + ;
      " JPVENDEDOR.VDDESCRI" + ;


qual a tabela base e filtro, que neste caso só usa a tabela base

      " FROM JPPEDIDO" + ;
     ..
      " WHERE IDPEDIDO = " + NumberSQL( nIdPedido )


e os relacionamentos, como se fosse uma linha pra cada SEEK em cada tabela

      " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
      " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
      " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
      " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
      " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
      " LEFT JOIN JPVENDEDOR ON JPVENDEDOR.IDVENDEDOR = JPPEDIDO.PDVENDEDOR" + ;
      " LEFT JOIN JPFORPAG ON JPFORPAG.IDFORPAG = JPPEDIDO.PDFORPAG" + ;


É só fazer os SAYs... e o TBrowse.
No caso dos produtos, já é feito o relacionamento de cada produto JPITPED com JPITEM.

Uma única consulta, e tá tudo na mão.
NADA de abrir arquivo, índice, etc. etc. etc.
NADA de pasta compartilhada, antivírus enchendo o saco, terminal fuçando arquivo, nada disso.
Vai uma mensagem pro servidor, volta uma mensagem com a informação.

Também é um bom exemplo pra mostrar o seguinte:
Eu poderia ter feito igual DBF, pesquisando uma coisa de cada vez?
Sim poderia.
Mas...

Se usar SQL igual DBF:

- pesquisar pedido
- pesquisar cliente
- pesquisar vendedor
- pesquisar transação
- pesquisar forma de pagamento
- pesquisar produtos de pedido
- fazer várias pesquisas de cada produto pra pegar descrição

Porque enviar/receber tanta mensagem, se uma única resolve?

Agora duas coisas interessantes:

- passar pra SQL ANTES de passar pra tela GUI/gráfica, pode facilitar muito.

- Isso acima é recurso de SQL, que NÃO encontramos em DBF.

E convém lembrar: SQL é recurso do servidor. Se está usando ADO ou SQLMIX ou outro, tanto faz.

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 12:59
por JoséQuintas
Tem mais uma coisa interessante:

Como uso nomes de campos diferentes em cada tabela, posso fazer esse JPPEDIDO.*, JPITPED.*
Se tivesse nome repetido, por exemplo CODCLI nos dois, aí já não daria pra fazer isso.

É por isso que a migração de DBF pra SQL pode ser diferente pra cada pessoa, porque cada um tem suas tabelas, e seu estilo de fazer as coisas.
De repente pode até valer a pena dar uma geral nos arquivos, e até nos fontes, preparando tudo antes de ir em frente.

De repente, o ideal pode ser mesmo do jeito que eu fiz.
Fui fazendo um arquivo por vez, e confirmando o que poderia facilitar em cada um.
E depois a gravação dupla, que já deu uma visão maior sobre tudo de uma vez.

Como eu disse: estou aprendendo conforme vou fazendo. Devagar meu uso/conhecimento de SQL foi/continua melhorando.

Até que comecei bem devagar, mas com o tempo, conforme fui me sentindo mais "confortável", com as coisas "se encaixando", fui acelerando um pouco mais.
Tô quase lá....

Certo? errado? sei lá.. escolhi esse caminho, gostei dele, e estou seguindo.
Por enquanto basta que funcione, da melhor maneira que eu puder fazer.
Depois... aí fica pra depois....
Aliás... desde o começo pensei assim... algumas coisas que fiz antes até já mudei depois kkkkk
Mas fazer o que? é assim mesmo, é ir fazendo e aprendendo, e fazendo e aprendendo....

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 18:52
por asimoes
Quintas,

A sua tela iria ficar bunitinha na hwgui ou minigui

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 21:09
por JoséQuintas
asimoes escreveu:Quintas,
A sua tela iria ficar bunitinha na hwgui ou minigui


restadbf.png


Olhe lá: 116MB do JPFINAN já não é usado.
Agora 163MB e 175MB de pedidos/produtos sendo eliminados.
Vai sobrar muito pouco em DBF.

Mexer com visual agora pode atrapalhar.
Mas depois... vai ser só visual pra brincar.

Aliás... tá na hora de apagar esse JPFINAN que não serve pra mais nada....

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 21:16
por Vlademiro
Tem que avaliar bem se compensa trocar o código Harbour por store procedure. O servidor pode ficar lento. O ideal é testar muito, e com bases realmente grandes.

Outra coisa. Esse código não é redundante?
  FOR EACH cField IN { "PDPEDCLI" }
         :QueryAdd( cField, StrZero( :Number( cField ), 9 ) )
      NEXT



Não precisa do loop.

Meu modo de trabalho

MensagemEnviado: 14 Jun 2020 22:07
por JoséQuintas
Vlademiro escreveu:Outra coisa. Esse código não é redundante?


Pois é, preparei pra adicionar mais campos, mas acabei usando um só, e não precisava mais do FOR/EACH.

Meu modo de trabalho

MensagemEnviado: 15 Jun 2020 09:06
por JoséQuintas
Fiz umas correções de última hora, mas...
Instalado em cliente e em funcionamento.

Meu modo de trabalho

MensagemEnviado: 16 Jun 2020 13:09
por JoséQuintas
jpitped.png


Uia....
48 linhas de fonte usando produtos de pedido: jpitped->
E isso inclui as conversões.
Vou apagar jpitped a qualquer momento....

jpitped2.png
jpitped2.png (10.38 KiB) Visualizado 1251 vezes


Acho que nem precisa dizer, mas se estou mexendo em produtos de pedido, acabo mexendo em pedidos também....
pedidos vai ser o próximo, e já está meio caminho andado.

Os DBFs estão acabando.....

Meu modo de trabalho

MensagemEnviado: 17 Jun 2020 13:52
por JoséQuintas
UIA

jpa.png


DBF tá sendo eliminado....
Falta pouco...

Meu modo de trabalho

MensagemEnviado: 17 Jun 2020 15:55
por JoséQuintas
jppedido.png


O uso de JPPEDIDO tá parecendo o uso de JPITPED há 5 dias atrás.
195 ocorrências.

O chato é que estou tendo que alterar até, por exemplo, a geração de SPED Fiscal/Contribuições, sendo que nenhum cliente usa isso.
Mas ou faço isso ou apago tudo, e não quero apagar.

jppedido2.png


Nessa parte, por exemplo, só aí já elimina umas 50 ocorrências.

Meu modo de trabalho

MensagemEnviado: 17 Jun 2020 20:28
por Itamar M. Lins Jr.
Ola!
a geração de SPED Fiscal/Contribuições

Vc tem essa rotina solta ?
Não estava querendo mexer nisso, mas parece que terei que estudar essa parte.

Saudações,
Itamar M. Lins Jr.

Meu modo de trabalho

MensagemEnviado: 17 Jun 2020 21:18
por JoséQuintas
Antes se chamava SPED pis/cofins, e parece que mudou o nome pra contribuições.
Nunca mais olhei pra atualizar, apenas tento manter o que tinha feito.
Lembrando que meus clientes não trabalham com cupom fiscal, por isso muitos blocos estão apenas comentados.

Aqui dá uma boa idéia do que tem.

/*
PFISCSPED - SPED PIS/COFINS
2011.09 - José Quintas
*/

#include "josequintas.ch"
#include "inkey.ch"
#define SPED_SEPARADOR "|"

MEMVAR aBlocoList, aBlocoTotList
MEMVAR mDatIni, mDatFim, mTipoSped, mPerfil, mLayOut, aPisList, aCofinsList, aPisBasList, aCofinsBasList
MEMVAR mPisVal, mCofVal

PROCEDURE pFiscSped

   LOCAL mFileSped, GetList := {}
   PRIVATE mDatIni, mDatFim, mTipoSped, mPerfil, mLayOut, aBlocoList, aBlocoTotList, aPisList, aCofinsList, aPisBasList, aCofinsBasList

   IF ! AbreArquivos( "jppedido", "jpcadastro", "jpitem", "jpcidade", "jpempresa", "jptabel" )
      RETURN
   ENDIF
   mDatIni   := Date() - Day( Date() )
   mDatIni   := mDatIni - Day( mDatIni ) + 1
   mDatFim   := Date() - Day( Date() )
   mTipoSped := "P"
   mPerfil   := "A"

   @ 5, 5 SAY "Data Inicial......:" GET mDatIni
   @ 6, 5 SAY "Data Final........:" GET mDatFim
   @ 7, 5 SAY "Sped Fiscal ou Pis:" GET mTipoSped PICTURE "!A" VALID mTipoSped $ "FP"
   @ 8, 5 SAY "Perfil............:" GET mPerfil PICTURE "!A" VALID mPerfil $ "ABC"
   @ 10, 5 SAY "Temporariamente retirado bloco C110 (Pis/Cof) ref informações complementares"
   Mensagem( "Digite campos, ESC Sai" )
   READ
   Mensagem()

   IF LastKey() == K_ESC
      RETURN
   ENDIF

   IF ! MsgYesNo( "Confirma geração" )
      RETURN
   ENDIF

   SayScroll( "Gerando SPED" )

   IF mTipoSped == "P"
      mLayout := "003" // 2.01A
      mFileSped := "EXPORTA\SPPC" + Substr( Dtos( mDatFim ), 3, 4 ) + ".TXT"
   ELSE
      mLayOut := "006"
      mFileSped := "EXPORTA\SPFI" + Substr( Dtos( mDatFim ), 3, 4 ) + ".TXT"
   ENDIF

   SET ALTERNATE TO ( mFileSped )
   SET ALTERNATE ON
   SET CONSOLE OFF

   aBlocoList := {}
   aBlocoTotList := {} // usado nos totalizadores
   aPisList := {}
   aPisBasList := {}
   aCofinsList := {}
   aCofinsBasList := {} // totaliza C170 para M400 e M800

   // --------------------- Bloco0 - Abertura, Identificacao e Referencias

   Bloco0000()   // Abertura Bloco 0
   Bloco0001()   // Abertura Bloco 0
   IF mTipoSped == "F"
      Bloco0005()
      Bloco0015()
   ENDIF
   Bloco0100()   // Contabilista
   IF mTipoSped == "P"
      Bloco0110()   // Regime de Apuracao
      Bloco0111()   // Tabela de Receita Bruta Mensal
      Bloco0120()   // Identif. Periodos dispensados da escrituracao digital
      Bloco0140()   // Tabela de Cadastro Estabelecimento
   ENDIF
   Bloco0150()   // Tabela de Cadastro de Participantes
   IF mTipoSped == "F"
      Bloco0175()
   ENDIF
   Bloco0190()   // Tabela de Unidades de Medida
   Bloco0200()   // Tabela de Produtos e Servicos
   // Bloco0200-Bloco0205() // Alteração do produto/serviço
   // Bloco0200-Bloco0206() // Tabela ANP
   IF mTipoSped == "P"
      Bloco0208()   // Codigo de Grupos por Marca (Bebidas Frias)
   ENDIF
   IF mTipoSped == "F"
      Bloco0220() // Fatores de Conversao
      Bloco0300() // Bens ou Componentes do Ativo
   ENDIF
   Bloco0400()   // Tabela de Natureza de Operacao
   Bloco0450()   // Tabela de Informacao Complementar
   IF mTipoSped == "F"
      Bloco0460()
   ENDIF
   Bloco0500()   // Plano de Contas Contabil
   Bloco0600()   // Centros de Custo
   Bloco0990()   // Encerramento

   IF mTipoSped == "P"

      // --------------------- BlocoA - Documentos Fiscais Servicos ISS

      BlocoA001()  // Abertura Bloco A
      // BlocoA010() // Identificacao do Estabelecimento
      // BlocoA100() // Documento NF Servico
      // BlocoA100-BlocoA110() // Complemento - Informacao Complementar
      // BlocoA100-BlocoA111() // Processo Referenciado
      // BlocoA100-BlocoA120() // Complemento - Importacao
      // BlocoA100-BlocoA170() // Complemento - Itens do Documento
      BlocoA990() // Encerramento Bloco A
   ENDIF

   // -------------------- BlocoC - Documentos Fiscais I Mercadorias ICMS/IPI

   BlocoC001() // Abertura
   IF mTipoSped == "P"
      BlocoC010() // Identificacao do Estabelecimento
   ENDIF
   BlocoC100() // Nota Fiscal
   // BlocoC100-BlocoC110( xxLeis ) // Complemento
   IF mTipoSped == "F"
      // BlocoC100-BlocoC105()
      // BlocoC100-BlocoC110( xxLeis )
   ENDIF
   // BlocoC100-BlocoC111() // Processo Referenciado
   // BlocoC100-BlocoC120() // Complemento Importacao
   // BlocoC100-BlocoC170( nIdPedido ) // Itens do Documento
   // BlocoC180() // Consolidacao das Notas Emitidas
   // BlocoC181() // Detalhamento da Consolidacao Pis
   // BlocoC185() // Detalhamento da Consolidacao Cofins
   // BlocoC188() // Processo Referenciado
   // BlocoC190() // Consolidacao de NFE Aquisicao e Devolucao Compras/Vendas
   // BlocoC191() // Detalhamento 190 Pis
   // BlocoC195() // Detalhamento 190 Cofins
   // BlocoC198() // Processo Referenciado
   // BlocoC199() // Complemento Importacao
   // BlocoC380() // Consolidacao NF Consumidor Emitidos
   // BlocoC381() // Detalhamento 380 Pis
   // BlocoC385() // Detalhamento 380 Cofins
   // BlocoC395() // NF Consumidor Aquisicoes
   // BlocoC396() // Produtos da NF Consumidor 395
   // BlocoC400() // Equipamento ECF
   // BlocoC405() // Reducao Z
   // BlocoC481() // Resumo Diario ECF Pis
   // BlocoC485() // Resumo Diario ECF Cofins
   // BlocoC489() // Processo Referenciado
   // BlocoC490() // Consolidacao ECF
   // BlocoC491() // Detalhamento 0490 Pis
   // BlocoC495() // Detalhamento 0490 Cofins
   // BlocoC499() // Processo Referenciado ECF
   // BlocoC500() // Luz, Agua e Gas
   // BlocoC501() // Complemento 500 Pis
   // BlocoC505() // Complemento 500 Cofins
   // BlocoC600() // Consolidacao Luz,Agua,Gas
   // BlocoC601() // Complemento 0600 Pis
   // BlocoC605() // Complemento 0600 Cofins
   // BlocoC609() // Processo Referenciado 0600
   // BlocoC800() // Cupom Fiscal Eletronico
   // BlocoC810() // Detalhamento C800 Pis
   // BlocoC820() // Detalhamento C800 Cofins
   // BlocoC830() // Processo Referenciado C800
   // BlocoC860() // Identificacao Equipamento SAT-CFe
   // BlocoC870() // Detalhamento Cupom Pis
   // BlocoC880() // Detalhamento Cupom Cofins
   BlocoC990() // Encerramento Bloco C

   // --------------------- BlocoD - Documentos Fiscais II Servicos ICMS

   BlocoD001() // Abertura
   // BlocoD010() // Identificacao do Estabelecimento
   // BlocoD100() // Aquisicao Serv Transp
   // BlocoD101() // Complemento Pis
   // BlocoD105() // Complemento Cofins
   // BlocoD111() // Processo Referenciado
   // BlocoD200() // Resumo Diario Serv Transp
   // BlocoD201() // Total Diario Pis
   // BlocoD205() // Total Diario Cofins
   // BlocoD209() // Processo Referenciado
   // BlocoD300() // Resumo Diario
   // BlocoD309() // Processo Referenciado
   // BlocoD350() // Resumo Diario Cupom ECF
   // BlocoD359() // Processo Referenciado
   // BlocoD500() // NF Comunicacao/Telecomunicacao
   // BlocoD501() // Complemento Pis
   // BlocoD509() // Complemento Cofins
   // BlocoD600() // Processo Referenciado
   // BlocoD601() // Consolidacao
   // BlocoD605() // Complemento Consolidacao
   // BlocoD609() // Processo Referenciado
   BlocoD990() // Encerramento

   IF mTipoSped == "F"
      // --------------------- BLOCOE -
      BlocoE001() // Abertura
      BlocoE100() // Movimento
      BlocoE110() // Apuracao ICMS
      BlocoE990() // Encerramento
   ENDIF

   // --------------------- BlocoF - Demais Documentos e Operacoes

   IF mTipoSped == "P"
      BlocoF001() // Abertura
      // BlocoF010() // Identificacao do Estabelecimento
      // BlocoF100() // Demais Doc
      // BlocoF111() // Processo Referenciado
      // BlocoF120() // Bens Ativo Depreciacao
      // BlocoF129() // Processo Referenciado
      // BLocoF130() // Bens Ativo Aquisicao
      // BlocoF139() // Processo Referenciado
      // BlocoF150() // Credito Presumido sobre estoque
      // BlocoF200() // Ativ.Imobiliaria Venda
      // BlocoF205() // Ativ.Imobiliaria Custo
      // BlocoF210() // Ativ.Imobiliaria Custo
      // BlocoF211() // Processo Referenciado
      // BlocoF500() // Consolidacao Regime de Caixa
      // BlocoF509() // Processo Referenciado
      // BlocoF510() // Consolidacao Regime de Caixa por unidade
      // BlocoF519() // Processo Referenciado
      // BlocoF525() // Composicao Receita Regime de Caixa
      // BlocoF550() // Consolidacao Regime de Competencia
      // BlocoF559() // Processo Referenciado
      // BlocoF560() // Consolidacao Regime Competencia por Unidade
      // BlocoF569() // Processo Referenciado
      // BlocoF600() // Contribuicao Retida na fonte
      // BlocoF700() // Deducoes Diversas
      // BlocoF800() // Creditos Incorporacao,Fusao e Cisao
      BlocoF990() // Encerramento
   ENDIF

   IF mTipoSped == "F"

      // --------------------BlocoG - Controle de Credito de ICMS do Ativo Permanente - CIAP
      BlocoG001()
      BlocoG990()

      // --------------------BlocoH - Inventario Fisico
      BlocoH001()
      BlocoH990()

   ENDIF

   // -------------------- BlocoI

   IF mTipoSped == "P"

      // -------------------- BlocoM - Apuracao da Contribuicao e Credito PIS e COFINS

      BlocoM001() // Abertura
      // BlocoM100() // Credito Pis Periodo
      // BlocoM105() // Detalhamento Pis
      // BlocoM110() // Ajustes de Credito
      BlocoM200() // Consolidacao Pis
      // BlocoM210()// Detalhamento Pis
      // BlocoM211() // Cooperativas Pis
      // BlocoM220() // Ajustes Pis
      // BlocoM230() // Inf. Adicionais Pis
      // BlocoM300() // Pis Anteriores
      // BlocoM350() // Pis Folha de Salarios
      BlocoM400() // Receitas Isentas
      BlocoM410() // Detalhamento Isentas
      // BlocoM500() // Credito Cofins
      // BlocoM505() // Consolidacao Cofins
      // BlocoM510() // Ajustes Cofins
      BlocoM600() // Consolidacao Cofins
      // BlocoM610() // Detalhamento Cofins
      // BlocoM611() // Cooperativas Cofins
      // BlocoM620() // Ajustes Cofins
      // BlocoM630() // Inf.Adicionais Cofins
      // BlocoM700() // Cofins Anteriores
      BlocoM800() // Isentas Cofins
      BlocoM810() // Detalhamento Isentas
      BlocoM990() // Encerramento

   ENDIF

   // -------------------- BlocoP

   // ------------------- Bloco1 - Complemento da Escrituracao
   Bloco1001()
   // Bloco1010()
   // Bloco1020()
   // Bloco1100()
   // Bloco1101()
   // Bloco1102()
   // Bloco1200()
   // Bloco1210()
   // Bloco1220()
   // Bloco1300()
   // Bloco1500()
   // Bloco1501()
   // Bloco1502()
   // Bloco1600()
   // Bloco1610()
   // Bloco1620()
   // Bloco1700()
   // Bloco1800()
   // Bloco1809()
   // Bloco1900()
   Bloco1990()

   // --------------------- Bloco9 - Encerramento

   Bloco9001() // Abertura
   Bloco9900() // Totalizacao dos blocos
   Bloco9990() // Encerramento bloco
   Bloco9999() // Encerramento Geral

   SET CONSOLE ON
   SET ALTERNATE OFF
   SET ALTERNATE TO
   fDelEof( mFileSped )

   MsgExclamation( "Fim da Geracao" )

   RETURN

Meu modo de trabalho

MensagemEnviado: 17 Jun 2020 22:39
por JoséQuintas
jppedido.png


De 195 linhas, baixou pra 133.

Só lembrando:
- primeiro é eliminar a necessidade de leitura do JPPEDIDO.DBF, parte desses vão continuar existindo nesta etapa
- só depois, é eliminar a gravação no DBF, o que significa apagar tudo que sobrar.

Sem pressa, apesar de muito empolgado em terminar.

Meu modo de trabalho

MensagemEnviado: 18 Jun 2020 18:18
por JoséQuintas
jppedido.png


Tá quase, 62 linhas - provavelmente metade disso.

Meu modo de trabalho

MensagemEnviado: 19 Jun 2020 11:55
por JoséQuintas
jppedido.png


Acho que acabou.
Parece que só sobraram gravação dupla e conversão.

Vou ter que atualizar no cliente agora.

Situação atual: está atualizando JPPEDIDO.DBF, e não usa pra mais nada.

Próximas etapas:

1. Eliminar gravações em JPPEDIDO.dbf, não precisa mais salvar

2. Eliminar SELECT, SEEK, etc. em JPPEDIDO, já que não salva, e nem usa

3. Eliminar abertura de JPPEDIDO, reindexação,etc.

4. Apagar o DBF

Seguindo essa ordem, os fontes estão sempre "usáveis".

Meu modo de trabalho

MensagemEnviado: 19 Jun 2020 12:51
por JoséQuintas
Sempre tem mais ajustes.

   @ Row()+1, 1 SAY "Pedido...........:" GET nIdPedido PICTURE "999999" VALID nIdPedido == 0 .OR. Encontra( StrZero( nIdPedido, 6 ), "jppedido", "pedido" )


Nesse não tem o JPPEDIDO->, mas faz uso dele

Mas a alteração é simples, parece até mais "legível".

   @ Row()+1, 1 SAY "Pedido...........:" GET nIdPedido PICTURE "999999" VALID nIdPedido == 0 .OR. ADORecCount( "JPPEDIDO", "IDPEDIDO = " + NumberSQL( nIdPedido ) ) != 0


ADORecCount() é uma função que conta registros. é passar a tabela e o "WHERE".
Equivale ao anterior, mas sem precisar abrir arquivo nem índice, basta pedir ao SQL.

Então.... no meu modo de trabalho, tranquilo alterar isso agora.
Mas dependendo do fonte de cada pessoa, poderia ser problema.

Meu modo de trabalho

MensagemEnviado: 19 Jun 2020 13:01
por JoséQuintas
Nesta penúltima etapa é pesquisar JPPEDIDO->, SELECT JPPEDIDO, e "JPPEDIDO", todos os possíveis usos.

jppedido.png


Tá sobrando só em AbreArquivos(), e em comando ADO.

Porque já não apagar o uso em AbreArquivos()?
Ter o arquivo aberto não é problema, o problema é precisar dele e ele NÃO estar aberto.
Então essa vai ser a parte final.

É ter o aplicativo sempre funcionando... não importa se terminei ou não, ou se vou trocar no cliente ou não.
A qualquer momento, se precisar instalar o aplicativo por algum erro... vou poder instalar.

Meu modo de trabalho

MensagemEnviado: 19 Jun 2020 13:24
por JoséQuintas
30/12/2019  21:50               163 cthisto.dbf
30/12/2019  21:50               259 jpbaauto.dbf
30/12/2019  21:52               291 jprefcta.dbf
30/12/2019  21:50               355 ctlotes.dbf
30/12/2019  21:50               387 ctlanca.dbf
30/12/2019  21:50               483 jpcontabil.dbf
03/03/2020  02:44             2.799 jpfiscal.DBF
30/12/2019  21:50             5.335 jpempresa.dbf
18/06/2020  14:10             5.923 jpnumero.dbf
30/12/2019  21:50             6.691 ctplano.dbf
30/12/2019  21:52             7.416 jpuf.dbf
30/12/2019  21:50             7.839 jpbagrup.dbf
18/06/2020  14:10            10.278 jpconfi.dbf
30/12/2019  21:52           118.525 jptabel.dbf
26/05/2020  10:07           273.170 jpsenha.dbf
30/12/2019  21:50           301.112 jpdolar.dbf
08/06/2020  17:11           478.728 jppreco.DBF
03/03/2020  02:33         1.254.539 jpcidade.dbf
29/05/2020  11:56         1.254.826 jppretab.DBF
18/06/2020  14:12         1.373.222 jpitem.DBF
30/03/2020  06:42         2.264.308 jpmdfcab.DBF
30/03/2020  06:41         5.929.799 jpmdfdet.DBF
16/06/2020  12:09        11.004.331 jpcadastro.DBF
08/06/2020  16:59        16.290.814 jpbancario.dbf
18/06/2020  14:12       163.474.850 JPPEDIDO.DBF
              25 arquivo(s)    204.066.443 bytes


Muito trabalho ainda pela frente, apesar de estar eliminando o maior que restava, pra maioria dos clientes.

Dentre esses, o CONTABIL é o que mais vai dar trabalho, porque representa uma aplicação inteira, muitos fontes, apesar de mais simples que todo resto.
Ou talvez o fiscal.... Se eu deixar só o SPED e apagar tudo que é relatório, vai simplificar.

Meu modo de trabalho

MensagemEnviado: 21 Jun 2020 05:46
por JoséQuintas
falta.png


Bela mudança.
Apesar de tudo, NÃO faz sentido precisar do cadastro de clientes e de produtos em DBF.
Mas.... aí também entra uma parte que comentei: o FISCAL, contendo muita coisa relativamente fora de uso, ainda precisa deles.

Pensando bem... talvez compense criar temporários locais, só pra manter alguns funcionando até decidir o que fazer....

Meu modo de trabalho

MensagemEnviado: 27 Jun 2020 11:11
por JoséQuintas
Pra completar o cerco sobre nota fiscal/estoque/financeiro/notas/etc. falta eliminar produtos e cadastros
Não parece, mas esses menores podem dar mais trabalho do que os grandes.

cadastros.png


produtos.png


Vou pesquisando aonde falta, e escolhendo aonde mexer primeiro.
Fazer o que....

Meu modo de trabalho

MensagemEnviado: 30 Jun 2020 15:10
por JoséQuintas
Situação anterior: linhas com jpcadastro-> 230 e linhas com jpitem-> 191
Situação de hoje: linhas com jpcadastro-> 166 e linhas com jpitem -> 172

Não estou seguindo uma ordem específica, nem me limitando a esses dois DBFs.
Apenas dou uma olhada aonde tem, e se vou querer mexer em algum fonte.
Hoje, por exemplo, alterei o livro fiscal de entradas/saídas totalmente pra MySQL, eliminando dele o uso do jpcadastro.dbf, jpfiscal.dbf e algum outro.
Apesar do livro fiscal estar praticamente fora de uso, decidi manter.

E assim vai indo, um pouquinho por vez....
Acho que até o final do ano acaba tudo.

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 15:12
por JoséQuintas
Uma alteração interessante de agora:

   WITH OBJECT cnSQL
      :ExecuteCmd( "DELETE FROM JPNOTFIS WHERE NFFILIAL = " + NumberSQL( nIdFilial ) + " AND NFNOTFIS = " + NumberSQL( nNumNotFis ) )
      :cSQL := "SELECT JPPEDIDO.*" + ;
         " FROM JPPEDIDO" + ;
         " WHERE IDPEDIDO = " + NumberSQL( nIdPedido )
      :Execute()
      ...
      IF ! "SEMFIN" $ ReacaoJPTRANSA( :Number( "PDTRANSA" ) )
         FOR nCont := 1 TO Len( oParcelas )
            :QueryCreate()
...
            :QueryAdd( "FIPORTADOR", StrZero( nIdPortador, 6 ) )
            Encontra( StrZero( :Number( "PDCADASTRO" ), 6 ), "jpcadastro", "numlan" ) // Posiciona cliente
            :QueryAdd( "FIVENDEDOR", StrZero( iif( :Number( "PDVENDEDOR" ) == 0, Val( jpcadastro->cdVendedor ), :Number( "PDVENDEDOR" ) ), 6 ) )
            :QueryExecuteInsert( "JPFINAN" )
         NEXT
      ENDIF
      :CloseRecordset()
   ENDWITH


O detalhe é no final: pode gravar o vendedor do cliente, mas depende do dbf aberto e posicionado

Solução simples:
Se no MySQL está tudo disponível... e está usando um comando SQL.... só pegar pelo comando SQL.
      :cSQL := "SELECT JPPEDIDO.*, JPCADASTRO.CDVENDEDOR" + ;
         " FROM JPPEDIDO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
         " WHERE IDPEDIDO = " + NumberSQL( nIdPedido )
...
            :QueryAdd( "FIVENDEDOR", StrZero( iif( :Number( "PDVENDEDOR" ) == 0, :Number( "CDVENDEDOR" ), :Number( "PDVENDEDOR" ) ), 6 ) )
            :QueryExecuteInsert( "JPFINAN" )


Pronto, não precisa mais do DBF pra isso.
Quando alterei antes não percebi isso, mas agora está resolvido.
Conforme vou alterando, as coisas vão ficando mais "visíveis".
E assim vou eliminando a leitura de jpcadastro.dbf

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 15:49
por JoséQuintas
Mas nem todas são simples assim, cheguei numa geração de arquivo Sintegra.
Este está quase totalmente em DBF.
Estou eliminando só cadastro, mas.... posso eliminar TODOS os DBFs deste fonte de uma vez.

É nessas horas que tenho 3 opções:

- deixar pra depois, afinal ninguém mais usa isso
- alterar só o uso do cadastro pra MySQL
- alterar tudo de uma vez pra MySQL
- deixar pra decidir depois, e ver as outras alterações primeiro

Mas é um bom exemplo do que comento sempre: tudo depende dos fontes e de nós
Por mais que se crie um roteiro de trabalho, vamos alterando o roteiro conforme o que vamos encontrando pela frente nos fontes.
E também conforme vamos nos sentindo confortáveis em mexer.

No começo da minha migração, eu alteraria só a parte de cadastro, que é o foco atual, mas agora, tô mais pra alterar o fonte inteiro de uma vez.
Todos os arquivos envolvidos estão em gravação dupla DBF+MySQL, então isso é possível.
Só tentar confirmar primeiro, se realmente é o que parece...

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 17:33
por Itamar M. Lins Jr.
Ola!
deixar pra depois, afinal ninguém mais usa isso

Eu também pensei só que é obrigatório ainda!
Tem até uma postagem lá no forum de Legislação...

Saudações,
Itamar M. Lins Jr.

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 18:35
por JoséQuintas
Vixe...
Ainda bem que optei por deixar, e já está totalmente em MySQL !!!!!

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 18:58
por Itamar M. Lins Jr.
Ola!
Sintegra em 2020!
http://www.pctoledo.com.br/forum/viewtopic.php?f=20&t=24033

Saudações,
Itamar M. Lins Jr.

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 21:11
por JoséQuintas
Faltam só dois fontes, pra acabar com o jpcadastro.dbf.
Mas... como eu disse... deixei os mais complicados para o final.
Não sei qual dos dois é pior.... kkkkk

Além de já usar comando SQL, ainda tem dois temporários até chegar no relatório, que tem opções diferentes.
Quem mandou fazer assim.... kkkkk

Mas entra o que falei: deixando os mais difíceis pro final, "talvez" com o conhecimento adquirido com os outros, este fique mais fácil.
A tela do computador chega a ficar pequena pra entender as "tranqueiras" do fonte.

STATIC FUNCTION Imprime()

   LOCAL nCont, mTmpFile, oPDF, aTransacaoList, cTexto, nKey, cTxt, oFile
   LOCAL nNumTransa, mRelCliente, mRelVendedor, mRelItem, mRelRaiz, mCampos
   LOCAL mComissao, mCabeca, mChaves, mRaizCnpj, mStruOk
   LOCAL nTotMargem := Array(5), nTotComissao := Array(5 ), nTotCusto := Array(5), nTotVenda := Array(5)
   LOCAL nTotQtde := Array(5), nTotEmbalagem := Array(5), cTotChave := Array(5), nTotRecNo := Array(5)
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   mTmpFile := { MyTempFile( "dbf" ), MyTempFile( "cdx" ), MyTempFile( "dbf" ), MyTempFile( "cdx" ) }

   FOR nCont = 1 TO 4
      fErase( mTmpFile[ nCont ] )
   NEXT

   mStruOk := { ;
      { "CHAVE1",     "C", 50, 0 }, ;
      { "CHAVE2",     "C", 50, 0 }, ;
      { "CHAVE3",     "C", 50, 0 }, ;
      { "CHAVE4",     "C", 50, 0 }, ;
      { "PDCADASTRO", "C", 6, 0 }, ;
      { "CDCNPJ",     "C", 10, 0 }, ;
      { "PDVENDEDOR", "C", 6, 0 }, ;
      { "IPPRODUTO",  "C", 6, 0 }, ;
      { "DESCITEM",   "C", 30, 0 }, ;
      { "PDTRANSA",   "C", 6, 0 }, ;
      { "NF",         "C", 9, 0 }, ;
      { "OBS",        "C", 50, 0 }, ;
      { "DATEMI",     "D", 8, 0 }, ;
      { "IPQTDE",     "N", 14, 0 }, ;
      { "QTDEMB",     "N", 14, 0 }, ;
      { "IPVALCUS",   "N", 15, 2 }, ;
      { "IPVALNOT",   "N", 15, 2 }, ;
      { "MARGEM",     "N", 15, 2 }, ;
      { "PMARGEM",    "N", 8, 3 }, ;
      { "VALCOM",     "N", 15, 2 }, ;
      { "PERCOM",     "N", 4, 1 }, ;
      { "NIVEL",      "C", 1, 0 } } // usado para totalizar

   dbCreate( mTmpFile[ 1 ], mStruOk )
   dbCreate( mTmpFile[ 3 ], mStruOk )

   SELECT 0
   USE ( mTmpFile[ 3 ] ) ALIAS temp2
   SELECT 0
   USE ( mTmpFile[ 1 ] ) ALIAS Temp

   WITH OBJECT cnSQL
      :cSQL := "SELECT IDPEDIDO, PDCADASTRO, PDVENDEDOR, PDTRANSA," + ;
         " PDPEDCLI," + ;
         " IF( JPNOTFIS.NFNOTFIS IS NULL, JPPEDIDO.PDDATEMI, JPNOTFIS.NFDATEMI ) AS DATEMI," + ;
         " IF( JPNOTFIS.NFNOTFIS IS NULL, JPPEDIDO.PDPEDCLI, JPNOTFIS.NFNOTFIS ) AS NOTFIS," + ;
         " JPTRANSA.TRDESCRI AS TRANOME, JPTRANSA.TRREACAO AS REACAO," + ;
         " JPNOTFIS.NFNOTFIS AS NOTFIS," + ;
         " JPITPED.IPPRODUTO, JPITPED.IPQTDE, JPITPED.IPVALCUS, JPITPED.IPVALNOT," + ;
         " JPITEM.IEPRODEP, JPITEM.IEQTDCOM," + ;
         " JPCADASTRO.CDCNPJ" + ;
         " FROM JPPEDIDO" + ;
         " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
         " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
         " WHERE PDCONF = 'S'" + ;
         " AND PDTRANSA IN (" + ;
            " SELECT IDTRANSA FROM JPTRANSA AS LISTAA WHERE "
      IF nOpcCompraVenda == 1
         :cSQL += " TRREACAO LIKE '%VENDA%'"
      ELSEIF nOpcCompraVenda == 2
         :cSQL += " TRREACAO LIKE '%COMPRA%'"
      ELSE
         :cSQL += " ( TRREACAO LIKE '%COMPRA%' OR TRREACAO LIKE '%VENDA%' )"
      ENDIF
      IF nOpcDevol == 2
         :cSQL += " AND TRREACAO LIKE '%DEV%'"
      ENDIF
      :cSQL += " )"
      IF nOpcVendedor == 2
         :cSQL += " AND PDVENDEDOR = " + NumberSQL( mIdVendedor )
      ENDIF
      IF nOpcCadastro == 2
         :cSQL += " AND PDCADASTRO IN (" + ;
            " SELECT IDCADASTRO FROM JPCADASTRO AS LISTA WHERE LEFT( CDCNPJ, 11 ) = " + ;
            " ( SELECT LEFT( CDCNPJ, 11 ) FROM JPCADASTRO AS ESCOLHIDO WHERE IDCADASTRO = " + NumberSQL( nIdCadastro ) + " ) )"
      ENDIF
      IF nOpcProduto == 2
         :cSQL += " AND JPITEM.IDPRODUTO = " + NumberSQL( nIdProduto )
      ENDIF
      IF nOpcProDep == 2
         :cSQL += " AND JPITEM.IEPRODEP = " + NumberSQL( nIdProDep )
      ENDIF
      :cSQL += " AND ( JPNOTFIS.NFNOTFIS IS NOT NULL OR NOT" + ;
         " JPPEDIDO.PDTRANSA IN ( SELECT IDTRANSA FROM JPTRANSA AS LISTAB WHERE TRREACAO LIKE '%N+%' OR TRREACAO LIKE '%N-%' ) )" + ;
         " AND IF( JPNOTFIS.NFNOTFIS IS NULL, PDDATEMI, NFDATEMI ) BETWEEN CAST( " + DateSQL( dDataInicial ) + " AS DATE )" + ;
               " AND CAST( " + DateSQL( dDataFinal ) + " AS DATE )"
      :Execute()
      DO WHILE ! :Eof()
         GrafProc()
         Inkey()
         Encontra( StrZero( :Number( "PDCADASTRO" ), 6 ), "jpcadastro", "numlan" )
         Encontra( StrZero( :Number( "IPPRODUTO" ), 6 ), "jpitem", "item" )
         SELECT temp
         RecAppend()
         REPLACE ;
            temp->pdCadastro WITH StrZero( :Number( "PDCADASTRO" ), 6 ), ;
            temp->pdVendedor WITH StrZero( :Number( "PDVENDEDOR" ), 6 ), ;
            temp->ipProduto  WITH StrZero( :Number( "IPPRODUTO" ), 6 ), ;
            temp->cdCnpj     WITH :String( "CDCNPJ", 10 ), ;
            temp->pdTransa   WITH StrZero( :Number( "PDTRANSA" ), 6 ), ;
            temp->ipQtde     WITH :Number( "IPQTDE" ), ;
            temp->QtdEmb     WITH :Number( "IPQTDE" ) * Max( 1, :Number( "IEQTDCOM" ) ), ;
            temp->ipValCus   WITH :Number( "IPVALCUS" ), ;
            temp->ipValNot   WITH :Number( "IPVALNOT" ), ;
            temp->Obs        WITH :String( "TRANOME" ), ;
            temp->NF         WITH StrZero( :Number( "NOTFIS" ), 9 ), ;
            temp->DatEmi     WITH :Date( "DATEMI" ), ;
            temp->Margem     WITH :Number( "IPVALNOT" ) - :Number( "IPVALCUS" )
         IF "DEV" $ :String( "REACAO" )
            REPLACE ;
               temp->ipQtde   WITH -temp->ipQtde, ;
               temp->QtdEmb   WITH -temp->QtdEmb, ;
               temp->ipValCus WITH -temp->ipValCus, ;
               temp->ipValNot WITH -temp->ipValNot
         ENDIF
         IF temp->ipValNot <> 0 // Evita "estouro"
            mMargem := Max( Min( ( temp->ipValNot - temp->ipValCus ) / temp->ipValNot * 100, 999 ), -99.9 )
            REPLACE temp->PMargem WITH mMargem
         ELSE
            mMargem := 0
         ENDIF
         IF ADORecCount( "JPCOMISSAO", "CMVENDEDOR = " + NumberSQL( :Number( "PDVENDEDOR" ) ) + " AND CMPRODEP = " + NumberSQL( :Number( "IEPRODEP" ) ) ) != 0
            mComissao := ADOField( "CMVALOR", "N", "JPCOMISSAO", "CMVENDEDOR = " + NumberSQL( :Number( "PDVENDEDOR" ) ) + " AND CMPRODEP = " + NumberSQL( :Number( "IEPRODEP" ) ) )
         ELSE
            mComissao := ADOField( "VDCOMISSAO", "N", "JPVENDEDOR", "IDVENDEDOR = " + NumberSQL( :Number( "PDVENDEDOR" ) ) )
         ENDIF
         REPLACE ;
            temp->ValCom WITH temp->ipValNot * mComissao / 100, ;
            temp->PerCom WITH mComissao
         RecUnlock()
         :MoveNext()
      ENDDO
   ENDWITH

   mCabeca := { "VENDEDOR", "ITEM", "CNPJ", "CLIENTE" }

   DO CASE
   CASE nOpcOrdem == 1 ; mChaves := { 1, 2, 3, 4 }
   CASE nOpcOrdem == 2 ; mChaves := { 2, 1, 3, 4 }
   CASE nOpcOrdem == 3 ; mChaves := { 3, 4, 2, 1 }
   CASE nOpcOrdem == 4 ; mChaves := { 1, 3, 4, 2 }
   CASE nOpcOrdem == 5 ; mChaves := { 2, 3, 4, 1 }
   CASE nOpcOrdem == 6 ; mChaves := { 3, 4, 1, 2 }
   ENDCASE

   SELECT temp
   INDEX ON temp->cdCnpj + Str( 1000000 - RecNo(), 7 ) TO ( mTmpFile[ 2 ] )
   GOTO TOP
   aTransacaoList := {}
   DO WHILE ! Eof()
      mRaizCnpj := temp->cdCnpj
      Encontra( temp->pdCadastro, "jpcadastro", "numlan" ) // pra pegar desc cnpj
      mRelRaiz := iif( Eof(), "*RAIZ CNPJ* " + temp->cdCnpj, Pad( jpcadastro->cdNome, 40 ) + temp->cdCnpj )
      DO WHILE temp->cdCnpj == mRaizCnpj .AND. ! Eof()
         Encontra( temp->pdCadastro, "jpcadastro", "numlan" )
         mRelCliente := iif( Empty( jpcadastro->cdNome ), "*CLIENTE* " + temp->pdCadastro, Pad( jpcadastro->cdNome, 40 ) + temp->pdCadastro )
         mRelVendedor := Pad( ADOField( "VDDESCRI", "C", "JPVENDEDOR", "IDVENDEDOR=" + NumberSQL( temp->pdVendedor ) ), 40 ) + temp->pdVendedor
         Encontra(temp->ipProduto,"jpitem","item")
         mRelItem := iif( Empty( jpitem->ieDescri ), "*ITEM* " + temp->ipProduto, Pad( jpitem->ieDescri, 40 ) + temp->ipProduto )
         mCampos := { mRelVendedor, mRelItem, mRelRaiz, mRelCliente }
         nNumTransa := 0
         FOR nCont = 1 TO Len( aTransacaoList )
            IF aTransacaoList[ nCont, 1 ] == temp->pdTransa
               nNumTransa := nCont
               EXIT
            ENDIF
         NEXT
         IF nNumTransa == 0
            AAdd( aTransacaoList, { temp->pdTransa, 0 } )
            nNumTransa := Len( aTransacaoList )
         ENDIF
         aTransacaoList[ nNumTransa, 2 ] += temp->ipValNot
         RecLock()
         REPLACE ;
            temp->Chave1 WITH mCampos[ mChaves[ 1 ] ], ;
            temp->Chave2 WITH mCampos[ mChaves[ 2 ] ], ;
            temp->Chave3 WITH mCampos[ mChaves[ 3 ] ], ;
            temp->Chave4 WITH mCampos[ mChaves[ 4 ] ]
         RecUnlock()
         SKIP
      ENDDO
   ENDDO
   SET INDEX TO

   SELECT Temp
   INDEX ON temp->Chave1 + temp->Chave2 + temp->Chave3 + temp->Chave4 TO ( mTmpFile[ 4 ] )
   GOTO TOP
   STORE 0 To nTotQtde[ 1 ], nTotCusto[ 1 ], nTotVenda[ 1 ], nTotMargem[ 1 ], nTotComissao[ 1 ], nTotEmbalagem[ 1 ]
   SELECT temp2
   RecAppend()
   RecUnlock()
   SELECT temp
   GOTO TOP
   DO WHILE ! Eof()
      Inkey()
      STORE 0 To nTotQtde[ 2 ], nTotCusto[ 2 ], nTotVenda[ 2 ], nTotMargem[ 2 ], nTotComissao[ 2 ], nTotEmbalagem[ 2 ]
      cTotChave[ 2 ] := temp->Chave1
      SELECT Temp2
      RecAppend()
      REPLACE ;
         temp2->Chave1 WITH cTotChave[ 2 ], ;
         temp2->Nivel  WITH "1"
      RecUnlock()
      nTotRecNo[ 2 ] := RecNo()
      SELECT Temp
      DO WHILE ! Eof()
         Inkey()
         IF cTotChave[ 2 ] != temp->Chave1
            EXIT
         ENDIF
         STORE 0 To nTotQtde[ 3 ], nTotCusto[ 3 ], nTotVenda[ 3 ], nTotMargem[ 3 ], nTotComissao[ 3 ], nTotEmbalagem[ 3 ]
         cTotChave[ 3 ] := temp->Chave2
         SELECT temp2
         RecAppend()
         REPLACE ;
            temp2->Chave1 WITH cTotChave[ 2 ], ;
            temp2->Chave2 WITH cTotChave[ 3 ], ;
            temp2->Nivel  WITH  "2"
         RecUnlock()
         nTotRecNo[ 3 ] := RecNo()
         SELECT temp
         DO WHILE ! Eof()
            Inkey()
            IF cTotChave[ 2 ] != temp->Chave1
               EXIT
            ENDIF
            IF cTotChave[ 3 ] != temp->Chave2
               EXIT
            ENDIF
            STORE 0 TO nTotQtde[ 4 ], nTotCusto[ 4 ], nTotVenda[ 4 ], nTotMargem[ 4 ], nTotComissao[ 4 ], nTotEmbalagem[ 4 ]
            cTotChave[ 4 ] := temp->Chave3
            SELECT temp2
            RecAppend()
            REPLACE ;
               temp2->Chave1 WITH cTotChave[ 2 ], ;
               temp2->Chave2 WITH cTotChave[ 3 ], ;
               temp2->Chave3 WITH cTotChave[ 4 ], ;
               temp2->Nivel  WITH  "3"
            RecUnlock()
            nTotRecNo[ 4 ] := RecNo()
            SELECT temp
            DO WHILE ! Eof()
               Inkey()
               IF cTotChave[ 2 ] != temp->Chave1
                  EXIT
               ENDIF
               IF cTotChave[ 3 ] != temp->Chave2
                  EXIT
               ENDIF
               IF cTotChave[ 4 ] != temp->Chave3
                  EXIT
               ENDIF
               STORE 0 To nTotQtde[ 5 ], nTotCusto[ 5 ], nTotVenda[ 5 ], nTotMargem[ 5 ], nTotComissao[ 5 ], nTotEmbalagem[ 5 ]
               cTotChave[ 5 ] := temp->Chave4
               SELECT temp2
               RecAppend()
               REPLACE ;
                  temp2->Chave1 WITH cTotChave[ 2 ], ;
                  temp2->Chave2 WITH cTotChave[ 3 ], ;
                  temp2->Chave3 WITH cTotChave[ 4 ], ;
                  temp2->Chave4 WITH cTotChave[ 5 ], ;
                  temp2->Nivel  WITH "4"
               RecUnlock()
               nTotRecNo[ 5 ] := RecNo()
               SELECT temp
               DO WHILE ! Eof()
                  Inkey()
                  IF cTotChave[ 2 ] != temp->Chave1
                     EXIT
                  ENDIF
                  IF cTotChave[ 3 ] != temp->Chave2
                     EXIT
                  ENDIF
                  IF cTotChave[ 4 ] != temp->Chave3
                     EXIT
                  ENDIF
                  IF cTotChave[ 5 ] != temp->Chave4
                     EXIT
                  ENDIF
                  SELECT temp2
                  RecAppend()
                  FOR nCont = 1 TO FCount()
                     FieldPut( nCont, temp->( FieldGet( nCont ) ) )
                  NEXT
                  REPLACE temp2->Nivel WITH  "5"
                  SELECT temp
                  nTotQtde[ 5 ]      += temp->ipQtde
                  nTotEmbalagem[ 5 ] += temp->QtdEmb
                  nTotCusto[ 5 ]     += temp->ipValCus
                  nTotVenda[ 5 ]     += temp->ipValNot
                  nTotMargem[ 5 ]    += temp->Margem
                  nTotComissao[ 5 ]  += temp->ValCom
                  SKIP
               ENDDO
               SELECT temp2
               GOTO ( nTotRecNo[ 5 ] )
               RecLock()
               REPLACE ;
                  temp2->ipQtde   WITH nTotQtde[ 5 ], ;
                  temp2->QtdEmb   WITH nTotEmbalagem[ 5 ], ;
                  temp2->ipValCus WITH nTotCusto[ 5 ], ;
                  temp2->ipValNot WITH nTotVenda[ 5 ], ;
                  temp2->Margem   WITH nTotMargem[ 5 ], ;
                  temp2->ValCom   WITH nTotComissao[ 5 ], ;
                  temp2->PMargem  WITH Max( Min( ( nTotVenda[ 5 ] - nTotCusto[ 5 ] ) / nTotVenda[ 5 ] * 100, 999 ), -99.9 )
               RecUnlock()
               SELECT temp
               nTotQtde[ 4 ]      += nTotQtde[ 5 ]
               nTotEmbalagem[ 4 ] += nTotEmbalagem[ 5 ]
               nTotCusto[ 4 ]     += nTotCusto[ 5 ]
               nTotVenda[ 4 ]     += nTotVenda[ 5 ]
               nTotMargem[ 4 ]    += nTotMargem[ 5 ]
               nTotComissao[ 4 ]  += nTotComissao[ 5 ]
            ENDDO
            SELECT temp2
            GOTO ( nTotRecNo[ 4 ] )
            RecLock()
            REPLACE ;
               temp2->ipQtde   WITH nTotQtde[ 4 ], ;
               temp2->QtdEmb   WIth nTotEmbalagem[ 4 ], ;
               temp2->ipValCus WITH nTotCusto[ 4 ], ;
               temp2->ipValNot WITH nTotVenda[ 4 ], ;
               temp2->Margem   WITH nTotMargem[ 4 ], ;
               temp2->ValCom   WITH nTotComissao[ 4 ], ;
               temp2->PMargem  WITH Max( Min( ( nTotVenda[ 4 ] - nTotCusto[ 4 ] ) / nTotVenda[ 4 ] * 100, 999 ), -99.9 )
            RecUnlock()
            SELECT temp
            nTotQtde[ 3 ]      += nTotQtde[ 4 ]
            nTotEmbalagem[ 3 ] += nTotEmbalagem[ 4 ]
            nTotCusto[ 3 ]     += nTotCusto[ 4 ]
            nTotVenda[ 3 ]     += nTotVenda[ 4 ]
            nTotMargem[ 3 ]    += nTotMargem[ 4 ]
            nTotComissao[ 3 ]  += nTotComissao[ 4 ]
         ENDDO
         SELECT temp2
         GOTO ( nTotRecNo[ 3 ] )
         RecLock()
         REPLACE ;
            temp2->ipQtde   WITH nTotQtde[ 3 ], ;
            temp2->QtdEmb   WITH nTotEmbalagem[ 3 ], ;
            temp2->ipValCus WITH nTotCusto[ 3 ], ;
            temp2->ipValNot WITH nTotVenda[ 3 ], ;
            temp2->Margem   WITH nTotMargem[ 3 ], ;
            temp2->ValCom   WITH nTotComissao[ 3 ], ;
            temp2->PMargem  WITH Max( Min( ( nTotVenda[ 3 ] - nTotCusto[ 3 ] ) / nTotVenda[ 3 ] * 100, 999 ), -99.9 )
         RecUnlock()
         SELECT temp
         nTotQtde[ 2 ]      += nTotQtde[ 3 ]
         nTotEmbalagem[ 2 ] += nTotEmbalagem[ 3 ]
         nTotCusto[ 2 ]     += nTotCusto[ 3 ]
         nTotVenda[ 2 ]     += nTotVenda[ 3 ]
         nTotMargem[ 2 ]    += nTotMargem[ 3 ]
         nTotComissao[ 2 ]  += nTotComissao[ 3 ]
      ENDDO
      SELECT temp2
      GOTO ( nTotRecNo[ 2 ] )
      RecLock()
      REPLACE ;
         temp2->ipQtde   WITH nTotQtde[ 2 ], ;
         temp2->QtdEmb   WITH nTotEmbalagem[ 2 ], ;
         temp2->ipValCus WITH nTotCusto[ 2 ], ;
         temp2->ipValNot WITH nTotVenda[ 2 ], ;
         temp2->Margem   WITH nTotMargem[ 2 ], ;
         temp2->ValCom   WITH nTotComissao[ 2 ], ;
         temp2->PMargem  WITH Max( Min( ( nTotVenda[ 2 ] - nTotCusto[ 2 ] ) / nTotVenda[ 2 ] * 100, 999 ), -99.9 )
      RecUnlock()
      SELECT temp
      nTotQtde[ 1 ]      += nTotQtde[ 2 ]
      nTotEmbalagem[ 1 ] += nTotEmbalagem[ 2 ]
      nTotCusto[ 1 ]     += nTotCusto[ 2 ]
      nTotVenda[ 1 ]     += nTotVenda[ 2 ]
      nTotMargem[ 1 ]    += nTotMargem[ 2 ]
      nTotComissao[ 1 ]  += nTotComissao[ 2 ]
   ENDDO
   SELECT temp2
   GOTO TOP
   RecLock()
   REPLACE ;
      temp2->ipQtde   WITH nTotQtde[ 1 ], ;
      temp2->QtdEmb   WITH nTotEmbalagem[ 1 ], ;
      temp2->ipValCus WITH nTotCusto[ 1 ], ;
      temp2->ipValNot WITH nTotVenda[ 1 ], ;
      temp2->Margem   WITH nTotMargem[ 1 ], ;
      temp2->ValCom   WITH nTotComissao[ 1 ], ;
      temp2->PMargem  WITH Max( Min( ( nTotVenda[ 1 ] - nTotCusto[ 1 ] ) / nTotVenda[ 1 ] * 100, 999 ), -99.9 )
   RecUnlock()
   SELECT temp
   USE

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey := 0
   oPDF:acHeader := { "COMPRAS/VENDAS NO PERIODO" }
   cTexto := "Periodo:" + iif( nOpcData == 1, "Tudo", Dtoc( dDataInicial ) + " a " + Dtoc( dDataFinal ) )
   cTexto += " Tipo:" + acTxtCompraVenda[ nOpcCompraVenda ]
   cTexto += " " + acTxtDetalhe[ nOpcDetalhe ]
   cTexto += " Depto:" + iif( nOpcProDep == 1, "Todos", NumberSQL( nIdProDep ) + "-" + Trim( AUXPRODEPClass():Descricao( nIdProDep ) ) )
   Encontra( StrZero( nIdProduto, 6 ), "jpitem", "item" )
   cTexto += " Produto:" + iif( nOpcProduto==1, "Todos", NumberSQL( nIdProduto ) + "-" + Trim( jpitem->ieDescri ) )
   cTexto += " Vendedor:" + iif( nOpcVendedor == 1, "Todos", NumberSQL( mIdVendedor ) + "-" + ADOField( "VDDESCRI", "C", "JPVENDEDOR", "IDVENDEDOR=" + NumberSQL( mIdVendedor ) ) )
   AAdd( oPDF:acHeader, cTexto )
   cTexto := Pad( mCabeca[ mChaves[ 1 ] ] + "/" + mCabeca[ mChaves[ 2 ] ] + "/" + mCabeca[ mChaves[ 3 ] ], 50 ) + ;
      " ----QTD---- --QTD.NF.-- " + Iif( nOpcCusto == 2, "---VL.CUSTO--- ", "" ) + ;
      "---VL.PEDIDO-- " + Iif( nOpcCusto == 2, "---VL.MARGEM-- %MARGEM ", "" ) + "    COMISSAO/%"
   AAdd( oPDF:acHeader, cTexto )
   oPDF:PageHeader()
   STORE Chr(205) TO cTotChave[ 2 ], cTotChave[ 3 ], cTotChave[ 4 ]
   SELECT temp2
   GOTO TOP
   SKIP // Primeiro registro total
   DO WHILE nKey != K_ESC .AND. ! Eof()
      GrafProc()
      nKey := Inkey()
      IF temp2->Nivel > Str( nOpcDetalhe, 1 )
         SKIP
         LOOP
      ENDIF
      oPDF:MaxRowTest()
      cTxt := ""
      IF temp2->Nivel == "1" // cTotChave[ 2 ] != temp2->Chave1
         oPDF:nRow += 1
         oPDF:MaxRowTest()
         cTxt := Pad( temp2->Chave1, 40 )
         cTotChave[ 2 ] := temp2->Chave1
         cTotChave[ 3 ] := Chr(205)
         cTotChave[ 4 ] := Chr(205)
         cTotChave[ 5 ] := Chr(205)
      ELSEIF temp2->Nivel == "2" // cTotChave[ 3 ] != temp2->Chave2 .AND. nOpcDetalhe > 1
         oPDF:MaxRowTest()
         cTotChave[ 3 ] := Pad( temp2->Chave2, 40 )
         cTxt := Space(2) + temp2->Chave2
         cTotChave[ 4 ] := Chr(205)
         cTotChave[ 5 ] := Chr(205)
      ELSEIF temp2->Nivel == "3" // cTotChave[ 4 ] != temp2->Chave3 .AND. nOpcDetalhe > 2
         cTotChave[ 4 ] := temp2->Chave3
         oPDF:MaxRowTest()
         cTxt := Space(4) + Pad( temp2->Chave3, 40 )
         cTotChave[ 5 ] := Chr(205)
      ELSEIF temp2->Nivel == "4" // cTotChave[ 5 ] != temp2->Chave4 .AND. nOpcDetalhe > 3
         cTotChave[ 5 ] := temp2->Chave4
         oPDF:MaxRowTest()
         cTxt := Space(6) + Pad( temp2->Chave4, 40 )
      ELSEIF temp2->Nivel == "5"
         cTxt := Space(22) + temp2->Nf + " " + Dtoc( temp2->DatEmi )
      ENDIF
      cTxt := Pad( cTxt, 50 )
      cTxt += " " + Transform( temp2->QtdEmb, PicVal(9) )
      cTxt += " " + Transform( temp2->ipQtde, PicVal(9) )
      IF nOpcCusto == 2
         cTxt += " " + Transform( temp2->ipValCus, PicVal(11,2) )
      ENDIF
      cTxt += " " + Transform( temp2->ipValNot, PicVal(11,2) )
      IF temp2->Nivel < "3"
         oPDF:DrawZebrado(2)
      ELSE
         oPDF:DrawZebrado(1)
      ENDIF
      IF nOpcCusto == 2
         cTxt += " " + Transform( temp2->Margem, PicVal(11,2) )
         cTxt += " " + Transform( temp2->PMargem, "@E 999.9" )
      ENDIF
      IF nOpcComComissao == 2
         cTxt += " " + Transform( temp2->ValCom, PicVal( 8, 2 ) )
         cTxt += " " + "(" + Transform( temp2->PerCom, "@Z 999.999" ) + ")"
      ENDIF
      oPDF:DrawText( oPDF:nRow, 0, cTxt )
      IF nOpcDetalhe == 5 .AND. ! Empty( temp2->Obs )
         oPDF:nRow += 1
         cTxt := " " + temp2->Obs
         cTxt := Padl( cTxt, oPDF:MaxCol() )
         oPDF:DrawText( oPDF:nRow, 0, cTxt )
      ELSE
      ENDIF
      oPDF:nRow += 1
      SKIP
   ENDDO
   GOTO TOP
   oPDF:nRow += 2
   oPDF:MaxRowTest()
   cTxt := Pad( "***TOTAIS***", 50 )
   cTxt += " " + Transform( temp2->QtdEmb, PicVal(9) )
   cTxt += " " + Transform( temp2->ipQtde, PicVal(9) )
   IF nOpcCusto == 2
      cTxt += " " + Transform( temp2->ipValCus, PicVal(11,2) )
   ENDIF
   cTxt += " " + Transform( temp2->ipValNot, PicVal(11,2) )
   IF nOpcCusto == 2
      cTxt += " " + Transform( temp2->Margem, PicVal(11,2) )
      cTxt += " " + Transform( temp2->PMargem, "@E 999.9" )
   ENDIF
   cTxt += " " + Transform( temp2->ValCom, PicVal(8,2) )
   oPDF:DrawText( oPDF:nRow, 0, cTxt )
   IF nOpcAnalise == 2
      oPDF:nRow += 2
      oPDF:MaxRowTest()
      FOR nCont = 1 TO Len( aTransacaoList )
         oPDF:MaxRowTest()
         cTxt := aTransacaoList[ nCont, 1 ]
         cTxt += " " + Transform( aTransacaoList[ nCont, 2 ], PicVal( 14, 2 ) )
         cTxt += " " + DescricaoJPTRANSA( aTransacaoList[ nCont, 1 ] )
         oPDF:DrawText( oPDF:nRow, 0, cTxt )
         oPDF:nRow += 1
      NEXT
   ENDIF
   SELECT temp2
   USE
   FOR EACH oFile IN mTmpFile
      fErase( oFile )
   NEXT
   oPDF:End()

   RETURN NIL


Até seria mais fácil gravando o que precisa no temporário.... mas sabe como é... tentar eliminar fonte Harbour.

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 21:15
por JoséQuintas
Nessas horas eu vou rodeando primeiro....
Por exemplo aqui:

         nNumTransa := 0
         FOR nCont = 1 TO Len( aTransacaoList )
            IF aTransacaoList[ nCont, 1 ] == temp->pdTransa
               nNumTransa := nCont
               EXIT
            ENDIF
         NEXT
         IF nNumTransa == 0
            AAdd( aTransacaoList, { temp->pdTransa, 0 } )
            nNumTransa := Len( aTransacaoList )
         ENDIF


O primeiro FOR/NEXT pode ser eliminado com o uso de hb_AScan().
Já reduz um pouquinho o fonte.....

         nNumTransa := hb_ASCan( aTransacaoList, { | e | e[ 1 ] == temp->pdTransa } )
         IF nNumTransa == 0
            AAdd( aTransacaoList, { temp->pdTransa, 0 } )
            nNumTransa := Len( aTransacaoList )
         ENDIF


Menos fonte pra resolver....

Meu modo de trabalho

MensagemEnviado: 03 Jul 2020 23:26
por JoséQuintas
Por enquanto, a idéia é tentar eliminar o primeiro temporário.
Para isso, procurei passar o máximo possível para o comando SQL, assim vém pronto.

      :Execute()
      DO WHILE ! :Eof()
         GrafProc()
         Inkey()
         Encontra( StrZero( :Number( "PDCADASTRO" ), 6 ), "jpcadastro", "numlan" )
         Encontra( StrZero( :Number( "IPPRODUTO" ), 6 ), "jpitem", "item" )
         SELECT temp
         RecAppend()
         REPLACE ;
            temp->pdCadastro WITH StrZero( :Number( "PDCADASTRO" ), 6 ), ;
            temp->pdVendedor WITH StrZero( :Number( "PDVENDEDOR" ), 6 ), ;
            temp->ipProduto  WITH StrZero( :Number( "IPPRODUTO" ), 6 ), ;
            temp->cdCnpj     WITH :String( "CDCNPJ", 10 ), ;
            temp->pdTransa   WITH StrZero( :Number( "PDTRANSA" ), 6 ), ;
            temp->ipQtde     WITH :Number( "FATOR" ) * :Number( "IPQTDE" ), ;
            temp->QtdEmb     WITH :Number( "FATOR" ) * :Number( "IPQTDE" ) * Max( 1, :Number( "IEQTDCOM" ) ), ;
            temp->ipValCus   WITH :Number( "FATOR" ) * :Number( "IPVALCUS" ), ;
            temp->ipValNot   WITH :Number( "FATOR" ) * :Number( "IPVALNOT" ), ;
            temp->Obs        WITH :String( "TRANOME" ), ;
            temp->NF         WITH StrZero( :Number( "NOTFIS" ), 9 ), ;
            temp->DatEmi     WITH :Date( "DATEMI" ), ;
            temp->Margem     WITH :Number( "IPVALNOT" ) - :Number( "IPVALCUS" )
            temp->ValCom     WITH :Number( "FATOR" ) * temp->ipValNot * :Number( "COMISSAO" ) / 100, ;
            temp->PerCom     WITH :Number( "COMISSAO" )
         IF temp->ipValNot <> 0 // Evita "estouro"
            mMargem := Max( Min( ( Abs( temp->ipValNot ) - Abs( temp->ipValCus ) ) / Abs( temp->ipValNot ) * 100, 999 ), -99.9 )
            REPLACE temp->PMargem WITH mMargem
         ELSE
            mMargem := 0
         ENDIF
         RecUnlock()
         :MoveNext()
      ENDDO
      :CloseRecordset()
   ENDWITH


O que sobrou é resultado de cálculo, então, talvez pelo menos seja eliminado um temporário.
Não vejo a hora de terminar tudo, mas... melhor um pouco de cada vez.

Meu modo de trabalho

MensagemEnviado: 04 Jul 2020 05:17
por JoséQuintas
Foi o que deu: ainda resta um temporário pra eliminar, mas pelo menos ficou mais rápido e um pouco menor.

STATIC FUNCTION Imprime()

   LOCAL nCont, mTmpFile, oPDF, aTransacaoList, cTexto, nKey, cTxt, oFile
   LOCAL nNumTransa, mStruOk
   LOCAL nTotMargem := Array(5), nTotComissao := Array(5 ), nTotCusto := Array(5), nTotVenda := Array(5)
   LOCAL nTotQtde := Array(5), nTotEmbalagem := Array(5), cTotChave := Array(4), nTotRecNo := Array(5)
   LOCAL aChaveList
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   mTmpFile := { MyTempFile( "dbf" ), MyTempFile( "cdx" ), MyTempFile( "dbf" ), MyTempFile( "cdx" ) }

   FOR nCont = 1 TO 4
      fErase( mTmpFile[ nCont ] )
   NEXT
   DO CASE
   CASE nOpcOrdem == 1; aChaveList := { "VENDEDOR", "PRODUTO", "GRUPOCLIENTE", "CLIENTE" }
   CASE nOpcOrdem == 2; aChaveList := { "PRODUTO", "VENDEDOR", "GRUPOCLIENTE", "CLIENTE" }
   CASE nOpcOrdem == 3; aChaveList := { "GRUPOCLIENTE", "CLIENTE", "PRODUTO", "VENDEDOR" }
   CASE nOpcOrdem == 4; aChaveList := { "VENDEDOR", "GRUPOCLIENTE", "CLIENTE", "PRODUTO" }
   CASE nOpcOrdem == 5; aChaveList := { "PRODUTO", "GRUPOCLIENTE", "CLIENTE", "VENDEDOR" }
   CASE nOpcOrdem == 6; aChaveList := { "GRUPOCLIENTE", "CLIENTE", "VENDEDOR", "PRODUTO" }
   ENDCASE

   mStruOk := { ;
      { "CHAVE1",     "C", 50, 0 }, ;
      { "CHAVE2",     "C", 50, 0 }, ;
      { "CHAVE3",     "C", 50, 0 }, ;
      { "CHAVE4",     "C", 50, 0 }, ;
      { "PDCADASTRO", "C", 6, 0 }, ;
      { "CDCNPJ",     "C", 10, 0 }, ;
      { "PDVENDEDOR", "C", 6, 0 }, ;
      { "IPPRODUTO",  "C", 6, 0 }, ;
      { "DESCITEM",   "C", 30, 0 }, ;
      { "PDTRANSA",   "C", 6, 0 }, ;
      { "NF",         "C", 9, 0 }, ;
      { "OBS",        "C", 50, 0 }, ;
      { "DATEMI",     "D", 8, 0 }, ;
      { "IPQTDE",     "N", 14, 0 }, ;
      { "QTDEMB",     "N", 14, 0 }, ;
      { "IPVALCUS",   "N", 15, 2 }, ;
      { "IPVALNOT",   "N", 15, 2 }, ;
      { "MARGEM",     "N", 15, 2 }, ;
      { "PMARGEM",    "N", 8, 3 }, ;
      { "VALCOM",     "N", 15, 2 }, ;
      { "PERCOM",     "N", 4, 1 }, ;
      { "NIVEL",      "C", 1, 0 } } // usado para totalizar

   dbCreate( mTmpFile[ 1 ], mStruOk )

   SELECT 0
   USE ( mTmpFile[ 1 ] ) ALIAS temp2

   WITH OBJECT cnSQL
      :cSQL := "SELECT IDPEDIDO, PDCADASTRO, PDVENDEDOR, PDTRANSA," + ;
         " PDPEDCLI," + ;
         " IF( JPNOTFIS.NFNOTFIS IS NULL, JPPEDIDO.PDDATEMI, JPNOTFIS.NFDATEMI ) AS DATEMI," + ;
         " IF( JPNOTFIS.NFNOTFIS IS NULL, JPPEDIDO.PDPEDCLI, JPNOTFIS.NFNOTFIS ) AS NOTFIS," + ;
         " JPTRANSA.TRDESCRI AS TRANOME, JPTRANSA.TRREACAO AS REACAO," + ;
         " JPNOTFIS.NFNOTFIS AS NOTFIS," + ;
         " JPITPED.IPPRODUTO," + ;
         " JPITPED.IPQTDE," + ;
         " JPITPED.IPVALCUS, JPITPED.IPVALNOT," + ;
         " JPITEM.IEPRODEP," + ;
         " CONCAT( RPAD( JPVENDEDOR.VDDESCRI, 40, ' ' ), LPAD( PDVENDEDOR, 6, ' ' ) ) AS VENDEDOR," + ;
         " CONCAT( RPAD( JPITEM.IEDESCRI, 40, ' ' ), LPAD( IPPRODUTO, 6, ' ' ) ) AS PRODUTO," + ;
         " CONCAT( RPAD( PRECNPJ.CNPJNOME, 40, ' ' ), LPAD( JPCADASTRO.CDCNPJ, 10, ' ' ) ) AS GRUPOCLIENTE," + ;
         " CONCAT( RPAD( JPCADASTRO.CDNOME, 40, ' ' ), LPAD( PDCADASTRO, 10, ' ' ) ) AS CLIENTE," + ;
         " IF( JPITEM.IEQTDCOM = 0, 1, JPITEM.IEQTDCOM ) * JPITPED.IPQTDE AS QTDEMBALAGEM," + ;
         " JPITPED.IPVALNOT - JPITPED.IPVALCUS AS VMARGEM," + ;
         " IF( JPITPED.IPVALNOT = 0, 0, ( JPITPED.IPVALNOT - JPITPED.IPVALCUS ) / JPITPED.IPVALNOT * 100 ) AS PMARGEM," + ;
         " RPAD( JPCADASTRO.CDCNPJ, 10, ' ' ) AS CNPJRAIZ," + ;
         " IF( JPTRANSA.TRREACAO LIKE '%DEV%', -1, 1 ) AS FATOR," + ;
         " IF( JPCOMISSAO.CMVALOR IS NOT NULL, JPCOMISSAO.CMVALOR, IF( JPVENDEDOR.VDCOMISSAO IS NULL, 0, JPVENDEDOR.VDCOMISSAO ) ) AS PCOMISSAO," + ;
         " IF( JPCOMISSAO.CMVALOR IS NOT NULL, JPCOMISSAO.CMVALOR, IF( JPVENDEDOR.VDCOMISSAO IS NULL, 0, JPVENDEDOR.VDCOMISSAO ) ) * JPITPED.IPVALNOT / 100 AS VCOMISSAO" + ;
         " FROM JPPEDIDO" + ;
         " LEFT JOIN JPNOTFIS ON JPNOTFIS.NFPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPTRANSA ON JPTRANSA.IDTRANSA = JPPEDIDO.PDTRANSA" + ;
         " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPPEDIDO.PDCADASTRO" + ;
         " LEFT JOIN JPCOMISSAO ON JPCOMISSAO.CMVENDEDOR = PDVENDEDOR AND CMPRODEP = JPITEM.IEPRODEP" + ;
         " LEFT JOIN JPVENDEDOR ON JPVENDEDOR.IDVENDEDOR = JPPEDIDO.PDVENDEDOR" + ;
         " LEFT JOIN (" + ;
         " SELECT RPAD( CDCNPJ, 10, ' ' ) AS CNPJPREFIXO, CDNOME AS CNPJNOME FROM JPCADASTRO GROUP BY RPAD( CDCNPJ, 10, ' ' ) ) AS PRECNPJ" + ;
         " ON RPAD( JPCADASTRO.CDCNPJ, 10, ' ' ) = PRECNPJ.CNPJPREFIXO" + ;
         " WHERE PDCONF = 'S'" + ;
         " AND PDTRANSA IN (" + ;
         " SELECT IDTRANSA FROM JPTRANSA AS LISTAA WHERE "
      IF nOpcCompraVenda == 1
         :cSQL += " TRREACAO LIKE '%VENDA%'"
      ELSEIF nOpcCompraVenda == 2
         :cSQL += " TRREACAO LIKE '%COMPRA%'"
      ELSE
         :cSQL += " ( TRREACAO LIKE '%COMPRA%' OR TRREACAO LIKE '%VENDA%' )"
      ENDIF
      IF nOpcDevol == 2
         :cSQL += " AND TRREACAO LIKE '%DEV%'"
      ENDIF
      :cSQL += " )"
      IF nOpcVendedor == 2
         :cSQL += " AND PDVENDEDOR = " + NumberSQL( mIdVendedor )
      ENDIF
      IF nOpcCadastro == 2
         :cSQL += " AND PDCADASTRO IN (" + ;
            " SELECT IDCADASTRO FROM JPCADASTRO AS LISTA WHERE LEFT( CDCNPJ, 10 ) = " + ;
            " ( SELECT LEFT( CDCNPJ, 10 ) FROM JPCADASTRO AS ESCOLHIDO WHERE IDCADASTRO = " + NumberSQL( nIdCadastro ) + " ) )"
      ENDIF
      IF nOpcProduto == 2
         :cSQL += " AND JPITEM.IDPRODUTO = " + NumberSQL( nIdProduto )
      ENDIF
      IF nOpcProDep == 2
         :cSQL += " AND JPITEM.IEPRODEP = " + NumberSQL( nIdProDep )
      ENDIF
      :cSQL += " AND ( JPNOTFIS.NFNOTFIS IS NOT NULL OR NOT" + ;
         " JPPEDIDO.PDTRANSA IN ( SELECT IDTRANSA FROM JPTRANSA AS LISTAB WHERE TRREACAO LIKE '%N+%' OR TRREACAO LIKE '%N-%' ) )" + ;
         " AND IF( JPNOTFIS.NFNOTFIS IS NULL, PDDATEMI, NFDATEMI ) BETWEEN CAST( " + DateSQL( dDataInicial ) + " AS DATE )" + ;
         " AND CAST( " + DateSQL( dDataFinal ) + " AS DATE )"
      :cSQL += " ORDER BY " + aChaveList[ 1 ] + ", " + aChaveList[ 2 ] + ", " + aChaveList[ 3 ] + ", " + aChaveList[ 4 ]
      :Execute()
      STORE 0 To nTotQtde[ 1 ], nTotCusto[ 1 ], nTotVenda[ 1 ], nTotMargem[ 1 ], nTotComissao[ 1 ], nTotEmbalagem[ 1 ]
      RecAppend()
      RecUnlock()
      aTransacaoList := {}
      DO WHILE ! :Eof()
         Inkey()
         STORE 0 To nTotQtde[ 2 ], nTotCusto[ 2 ], nTotVenda[ 2 ], nTotMargem[ 2 ], nTotComissao[ 2 ], nTotEmbalagem[ 2 ]
         cTotChave[ 1 ] := :String( aChaveList[ 1 ] )
         RecAppend()
         REPLACE ;
            temp2->Chave1 WITH cTotChave[ 1 ], ;
            temp2->Nivel  WITH "1"
         RecUnlock()
         nTotRecNo[ 2 ] := RecNo()
         DO WHILE ! :Eof()
            Inkey()
            IF cTotChave[ 1 ] != :String( aChaveList[ 1 ] )
               EXIT
            ENDIF
            STORE 0 To nTotQtde[ 3 ], nTotCusto[ 3 ], nTotVenda[ 3 ], nTotMargem[ 3 ], nTotComissao[ 3 ], nTotEmbalagem[ 3 ]
            cTotChave[ 2 ] := :String( aChaveList[ 2 ] )
            RecAppend()
            REPLACE ;
               temp2->Chave1 WITH cTotChave[ 1 ], ;
               temp2->Chave2 WITH cTotChave[ 2 ], ;
               temp2->Nivel  WITH  "2"
            RecUnlock()
            nTotRecNo[ 3 ] := RecNo()
            DO WHILE ! :Eof()
               Inkey()
               IF cTotChave[ 1 ] != :String( aChaveList[ 1 ] )
                  EXIT
               ENDIF
               IF cTotChave[ 2 ] != :String( aChaveList[ 2 ] )
                  EXIT
               ENDIF
               STORE 0 TO nTotQtde[ 4 ], nTotCusto[ 4 ], nTotVenda[ 4 ], nTotMargem[ 4 ], nTotComissao[ 4 ], nTotEmbalagem[ 4 ]
               cTotChave[ 3 ] := :String( aChaveList[ 3 ] )
               RecAppend()
               REPLACE ;
                  temp2->Chave1 WITH cTotChave[ 1 ], ;
                  temp2->Chave2 WITH cTotChave[ 2 ], ;
                  temp2->Chave3 WITH cTotChave[ 3 ], ;
                  temp2->Nivel  WITH  "3"
               RecUnlock()
               nTotRecNo[ 4 ] := RecNo()
               DO WHILE ! :Eof()
                  Inkey()
                  IF cTotChave[ 1 ] != :String( aChaveList[ 1 ] )
                     EXIT
                  ENDIF
                  IF cTotChave[ 2 ] != :String( aChaveList[ 2 ] )
                     EXIT
                  ENDIF
                  IF cTotChave[ 3 ] != :String( aChaveList[ 3 ] )
                     EXIT
                  ENDIF
                  STORE 0 To nTotQtde[ 5 ], nTotCusto[ 5 ], nTotVenda[ 5 ], nTotMargem[ 5 ], nTotComissao[ 5 ], nTotEmbalagem[ 5 ]
                  cTotChave[ 4 ] := :String( aChaveList[ 4 ] )
                  RecAppend()
                  REPLACE ;
                     temp2->Chave1 WITH cTotChave[ 1 ], ;
                     temp2->Chave2 WITH cTotChave[ 2 ], ;
                     temp2->Chave3 WITH cTotChave[ 3 ], ;
                     temp2->Chave4 WITH cTotChave[ 4 ], ;
                     temp2->Nivel  WITH "4"
                  RecUnlock()
                  nTotRecNo[ 5 ] := RecNo()
                  DO WHILE ! :Eof()
                     Inkey()
                     IF cTotChave[ 1 ] != :String( aChaveList[ 1 ] )
                        EXIT
                     ENDIF
                     IF cTotChave[ 2 ] != :String( aChaveList[ 2 ] )
                        EXIT
                     ENDIF
                     IF cTotChave[ 3 ] != :String( aChaveList[ 3 ] )
                        EXIT
                     ENDIF
                     IF cTotChave[ 4 ] != :String( aChaveList[ 4 ] )
                        EXIT
                     ENDIF
                     nNumTransa   := hb_ASCan( aTransacaoList, { | e | e[ 1 ] == :Number( "PDTRANSA" ) } )
                     IF nNumTransa == 0
                        AAdd( aTransacaoList, { :Number( "PDTRANSA" ), 0 } )
                        nNumTransa := Len( aTransacaoList )
                     ENDIF
                     aTransacaoList[ nNumTransa, 2 ] += :Number( "IPVALNOT" )
                     RecAppend()
                     REPLACE ;
                        temp2->Chave1     WITH :String( aChaveList[ 1 ] ), ;
                        temp2->Chave2     WITH :String( aChaveList[ 2 ] ), ;
                        temp2->Chave3     WITH :String( aChaveList[ 3 ] ), ;
                        temp2->Chave4     WITH :String( aChaveList[ 4 ] ), ;
                        temp2->pdCadastro WITH StrZero( :Number( "PDCADASTRO" ), 6 ), ;
                        temp2->cdCnpj     WITH :String( "CNPJRAIZ" ), ;
                        temp2->pdVendedor WITH StrZero( :Number( "PDVENDEDOR" ), 6 ), ;
                        temp2->ipProduto  WITH StrZero( :Number( "IPPRODUTO" ), 6 ), ;
                        temp2->DescItem   WITH :String( "PRODUTO" ), ;
                        temp2->pdTransa   WITH StrZero( :Number( "PDTRANSA" ), 6 ), ;
                        temp2->NF         WITH StrZero( :Number( "NOTFIS" ), 9 ), ;
                        temp2->DatEmi     WITH :Date( "DATEMI" ), ;
                        temp2->ipQtde     WITH :Number( "FATOR" ) * :Number( "IPQTDE" ), ;
                        temp2->QtdEmb     WITH :Number( "FATOR" ) * :Number( "QTDEMBALAGEM" ), ;
                        temp2->ipValCus   WITH :Number( "FATOR" ) * :Number( "IPVALCUS" ), ;
                        temp2->ipValNot   WITH :Number( "FATOR" ) * :Number( "IPVALNOT" ), ;
                        temp2->Margem     WITH :Number( "FATOR" ) * :Number( "VMARGEM" ), ;
                        temp2->PMargem    WITH :Number( "PMARGEM" ), ;
                        temp2->ValCom     WITH :Number( "FATOR" ) * :Number( "VCOMISSAO" ), ;
                        temp2->PerCom     WITH :Number( "PCOMISSAO" ), ;
                        temp2->Nivel      WITH "5"
                     nTotQtde[ 5 ]      += :Number( "FATOR" ) * :Number( "IPQTDE" )
                     nTotEmbalagem[ 5 ] += :Number( "FATOR" ) * :Number( "QTDEMBALAGEM" )
                     nTotCusto[ 5 ]     += :Number( "FATOR" ) * :Number( "IPVALCUS" )
                     nTotVenda[ 5 ]     += :Number( "FATOR" ) * :Number( "IPVALNOT" )
                     nTotMargem[ 5 ]    += :Number( "FATOR" ) * :Number( "VMARGEM" )
                     nTotComissao[ 5 ]  += :Number( "FATOR" ) * :Number( "VCOMISSAO" )
                     :MoveNext()
                  ENDDO
                  GOTO ( nTotRecNo[ 5 ] )
                  RecLock()
                  REPLACE ;
                     temp2->ipQtde   WITH nTotQtde[ 5 ], ;
                     temp2->QtdEmb   WITH nTotEmbalagem[ 5 ], ;
                     temp2->ipValCus WITH nTotCusto[ 5 ], ;
                     temp2->ipValNot WITH nTotVenda[ 5 ], ;
                     temp2->Margem   WITH nTotMargem[ 5 ], ;
                     temp2->ValCom   WITH nTotComissao[ 5 ], ;
                     temp2->PMargem  WITH Max( Min( ( nTotVenda[ 5 ] - nTotCusto[ 5 ] ) / nTotVenda[ 5 ] * 100, 999 ), -99.9 )
                  RecUnlock()
                  nTotQtde[ 4 ]      += nTotQtde[ 5 ]
                  nTotEmbalagem[ 4 ] += nTotEmbalagem[ 5 ]
                  nTotCusto[ 4 ]     += nTotCusto[ 5 ]
                  nTotVenda[ 4 ]     += nTotVenda[ 5 ]
                  nTotMargem[ 4 ]    += nTotMargem[ 5 ]
                  nTotComissao[ 4 ]  += nTotComissao[ 5 ]
               ENDDO
               GOTO ( nTotRecNo[ 4 ] )
               RecLock()
               REPLACE ;
                  temp2->ipQtde   WITH nTotQtde[ 4 ], ;
                  temp2->QtdEmb   WIth nTotEmbalagem[ 4 ], ;
                  temp2->ipValCus WITH nTotCusto[ 4 ], ;
                  temp2->ipValNot WITH nTotVenda[ 4 ], ;
                  temp2->Margem   WITH nTotMargem[ 4 ], ;
                  temp2->ValCom   WITH nTotComissao[ 4 ], ;
                  temp2->PMargem  WITH Max( Min( ( nTotVenda[ 4 ] - nTotCusto[ 4 ] ) / nTotVenda[ 4 ] * 100, 999 ), -99.9 )
               RecUnlock()
               nTotQtde[ 3 ]      += nTotQtde[ 4 ]
               nTotEmbalagem[ 3 ] += nTotEmbalagem[ 4 ]
               nTotCusto[ 3 ]     += nTotCusto[ 4 ]
               nTotVenda[ 3 ]     += nTotVenda[ 4 ]
               nTotMargem[ 3 ]    += nTotMargem[ 4 ]
               nTotComissao[ 3 ]  += nTotComissao[ 4 ]
            ENDDO
            GOTO ( nTotRecNo[ 3 ] )
            RecLock()
            REPLACE ;
               temp2->ipQtde   WITH nTotQtde[ 3 ], ;
               temp2->QtdEmb   WITH nTotEmbalagem[ 3 ], ;
               temp2->ipValCus WITH nTotCusto[ 3 ], ;
               temp2->ipValNot WITH nTotVenda[ 3 ], ;
               temp2->Margem   WITH nTotMargem[ 3 ], ;
               temp2->ValCom   WITH nTotComissao[ 3 ], ;
               temp2->PMargem  WITH Max( Min( ( nTotVenda[ 3 ] - nTotCusto[ 3 ] ) / nTotVenda[ 3 ] * 100, 999 ), -99.9 )
            RecUnlock()
            nTotQtde[ 2 ]      += nTotQtde[ 3 ]
            nTotEmbalagem[ 2 ] += nTotEmbalagem[ 3 ]
            nTotCusto[ 2 ]     += nTotCusto[ 3 ]
            nTotVenda[ 2 ]     += nTotVenda[ 3 ]
            nTotMargem[ 2 ]    += nTotMargem[ 3 ]
            nTotComissao[ 2 ]  += nTotComissao[ 3 ]
         ENDDO
         GOTO ( nTotRecNo[ 2 ] )
         RecLock()
         REPLACE ;
            temp2->ipQtde   WITH nTotQtde[ 2 ], ;
            temp2->QtdEmb   WITH nTotEmbalagem[ 2 ], ;
            temp2->ipValCus WITH nTotCusto[ 2 ], ;
            temp2->ipValNot WITH nTotVenda[ 2 ], ;
            temp2->Margem   WITH nTotMargem[ 2 ], ;
            temp2->ValCom   WITH nTotComissao[ 2 ], ;
            temp2->PMargem  WITH Max( Min( ( nTotVenda[ 2 ] - nTotCusto[ 2 ] ) / nTotVenda[ 2 ] * 100, 999 ), -99.9 )
         RecUnlock()
         nTotQtde[ 1 ]      += nTotQtde[ 2 ]
         nTotEmbalagem[ 1 ] += nTotEmbalagem[ 2 ]
         nTotCusto[ 1 ]     += nTotCusto[ 2 ]
         nTotVenda[ 1 ]     += nTotVenda[ 2 ]
         nTotMargem[ 1 ]    += nTotMargem[ 2 ]
         nTotComissao[ 1 ]  += nTotComissao[ 2 ]
      ENDDO
      GOTO TOP
      RecLock()
      REPLACE ;
         temp2->ipQtde   WITH nTotQtde[ 1 ], ;
         temp2->QtdEmb   WITH nTotEmbalagem[ 1 ], ;
         temp2->ipValCus WITH nTotCusto[ 1 ], ;
         temp2->ipValNot WITH nTotVenda[ 1 ], ;
         temp2->Margem   WITH nTotMargem[ 1 ], ;
         temp2->ValCom   WITH nTotComissao[ 1 ], ;
         temp2->PMargem  WITH Max( Min( ( nTotVenda[ 1 ] - nTotCusto[ 1 ] ) / nTotVenda[ 1 ] * 100, 999 ), -99.9 )
      RecUnlock()
      :CloseRecordset()
   ENDWITH

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey := 0
   oPDF:acHeader := { "COMPRAS/VENDAS NO PERIODO" }
   cTexto := "Periodo:" + iif( nOpcData == 1, "Tudo", Dtoc( dDataInicial ) + " a " + Dtoc( dDataFinal ) )
   cTexto += " Tipo:" + acTxtCompraVenda[ nOpcCompraVenda ]
   cTexto += " " + acTxtDetalhe[ nOpcDetalhe ]
   cTexto += " Depto:" + iif( nOpcProDep == 1, "Todos", NumberSQL( nIdProDep ) + "-" + Trim( AUXPRODEPClass():Descricao( nIdProDep ) ) )
   Encontra( StrZero( nIdProduto, 6 ), "jpitem", "item" )
   cTexto += " Produto:" + iif( nOpcProduto==1, "Todos", NumberSQL( nIdProduto ) + "-" + Trim( jpitem->ieDescri ) )
   cTexto += " Vendedor:" + iif( nOpcVendedor == 1, "Todos", NumberSQL( mIdVendedor ) + "-" + ADOField( "VDDESCRI", "C", "JPVENDEDOR", "IDVENDEDOR=" + NumberSQL( mIdVendedor ) ) )
   AAdd( oPDF:acHeader, cTexto )
   cTexto := Pad( aChaveList[ 1 ] + "/" + aChaveList[ 2 ] + "/" + aChaveList[ 3 ], 50 ) + ;
      " ----QTD---- --QTD.NF.-- " + Iif( nOpcCusto == 2, "---VL.CUSTO--- ", "" ) + ;
      "---VL.PEDIDO-- " + Iif( nOpcCusto == 2, "---VL.MARGEM-- %MARGEM ", "" ) + "    COMISSAO/%"
   AAdd( oPDF:acHeader, cTexto )
   oPDF:PageHeader()
   STORE Chr(205) TO cTotChave[ 1 ], cTotChave[ 2 ], cTotChave[ 3 ]
   GOTO TOP
   SKIP // Primeiro registro total
   DO WHILE nKey != K_ESC .AND. ! Eof()
      GrafProc()
      nKey := Inkey()
      IF temp2->Nivel > Str( nOpcDetalhe, 1 )
         SKIP
         LOOP
      ENDIF
      oPDF:MaxRowTest()
      cTxt := ""
      IF temp2->Nivel == "1" // cTotChave[ 1 ] != temp2->Chave1
         oPDF:nRow += 1
         oPDF:MaxRowTest()
         cTxt := Pad( temp2->Chave1, 40 )
         cTotChave[ 1 ] := temp2->Chave1
         cTotChave[ 2 ] := Chr(205)
         cTotChave[ 3 ] := Chr(205)
         cTotChave[ 4 ] := Chr(205)
      ELSEIF temp2->Nivel == "2" // cTotChave[ 2 ] != temp2->Chave2 .AND. nOpcDetalhe > 1
         oPDF:MaxRowTest()
         cTotChave[ 2 ] := Pad( temp2->Chave2, 40 )
         cTxt := Space(2) + temp2->Chave2
         cTotChave[ 3 ] := Chr(205)
         cTotChave[ 4 ] := Chr(205)
      ELSEIF temp2->Nivel == "3" // cTotChave[ 3 ] != temp2->Chave3 .AND. nOpcDetalhe > 2
         cTotChave[ 3 ] := temp2->Chave3
         oPDF:MaxRowTest()
         cTxt := Space(4) + Pad( temp2->Chave3, 40 )
         cTotChave[ 4 ] := Chr(205)
      ELSEIF temp2->Nivel == "4" // cTotChave[ 4 ] != temp2->Chave4 .AND. nOpcDetalhe > 3
         cTotChave[ 4 ] := temp2->Chave4
         oPDF:MaxRowTest()
         cTxt := Space(6) + Pad( temp2->Chave4, 40 )
      ELSEIF temp2->Nivel == "5"
         cTxt := Space(22) + temp2->Nf + " " + Dtoc( temp2->DatEmi )
      ENDIF
      cTxt := Pad( cTxt, 50 )
      cTxt += " " + Transform( temp2->QtdEmb, PicVal(9) )
      cTxt += " " + Transform( temp2->ipQtde, PicVal(9) )
      IF nOpcCusto == 2
         cTxt += " " + Transform( temp2->ipValCus, PicVal(11,2) )
      ENDIF
      cTxt += " " + Transform( temp2->ipValNot, PicVal(11,2) )
      IF temp2->Nivel < "3"
         oPDF:DrawZebrado(2)
      ELSE
         oPDF:DrawZebrado(1)
      ENDIF
      IF nOpcCusto == 2
         cTxt += " " + Transform( temp2->Margem, PicVal(11,2) )
         cTxt += " " + Transform( temp2->PMargem, "@E 999.9" )
      ENDIF
      IF nOpcComComissao == 2
         cTxt += " " + Transform( temp2->ValCom, PicVal( 8, 2 ) )
         cTxt += " " + "(" + Transform( temp2->PerCom, "@Z 999.999" ) + ")"
      ENDIF
      oPDF:DrawText( oPDF:nRow, 0, cTxt )
      IF nOpcDetalhe == 5 .AND. ! Empty( temp2->Obs )
         oPDF:nRow += 1
         cTxt := " " + temp2->Obs
         cTxt := Padl( cTxt, oPDF:MaxCol() )
         oPDF:DrawText( oPDF:nRow, 0, cTxt )
      ELSE
      ENDIF
      oPDF:nRow += 1
      SKIP
   ENDDO
   GOTO TOP
   oPDF:nRow += 2
   oPDF:MaxRowTest()
   cTxt := Pad( "***TOTAIS***", 50 )
   cTxt += " " + Transform( temp2->QtdEmb, PicVal(9) )
   cTxt += " " + Transform( temp2->ipQtde, PicVal(9) )
   IF nOpcCusto == 2
      cTxt += " " + Transform( temp2->ipValCus, PicVal(11,2) )
   ENDIF
   cTxt += " " + Transform( temp2->ipValNot, PicVal(11,2) )
   IF nOpcCusto == 2
      cTxt += " " + Transform( temp2->Margem, PicVal(11,2) )
      cTxt += " " + Transform( temp2->PMargem, "@E 999.9" )
   ENDIF
   cTxt += " " + Transform( temp2->ValCom, PicVal(8,2) )
   oPDF:DrawText( oPDF:nRow, 0, cTxt )
   IF nOpcAnalise == 2
      oPDF:nRow += 2
      oPDF:MaxRowTest()
      FOR nCont = 1 TO Len( aTransacaoList )
         oPDF:MaxRowTest()
         cTxt := Str( aTransacaoList[ nCont, 1 ], 6 )
         cTxt += " " + Transform( aTransacaoList[ nCont, 2 ], PicVal( 14, 2 ) )
         cTxt += " " + DescricaoJPTRANSA( aTransacaoList[ nCont, 1 ] )
         oPDF:DrawText( oPDF:nRow, 0, cTxt )
         oPDF:nRow += 1
      NEXT
   ENDIF
   SELECT temp2
   USE
   FOR EACH oFile IN mTmpFile
      fErase( oFile )
   NEXT
   oPDF:End()

   RETURN NIL

Meu modo de trabalho

MensagemEnviado: 04 Jul 2020 12:06
por JoséQuintas
Pois é...
Com isso só falta um fonte, mas.... é relacionado a um DBF que ainda só está em DBF.
Agora avaliar se faço uma alteração quebra-galho, ou se já começo a eliminação de mais outro DBF.

O quebra-galho pode ser interessante, porque esse é o último fonte dependente de jpcadastro.dbf

Meu modo de trabalho

MensagemEnviado: 04 Jul 2020 12:34
por JoséQuintas
Pois é... complicou mais.... talvez....

Esse último fonte é da listagem de tabela de preços diferenciados.
Essa tabela está em DBF.
E ela tem um histórico relacionado, já em MySQL.

A chave é cliente + produto + prazo, inclusive no histórico.
Ficaria mais interessante definir uma ID pra isso, e já vincular essa ID também no histórico, além de ser chave pra MySQL.
Pensar mais... agora trata-se de migrar outro DBF pra MySQL, e alterar uma base MySQL já existente.
A alteração desse vínculo fica até mais fácil em MySQL, porque poderia atualizar o ID do histórico fazendo apenas um relacionamento entre os dois.

É assim:

no preço é cliente + produto + prazo
no histórico é cliente + produto + prazo + data

Colocando um ID no preço, o histórico seria ID + data, bem mais simplificado, só por definir uma ID.

É... talvez melhor ficar no quebra-galho kkkkk

Meu modo de trabalho

MensagemEnviado: 05 Jul 2020 09:53
por JoséQuintas
Aqui é apenas comentário.

28/06/2020 06:31 6.544.816 jpa.exe
05/07/2020 09:48 6.536.192 jpa.exe

Mesmo entrando novas rotinas de conversão/atualização, o EXE está um pouco menor.
É apenas comentário, porque isso nem importa.

Com certeza, quando apagar tudo que é conversão/atualização, vai ficar menor mesmo.
Mas nem espero grandes mudanças de tamanho, porque a maior parte se refere a ícones/resources.

Curiosidade de anteriores, incluindo versão CLIPPER e console puro.

16/09/2002 14:31 1.012.864 JPA2002.EXE
08/09/2006 20:32 1.030.208 JPA2004.EXE
27/03/2011 16:24 1.323.392 jpa2010.exe
16/04/2013 20:29 2.149.888 jpa2013.exe
14/04/2015 10:08 1.666.048 JPA2015.EXE
14/02/2018 19:51 2.419.000 jpa2018.exe
10/03/2020 20:34 6.750.512 jpa2020.exe
02/07/2020 07:30 6.544.816 JPA.EXE

Meu modo de trabalho

MensagemEnviado: 12 Jul 2020 22:29
por JoséQuintas

03/03/2020  02:33         1.254.539 jpcidade.dbf
09/07/2020  19:02         1.376.894 jpitem.DBF
09/07/2020  19:00        11.028.454 jpcadastro.DBF
09/07/2020  21:18        16.377.672 jpbancario.dbf
              20 arquivo(s)     31.322.316 bytes


Tô eliminando tudo, daqui a pouco só vai sobrar o contábil.
Esse vou remodelar antes de passar pra MySQL.

Meu modo de trabalho

MensagemEnviado: 13 Jul 2020 17:41
por JoséQuintas
Não pensei que ia dar tanto trabalho ficar apagando fonte.

09/07/2020  21:18           502.533 jppreco.DBF
03/03/2020  02:33         1.254.539 jpcidade.dbf
09/07/2020  19:02         1.376.894 jpitem.DBF
09/07/2020  21:18        16.377.672 jpbancario.dbf
              19 arquivo(s)     20.294.143 bytes


Menos outro.

O que fui deixando pra depois.... pois é... em breve não tem depois....
A primeira vista, esse bancário vai dar trabalho, porque ainda não sei como vou fazer certas coisas em SQL.

Meu modo de trabalho

MensagemEnviado: 13 Jul 2020 21:40
por JoséQuintas
O fonte de agora é daqueles....

fonte Harbour, pra gerar VBScript pra Html, e mais o ADO.... rs

Isso junta PRG, HTML, VBScript, ADO, tudo no mesmo fonte.... aff

E isso, só pra não apagar o fonte fora de uso.

Meu modo de trabalho

MensagemEnviado: 14 Jul 2020 17:22
por JoséQuintas
Tem coisa no SQL que pode ser considerada chata ou não, depende do ponto de vista.

Com o DBF posicionado, bastava verificar isto:
" DE ICMS " $ jpitem->ieDescri

mas com SQL:
STATIC FUNCTION IsComplementoICMS( nIdNotFis )

   LOCAL lSim := .F.
   LOCAL cnSQL := ADOClass():New( AppConexao() )
   
   WITH OBJECT cnSQL
      :cSQL := "SELECT COUNT(*) AS QTD" + ;
         " FROM JPNOTFIS" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPNOTFIS.NFPEDIDO" + ;
         " LEFT JOIN JPITPED ON JPITPED.IPPEDIDO = JPPEDIDO.IDPEDIDO" + ;
         " LEFT JOIN JPITEM ON JPITEM.IDPRODUTO = JPITPED.IPPRODUTO" + ;
         " WHERE JPNOTFIS.IDNOTFIS = " + NumberSQL( nIdNotFis ) + ;
         " AND JPITEM.IEDESCRI LIKE '% DE ICMS %'"
      :Execute()
      lSim := :Number( "QTD" ) != 0
      :CloseRecordset()
   ENDWITH
   
   RETURN lSim


Olhando como pessimista: muito fonte pra uma coisa que era simples
Olhando como otimista: nenhum arquivo aberto, apenas está perguntando pro servidor.

Comando complicado? só lembrar do dBase

- faz o relacionamento entre os arquivos, ao invés de SET RELATION é JOIN
- dá o filtro, ao invés de FOR xxxx é WHERE xxxx
- A diferença no SQL é que tudo vém, não apenas o registro atual de JPNOTFIS, vém todos os produtos relacionados

O comando relaciona nota fiscal com pedidos, pedidos com produtos de pedido, produto de pedido com o cadastro de produtos.
A intenção aqui é trabalhar com a descrição de todos os produtos que estão na nota/pedido
O SELECT vai considerar todos os produtos do pedido que gerou a nota fiscal (de cada nota, se fossem várias).
O filtro vai considerar apenas descrição contendo " DE ICMS " na descrição e somente da nota fiscal indicada.
O retorno é a variável QTD que contém a quantidade de registros.
É um comando grande pra só retornar um número, mas tem muita tabela envolvida.
Se o número for diferente de zero, quer dizer que encontrou.

Não importa o tamanho do comando, é rápido, e pela rede só passa esse número do resultado, o que não ocupa tempo de rede ou de terminal.

Apesar de posicionado, em DBF ser prático, se fosse fazer inteiro em DBF, teria USE/SELECT/SEEK/DO WHILE/etc,
Desse jeito, não precisa estar aberto, não tem erro de usar índice errado, índice corrompido, etc. etc., sempre funciona.
Como eu disse, dá pra olhar pelo lado otimista e pessimista.
Prefiro pelo lado otimista, de que sempre funciona, é rápido, e não precisa se preocupar com índices, arquivos abertos, etc.

Estou usando isso na geração de XML da nota fiscal, na parte de cobrança.
Também poderia pegar essa informação durante a geração do bloco produtos, o que eliminaria essa rotina desse bloco.
Ou poderia ter a nota inteira numa variável, numa classe guardando o conteúdo da nota... seria válido também.

Meu modo de trabalho

MensagemEnviado: 16 Jul 2020 16:17
por JoséQuintas
Agora mudei este, de reajuste de preços.

      Mensagem( "Aguarde, aplicando reajuste" )
      SELECT jppreco
      GOTO TOP
      DO WHILE ! Eof()
         IF Val( jppreco->pcProduto ) != nIdProduto
            SKIP
            LOOP
         ENDIF
         IF Val( jppreco->pcStatus ) != 1
            SKIP
            LOOP
         ENDIF
         IF jppreco->pcReajuste != mpcReajuste
            SKIP
            LOOP
         ENDIF
         RecLock()
         IF mTipoReajuste == "P"
            REPLACE ;
               jppreco->pcValor  WITH jppreco->pcValor + ( jppreco->pcValor * mPercent / 100 ), ;
               jppreco->pcInfAlt WITH LogInfo()
         ELSE
            REPLACE ;
               jppreco->pcValor  WITH jppreco->pcValor + mPercent, ;
               jppreco->pcInfAlt WITH LogInfo()
         ENDIF
         RecUnlock()
         WITH OBJECT cnSQL
            :QueryCreate()
            :QueryAdd( "PHCADASTRO",  jppreco->pcCadastro )
            :QueryAdd( "PHPRODUTO",   jppreco->pcProduto )
            :QueryAdd( "PHFORPAG", jppreco->pcForPag )
            :QueryAdd( "PHVALOR",  jppreco->pcValor )
            :QueryAdd( "PHOBS",    "REAJ." + iif( mTipoReajuste == "P", "PERCENTUAL", "VALOR" ) + " " + iif( mPercent > 0, "+", "" ) + Ltrim( Str( mPercent ) ) )
            :QueryAdd( "PHDATA",   mData )
            :QueryAdd( "PHHORA",   iif( mData == Date(), Time(), "" ) )
            :QueryAdd( "PHINFINC", jppreco->pcInfAlt )
            :QueryExecuteInsert( "JPPREHIS" )
         ENDWITH
         SELECT jppreco
         SKIP
      ENDDO
      MsgExclamation( "Fim do reajuste!" )
   ENDDO


mudei pra isto:

      Mensagem( "Aguarde, aplicando reajuste" )
      WITH OBJECT cnSQL
         nMultiplica := iif( cTipoReajuste == "P", ( 100 + nPercent ) / 100, 1 )
         nSoma       := iif( cTipoReajuste == "P", 0, nPercent )
         cLogInfo    := LogInfo()
         cTime       := Time()
         :cSQL := "UPDATE JPPRECO" + ;
            " SET PCVALOR = PCVALOR * " + NumberSQL( nMultiplica ) + " + " + NumberSQL( nSoma ) + "," + ;
            " PCINFALT = " + StringSQL( cLogInfo ) + ;
            " WHERE PCPRODUTO = " + NumberSQL( nIdProduto ) + " AND PCSTATUS = 1" + ;
            " AND PCREAJUSTE = " + StringSQL( mpcReajuste )
         :ExecuteCmd()
         :cSQL := "INSERT INTO JPPREHIS" + ;
            " ( PHCADASTRO, PHPRODUTO, PHFORPAG, PHVALOR, PHREAJUSTE, PHOBS, PHDATA, PHHORA, PHINFINC )" + ;
            " SELECT PCCADASTRO, PCPRODUTO, PCFORPAG, PCVALOR, PCREAJUSTE, " + ;
               StringSQL( "REAJ." + iif( cTipoReajuste == "P", "PERCENTUAL", "VALOR" ) + " " + ;
                  iif( nPercent > 0, "+", "" ) + NumberSQL( nPercent ) ) + " AS OBS," + ;
               DateSQL( mData ) + " AS DATA, " + StringSQL( cTime ) + " AS HORA, " + StringSQL( cLogInfo ) + " AS INFINC FROM JPPRECO" + ;
               " WHERE PCINFALT = " + StringSQL( cLogInfo )
         :ExecuteCmd()
      ENDWITH
      MsgExclamation( "Fim do reajuste!" )
   ENDDO


Só sei que finalmente não deu erro, mas não sei se deu certo.
É que a parte de consulta deu erro porque ainda precisa do DBF kkkk
Só vou saber se deu certo depois.

Decidi acabar com o jppreco.dbf de vez, porque usa em poucos fontes.

Meu modo de trabalho

MensagemEnviado: 16 Jul 2020 16:21
por JoséQuintas
Comentario do fonte anterior:

O reajuste pode ser por percentual ou valor.
No fonte anterior eu fazia calculo separado: ou vezes o percentual ou soma o valor.

No SQL usei uma forma simples de aplicar os dois de uma vez, VALOR * percentual + soma

Se NÃO é por percentual, é deixar percentual 1, multiplicando por 1 não altera o valor
Se NÃO é por soma, é deixar valor 0, se somar 0 não altera valor.
Uma mesma fórmula atendendo os dois casos de uma vez.

Meu modo de trabalho

MensagemEnviado: 16 Jul 2020 16:25
por JoséQuintas
Lembrei de uma coisa:

O que acontece se o cálculo tiver mais decimais do que a tabela?

Na dúvida, pra evitar erro, coloquei ROUND() na fórmula.

Meu modo de trabalho

MensagemEnviado: 16 Jul 2020 16:35
por JoséQuintas
E mais outra:

Depois.... vou alterar esse histórico.

Coisa simples: PRA QUE vincular produto + cadastro + forma de pagamento no histórico.... se agora o preco tem uma ID única?
Pois é.... 3 campos de vínculo, só porque eu não tinha criado uma identificação no DBF.
Agora que tem chave no arquivo de preços, o histórico só precisa dessa chave, e não mais de 3 campos.

Mas vou alterar isso depois, é perigoso mexer agora durante migração, agora que já comecei desse jeito.

Com MySQL, vai ser só fazer o relacionamento e atualizar, então fica pra uma versão futura, com calma.

Agora é terminar desse jeito, só falta um fonte.

Meu modo de trabalho

MensagemEnviado: 17 Jul 2020 14:35
por JoséQuintas
30/12/2019  21:50               163 cthisto.dbf
30/12/2019  21:52               291 jprefcta.dbf
30/12/2019  21:50               355 ctlotes.dbf
30/12/2019  21:50               387 ctlanca.dbf
30/12/2019  21:50               483 jpcontabil.dbf
03/03/2020  02:44             2.799 jpfiscal.DBF
30/12/2019  21:50             5.335 jpempresa.dbf
13/07/2020  17:13             5.923 jpnumero.dbf
30/12/2019  21:50             6.691 ctplano.dbf
30/12/2019  21:52             7.416 jpuf.dbf
09/07/2020  21:18             8.123 jpbaccusto.DBF
13/07/2020  17:13             9.997 jpconfi.dbf
10/07/2020  14:32           119.029 jptabel.dbf
30/12/2019  21:50           301.112 jpdolar.dbf
06/07/2020  11:25           314.624 jpsenha.dbf
03/03/2020  02:33         1.254.539 jpcidade.dbf
13/07/2020  15:30        16.385.892 jpbancario.dbf
              17 arquivo(s)     18.423.159 bytes


E os DBFs estão reduzindo....

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 11:01
por JoséQuintas
Este é um daqueles complicados.
É um tbrowse, que por si só já é uma exceção.

/*
PBANCOCOMPARAMES - COMPARATIVO MES A MES
1994.01 José Quintas
*/

#include "inkey.ch"
#include "josequintas.ch"

MEMVAR m_MostraDol, m_MostraTot, m_Ano, m_Mes, m_CodResumo, m_Tabela, nQtdCols

PROCEDURE pBancoComparaMes

   LOCAL m_Texto, m_TmpMes, m_TmpAno, oBrowse, nKey, mTop, mLeft, mBottom, mRight, ColPos
   LOCAL m_TmpMov, nMCol, nMRow, oTBrowse

   IF ! AbreArquivos( "jpempresa", "jptabel", "jpconfi", "jpbaccusto", "jpbancario" )
      RETURN
   ENDIF
   SELECT jpbancario
   OrdSetFocus( "bancario2" )
   SELECT jpbaccusto
   OrdSetFocus( "jpbaccusto2" )
   GOTO TOP
   m_Tabela := PegaContas( .T. )
   IF Len( m_Tabela ) == 2 // So contas de totais
      MsgWarning( "Não há dados p/ comparativo!" )
      RETURN
   ENDIF

   m_CodResumo           := 1
   m_Ano                 := Year( Date() )
   m_Mes                 := Month( Date() )
   m_mostratot           := .F.
   m_mostradol           := .F.
   mTop                  := 4
   mLeft                 := 0
   mBottom               := MaxRow() - 5
   mRight                := MaxCol() - 2

   oBrowse               := TBrowseDb( mTop, mLeft, mBottom, mRight )
   oBrowse:SkipBlock     := { | m_Regs | SkipBrow2( m_Regs ) }
   oBrowse:GoTopBlock    := { || TopBrow2() }
   oBrowse:GoBottomBlock := { || botbrow2() }
   oBrowse:HeadSep       := Chr(196)
   oBrowse:ColSep        := Chr(179)
   oBrowse:FootSep       := Chr(196)
   oBrowse:FrameColor    := SetColorTbrowseFrame()
   ColPos                := 2
   nQtdCols              := 5

   oTBrowse := { ;
      { "", { || FldBrow2( -1 ) } }, ;
      { "", { || FldBrow2( 0  ) } }, ;
      { "", { || FldBrow2( 1  ) } }, ;
      { "", { || FldBrow2( 2  ) } }, ;
      { "", { || FldBrow2( 3  ) } }, ;
      { "", { || FldBrow2( 4  ) } } }
   ToBrowse( oTBrowse, oBrowse )

   TitBrow2()

   oBrowse:right()

   DO WHILE ! oBrowse:Stable
      oBrowse:Stabilize()
   ENDDO
   DO WHILE .T.
      Mensagem( "SETAS, T Totais, ENTER Lançamentos, R Desp/Rec, ESC Sai" )
      nKey := 0
      DO WHILE nKey == 0 .AND. ! oBrowse:Stable
         oBrowse:Stabilize()
         nKey := Inkey()
      ENDDO

      IF oBrowse:stable()
         oBrowse:RefreshCurrent()
         DO WHILE ! oBrowse:Stabilize()
         ENDDO
         nKey = Inkey( INKEY_IDLE, HB_INKEY_ALL - INKEY_MOVE + HB_INKEY_GTEVENT )
         IF nKey == 0
            KEYBOARD Chr( K_ESC )
            LOOP
         ENDIF
      ENDIF
      nMRow := MROW()
      NMCol := MCOL()

      DO CASE
      CASE SetKey( nKey ) != NIL
         eval( SetKey( nKey ), procname(), procline(), readvar() )
      CASE nKey > 999
         DO CASE
         CASE mBrzMove( oBrowse, nMRow, nMCol, mTop + 1, mLeft + 1, mBottom - 1, mRight - 1 ) // Move cursor
         CASE mBrzClick( oBrowse, nMRow, nMCol ) // click no TBrowse atual
            //KEYBOARD Chr( K_ENTER )
         ENDCASE

      CASE nKey == K_ESC       ; EXIT

      CASE nKey == K_DOWN      ; oBrowse:Down()

      CASE nKey == K_UP        ; oBrowse:Up()

      CASE nKey == K_CTRL_DOWN ; oBrowse:PageDown()

      CASE nKey == K_CTRL_UP   ; oBrowse:PageUp()

      CASE nKey == K_LEFT
         IF ColPos == 2
            m_Ano := iif( m_Mes == 12, m_Ano + 1, m_Ano )
            m_Mes := iif( m_Mes == 12, 1, m_Mes + 1 )
            TitBrow2()
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ELSEIF ColPos > 1
            oBrowse:left()
            ColPos--
         ENDIF

      CASE nKey == K_RIGHT
         IF ColPos == ( nQtdCols + 1 )
            m_Ano = iif( m_Mes == 1, m_Ano - 1, m_Ano )
            m_Mes = iif( m_Mes == 1, 12, m_Mes - 1 )
            TitBrow2()
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ELSE
            oBrowse:right()
            ColPos++
         ENDIF

      CASE nKey == Asc( "R" ) .OR. nKey == Asc( "r" )
         IF Len( m_Tabela[ m_CodResumo ] ) == 10
            SELECT jpbaccusto
            SEEK m_Tabela[ m_CodResumo ]
            DO WHILE jpbaccusto->cuGrupo == m_Tabela[ m_CodResumo ] .AND. ! Eof()
               RecLock()
               REPLACE jpbaccusto->cuMostra WITH iif( jpbaccusto->cuMostra == "S", "N", "S" )
               RecUnlock()
               SKIP
            ENDDO
            m_Tabela := PegaContas()
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ENDIF

      CASE nKey == K_ENTER
         DO WHILE ! oBrowse:stabilize()
            GrafProc()
         ENDDO
         m_TmpMes := m_Mes - iif( ColPos > 1, ColPos - 2, 0 )
         m_TmpAno := m_Ano - iif( m_TmpMes < 1, 1, 0 )
         m_TmpMes := m_TmpMes + iif( m_TmpMes < 1, 12, 0 )
         WSave()
         Mensagem( "Aguarde, pesquisando movimentação..." )
         Cls()
         @ 2, 0 SAY "Desp/Rec:" + Trim( Left( m_Tabela[ m_CodResumo ], 10 ) ) + iif( Len( m_Tabela[ m_CodResumo ] ) == 10, "", ;
            ", Resumo:" + Trim( Right( m_Tabela[ m_CodResumo ], 10 ) ) ) + ", mes:" + StrZero( m_TmpMes, 2 ) + "/" + StrZero( m_TmpAno, 4 )
         @ 3, 0 SAY "BANCO EMISS __________HISTORICO__________ ___VALOR (NA DATA)__"
         m_tmpmov := {}
         SELECT jpbaccusto
         SEEK Left( m_Tabela[ m_CodResumo ], 10 )
         DO WHILE jpbaccusto->cuGrupo == Left( m_Tabela[ m_CodResumo ], 10 ) .AND. ! Eof()
            GrafProc()
            IF jpbaccusto->cuResumo != Right( m_Tabela[ m_CodResumo ], 10 ) .AND. Len( m_Tabela[ m_CodResumo ] ) > 10
               SKIP
               LOOP
            ENDIF
            SELECT jpbancario
            SEEK jpbaccusto->bcResumo + StrZero( m_TmpAno, 4 ) + StrZero( m_TmpMes, 2 )
            DO WHILE jpbancario->baResumo = jpbaccusto->cuCCusto .AND. Year( jpbancario->baDatEmi ) == m_TmpAno .AND. Month( jpbancario->baDatEmi ) == m_TmpMes .AND. ! Eof()
               GrafProc()
               m_Texto  = iif( jpbancario->baDatBan = Stod( "29991231" ), Space(5), Left( Dtoc( jpbancario->baDatBan ), 5 ) )
               m_Texto += Chr(179) + Left( Dtoc( jpbancario->baDatEmi ), 5 )
               m_Texto += Chr(179) + Left( jpbancario->baHist, 26 )
               m_Texto += Chr(179)
               m_Texto += Transform( jpbancario->bavalor, PicVal(14,2) )
               m_Texto += "<" + Chr(179)
               m_Texto += Space(14)
               m_Texto += " "
               AAdd( m_tmpmov, m_Texto )
               SKIP
            ENDDO
            SELECT jpbaccusto
            SKIP
         ENDDO
         IF Len( m_tmpmov ) > 0
            Mensagem( "Movimente com as setas, ENTER ou ESC sai" )
            achoice( 4, 0, 21, 79, m_tmpmov )
         ENDIF
         WRestore()

      CASE nKey == K_HOME
         oBrowse:GoTop()

      CASE nKey == K_END
         oBrowse:GoBottom()

      CASE Chr( nKey ) $ "Tt"
         m_mostratot = ( ! m_mostratot )
         TitBrow2()
         oBrowse:Invalidate()
         oBrowse:RefreshAll()

      CASE Chr( nKey ) $ "Dd"
         m_mostradol = ( ! m_mostradol )
         TitBrow2()
         oBrowse:Invalidate()
         oBrowse:RefreshAll()

      ENDCASE
   ENDDO

   RETURN

STATIC FUNCTION TopBrow2()

   m_CodResumo := 1

   RETURN .T.

STATIC FUNCTION botbrow2()

   m_CodResumo := Len( m_Tabela )

   RETURN .T.

STATIC FUNCTION SkipBrow2( nSkip )

   LOCAL nSkipped := 0

   IF nSkip == 0
   ELSEIF nSkip > 0 .AND. m_CodResumo < Len( m_Tabela )
      DO WHILE nSkipped < nSkip .AND. m_CodResumo < Len( m_Tabela )
         GrafProc()
         m_CodResumo++
         nSkipped++
      ENDDO
   ELSEIF nSkip < 0
      DO WHILE nSkipped > nSkip .AND. m_CodResumo > 1
         GrafProc()
         m_CodResumo--
         nSkipped--
      ENDDO
   ENDIF

   RETURN nSkipped

STATIC FUNCTION FldBrow2( nCont )

   LOCAL m_Retorno, m_TmpMes, m_TmpAno, m_Select

   m_TmpAno := iif( m_Mes - nCont <= 0, m_Ano - 1, m_Ano )
   m_TmpMes := iif( m_Mes - nCont <= 0, m_Mes - nCont + 12, m_Mes - nCont )
   m_Select := select()
   DO CASE
   CASE nCont == -1
      IF Len( m_Tabela[ m_CodResumo ] ) == 10
         m_Retorno := "->" + Left( m_Tabela[ m_CodResumo ], 10 )
      ELSE
         m_Retorno := "  " + Right( m_Tabela[ m_CodResumo ], 10 )
      ENDIF
   CASE ( m_Tabela[ m_CodResumo ] == ">ENTRADAS" .OR. m_Tabela[ m_CodResumo] == ">SAIDAS" ) .AND. ! m_MostraTot
      m_Retorno := ""
   CASE m_Tabela[ m_CodResumo ] = ">ENTRADAS"
      m_Retorno := Transform( SomaEntradas( m_TmpAno, m_TmpMes ), PicVal(14,2) )
   CASE m_Tabela[ m_CodResumo ] = ">SAIDAS"
      m_Retorno := Transform( SomaSaidas( m_TmpAno, m_TmpMes ), PicVal(14,2) )
   CASE Len(m_Tabela[ m_CodResumo ]) == 10
      m_Retorno := Transform( SomaGrupo( m_Tabela[ m_CodResumo ], m_TmpAno, m_TmpMes ), PicVal(14,2) )
   OTHERWISE
      m_Retorno := Transform( SomaResumo( Right( m_Tabela[ m_CodResumo ], 10 ), m_TmpAno, m_TmpMes ), PicVal(14,2) )
   ENDCASE
   SELECT ( m_Select )

   RETURN m_Retorno

STATIC FUNCTION TitBrow2()

   LOCAL nCont

   @ 2, 0 SAY Padc( "VALORES EM MOEDA VIGENTE", MaxCol() )
   @ 3, 1 SAY "Item"
   FOR nCont = 0 TO ( nQtdCols - 1 )
      @ 3, 16 + nCont * 20 SAY Padc( Space(3) + iif( m_Mes - nCont <= 0, ;
         StrZero( m_Mes - nCont + 12, 2 ) + "/" + StrZero( m_Ano - 1, 4 ), ;
         StrZero( m_Mes - nCont, 2 ) + "/" + StrZero( m_Ano, 4 ) ), 20 )
   NEXT

   RETURN .T.

STATIC FUNCTION SomaEntradas( m_Ano, m_Mes )

   LOCAL nTotal := 0, cResumo, nTotalTmp, nSelect := Select(), nRecNo

   SELECT jpbancario
   nRecNo := RecNo()
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      cResumo   := jpbancario->baResumo
      nTotalTmp := 0
      SEEK cResumo + StrZero( m_Ano, 4 ) + StrZero( m_Mes, 2 )
      DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == m_Ano .AND. Month( jpbancario->baDatEmi ) == m_Mes .AND. ! Eof()
         GrafProc()
         IF jpbancario->baResumo == Pad( "APLIC", 10 ) .OR. jpbancario->baResumo == Pad( "NENHUM", 10 )
            EXIT
         ENDIF
         nTotalTmp += jpbancario->baValor
         SKIP
      ENDDO
      IF nTotalTmp > 0
         nTotal += nTotalTmp
      ENDIF
      SEEK cResumo + "XXXX" SOFTSEEK
   ENDDO
   GOTO nRecNo
   SELECT ( nSelect )

   RETURN nTotal

STATIC FUNCTION SomaSaidas( nAno, nMes )

   LOCAL nTotal := 0, cResumo, nTotalTmp, nSelect := Select(), nRecNo

   SELECT jpbancario
   nRecNo := RecNo()
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      cResumo := jpbancario->baResumo
      nTotalTmp := 0
      SEEK cResumo + StrZero( nAno, 4 ) + StrZero( nMes, 2 )
      DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == nAno .AND. Month( jpbancario->baDatEmi ) == nMes .AND. ! Eof()
         GrafProc()
         IF jpbancario->baResumo == Pad( "APLIC", 10 ) .OR. jpbancario->baResumo == Pad( "NENHUM", 10 )
            EXIT
         ENDIF
         nTotalTmp += jpbancario->baValor
         SKIP
      ENDDO
      IF nTotalTmp < 0
         nTotal += nTotalTmp
      ENDIF
      SEEK cResumo + "XXXX" SOFTSEEK
   ENDDO
   GOTO nRecNo
   SELECT ( nSelect )

   RETURN nTotal

STATIC FUNCTION SomaGrupo( cGrupo, nAno, nMes )

   LOCAL nTotal := 0, nSelect := Select()

   SELECT jpbaccusto
   SEEK cGrupo
   DO WHILE jpbaccusto->cuGrupo == cGrupo .AND. ! Eof()
      GrafProc()
      nTotal += SomaResumo( jpbaccusto->cuCCusto, nAno, nMes )
      SKIP
   ENDDO
   SELECT ( nSelect )

   RETURN nTotal

STATIC FUNCTION SomaResumo( cResumo, nAno, nMes )

   LOCAL nTotal := 0, nSelect := Select()

   SELECT jpbancario
   SEEK cResumo + StrZero( nAno, 4 ) + StrZero( nMes, 2 )
   DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == nAno .AND. Month( jpbancario->baDatEmi ) == nMes .AND. ! Eof()
      GrafProc()
      nTotal += jpbancario->baValor
      SKIP
   ENDDO
   SELECT ( nSelect )

   RETURN nTotal

STATIC FUNCTION PegaContas( lPrimeiraVez )

   LOCAL mLista := {}, m_Grupo

   hb_Default( @lPrimeiraVez, .F. )
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      AAdd( mLista, jpbaccusto->cuGrupo )
      m_Grupo = jpbaccusto->cuGrupo
      DO WHILE jpbaccusto->cuGrupo == m_Grupo .AND. ! Eof()
         GrafProc()
         IF lPrimeiraVez .AND. jpbaccusto->cuMostra == "S"
            RecLock()
            REPLACE jpbaccusto->cuMostra WITH  "N"
            RecUnlock()
         ENDIF
         IF jpbaccusto->cuMostra=="S"
            AAdd( mLista, jpbaccusto->cuGrupo + jpbaccusto->cuCCusto )
         ENDIF
         SKIP
      ENDDO
   ENDDO
   AAdd( mLista, ">ENTRADAS" )
   AAdd( mLista, ">SAIDAS" )

   RETURN mLista


o tbrowse é feito encima de grupos de centro de custo e centros de custo, com/sem total geral.
Cada coluna se refere a um mês, sendo que a navegação na horizontal entre meses é infinita.
Está sendo usado um DBF pro browse, e as colunas são calculadas.

À primeira vista, seria trazer pronto do SQL, mas pra isso teria que limitar os meses das colunas.
Isso poderia ser resolvido se, ao ultrapassar a coluna, refizesse o comando SQL pra trazer todo browse.
Mesmo assim, teria o browse vertical das linhas, que pode variar.

Acho que a saída vai ser fazer um browse em array, ou em um recordset ADO, e as colunas continuarem sendo calculadas.

Numa primeira alteração, posso eliminar apenas o uso do arquivo de movimentação.
Esse arquivo representa 16MB dos 18MB que faltam pra converter.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 11:56
por JoséQuintas
Fazer por etapa, primeiro alguns cálculos.
Este pra começar

STATIC FUNCTION SomaResumo( cResumo, nAno, nMes )

   LOCAL nTotal := 0, nSelect := Select()

   SELECT jpbancario
   SEEK cResumo + StrZero( nAno, 4 ) + StrZero( nMes, 2 )
   DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == nAno .AND. Month( jpbancario->baDatEmi ) == nMes .AND. ! Eof()
      GrafProc()
      nTotal += jpbancario->baValor
      SKIP
   ENDDO
   SELECT ( nSelect )

   RETURN nTotal

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 12:42
por JoséQuintas
Lembram que comentei que a programação tende a seguir o "jeito humano"?
Então, por isso o comando SQL as vezes parece mais "humano" que o DBF, ou pelo menos mais claro.

STATIC FUNCTION SomaResumo( cResumo, nAno, nMes )

   LOCAL nTotal
   LOCAL cnSQL := ADOClass():New( AppConexao() )
   
   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( BAVALOR ) AS SOMA" + ;
         " FROM JPBANCARIO" + ;
         " WHERE BARESUMO = " + StringSQL( cResumo ) + ;
         " AND YEAR( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND MONTH( BADATEMI ) = " + NumberSQL( nMes )
      :Execute()
      nTotal := :Number( "SOMA" )
      :CloseRecordset()
   ENDWITH   

   RETURN nTotal


Some o valor, de um c.custo específico e de mes/ano específicos.

STATIC FUNCTION SomaGrupo( cGrupo, nAno, nMes )

   LOCAL nTotal := 0, nSelect := Select()

   SELECT jpbaccusto
   SEEK cGrupo
   DO WHILE jpbaccusto->cuGrupo == cGrupo .AND. ! Eof()
      GrafProc()
      nTotal += SomaResumo( jpbaccusto->cuCCusto, nAno, nMes )
      SKIP
   ENDDO
   SELECT ( nSelect )

   RETURN nTotal


Esse é um grupo de centro de custo, que soma os centros de custo dele.
Faz uso da rotina anterior, mas acho que no SQL é melhor somar de uma vez, e não ficar fazendo somas menores.
No DBF faz sentido porque permite fazer uso da chave de pesquisa.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 12:57
por JoséQuintas
Ficou igual o anterior, mas relacionado com a tabela de centro de custo, e indicando total por grupo.

STATIC FUNCTION SomaGrupo( cGrupo, nAno, nMes )

   LOCAL nTotal := 0
   
   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( BAVALOR ) AS SOMA" + ;
         " FROM JPBANCARIO" + ;
         " LEFT JOIN JPBACCUSTO ON JPBACCUSTO.CCUSTO = JPBANCARIO.BARESUMO" + ;
         " WHERE YEAR( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND MONTH( BADATEMI ) = " + NumberSQL( nMes ) + ;
         " AND JPBACCUSTO.CUGRUPO = " + StringSQL( cGrupo )
      :Execute()
      nTotal := :Number( "SOMA" )
      :CloseRecordset()
   ENDWITH


Eu deveria já ter renomeado BARESUMO pra BACCUSTO, mas ficou pra depois.
Começou assim, vai assim até terminar.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 12:59
por JoséQuintas
Complemento: eu ia usar CC ou BC pro c.custo bancario, iniciais dos campos, mas vi que tava errando muito ao digitar CCCCUSTO por exemplo, muito C junto.
Acabei usando C.U.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 13:37
por JoséQuintas
No final, a gente encontra destas coisas: duas rotinas iguais, a diferença é que uma soma positivos e a outra soma negativos:


STATIC FUNCTION SomaEntradas( m_Ano, m_Mes )

   LOCAL nTotal := 0, cResumo, nTotalTmp, nSelect := Select(), nRecNo

   SELECT jpbancario
   nRecNo := RecNo()
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      cResumo   := jpbancario->baResumo
      nTotalTmp := 0
      SEEK cResumo + StrZero( m_Ano, 4 ) + StrZero( m_Mes, 2 )
      DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == m_Ano .AND. Month( jpbancario->baDatEmi ) == m_Mes .AND. ! Eof()
         GrafProc()
         IF jpbancario->baResumo == Pad( "APLIC", 10 ) .OR. jpbancario->baResumo == Pad( "NENHUM", 10 )
            EXIT
         ENDIF
         nTotalTmp += jpbancario->baValor
         SKIP
      ENDDO
      IF nTotalTmp > 0
         nTotal += nTotalTmp
      ENDIF
      SEEK cResumo + "XXXX" SOFTSEEK
   ENDDO
   GOTO nRecNo
   SELECT ( nSelect )

   RETURN nTotal

STATIC FUNCTION SomaSaidas( nAno, nMes )

   LOCAL nTotal := 0, cResumo, nTotalTmp, nSelect := Select(), nRecNo

   SELECT jpbancario
   nRecNo := RecNo()
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      cResumo := jpbancario->baResumo
      nTotalTmp := 0
      SEEK cResumo + StrZero( nAno, 4 ) + StrZero( nMes, 2 )
      DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == nAno .AND. Month( jpbancario->baDatEmi ) == nMes .AND. ! Eof()
         GrafProc()
         IF jpbancario->baResumo == Pad( "APLIC", 10 ) .OR. jpbancario->baResumo == Pad( "NENHUM", 10 )
            EXIT
         ENDIF
         nTotalTmp += jpbancario->baValor
         SKIP
      ENDDO
      IF nTotalTmp < 0
         nTotal += nTotalTmp
      ENDIF
      SEEK cResumo + "XXXX" SOFTSEEK
   ENDDO
   GOTO nRecNo
   SELECT ( nSelect )

   RETURN nTotal


Melhor até transformar em uma só, antes de converter.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 22:48
por JoséQuintas
Só um comentário:

O Browse ficou interessante.
Neste instante, CADA célula do browse pode vir de DBF ou SQL ou Array.

Meu modo de trabalho

MensagemEnviado: 18 Jul 2020 23:39
por JoséQuintas
STATIC FUNCTION SomaSaidas( nAno, nMes )

   LOCAL nTotal := 0, cResumo, nTotalTmp, nSelect := Select(), nRecNo

   SELECT jpbancario
   nRecNo := RecNo()
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      cResumo := jpbancario->baResumo
      nTotalTmp := 0
      SEEK cResumo + StrZero( nAno, 4 ) + StrZero( nMes, 2 )
      DO WHILE jpbancario->baResumo = cResumo .AND. Year( jpbancario->baDatEmi ) == nAno .AND. Month( jpbancario->baDatEmi ) == nMes .AND. ! Eof()
         GrafProc()
         IF jpbancario->baResumo == Pad( "APLIC", 10 ) .OR. jpbancario->baResumo == Pad( "NENHUM", 10 )
            EXIT
         ENDIF
         nTotalTmp += jpbancario->baValor
         SKIP
      ENDDO
      IF nTotalTmp < 0
         nTotal += nTotalTmp
      ENDIF
      SEEK cResumo + "XXXX" SOFTSEEK
   ENDDO
   GOTO nRecNo
   SELECT ( nSelect )

   RETURN nTotal


Até que ficou simples em SQL

STATIC FUNCTION SomaMovimento( nAno, nMes, nTipo )

   LOCAL nTotal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( SOMA ) AS TOTAL FROM" + ;
         " ( SELECT BARESUMO, SUM( BAVALOR ) AS SOMA" + ;
            " FROM JPBANCARIO" + ;
            " WHERE BARESUMO NOT IN ( 'APLIC', 'NENHUM' )" + ;
            " AND YEAR( BADATEMI ) = " + NumberSQL( nAno ) + ;
            " AND MONTH( BADATEMI ) = " + NumberSQL( nMes ) + ;
            " GROUP BY BARESUMO" + ;
            " HAVING SOMA " + iif( nTipo == 1, ">", "<" ) + " 0 ) AS A"
      :Execute()
      nTotal := :Number( "TOTAL" )
      :CloseRecordset()
   ENDWITH

   RETURN nTotal

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 11:58
por JoséQuintas
Realmente, esse tbrowse é daqueles, bem fora do normal.
Pra não usar mais DBF, alterei pra array multidimensional.

Alterei pra multidimensional
STATIC FUNCTION PegaContas()

   LOCAL aCCustoList := {}, cGrupo
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT CUGRUPO, CUCCUSTO FROM JPBACCUSTO ORDER BY CUGRUPO, CUCCUSTO"
      :Execute()
      DO WHILE ! :Eof()
         AAdd( aCCustoList, { :String( "CUGRUPO" ), "", .T. } )
         cGrupo = :String( "CUGRUPO" )
         DO WHILE cGrupo == :String( "CUGRUPO" ) .AND. ! Eof()
            AAdd( aCCustoList, { :String( "CUGRUPO" ), :String( "CUCCUSTO" ), .F. } )
            :MoveNext()
         ENDDO
      ENDDO
      :CloseRecordset()
   ENDWITH
   AAdd( aCCustoList, { ">ENTRADAS", "", .F. } )
   AAdd( aCCustoList, { ">SAIDAS", "", .F. } )

   RETURN aCCustoList


O detalhe é o último item da lista, .T. ou .F.
Se for .F., NÃO é pra aparecer na tela.
Então tive que criar skip/top/bottom especiais.

STATIC FUNCTION TopCCusto()

   m_CodResumo := 1
   DO WHILE ! aCCustoList[ m_CodResumo, 3 ]
      m_CodResumo++
   ENDDO

   RETURN .T.

STATIC FUNCTION BottomCCusto()

   m_CodResumo := Len( aCCustoList )
   DO WHILE ! aCCustoList[ m_CodResumo, 3 ]
      m_CodResumo--
   ENDDO

   RETURN .T.

STATIC FUNCTION SkipCCusto( nSkip )

   LOCAL nSkipped := 0

   IF nSkip == 0
   ELSEIF nSkip > 0 .AND. m_CodResumo < Len( aCCustoList )
      DO WHILE nSkipped < nSkip .AND. m_CodResumo <= Len( aCCustoList )
         m_CodResumo++
         IF m_CodResumo <= Len( aCCustoList ) .AND. aCCustoList[ m_CodResumo, 3 ]
            nSkipped++
         ENDIF
      ENDDO
      IF m_CodResumo > Len( aCCustoList )
         BottomCCusto()
      ENDIF
   ELSEIF nSkip < 0
      DO WHILE nSkipped > nSkip .AND. m_CodResumo >= 1
         m_CodResumo--
         IF m_CodResumo > 0 .AND. aCCustoList[ m_CodResumo, 3 ]
            nSkipped--
         ENDIF
      ENDDO
      IF m_CodResumo < 1
         TopCCusto()
      ENDIF
   ENDIF

   RETURN nSkipped


NÃO é pra aparecer na tela, mas.... depende desse valor.

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 12:01
por JoséQuintas
Se apertar a letra T, aparecem os totais.

      CASE Chr( nKey ) $ "Tt"
         aCCustoList[ Len( aCCustoList ) - 1, 3 ] := ! aCCustoList[ Len( aCCustoList ) - 1, 3 ]
         aCCustoList[ Len( aCCustoList ), 3 ] := ! aCCustoList[ Len( aCCustoList ), 3 ]
         TitBrow2()
         oBrowse:Invalidate()
         oBrowse:RefreshAll()


E se teclar a letra C, aparece o detalhamento de CCusto sobre o grupo que está posicionado.

      CASE nKey == Asc( "C" ) .OR. nKey == Asc( "c" )
         IF Empty( aCCustoList[ m_CodResumo, 2 ] )
            FOR EACH oElement IN aCCustoList
               IF oElement[ 1 ] == aCCustoList[ m_CodResumo, 1 ] .AND. ! Empty( oElement[ 2 ] )
                  oElement[ 3 ] := ! oElement[ 3 ]
               ENDIF
            NEXT
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ENDIF

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 12:04
por JoséQuintas
Atualizei o nome, não faz mais sentido m_CodResumo, troquei pra nIndexCCusto
nIndex, porque é a chave, e CCusto, porque se refere a Centro de Custo.

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 12:10
por JoséQuintas
o tbrowse em questão é este aqui:

banco.png


Dá pra escolher pra detalhar o grupo que quiser, tantos quantos quiser.
Na imagem, só detalhei o grupo bens.

Então, ao teclar C em "BENS", ou detalha ou esconde os Centros de Custo desse grupo.

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 15:19
por JoséQuintas
Não serve de referência pra nada, porque é diferente de tudo, mas.... tá feito.

/*
PBANCOCOMPARAMES - COMPARATIVO MES A MES
1994.01 José Quintas
*/

#include "inkey.ch"
#include "josequintas.ch"
#define BA_ENTRADAS  1
#define BA_SAIDAS   -1

MEMVAR nIndexAno, nIndexMes, nIndexCCusto, aCCustoList, nQtdCols

PROCEDURE pBancoComparaMes

   LOCAL m_TmpMes, m_TmpAno, oBrowse, nKey, mTop, mLeft, mBottom, mRight, ColPos
   LOCAL nMCol, nMRow, oTBrowse, oElement

   IF ! AbreArquivos( "jpempresa", "jptabel", "jpconfi", "jpbaccusto", "jpbancario" )
      RETURN
   ENDIF
   aCCustoList := PegaContas( .T. )

   nIndexCCusto          := 1
   nIndexAno             := Year( Date() )
   nIndexMes             := Month( Date() )
   mTop                  := 4
   mLeft                 := 0
   mBottom               := MaxRow() - 5
   mRight                := MaxCol() - 2

   oBrowse               := TBrowseDb( mTop, mLeft, mBottom, mRight )
   oBrowse:SkipBlock     := { | nSkipRows | CCustoSkip( nSkipRows ) }
   oBrowse:GoTopBlock    := { || CCustoGoTop() }
   oBrowse:GoBottomBlock := { || CCustoGoBottom() }
   oBrowse:HeadSep       := Chr(196)
   oBrowse:ColSep        := Chr(179)
   oBrowse:FootSep       := Chr(196)
   oBrowse:FrameColor    := SetColorTbrowseFrame()
   ColPos                := 2
   nQtdCols              := 5

   oTBrowse := { ;
      { "", { || CCustoColuna( -1 ) } }, ;
      { "", { || CCustoColuna( 0  ) } }, ;
      { "", { || CCustoColuna( 1  ) } }, ;
      { "", { || CCustoColuna( 2  ) } }, ;
      { "", { || CCustoColuna( 3  ) } }, ;
      { "", { || CCustoColuna( 4  ) } } }
   ToBrowse( oTBrowse, oBrowse )

   CCustoTitulo()

   oBrowse:Right()

   DO WHILE .T.
      Mensagem( "SETAS, T Totais, ENTER Lançamentos, D Detalhes, ESC Sai" )
      nKey := 0
      DO WHILE nKey == 0 .AND. ! oBrowse:Stable
         oBrowse:Stabilize()
         nKey := Inkey()
      ENDDO

      IF oBrowse:Stable()
         oBrowse:RefreshCurrent()
         DO WHILE ! oBrowse:Stabilize()
         ENDDO
         nKey = Inkey( INKEY_IDLE, HB_INKEY_ALL - INKEY_MOVE + HB_INKEY_GTEVENT )
         IF nKey == 0
            KEYBOARD Chr( K_ESC )
            LOOP
         ENDIF
      ENDIF
      nMRow := MROW()
      NMCol := MCOL()

      DO CASE
      CASE SetKey( nKey ) != NIL
         eval( SetKey( nKey ), procname(), procline(), readvar() )
      CASE nKey > 999
         DO CASE
         CASE mBrzMove( oBrowse, nMRow, nMCol, mTop + 1, mLeft + 1, mBottom - 1, mRight - 1 )
         CASE mBrzClick( oBrowse, nMRow, nMCol )
         ENDCASE

      CASE nKey == K_ESC       ; EXIT
      CASE nKey == K_DOWN      ; oBrowse:Down()
      CASE nKey == K_UP        ; oBrowse:Up()
      CASE nKey == K_CTRL_DOWN ; oBrowse:PageDown()
      CASE nKey == K_CTRL_UP   ; oBrowse:PageUp()
      CASE nKey == K_HOME      ; oBrowse:GoTop()
      CASE nKey == K_END       ; oBrowse:GoBottom()

      CASE nKey == K_LEFT
         IF ColPos == 2
            nIndexAno := iif( nIndexMes == 12, nIndexAno + 1, nIndexAno )
            nIndexMes := iif( nIndexMes == 12, 1, nIndexMes + 1 )
            CCustoTitulo()
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ELSEIF ColPos > 1
            oBrowse:Left()
            ColPos--
         ENDIF

      CASE nKey == K_RIGHT
         IF ColPos == ( nQtdCols + 1 )
            nIndexAno = iif( nIndexMes == 1, nIndexAno - 1, nIndexAno )
            nIndexMes = iif( nIndexMes == 1, 12, nIndexMes - 1 )
            CCustoTitulo()
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ELSE
            oBrowse:Right()
            ColPos++
         ENDIF

      CASE nKey == Asc( "D" ) .OR. nKey == Asc( "d" )
         IF Empty( aCCustoList[ nIndexCCusto, 2 ] )
            FOR EACH oElement IN aCCustoList
               IF oElement[ 1 ] == aCCustoList[ nIndexCCusto, 1 ] .AND. ! Empty( oElement[ 2 ] )
                  oElement[ 3 ] := ! oElement[ 3 ]
               ENDIF
            NEXT
            oBrowse:Invalidate()
            oBrowse:RefreshAll()
         ENDIF

      CASE nKey == K_ENTER
         DO WHILE ! oBrowse:stabilize()
            GrafProc()
         ENDDO
         m_TmpMes := nIndexMes - iif( ColPos > 1, ColPos - 2, 0 )
         m_TmpAno := nIndexAno - iif( m_TmpMes < 1, 1, 0 )
         m_TmpMes := m_TmpMes + iif( m_TmpMes < 1, 12, 0 )
         CCustoDetalhes( aCCustoList[ nIndexCCusto, 1 ], aCCustoList[ nIndexCCusto, 2 ], m_TmpMes, m_TmpAno )

      CASE Chr( nKey ) $ "Tt"
         aCCustoList[ Len( aCCustoList ) - 1, 3 ] := ! aCCustoList[ Len( aCCustoList ) - 1, 3 ]
         aCCustoList[ Len( aCCustoList ), 3 ] := ! aCCustoList[ Len( aCCustoList ), 3 ]
         CCustoTitulo()
         oBrowse:Invalidate()
         oBrowse:RefreshAll()

      ENDCASE
   ENDDO
   CLOSE DATABASES

   RETURN

STATIC FUNCTION CCustoGoTop()

   nIndexCCusto := 1
   DO WHILE ! aCCustoList[ nIndexCCusto, 3 ]
      nIndexCCusto++
   ENDDO

   RETURN .T.

STATIC FUNCTION CCustoGoBottom()

   nIndexCCusto := Len( aCCustoList )
   DO WHILE ! aCCustoList[ nIndexCCusto, 3 ]
      nIndexCCusto--
   ENDDO

   RETURN .T.

STATIC FUNCTION CCustoSkip( nSkip )

   LOCAL nSkipped := 0

   IF nSkip == 0
   ELSEIF nSkip > 0 .AND. nIndexCCusto < Len( aCCustoList )
      DO WHILE nSkipped < nSkip .AND. nIndexCCusto <= Len( aCCustoList )
         nIndexCCusto++
         IF nIndexCCusto <= Len( aCCustoList ) .AND. aCCustoList[ nIndexCCusto, 3 ]
            nSkipped++
         ENDIF
      ENDDO
      IF nIndexCCusto > Len( aCCustoList )
         CCustoGoBottom()
      ENDIF
   ELSEIF nSkip < 0
      DO WHILE nSkipped > nSkip .AND. nIndexCCusto >= 1
         nIndexCCusto--
         IF nIndexCCusto > 0 .AND. aCCustoList[ nIndexCCusto, 3 ]
            nSkipped--
         ENDIF
      ENDDO
      IF nIndexCCusto < 1
         CCustoGoTop()
      ENDIF
   ENDIF

   RETURN nSkipped

STATIC FUNCTION CCustoColuna( nCont )

   LOCAL m_Retorno, m_TmpMes, m_TmpAno

   m_TmpAno := iif( nIndexMes - nCont <= 0, nIndexAno - 1, nIndexAno )
   m_TmpMes := iif( nIndexMes - nCont <= 0, nIndexMes - nCont + 12, nIndexMes - nCont )
   DO CASE
   CASE nCont == -1
      IF Empty( aCCustoList[ nIndexCCusto, 2 ] )
         m_Retorno := "->" + Pad( aCCustoList[ nIndexCCusto, 1 ], 10 )
      ELSE
         m_Retorno := "  " + Pad( aCCustoList[ nIndexCCusto, 2 ], 10 )
      ENDIF
   CASE ! aCCustoList[ nIndexCCusto, 3 ]
      m_Retorno := ""
   CASE aCCustoList[ nIndexCCusto, 1 ] = ">ENTRADAS"
      m_Retorno := Transform( SomaMovimento( m_TmpAno, m_TmpMes, BA_ENTRADAS ), PicVal(14,2) )
   CASE aCCustoList[ nIndexCCusto, 1 ] = ">SAIDAS"
      m_Retorno := Transform( SomaMovimento( m_TmpAno, m_TmpMes, BA_SAIDAS ), PicVal(14,2) )
   CASE Empty( aCCustoList[ nIndexCCusto, 2 ] )
      m_Retorno := Transform( SomaGrupo( aCCustoList[ nIndexCCusto, 1 ], m_TmpAno, m_TmpMes ), PicVal(14,2) )
   OTHERWISE
      m_Retorno := Transform( SomaResumo( aCCustoList[ nIndexCCusto, 2 ], m_TmpAno, m_TmpMes ), PicVal(14,2) )
   ENDCASE

   RETURN m_Retorno

STATIC FUNCTION CCustoTitulo()

   LOCAL nCont

   @ 2, 0 SAY Padc( "VALORES EM MOEDA VIGENTE", MaxCol() )
   @ 3, 1 SAY "Item"
   FOR nCont = 0 TO ( nQtdCols - 1 )
      @ 3, 16 + nCont * 20 SAY Padc( Space(3) + iif( nIndexMes - nCont <= 0, ;
         StrZero( nIndexMes - nCont + 12, 2 ) + "/" + StrZero( nIndexAno - 1, 4 ), ;
         StrZero( nIndexMes - nCont, 2 ) + "/" + StrZero( nIndexAno, 4 ) ), 20 )
   NEXT

   RETURN .T.

STATIC FUNCTION SomaMovimento( nAno, nMes, nTipo )

   LOCAL nTotal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( SOMA ) AS TOTAL FROM" + ;
         " ( SELECT BARESUMO, SUM( BAVALOR ) AS SOMA" + ;
         " FROM JPBANCARIO" + ;
         " WHERE BARESUMO NOT IN ( 'APLIC', 'NENHUM' )" + ;
         " AND Year( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND Month( BADATEMI ) = " + NumberSQL( nMes ) + ;
         " GROUP BY BARESUMO" + ;
         " HAVING SOMA " + iif( nTipo == 1, ">", "<" ) + " 0 ) AS A"
      :Execute()
      nTotal := :Number( "TOTAL" )
      :CloseRecordset()
   ENDWITH

   RETURN nTotal

STATIC FUNCTION SomaGrupo( cGrupo, nAno, nMes )

   LOCAL nTotal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( BAVALOR ) AS SOMA" + ;
         " FROM JPBANCARIO" + ;
         " LEFT JOIN JPBACCUSTO ON JPBACCUSTO.CUCCUSTO = JPBANCARIO.BARESUMO" + ;
         " WHERE Year( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND Month( BADATEMI ) = " + NumberSQL( nMes ) + ;
         " AND JPBACCUSTO.CUGRUPO = " + StringSQL( cGrupo )
      :Execute()
      nTotal := :Number( "SOMA" )
      :CloseRecordset()
   ENDWITH

   RETURN nTotal

STATIC FUNCTION SomaResumo( cResumo, nAno, nMes )

   LOCAL nTotal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT SUM( BAVALOR ) AS SOMA" + ;
         " FROM JPBANCARIO" + ;
         " WHERE BARESUMO = " + StringSQL( cResumo ) + ;
         " AND Year( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND Month( BADATEMI ) = " + NumberSQL( nMes )
      :Execute()
      nTotal := :Number( "SOMA" )
      :CloseRecordset()
   ENDWITH

   RETURN nTotal

STATIC FUNCTION PegaContas()

   LOCAL aCCustoList := {}, cGrupo
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SELECT CUGRUPO, CUCCUSTO FROM JPBACCUSTO ORDER BY CUGRUPO, CUCCUSTO"
      :Execute()
      DO WHILE ! :Eof()
         AAdd( aCCustoList, { :String( "CUGRUPO" ), "", .T. } )
         cGrupo = :String( "CUGRUPO" )
         DO WHILE cGrupo == :String( "CUGRUPO" ) .AND. ! Eof()
            AAdd( aCCustoList, { :String( "CUGRUPO" ), :String( "CUCCUSTO" ), .F. } )
            :MoveNext()
         ENDDO
      ENDDO
      :CloseRecordset()
   ENDWITH
   AAdd( aCCustoList, { ">ENTRADAS", "", .F. } )
   AAdd( aCCustoList, { ">SAIDAS", "", .F. } )

   RETURN aCCustoList

STATIC FUNCTION CCustoDetalhes( cGrupo, cCCusto, nMes, nAno )

   LOCAL oTBrowse
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WSave()
   Mensagem( "Aguarde, pesquisando movimentação..." )
   Cls()
   @ 2, 0 SAY "Grupo:" + cGrupo + iif( Empty( cCCusto ), "", ", CCusto:" + cCCusto ) + ;
      ", mes:" + StrZero( nMes, 2 ) + "/" + StrZero( nAno, 4 )
   WITH OBJECT cnSQL
      :cSQL := "SELECT JPBACCUSTO.CUGRUPO, IDBANCARIO, BACONTA, BARESUMO," + ;
         " BADATBAN, BADATEMI, BAHIST, BAVALOR" + ;
         " FROM JPBANCARIO" + ;
         " LEFT JOIN JPBACCUSTO ON JPBACCUSTO.CUCCUSTO = JPBANCARIO.BARESUMO" + ;
         " WHERE Year( BADATEMI ) = " + NumberSQL( nAno ) + ;
         " AND Month( BADATEMI ) = " + NumberSQL( nMes ) + ;
         " AND JPBACCUSTO.CUGRUPO = " + StringSQL( cGrupo )
      IF ! Empty( cCCusto )
         :cSQL += " AND BARESUMO = " + StringSQL( cCCusto )
      ENDIF
      :cSQL += " ORDER BY BACONTA, BADATBAN, BADATEMI, IDBANCARIO"
      :Execute()
      oTBrowse := { ;
         { "BANCO",     { || iif( :Date( "BADATBAN" ) == Stod( "29991231" ), Space(8), :Date( "BADATBAN" ) ) } }, ;
         { "EMISSAO",   { || :Date( "BADATEMI" ) } }, ;
         { "HISTORICO", { || :String( "BAHIST", 50 ) } }, ;
         { "VALOR",     { || Transform( :Number( "BAVALOR" ), PicVal(14,2) ) } } }
      BrowseADO( cnSQL, oTBrowse, "BAHIST", { || "" } )
      :CloseRecordset()
   ENDWITH
   KEYBOARD ""
   WRestore()

   RETURN NIL



Aproveitei pra usar nomes melhores, dividir melhor, etc. etc.
Até eliminei o uso de variáveis PRIVATE, mas voltei atrás.
Também pensei em deixar as variáveis numa classe, pra simplificar, mas... dá no mesmo de variável PRIVATE, então ficou assim.

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 17:43
por JoséQuintas
Cabe um comentário interessante sobre o fonte:

No geral, as modificações foram para organizar o fonte, e não para o SQL.
Os 3 módulos que pegariam do DBF, agora pegam do SQL, e até que ficaram relativamente simples.

Antes, acabava gravando no DBF, só por causa da visualização.
A gravação foi eliminada com o uso de array.
O resto passou a ser só consulta, que é o que deveria ser desde o começo.

Também poderia ser um recordset ADO local, ao invés de array.... mas assim tá bom.

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 17:55
por JoséQuintas
Este outro eu até já sei o que vou usar.
Este altera toda ordem do bancário, pra mostrar tudo junto e misturado, não importa a conta.
Outra gravação no DBF que não deveria existir.

Usando o resultado de outras pesquisas pela internet, vai ser substituído por um único comando SQL.

/*
PBANCOCONSOLIDA - SALDO CONSOLIDADO DAS CONTAS
1994.04 José Quintas
*/

#include "inkey.ch"

MEMVAR mRecalcAuto, m_Filtro

PROCEDURE pBancoConsolida

   LOCAL mDataIni, m_Saldo, m_DtBco, m_DtEmi, oTBrowse
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   m_FIltro := {} // pra usar na funcao chamada
   mRecalcAuto := .T. // necessaria ref. recalculo
   mDataIni := Ctod("") // para uso da funcao pBancoLanca
   HB_SYMBOL_UNUSED( mRecalcAuto + mDataIni )
   IF ! AbreArquivos( "jpempresa", "jptabel", "jpconfi", "jpbaccusto", "jpbancario" )
      RETURN
   ENDIF
   WITH OBJECT cnSQL
      SELECT jpbaccusto
      GOTO TOP
      SELECT jpbancario

      Mensagem( "Aguarde... Efetuando cálculos..." )
      SELECT jpbancario
      OrdSetFocus( "bancario3" )
      GOTO TOP
      m_Saldo = 0
      DO WHILE ! Eof()
         Grafproc()
         m_saldo += jpbancario->bavalor
         :QueryCreate()
         :QueryAdd( "BASALDO", m_Saldo )
         jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
         :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
         SKIP
      ENDDO
      GOTO BOTTOM
      m_dtbco = Ctod("")
      m_dtemi = Ctod("")
      DO WHILE ! Bof()
         GrafProc()
         IF jpbancario->baDatBan != m_dtbco
            IF jpbancario->baImpSld != "S"
               :QueryCreate()
               :QueryAdd( "BAIMPSLD", "S" )
               jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
               :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
            ENDIF
         ELSEIF jpbancario->baDatBan == Stod( "29991231" ) .AND. jpbancario->baDatEmi != m_dtemi
            IF jpbancario->baImpSld != "S"
               :QueryCreate()
               :QueryAdd( "BAIMPSLD", "S" )
               jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
               :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
            ENDIF
         ELSEIF jpbancario->baImpSld == "S"
            :QueryCreate()
            :QueryAdd( "BAIMPSLD", "N" )
            jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
            :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
         ENDIF
         m_dtbco = jpbancario->baDatBan
         m_dtemi = jpbancario->baDatEmi
         SKIP -1
      ENDDO
      SEEK Dtos( Date() ) SOFTSEEK
      Mensagem( "I Inclui, A Altera, E Exclui, Ctrl-L Pesquisa, ESC Sai" )
      oTBrowse := { ;
         { "BANCO",         { || iif( jpbancario->baValor == 0, Replicate( "-", 8 ), iif( jpbancario->baDatBan == Stod( "29991231" ), Space(8), Dtoc( jpbancario->baDatBan ) ) ) } }, ;
         { "EMISSÃO",       { || iif( jpbancario->baValor == 0, Replicate( "-", 8 ), iif( jpbancario->baDatEmi == Stod( "29991231" ), Space(8), Dtoc( jpbancario->baDatEmi ) ) ) } }, ;
         { "DESP/REC",      { || iif( jpbancario->baValor == 0, Replicate( "-", Len( jpbancario->baResumo ) ), jpbancario->baResumo ) } }, ;
         { "HISTÓRICO",     { || iif( jpbancario->baValor == 0, Pad( jpbancario->baConta + iif( jpbancario->baAplic == "S", "(Aplicação)", "" ), Len( jpbancario->bahist ) ), jpbancario->baHist ) } }, ;
         { "ENTRADA",       { || iif( jpbancario->baValor > 0, Transform( Abs( jpbancario->baValor ), PicVal(14,2) ), Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
         { "SAÍDA",         { || iif( jpbancario->baValor < 0, Transform( Abs( jpbancario->baValor ), PicVal(14,2) ), Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
         { "SALDO",         { || iif( jpbancario->baImpSld == "S", Transform( jpbancario->baSaldo, PicVal(14,2) ), Space( Len( Transform( jpbancario->baSaldo, PicVal(14,2) ) ) ) ) } }, ;
         { " ",             { || ReturnValue( " ", vSay( 2, 0, "CONTA " + jpbancario->baConta ) ) } } }
      DO WHILE .T.
         BrowseDbfRC( 4, 0, MaxRow() - 3, MaxCol(), oTBrowse, { | b, k | DigBancoLanca( b, k ) } )
         IF LastKey() == K_ESC
            EXIT
         ENDIF
      ENDDO
      OrdSetFocus( "bancario1" )
   ENDWITH
   RecalculoBancario()
   CLOSE DATABASES

   RETURN

Meu modo de trabalho

MensagemEnviado: 19 Jul 2020 18:27
por JoséQuintas
Pronto, com duas coisas novas, pra mim, do MySQL:

#include "inkey.ch"

PROCEDURE pBancoConsolida

   LOCAL oTBrowse
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   WITH OBJECT cnSQL
      :cSQL := "SET @SOMA = 0"
      :ExecuteCmd()
      :cSQL := "SELECT BADATBAN, BADATEMI, BARESUMO, BAHIST, " + ;
         " IF( BAVALOR > 0, BAVALOR, 0 ) AS ENTRADA," + ;
         " IF( BAVALOR < 0, BAVALOR, 0 ) AS SAIDA," + ;
         " @SOMA := @SOMA + BAVALOR AS SALDO" + ;
         " FROM JPBANCARIO" + ;
         " WHERE BAVALOR <> 0"  + ;
         " ORDER BY BADATBAN, BADATEMI, IDBANCARIO"
      :Execute()
      oTBrowse := { ;
         { "BANCO",     { || if( :Date( "BADATBAN" ) = Stod( "29991231" ), Space(8), Dtoc( :Date( "BADATBAN" ) ) ) } }, ;
         { "EMISSAO",   { || :Date( "BADATEMI" ) } }, ;
         { "CCUSTO",    { || :String( "BARESUMO", 10 ) } }, ;
         { "HISTORICO", { || :String( "BAHIST", 50 ) } }, ;
         { "ENTRADAS",  { || Transform( :Number( "ENTRADA" ), "@ZE 999,999,999.99" ) } }, ;
         { "SAIDAS",    { || Transform( :Number( "SAIDA" ), "@ZE 999,999,999.99" ) } }, ;
         { "SALDO",     { || Transform( :Number( "SALDO" ), "@E 999,999,999.99" ) } } }
      BrowseADO( cnSQL, oTBrowse, "BAHIST", { || "" } )
      :cSQL := "SET @SOMA = NULL"
      :ExecuteCmd()
      :CloseRecordset()
   ENDWITH
   KEYBOARD ""
   CLOSE DATABASES

   RETURN


De novidade, criar uma variável no início e apagar no final.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 14:12
por JoséQuintas
Tem uma coisa que tive que retirar no browse anterior.... mas acho que tem solução.

Mostrar o saldo apenas no último lançamento do dia.

Vai ser um comando "diferente", mas acho que dá.
Um comando parecido, uma coluna S/N, mudou a data, altera pra S
O detalhe é que isso precisa ser em ordem decrescente de data, e o browse precisa em ordem crescente.

Pensei numa possibilidade, que parece maluca, mas é relativamente simples:

SELECT ..... ORDER BY ASC // este aqui só altera ordem
FROM
( SELECT ... ORDER BY DESC ) // este aqui altera mostrar s/n
FROM
( SELECT ... ORDER BY ASC ) // este aqui calcula saldo


Pego em ordem crescente pra fazer a conta do saldo
Depois em decrescente, pra colocar esse Sim ou Não quando alterar data
Depois novamente em crescente pra ficar na ordem da tela

Pra otimizar, e usar menos memória do servidor, TALVEZ fazer os selects só da parte que interessa, e no final relacionar e trazer o histórico.
TALVEZ, sei lá se isso ajuda ou atrapalha o servidor.

Talvez esteja na hora de usar alguma ferramenta que analise os processos do servidor.
Nenhum problema até agora, mas.... como eu digo sempre, melhor ver isso quando está tudo tranquilo, do que quando estiver com problema.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 21:38
por Vlademiro
Você poderia criar views para as consultas mais complexas, aí não precisaria escrever sempre selects mais complexos no seu código. Só precisa de um sistema de trabalho para gravar essas views (uma espécie de dicionário). As view não poupam recursos, porque o select continua sendo o mesmo, mas facilita para você.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 22:41
por JoséQuintas
Vlademiro escreveu:Você poderia criar views para as consultas mais complexas


Por enquanto ainda evitando isso, até porque ainda não me senti seguro com o backup do MySQL contendo VIEW.
Achei que no backup o view fica esquisito, diferente do comando usado pra criação.
Tô mais pra gravar o comando numa tabela normal do que num view.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 23:16
por Vlademiro
Pense na view como uma espécie de variável. O backup de uma view é só o seu comando de criação Create view ... Ela faz parte da estrutura do banco, não dos dados.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 23:25
por Vlademiro
O único problema grave que um banco de dados pode apresentar, na minha opinião, é ele ficar lento devido a triggers e store procedures que vc cria. A gente vai se empolgando e começa a passar toda a lógica de negócios para o banco. Se isso não for bem feito, o banco fica lento. Os especialistas, não eu, recomendam não abusar desse recurso. Quanto a view ela é uma forma de tornar o comando select mais fácil para vc manter no futuro. Se vc mecher em alguma tabela, mudar o nome dos campos, a view correspondente vai parar de funcionar. Outro cuidado é não criar selects muito complexos para não deixar o banco lento. As vezes se for um relatório de fim de mês compensa, mas para uso diário não. Para isso existem ferramentas que analisam o desempenho do select ente ajudam a encontrar bgargalos.

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 23:36
por Vlademiro
Já trabalhei com MySQL há uns 15 anos. Era um sistema que ficava na web recebendo dados de rastreadores que eram alugados. Cada rastreador mandava dados para o banco a cada minuto. Quando o carro parava o rastreador mandava a cada 30 minutos. Em uma semana o banco MySql tinha 40 milhões de registros. Como a estrutura de servidor e disco era modesta, a gente migrava o resumo para um outro banco a cada mês. MySQL é um ótimo banco, vc não deveria ter receio das views. Kkkkk

Meu modo de trabalho

MensagemEnviado: 20 Jul 2020 23:38
por Vlademiro
E olha que meu ex chefe, já de saudosa memória, era meio doido. O primeiro servidor era um windows xp . Só depois descobrimos que o xp recusava conexões TCP depois de um certo limite. A gente tava botando a culpa no MySQL mas era windows. Depois disso ele colocou um windows server.

Meu modo de trabalho

MensagemEnviado: 21 Jul 2020 00:11
por JoséQuintas
Vlademiro escreveu:Só depois descobrimos que o xp recusava conexões TCP depois de um certo limite


Antes do XP não havia limites. No XP há o limite de "half-open connections" de apenas 8.
Acima disso, começa a ser criada uma fila, que pode até travar tudo se ficar longa.
São conexões em andamento, abrindo e/ou fechando.
Segundo a Microsoft foi pra segurança, pra controlar melhor conexões porque usam sobrecarga pra invasão.

Já vi que conforme o VIEW, ele faz um SELECT COMPLETO, e não parcial, então também precisa tomar cuidado com ele.

Confundi, não é o VIEW que fica diferente no backup, é a STORED PROCEDURE.

Meu modo de trabalho

MensagemEnviado: 21 Jul 2020 21:58
por JoséQuintas
maiores DBFs ainda existentes, no cliente referência:

06/07/2020  11:25           314.624 jpsenha.dbf
03/03/2020  02:33         1.254.539 jpcidade.dbf
21/07/2020  16:07        16.368.631 jpbancario.dbf
              16 arquivo(s)     18.397.775 bytes


o bancário representa quase que o total dos DBFs.
E o último único fonte que precisa do DBF, ainda em andamento.

/*
PBANCOLANCA - MOVIMENTACAO BANCARIA
1989.09 José Quintas

2018.05.21 Opção de excluir tudo de uma conta
*/

#include "tbrowse.ch"
#include "inkey.ch"

MEMVAR m_Prog
MEMVAR m_Filtro, dDataInicial
MEMVAR m_Alterou
MEMVAR mbaConta, m_Aplic, m_Confirma

PROCEDURE pBancoLanca

   LOCAL GetList := {}, oTbrowse, cTempFile, oElement
   LOCAL oFrm := frmGuiClass():New()
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF ! AbreArquivos( "jpempresa", "jptabel", "jpconfi", "jpbancario" )
      RETURN
   ENDIF
   SELECT jpbancario

   dDataInicial := Date() - 20
   @ 12, 3 SAY "Data inicial: " GET dDataInicial
   Mensagem( "Digite data inicial a visualizar, ESC sai" )
   READ
   Mensagem()

   IF LastKey() == K_ESC
      CLOSE DATABASES
      RETURN
   ENDIF
   oFrm:cOptions := "IAE"
   oFrm:lNavigate := .F.
   AAdd( oFrm:acMenuOptions, "<F>Filtro" )
   AAdd( oFrm:acMenuOptions, "<Ctrl-L>Pesquisa" )
   AAdd( oFrm:acMenuOptions, "<R>Recalc." )
   AAdd( oFrm:acMenuOptions, "<S>SomaL" )
   AAdd( oFrm:acMenuOptions, "<P>Aplic" )
   AAdd( oFrm:acMenuOptions, "<C>Conta" )
   AAdd( oFrm:acMenuOptions, "<N>N.Conta" )
   AAdd( oFrm:acMenuOptions, "<F4>Exc.Conta" )
   AAdd( oFrm:acMenuOptions, "<T>T.Conta" )
   oFrm:FormBegin()

   IF IsMaquinaJPA()
      WITH OBJECT cnSQL
         :cSQL := "SELECT BACONTA, BAAPLIC, BADATBAN, BADATEMI, BAHIST, BAVALOR," + ;
            " IDBANCARIO, BASALDO, BARESUMO, BAIMPSLD, IF( BAVALOR < 0, 2, 1 ) AS ORDEM" + ;
            " FROM JPBANCARIO" + ;
            " WHERE BADATBAN >= CAST( " + DateSQL( dDataInicial - 1 ) + " AS DATE )" + ;
            " OR BAVALOR = 0" + ;
            " ORDER BY BACONTA, BAAPLIC, BADATBAN, BADATEMI, ORDEM, IDBANCARIO"
         oTBrowse := { ;
            { "BANCO",     { || iif( :Number( "BAVALOR" ) == 0, Space(8), ;
            iif( :Date( "BADATBAN" ) == Stod( "29991231" ), Space(8), :Date( "BADATBAN" ) ) ) } }, ;
            { "EMISSAO",   { || iif( :Number( "BAVALOR" ) == 0, Space(8), :Date( "BADATEMI" ) ) } }, ;
            { "CCUSTO",    { || iif( :Number( "BAVALOR" ) == 0, Space(10), :String( "BARESUMO", 10 ) ) } }, ;
            { "HISTORICO", { || iif( :Number( "BAVALOR" ) == 0, ;
                                Padc( :String( "BACONTA" ) + iif( :String( "BAAPLIC" ) == "S", "(Aplicacao)", "" ), 45 ), ;
                                :String( "BAHIST", 45 ) ) } }, ;
            { "ENTRADA",   { || iif( :Number( "BAVALOR" ) > 0, Transform( Abs( :Number( "BAVALOR" ) ), PicVal(14,2) ), ;
                                Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
            { "SAIDA",     { || iif( :Number( "BAVALOR" ) < 0, Transform( Abs( :Number( "BAVALOR" ) ), PicVal(14,2) ), ;
                                Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
            { "SALDO",     { || iif( :String( "BAIMPSLD" ) == "S", ;
                                Transform( :Number( "BASALDO" ), PicVal(14,2) ), ;
                                Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } } }
         FOR EACH oElement IN oTbrowse
            AAdd( oElement, { || iif( :Number( "BAVALOR" ) == 0, { 5, 2 }, { 1, 2 } ) } )
         NEXT
         :Execute()
         BrowseADORC( 7, 0, MaxRow() - 3, MaxCol(), cnSQL, oTBrowse, "BACONTA,BARESUMO,BAHIST", { || "" }, { || EditLanc( cnSQL ) } )
         KEYBOARD ""
      ENDWITH
   ENDIF

   SELECT jpbancario
   cTempFile := MyTempFile( "CDX" )
   INDEX ON jpbancario->baConta + jpbancario->baAplic + Dtos( jpbancario->baDatBan ) + Dtos( jpbancario->baDatEmi ) + ;
      iif( jpbancario->baValor > 0, "1", "2" ) + StrZero( jpbancario->( RecNo() ), 6 ) TAG TEMP TO ( cTempFile ) ;
      FOR Dtos( jpbancario->baDatBan ) >= Dtos( dDataInicial ) .OR. ( jpbancario->baValor == 0 )
   SET INDEX TO ( PathAndFile( "jpbancario" ) ), ( cTempFile )
   OrdSetFocus( "temp" )

   m_Filtro := {}
   SET FILTER TO Filtro()
   SEEK jpbancario->baConta + jpbancario->baAplic + Dtos( Date() ) SOFTSEEK
   SKIP -1

   oTBrowse := { ;
      { "BANCO",     { || iif( jpbancario->baValor == 0, Space(8), ;
                          iif( jpbancario->baDatBan == Stod( "29991231" ), Space(8), ;
                          Dtoc( jpbancario->baDatBan ) ) ) } }, ;
      { "EMISSÃO",   { || iif( jpbancario->baValor == 0, Space(8), ;
                          iif( jpbancario->baDatEmi == Stod( "29991231" ), Space(8), ;
                          Dtoc( jpbancario->baDatEmi ) ) ) } }, ;
      { "CCUSTO",    { || iif( jpbancario->baValor == 0, Space( Len( jpbancario->baResumo ) ), ;
                          jpbancario->baResumo ) } }, ;
      { "HISTÓRICO", { || iif( jpbancario->baValor == 0, Padc( jpbancario->baConta + iif( jpbancario->baAplic == "S", "(Aplicação)", "" ), Len( jpbancario->bahist ) ), ;
                          jpbancario->baHist ) } }, ;
      { "ENTRADA",   { || iif( jpbancario->baValor > 0, Transform( Abs( jpbancario->baValor ), PicVal(14,2) ), ;
                          Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
      { "SAÍDA",     { || iif( jpbancario->baValor < 0, Transform( Abs( jpbancario->baValor ), PicVal(14,2) ), ;
                          Space( Len( Transform( 0, PicVal(14,2) ) ) ) ) } }, ;
      { "SALDO",     { || iif( jpbancario->baImpSld == "S", Transform( jpbancario->baSaldo, PicVal(14,2) ), ;
                          Space( Len( Transform( jpbancario->baSaldo, PicVal(14,2) ) ) ) ) } } }
   FOR EACH oElement IN oTbrowse
      AAdd( oElement, { || iif( jpbancario->baValor == 0, { 5, 2 }, { 1, 2 } ) } )
   NEXT
   DO WHILE .T.
      Cls()
      Mensagem( "I Inclui, A Altera, E Exclui, C-L Pesquisa, P Aplicação, C Contas, " + ;
         "N Nova_conta, F Filtro,  R Recálculo, T Troca_conta, S Soma_Lançtos, " + ;
         "D Desliga_Recálculo, F4 Exclui_Conta, ESC sai" )
      KEYBOARD Chr( 205 )
      Inkey(0)
      BrowseDbfRC( 7, 0, MaxRow() - 3, MaxCol(), oTBrowse, { | b, k, cnSQL | DigBancoLanca( b, k, cnSQL ) } )
      Mensagem()
      IF LastKey() == K_ESC
         EXIT
      ENDIF
   ENDDO
   CLOSE DATABASES
   oFrm:FormEnd()
   fErase( cTempFile )

   RETURN

FUNCTION EditLanc( b, k, cnSQL )

   (b)
   (k)
   (cnSQL)

   RETURN TBR_CONTINUE

FUNCTION DigBancoLanca( ... ) // NAO STATIC usada em pBancoConsolida

   LOCAL nRecNo, m_Aplic, mbaConta

   IF LastKey() == K_ESC
      RETURN 0
   ENDIF
   m_Alterou = .F.
   DO CASE
   CASE Chr( LastKey() ) $ "Ss" .AND. m_Prog == "PBANCOLANCA"
      SomaFiltro()

   CASE Chr( LastKey() ) $ "Tt" .AND. m_Prog == "PBANCOLANCA"
      TrocaConta()

   CASE Chr( LastKey() ) $ "Rr"
      nRecNo := RecNo()
      BARecalcula()
      GOTO ( nRecNo )

   CASE Chr( LastKey() ) $ "Nn" .AND. m_Prog == "PBANCOLANCA"
      NovaConta()

   CASE Chr( LastKey() ) $ "Pp" .AND. m_Prog == "PBANCOLANCA"
      mbaConta = jpbancario->baConta
      m_Aplic = iif( jpbancario->baAplic == "S", "N", "S" )
      ve_Conta( mbaConta, m_Aplic )

   CASE Chr( LastKey() ) $ "Ff"
      DO DigFiltro

   CASE Chr( LastKey() ) $ "Cc" .AND. m_Prog == "PBANCOLANCA"
      DO DigConta

   CASE LastKey() == K_CTRL_L .AND. m_Prog == "PBANCOLANCA"
      pBancoLancaLocaliza()
      RETURN TBR_EXIT

   CASE Chr( LastKey() ) == "2"
      KEYBOARD Chr( K_DOWN )
      RETURN TBR_CONTINUE

   CASE Chr( LastKey() ) == "8"
      KEYBOARD Chr( K_UP )
      RETURN TBR_CONTINUE

   CASE LastKey() == K_HOME .OR. Chr( LastKey() ) == "7"
      KEYBOARD Chr( K_CTRL_PGUP )
      RETURN TBR_CONTINUE

   CASE LastKey() == K_CTRL_PGDN .OR. Chr( LastKey() ) == "1"
      KEYBOARD Chr( K_CTRL_PGDN )
      RETURN TBR_CONTINUE

   CASE LastKey() == K_INS .OR. Chr( LastKey() ) == "0" .OR. Chr( LastKey() ) $ "Ii"
      cadlanc( "INCLUSAO" )
      RETURN TBR_CONTINUE

   CASE LastKey() == K_DEL .OR. Chr( LastKey() ) == "." .OR. Chr( LastKey() ) $ "Ee"
      cadlanc( "EXCLUSAO" )
      RETURN TBR_CONTINUE

   CASE LastKey() == K_ENTER .OR. Chr( LastKey() ) $ "Aa"
      IF jpbancario->baValor != 0
         nRecNo := RecNo()
         cadlanc( "ALTERACAO" )
         IF nRecNo != RecNo() .OR. m_Alterou .OR. ! Filtro()
            RETURN TBR_EXIT
         ENDIF
      ENDIF
      RETURN TBR_CONTINUE

   CASE LastKey() == K_F4
      ExcluiConta()
      RETURN TBR_EXIT

   ENDCASE

   RETURN TBR_CONTINUE

STATIC PROCEDURE TrocaConta

   LOCAL GetList := {}, mbaConta, nRecNo, mbaContaOld
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   mbaConta    := jpbancario->baConta
   mbaContaOld := mbaConta
   WOpen( 5, 5, 9, 75, "Troca para Conta" )
   nRecNo := RecNo()
   @ 7, 15 SAY "Conta..:" GET mbaConta PICTURE "@!" VALID ValidBancarioConta( @mbaConta )
   Mensagem( "Digite Conta, F9 pesquisa, ESC Sai" )
   READ
   Mensagem()
   GOTO ( nRecNo )
   IF jpbancario->baConta != mbaConta .AND. LastKey() != K_ESC
      IF MsgYesNo( "Confirme transferência para esta Conta?" )
         WITH OBJECT cnSQL
            :QueryCreate()
            :QueryAdd( "BACONTA", mbaConta )
            jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
            :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
         ENDWITH
         nRecNo := RecNo()
         BARecalcula( mbaConta )
         BARecalcula( mbaContaOld )
         GOTO ( nRecNo )
      ENDIF
   ENDIF
   WClose()

   RETURN

STATIC FUNCTION Filtro()

   LOCAL oElement, mReturn

   mReturn := .T.
   IF jpbancario->baValor != 0
      FOR EACH oElement IN m_Filtro
         DO CASE
         CASE oElement $ jpbancario->baResumo
         CASE oElement $ jpbancario->baHist
         CASE oElement $ Dtoc( jpbancario->baDatEmi )
         CASE oElement $ Dtoc( jpbancario->baDatBan )
         CASE oElement $ jpbancario->baConta
         CASE Val( oElement ) != 0 .AND. Val( oElement ) == Abs( jpbancario->baValor )
         OTHERWISE
            mReturn := .F.
            EXIT
         ENDCASE
      NEXT
      IF Type( "dDataInicial" ) == "D"
         IF Dtos( dDataInicial ) > Dtos( jpbancario->baDatBan )
            mReturn := .F.
         ENDIF
      ENDIF
   ENDIF
   GrafProc()

   RETURN mReturn

STATIC FUNCTION ExcluiConta()

   LOCAL cConta := jpbancario->baConta, cConfirma := "NAO", GetList := {}, cOrdSetFocus

   Mensagem( "Confirme se vai excluir tudo sobre a conta " + cConta + " digitando SIM" )
   @ Row(), Col() + 2 GET cConfirma PICTURE "@!A"
   READ
   IF LastKey() == K_ESC .OR. cConfirma != "SIM"
      RETURN NIL
   ENDIF
   SELECT jpbancario
   cOrdSetFocus := OrdSetFocus()
   OrdSetFocus( "bancario1" )
   DO WHILE .T.
      SEEK cConta
      IF Eof()
         EXIT
      ENDIF
      RecLock()
      DELETE
      RecUnlock()
   ENDDO
   OrdSetFocus( cOrdSetFocus )

   RETURN NIL

STATIC FUNCTION NovaConta()

   LOCAL cTxt := Space(15), GetList := {}, nIdBancario
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   Mensagem( "Digite nova Conta, ESC Sai" )
   @ Row(), Col() + 2 GET cTxt PICTURE "@!"
   READ
   Mensagem()
   IF LastKey() != K_ESC
      WITH OBJECT cnSQL
         :QueryCreate()
         :QueryAdd( "BACONTA", cTxt )
         :QueryAdd( "BAAPLIC", "N" )
         nIdBancario := :QueryExecuteInsert( "JPBANCARIO" )
         :QueryAdd( "IDBANCARIO", StrZero( nIdBancario, 6 ) )
         jpbancario->( :DBFQueryExecuteInsert( "JPBANCARIO" ) )
      ENDWITH
   ENDIF

   RETURN NIL

STATIC FUNCTION ve_Conta

   LOCAL m_RecNo, nIdBancario
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   PARAMETERS mbaConta, m_Aplic, m_Confirma

   IF pcount() < 3
      PRIVATE m_Confirma

      m_Confirma := .T.
   ENDIF
   m_RecNo := RecNo()
   SEEK mbaConta + m_Aplic
   IF Eof()
      IF m_Confirma
         IF ! MsgYesNo( "Conta e/ou Aplicação não cadastrada! Cadastra?" )
            GOTO m_RecNo
            RETURN .F.
         ENDIF
      ENDIF
      WITH OBJECT cnSQL
         :QueryCreate()
         :QueryAdd( "BACONTA", mbaConta )
         :QueryAdd( "BAAPLIC", m_Aplic )
         nIdBancario := :QueryExecuteInsert( "JPBANCARIO" )
         :QueryAdd( "IDBANCARIO", StrZero( nIdBancario, 6 ) )
         jpbancario->( :DBFQueryExecuteInsert( "JPBANCARIO" ) )
         :QueryCreate()
         :QueryAdd( "BACONTA", mbaConta )
         :QueryAdd( "BAAPLIC", m_Aplic )
         :QueryAdd( "BADATBAN", Stod( "29991231" ) )
         :QueryAdd( "BADATEMI", Stod( "29991231" ) )
         :QueryAdd( "BAVALOR", 0 )
         nIdBancario := :QueryExecuteInsert( "JPBANCARIO" )
         :QueryAdd( "IDBANCARIO", StrZero( nIdBancario, 6 ) )
         jpbancario->( :DBFQueryExecuteInsert( "JPBANCARIO" ) )
      ENDWITH
   ELSE
      SEEK mbaConta + m_Aplic
      SEEK mbaConta + m_Aplic + Dtos( Date() ) SOFTSEEK
      SKIP -1
   ENDIF

   RETURN .T.

STATIC FUNCTION CadLanc( m_Tipo )

   LOCAL GetList := {}, m_MinDtBco, m_Aplic, mbaConta, m_Lin, m_DtEmi, m_DtBco, m_VlEnt
   LOCAL m_VlSai, m_Hist, m_Resumo, nRecNo
   LOCAL nIdBancario
   LOCAL cnSQL := ADOClass():new( AppConexao() )

   SET CURSOR ON
   WSave()
   m_Alterou    := .F.
   m_Lin        := Row()
   m_MinDtBco   := Stod( "29991231" )
   mbaConta     := jpbancario->baConta
   m_Aplic      := jpbancario->baAplic
   dDataInicial := iif( Type( "dDataInicial" ) != "D", Date() - 60, dDataInicial )
   DO CASE
   CASE m_Tipo == "EXCLUSAO"
      IF MsgYesNo( "Confirma exclusão?" )
         GravaOcorrencia( ,,"Exclusao BANCARIO de " + Dtoc( jpbancario->baDatEmi ) + ", " + Dtoc( jpbancario->baDatBan ) )
         m_DtEmi := jpbancario->baDatEmi
         m_DtBco := jpbancario->baDatBan
         jpbancario->( RecDelete() )
         SEEK mbaConta + m_Aplic + Dtos( m_DtBco ) + Dtos( m_DtEmi ) SOFTSEEK
         SKIP -1
         IF mbaConta != jpbancario->baConta .OR. m_Aplic != jpbancario->baAplic
            SEEK mbaConta + m_Aplic SOFTSEEK
         ENDIF
         m_Alterou  := .T.
      ENDIF
   CASE m_Tipo $ "ALTERACAO,INCLUSAO"
      DO WHILE .T.
         IF m_Tipo == "ALTERACAO"
            m_DtBco   := iif( jpbancario->baDatBan == Stod( "29991231" ), Ctod( "" ), jpbancario->baDatBan )
            m_DtEmi   := jpbancario->baDatEmi
            m_Resumo  := jpbancario->baResumo
            m_Hist    := jpbancario->baHist
            m_VlEnt   := iif( jpbancario->baValor < 0, 0, jpbancario->baValor  )
            m_VlSai   := iif( jpbancario->baValor > 0, 0, -jpbancario->baValor )
         ELSE
            Scroll( 5, 0, m_Lin, maxcol(), 1 )
            m_DtBco   := Ctod("")
            m_DtEmi   := Ctod("")
            m_Resumo  := EmptyValue( jpbancario->baResumo )
            m_Hist    := EmptyValue( jpbancario->baHist)
            m_VlEnt   := 0
            m_VlSai   := 0
         ENDIF
         wOpen( 10, 5, 20, 100, m_Tipo )
         @ 12, 12 SAY "Data do Banco"
         @ 12, 50 SAY "Data de Emissão"
         @ 13, 12 GET m_DtBco VALID OkData( @m_DtBco, dDataInicial )
         @ 13, 50 GET m_DtEmi VALID OkData( @m_DtEmi, dDataInicial )
         @ 15, 12 SAY "Resumo"
         @ 15, 30 SAY "Histórico"
         @ 16, 12 GET m_Resumo PICTURE "@K!" VALID ValidBancarioCCusto( @m_Resumo )
         @ 16, 30 GET m_Hist PICTURE "@K!" VALID ! Empty( m_Hist )
         @ 18, 12 SAY "Entrada"
         @ 18, 50 SAY "Saída"
         @ 19, 12 GET m_VlEnt PICTURE PicVal(14,2) VALID m_VlEnt >= 0 .AND. ReturnTrue( m_VlSai := iif( m_VlEnt != 0, 0, m_VlSai ) )
         @ 19, 50 GET m_VlSai PICTURE PicVal(14,2) VALID m_VlSai >= 0 WHEN m_VlEnt == 0
         Mensagem( "Digite campos, F9 Pesquisa, ESC abandona" )
         READ
         wClose()
         IF LastKey() == K_ESC
            EXIT
         ELSE
            WITH OBJECT cnSQL
               m_DtBco = iif( Empty( m_DtBco ), Stod( "29991231" ), m_DtBco )
               IF m_Tipo == "INCLUSAO"
                  IF m_Aplic != "S" .AND. m_Resumo = "APLIC"
                     ve_Conta( mbaConta, "S", .F. )
                     :QueryCreate()
                     :QueryAdd( "BACONTA", mbaConta )
                     :QueryAdd( "BAAPLIC", "S" )
                     :QueryAdd( "BADATBAN", m_DtBco )
                     :QueryAdd( "BADATEMI", m_DtEmi )
                     :QueryAdd( "BARESUMO", m_Resumo )
                     :QueryAdd( "BAHIST", m_Hist )
                     :QueryAdd( "BAVALOR", m_VlSai - m_VlEnt )
                     :QueryAdd( "BAINFINC", LogInfo() )
                     nIdBancario := :QueryExecuteInsert( "JPBANCARIO" )
                     :QueryAdd( "IDBANCARIO", StrZero( nIdBancario, 6 ) )
                     jpbancario->( :DBFQueryExecuteInsert( "JPBANCARIO" ) )
                     nRecNo := RecNo()
                     BARecalcula( mbaConta )
                     GOTO ( nRecNo )
                  ENDIF
                  :QueryCreate()
                  :QueryAdd( "BACONTA", mbaConta )
                  :QueryAdd( "BAAPLIC", m_Aplic )
                  :QueryAdd( "BADATBAN", m_DtBco )
                  nIdBancario := :QueryExecuteInsert( "JPBANCARIO" )
                  :QueryAdd( "IDBANCARIO", StrZero( nIdBancario, 6 ) )
                  jpbancario->( :DBFQueryExecuteInsert( "JPBANCARIO" ) )
                  m_Alterou  := .T.
                  m_MinDtBco := iif( m_MinDtBco < m_DtBco, m_MinDtBco, m_DtBco )
               ELSE
                  m_MinDtBco := iif( m_MinDtBco < jpbancario->baDatBan, m_MinDtBco, jpbancario->baDatBan )
               ENDIF
               :QueryCreate()
               IF jpbancario->baDatBan!= m_DtBco
                  :QueryAdd( "BADATBAN", m_DtBco )
                  m_MinDtBco = iif( m_MinDtBco < jpbancario->baDatBan, m_MinDtBco, jpbancario->baDatBan )
                  m_Alterou  := .T.
               ENDIF
               IF jpbancario->baDatEmi != m_DtEmi
                  :QueryAdd( "BADATEMI", m_DtEmi )
                  m_Alterou := .T.
               ENDIF
               IF jpbancario->baResumo != m_Resumo
                  :QueryAdd( "BARESUMO", m_Resumo )
                  m_Alterou := .T.
               ENDIF
               IF jpbancario->baHist != m_Hist
                  :QueryAdd( "BAHIST", m_Hist )
                  m_Alterou := .T.
               ENDIF
               IF jpbancario->baValor != ( m_VlEnt - m_VlSai )
                  :QueryAdd( "BAVALOR", m_VlEnt - m_VlSai )
                  m_Alterou := .T.
               ENDIF
               IF m_Alterou
                  :QueryAdd( "BAINFALT", LogInfo() )
                  jpbancario->( :DBFQueryExecuteUpdate( "JPBANCARIO" ) )
                  :QueryExecuteUpdate( "JPBANCARIO", "IDBANCARIO = " + NumberSQL( jpbancario->idBancario ) )
               ENDIF
            ENDWITH
         ENDIF
         IF m_Tipo == "ALTERACAO"
            EXIT
         ELSEIF LastKey() == 23
            KEYBOARD Chr( 205 )
            Inkey(0)
         ENDIF
      ENDDO
      IF LastKey() == K_ESC
         KEYBOARD Chr( 205 )
         Inkey(0)
      ENDIF
   ENDCASE
   IF m_Alterou
      nRecNo := RecNo()
      BARecalcula( jpbancario->baConta )
      GOTO nRecNo
      IF jpbancario->( Deleted() )
         jpbancario->( dbSkip( -1 ) )
      ENDIF
   ENDIF
   IF LastKey() == K_ESC
      KEYBOARD Chr(215)
      Inkey(0)
   ENDIF
   WRestore()

   RETURN .T.

STATIC FUNCTION DigFiltro()

   LOCAL oElement, m_Texto, m_Posi, GetList := {}

   m_Texto := ""
   FOR EACH oElement IN m_Filtro
      m_Texto += oElement + " "
   NEXT
   m_Texto = Pad( m_Texto, 200 )
   Scroll( 10, 0, 14, MaxCol(), 0 )
   @ 10, 0 to 14, MaxCol()
   @ 12, 1 to 12, MaxCol()-1
   @ 11, 1 SAY "Trechos de texto para filtro na apresentação dos dados"
   @ 13, 1 GET m_Texto PICTURE "@K!S75"
   READ
   IF LastKey() != K_ESC
      m_Filtro := {}
      m_Texto = Trim( m_Texto )
      DO WHILE Len( m_Texto ) != 0
         m_posi := At(" ",m_Texto+" ")
         AAdd( m_Filtro, Trim( Substr( m_Texto, 1, m_posi ) ) )
         m_Texto := lTrim( Substr( m_Texto, m_posi ) )
      ENDDO
      IF ! Filtro()
         GOTO TOP
      ENDIF
   ENDIF

   RETURN NIL

STATIC FUNCTION SomaFiltro()

   LOCAL m_RecNo := RecNo(), m_SomaEnt := 0, m_SomaSai := 0

   IF ! MsgYesNo( "Confirma a soma dos valores?" )
      RETURN NIL
   ENDIF
   GOTO TOP
   DO WHILE ! Eof()
      grafproc()
      IF ! Filtro()
         SKIP
         LOOP
      ENDIF
      IF jpbancario->baValor > 0
         m_SomaEnt += jpbancario->baValor
      ELSE
         m_SomaSai += jpbancario->baValor
      ENDIF
      SKIP
   ENDDO
   GOTO m_RecNo
   MsgExclamation( "Entradas:" + LTrim( Transform( m_SomaEnt, PicVal(14,2) ) ) + " Saídas:" + LTrim( Transform( m_SomaSai, PicVal(14,2) ) ) + ;
      " Dif:" + LTrim( Transform( m_SomaEnt + m_SomaSai, PicVal(14,2) ) ) )

   RETURN NIL

STATIC FUNCTION DigConta()

   LOCAL mbaConta := jpbancario->baConta, m_RecNo := RecNo(), m_NumConta := 1, m_NomeCta := {}, nCont

   GOTO TOP
   DO WHILE ! Eof()
      AAdd( m_NomeCta, jpbancario->baConta )
      SEEK jpbancario->baConta + "ZZZ" SOFTSEEK
   ENDDO
   IF Len( m_NomeCta ) == 0
      GOTO m_RecNo
   ELSE
      m_NumConta := hb_AScan( m_NomeCta, mbaConta )
      FOR nCont = 1 TO Len( m_NomeCta )
         m_NomeCta[ nCont ] := " " + Chr( 64 + nCont ) + " - " + m_NomeCta[ nCont ]
      NEXT
      WAchoice( 8, 9, m_NomeCta, @m_NumConta, "POSICIONAMENTO DE CONTA" )
      mbaConta = Substr( m_NomeCta[ m_NumConta ], 6 )
      SEEK mbaConta + "N" + Dtos( Date() ) SOFTSEEK
      SKIP -1
   ENDIF

   RETURN NIL

STATIC FUNCTION pBancoLancaLocaliza()

   LOCAL nCont, GetList := {}, m_RecNo := RecNo(), m_Struct, m_Sai

   THREAD STATIC m_Texto := " "

   wOpen( 5, 5, 10, MaxCol() - 1, "Texto a localizar" )
   m_Texto := Pad( m_Texto, 50 )
   // WSave( maxrow()-1, 0, maxrow(), maxcol() )
   // Mensagem( "Digite texto para localização afrente, ESC sai" )
   SET CURSOR ON
   @ 7, 7 GET m_Texto PICTURE "@K!"
   READ
   SET CURSOR OFF
   wClose()
   Mensagem()
   IF LastKey() != K_ESC
      Mensagem( "Aguarde... localizando texto afrente... ESC interrompe" )
      m_Texto = Trim(m_Texto)
      IF ! Eof()
         SKIP
      ENDIF
      m_Struct := dbStruct()
      m_Sai    := .F.
      DO WHILE ! m_Sai .AND. ! Eof()
         grafproc()
         FOR nCont = 1 TO fcount()
            m_Sai = ( Inkey() == K_ESC )
            DO CASE
            CASE m_Sai
            CASE m_Struct[ nCont, 2 ] == "N" ; m_Sai = ( m_Texto $ Str( FieldGet( nCont ) ) )
            CASE m_Struct[ nCont, 2 ] == "D" ; m_Sai = ( m_Texto $ Dtoc( FieldGet( nCont ) ) )
            CASE m_Struct[ nCont, 2 ] $ "CM" ; m_Sai = ( m_Texto $ Upper( FieldGet( nCont ) ) )
            OTHERWISE                        ; m_Sai = ( m_Texto $ Transform( FieldGet( nCont ), "" ) )
            ENDCASE
            IF m_Sai
               EXIT
            ENDIF
         NEXT
         IF m_Sai
            EXIT
         ENDIF
         SKIP
      ENDDO
      IF Eof()
         MsgWarning( "Nada foi localizado afrente!" )
         GOTO m_RecNo
      ELSE
         SKIP -1 // Porque retorna no registro seguinte
      ENDIF
   ENDIF
   KEYBOARD Chr( 205 )
   Inkey(0)

   RETURN NIL

FUNCTION BARecalcula( mbaConta ) // , dDataInicial, m_RecGeral )

   LOCAL aContaList := {}, oConta, nInicio, nFinal
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   Mensagem( "Recalculando" )
   nInicio := hb_MilliSeconds()
   WITH OBJECT cnSQL
      :cSQL := "SELECT DISTINCT BACONTA, BAAPLIC" + ;
         " FROM JPBANCARIO" + ;
         iif( mbaConta == NIL, "", " WHERE BACONTA = " + StringSQL( mbaConta ) )
      :Execute()
      DO WHILE ! :Eof()
         AAdd( aContaList, { :String( "BACONTA" ), :String( "BAAPLIC" ) } )
         :MoveNext()
      ENDDO
      FOR EACH oConta IN aContaList
         :ExecuteCmd( "SET @SOMA = 0" )
         :ExecuteCmd( "UPDATE JPBANCARIO" + ;
            " INNER JOIN" + ;
               "( SELECT IDBANCARIO, BADATBAN, BADATEMI, BAVALOR," + ;
               " BASALDO, IF( BAVALOR < 0, 2, 1 ) AS ORDEM, @SOMA := @SOMA + BAVALOR AS SALDO" + ;
               " FROM JPBANCARIO" + ;
               " WHERE BACONTA = " + StringSQL( oConta[ 1 ] ) + ;
               " AND BAAPLIC = " + StringSQL( oConta[2 ] ) + ;
               " ORDER BY BACONTA, BAAPLIC, BADATBAN, BADATEMI, ORDEM, IDBANCARIO ) AS A" + ;
               " ON JPBANCARIO.IDBANCARIO = A.IDBANCARIO" + ;
            " SET JPBANCARIO.BASALDO = A.SALDO" + ;
            " WHERE JPBANCARIO.BACONTA = " + StringSQL( oConta[ 1 ] ) + " AND JPBANCARIO.BASALDO <> A.SALDO" )
      NEXT
      :ExecuteCmd( "SET @SOMA = NULL" )
      :ExecuteCmd( "SET @CONTA = NULL" )
      :ExecuteCmd( "SET @APLIC = NULL" )
      :ExecuteCmd( "SET @DATBAN = NULL" )
      :ExecuteCmd( "SET @DATEMI = NULL" )
      :ExecuteCmd( "UPDATE JPBANCARIO" + ;
         " INNER JOIN" + ;
            "( SELECT BACONTA, BAAPLIC, IDBANCARIO, BADATBAN, BADATEMI, BAVALOR, BAIMPSLD," + ;
            " IF( BAVALOR < 0, 2, 1 ) AS ORDEM, @C = BACONTA AS CONTA, @D = BAAPLIC AS APLIC," + ;
            " @E = BADATBAN AS DATBAN, @F = BADATEMI AS DATEMI," + ;
            " IF( BACONTA <> @CONTA OR BAAPLIC <> @APLIC OR BADATBAN <> @DATBAN OR " + ;
            " ( BADATEMI = '2999-12-31' AND BADATEMI <> @DATEMI ), 'N', 'S' ) AS IMPSLD" + ;
            " FROM JPBANCARIO" + ;
            iif( mbaConta == NIL, "", " WHERE BACONTA = " + StringSQL( mbaConta ) ) + ;
            " ORDER BY BACONTA DESC, BAAPLIC DESC, BADATBAN DESC, BADATEMI DESC, ORDEM DESC, " + ;
            " IDBANCARIO DESC ) AS A" + ;
            " ON JPBANCARIO.IDBANCARIO = A.IDBANCARIO" + ;
         " SET JPBANCARIO.BAIMPSLD = A.IMPSLD" + ;
         " WHERE JPBANCARIO.BAIMPSLD <> A.IMPSLD" + ;
         iif( mbaConta == NIL, "", " AND BACONTA = " + StringSQL( mbaConta ) ) )
      :ExecuteCmd( "SET @CONTA = NULL" )
      :ExecuteCmd( "SET @APLIC = NULL" )
      :ExecuteCmd( "SET @DATBAN = NULL" )
      :ExecuteCmd( "SET @DATEMI = NULL" )
      :CloseRecordset()
   ENDWITH
   nFinal := hb_MilliSeconds()
   MsgExclamation( Str( nFinal - nInicio ) )

   RETURN .T.

STATIC FUNCTION OkData( dData, dDataInicial )

   IF Empty( dData ) .OR. dData > dDataInicial
      RETURN .T.
   ENDIF

   RETURN MsgYesNo( "Data menor que limite! Aceita?" )

FUNCTION ValidBancarioConta( cConta )

   LOCAL lOk := .T.

   IF ! Encontra( cConta, "jpbancario" )
      MsgWarning( "Conta bancária não cadastrada no movimento atual" )
      lOk := .F.
   ENDIF

   RETURN lOk


É um fonte diferente de todos os outros.
No momento, representa quase o total dos DBFs, em tamanho.

Terminado esse fonte, reduz de 18MB pra 2MB em DBF !!!!

Meu modo de trabalho

MensagemEnviado: 21 Jul 2020 22:07
por JoséQuintas
A tela pra curiosidade

bancario.png


bancario2.png


Em ADO continua igual, sem novidade.
As duas estão no fonte, até terminar.

Meu modo de trabalho

MensagemEnviado: 21 Jul 2020 22:29
por JoséQuintas
Na imagem deu pra ver que isto aqui não funcionou como eu pensava.

      :ExecuteCmd( "SET @SOMA = NULL" )
      :ExecuteCmd( "SET @CONTA = NULL" )
      :ExecuteCmd( "SET @APLIC = NULL" )
      :ExecuteCmd( "SET @DATBAN = NULL" )
      :ExecuteCmd( "SET @DATEMI = NULL" )
      :ExecuteCmd( "UPDATE JPBANCARIO" + ;
         " INNER JOIN" + ;
            "( SELECT BACONTA, BAAPLIC, IDBANCARIO, BADATBAN, BADATEMI, BAVALOR, BAIMPSLD," + ;
            " IF( BAVALOR < 0, 2, 1 ) AS ORDEM, @C = BACONTA AS CONTA, @D = BAAPLIC AS APLIC," + ;
            " @E = BADATBAN AS DATBAN, @F = BADATEMI AS DATEMI," + ;
            " IF( BACONTA <> @CONTA OR BAAPLIC <> @APLIC OR BADATBAN <> @DATBAN OR " + ;
            " ( BADATEMI = '2999-12-31' AND BADATEMI <> @DATEMI ), 'N', 'S' ) AS IMPSLD" + ;
            " FROM JPBANCARIO" + ;
            iif( mbaConta == NIL, "", " WHERE BACONTA = " + StringSQL( mbaConta ) ) + ;
            " ORDER BY BACONTA DESC, BAAPLIC DESC, BADATBAN DESC, BADATEMI DESC, ORDEM DESC, " + ;
            " IDBANCARIO DESC ) AS A" + ;
            " ON JPBANCARIO.IDBANCARIO = A.IDBANCARIO" + ;
         " SET JPBANCARIO.BAIMPSLD = A.IMPSLD" + ;
         " WHERE JPBANCARIO.BAIMPSLD <> A.IMPSLD" + ;
         iif( mbaConta == NIL, "", " AND BACONTA = " + StringSQL( mbaConta ) ) )
      :ExecuteCmd( "SET @CONTA = NULL" )
      :ExecuteCmd( "SET @APLIC = NULL" )
      :ExecuteCmd( "SET @DATBAN = NULL" )
      :ExecuteCmd( "SET @DATEMI = NULL" )

Meu modo de trabalho

MensagemEnviado: 23 Jul 2020 02:16
por JoséQuintas
30/12/2019  21:50               163 cthisto.dbf
30/12/2019  21:52               291 jprefcta.dbf
30/12/2019  21:50               355 ctlotes.dbf
30/12/2019  21:50               387 ctlanca.dbf
30/12/2019  21:50               483 jpcontabil.dbf
03/03/2020  02:44             2.799 jpfiscal.DBF
30/12/2019  21:50             5.335 jpempresa.dbf
13/07/2020  17:13             5.923 jpnumero.dbf
30/12/2019  21:50             6.691 ctplano.dbf
30/12/2019  21:52             7.416 jpuf.dbf
13/07/2020  17:13             9.997 jpconfi.dbf
10/07/2020  14:32           119.029 jptabel.dbf
30/12/2019  21:50           301.112 jpdolar.dbf
06/07/2020  11:25           314.624 jpsenha.dbf
03/03/2020  02:33         1.254.539 jpcidade.dbf
              15 arquivo(s)      2.029.144 bytes


UIA !!!!!
Reduziu pra 2MB em DBF
Sendo que desses 2MB, o jpcidade já está no MySQL.... o que reduz pra menos de 1MB !!!

Só que agora são os módulos fiscal e contábil....
O fiscal praticamente fora de uso, mas estou convertendo.
O contábil.... agora só instalando MySQL/MariaDB nos clientes que só usam o contábil.

Tá chegando ao fim.

Meu modo de trabalho

MensagemEnviado: 23 Jul 2020 15:47
por JoséQuintas
festa.png


Uia a coincidência
Meu aplicativo entrou em ritmo de festa !!!

Coincidiu, não foi por causa do MySQL....
É que perto do meu aniversário ele faz isso kkkkk

Meu modo de trabalho

MensagemEnviado: 25 Jul 2020 21:21
por JoséQuintas
Uma alteração simples de hoje, que é interessante pra quem ainda usa DBF.
É só uma tela mostrando quantos lançamentos de entrada/saída tem no livro fiscal, através de um browse.

Antes, em DBF

PROCEDURE pFiscTotais

   LOCAL mTmpFile, mStruOk, mTipLan, mMesLan, mTotal, mAnoLan

   IF ! AbreArquivos( "jpfiscal" )
      RETURN
   ENDIF
   SELECT jpfiscal

   IF ! MsgYesNo( "Faz somatória do LFiscal para resumo?" )
      RETURN
   ENDIF

   Mensagem( "Aguarde, somando..." )
   mTmpFile := TempFileArray(2)

   mStruOk := { { "MES", "C", 7, 0 }, { "ENTRADAS", "N", 10, 0 }, { "SAIDAS", "N", 10, 0 } }
   fErase( mTmpFile[ 1 ] )
   SELECT 0
   dbCreate( mTmpFile[ 1 ], mStruOk )
   USE ( mTmpFile[ 1] ) alias temp
   INDEX ON temp->Mes TO ( mTmpFile[ 2 ] )
   SELECT jpfiscal
   OrdSetFocus( "jpfiscal3" )
   GOTO TOP
   DO WHILE ! Eof()
      GrafProc()
      mTipLan := jpfiscal->lfTipLan
      mMesLan := Month( jpfiscal->lfDatLan )
      mAnoLan := Year( jpfiscal->lfDatLan )
      mTotal  := 0
      DO WHILE mMesLan == Month( jpfiscal->lfDatLan ) .AND. mAnoLan == Year( jpfiscal->lfDatLan ) .AND. ;
            mTipLan == jpfiscal->lfTipLan .AND. ! Eof()
         mTotal += 1
         SKIP
      ENDDO
      SELECT temp
      SEEK StrZero( mAnoLan, 4 ) + "/" + StrZero( mMesLan, 2 )
      IF Eof()
         RecAppend()
         REPLACE temp->Mes WITH StrZero( mAnoLan, 4 ) + "/" + StrZero( mMesLan, 2 )
         RecUnlock()
      ENDIF
      RecLock()
      IF mTipLan == "1"
         REPLACE temp->Saidas WITH  temp->Saidas + mTotal
      ELSE
         REPLACE temp->Entradas WITH  temp->Entradas + mTotal
      ENDIF
      RecUnlock()
      SELECT jpfiscal
   ENDDO
   SELECT temp
   GOTO TOP
   BrowseDBF( { { "MES   ENTRADAS   SAÍDAS", { || temp->Mes + " " + Str( temp->Entradas, 10 ) + " " + Str( temp->Saidas, 10 ) } } } )
   SELECT ( Select( "temp" ) )
   CLOSE DATABASES
   fErase( mTmpFile[ 1 ] )
   fErase( mTmpFile[ 2 ] )

   RETURN


Depois, em MySQL/ADO

PROCEDURE pFiscTotais

   LOCAL oTBrowse
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF ! MsgYesNo( "Faz somatória do LFiscal para resumo?" )
      RETURN
   ENDIF

   Mensagem( "Aguarde, somando..." )

   WITH OBJECT cnSQL
      :cSQL := "SELECT CONCAT_WS( '-', LPAD( YEAR( LFDATLAN ), 4, '0' ), LPAD( MONTH( LFDATLAN ), 2, '0' ) ) AS ANOMES, " + ;
         " SUM( IF( LFTIPLAN = 1, 1, 0 ) ) AS SAIDAS," + ;
         " SUM( IF( LFTIPLAN = 2, 1, 0 ) ) AS ENTRADAS" + ;
         " FROM JPFISCAL" + ;
         " GROUP BY CONCAT_WS( '-', LPAD( YEAR( LFDATLAN ), 4, '0' ), LPAD( MONTH( LFDATLAN ), 2, '0' ) )" + ;
         " ORDER BY ANOMES DESC"
      :Execute()
      oTBrowse := { ;
         { "ANO/MES", { || :String( "ANOMES", 7 ) } }, ;
         { "ENTRADAS", { || Transform( :Number( "ENTRADAS" ), PicVal(14,2) ) } }, ;
         { "SAIDAS",   { || Transform( :Number( "SAIDAS" ),   PicVal(14,2) ) } } }
      BrowseADO( cnSQL, oTBrowse, "ANOMES", { || "" } )
      KEYBOARD ""
      :CloseRecordset()
   ENDWITH

   RETURN


Pra quem já usa, isso é simples, estão carecas de saber....
Pra quem usa DBF... a diferença é grande: o aplicativo pede pro servidor, e o servidor entrega tudo pronto.

O legal não é somente o que se vê, é também o que não se vê:

- A rotina em DBF... é pra DBF... e DBF significa Clipper, Harbour, e produtos xbase
- A rotina SQL... é pra SQL... significa qualquer linguagem de programação, inclusive Harbour

Entendeu?
Tá achando vantagem que o aplicativo vai ficar atualizado?
Pois é... o programador também vai, vai estar preparado pra qualquer coisa atual.

E usar Harbour porque gosta, e não porque tá preso a xbase.

Meu modo de trabalho

MensagemEnviado: 26 Jul 2020 00:21
por JoséQuintas
Outro mais interessante ainda, que precisou precaução

Totais por dia ou por mês do fiscal

STATIC FUNCTION imprime()

   LOCAL oPDF, nKey, m_ClaIcmVl, m_ClaIcmIs, m_ClaIcmOu, m_TotVlCon, m_TotIcmBa, m_TotIcmIs, m_TotIcmOu, m_TotIpiIs, m_TotIpiOu, m_ClaVlCon
   LOCAL m_ClaIcmBa, m_ClaIpiBa, m_ClaIpiIs, m_ClaIpiOu, m_TotIpiVl, m_TotIcmVl, m_TotIpiBa, m_ClaIpiVl, m_Data

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey = 0
   oPDF:acHeader := {"","",""}
   oPDF:acHeader[ 1 ] = "RESUMO DA MOVIMENTACAO, POR DATA"
   IF nOpcData == 1
      oPDF:acHeader[ 2 ] = " "
   ELSE
      oPDF:acHeader[ 2 ] = "Periodo: " + Dtoc( m_datai ) + " a " + Dtoc( m_dataf ) + iif( nOpcGrf == 1, "", " " + acTxtGrf[ nOpcGrf ] )
   ENDIF
   oPDF:acHeader[ 3 ] = Space(10) + "--DATA--  ---VALOR CONTABIL--  --BASE DE CALCULO--  -IMPOSTO DEB/CRED.-  --ISENTOS/N.TRIBUT-  -------OUTROS------"

   STORE 0 to m_totvlcon, m_toticmba, m_toticmvl, ;
      m_toticmis, m_toticmou
   STORE 0 to m_totipiba, m_totipivl, m_totipiis, m_totipiou

   SELECT jpfiscal
   OrdSetFocus( "jpfiscal3" )
   SEEK "2" SOFTSEEK
   oPDF:PageHeader()
   oPDF:DrawText( oPDF:nRow, 0, "ENTRADAS:" )
   oPDF:nRow += 2
   DO WHILE nKey != K_ESC .AND. ! Eof()
      GrafProc()
      nKey = Inkey()
      IF jpfiscal->lfDatLan > m_dataf .AND. nOpcData == 2
         SKIP
         LOOP
      ENDIF
      IF jpfiscal->lfDatLan < m_datai .AND. nOpcData == 2
         SKIP
         LOOP
      ENDIF
      m_data := Substr( Dtoc( jpfiscal->lfDatLan ),iif( nOpcTotais == 1, 1, 4 ) )
      STORE 0 to m_clavlcon, m_claicmba, m_claicmvl, ;
         m_claicmis, m_claicmou
      STORE 0 to m_claipiba, m_claipivl, m_claipiis, m_claipiou
      DO WHILE nKey != K_ESC .AND. jpfiscal->lfTipLan=="2" .AND. ! Eof()
         IF Substr( Dtoc( jpfiscal->lfDatLan ),iif( nOpcTotais == 1, 1, 4 ) ) != m_Data
            EXIT
         ENDIF
         nKey = Inkey()
         GrafProc()
         IF jpfiscal->lfDatLan > m_dataf .AND. nOpcData == 2
            SKIP
            LOOP
         ENDIF
         IF jpfiscal->lfDatLan < m_datai .AND. nOpcData == 2
            SKIP
            LOOP
         ENDIF
         m_clavlcon = Round( m_clavlcon + jpfiscal->lfValCon, 2 )
         IF jpfiscal->lfIcmVal != 0
            m_claicmba = Round( m_claicmba + jpfiscal->lfIcmBas, 2 )
            m_claicmvl = Round( m_claicmvl + jpfiscal->lfIcmVal, 2 )
         ENDIF
         IF jpfiscal->lfValCon - jpfiscal->lfIcmBas - jpfiscal->lfIcmOut > 0
            m_claicmis = Round( m_claicmis + ( jpfiscal->lfValCon - jpfiscal->lfIcmBas - jpfiscal->lfIcmOut ), 2 )
         ENDIF
         IF jpfiscal->lfIcmOut != 0
            m_claicmou = Round( m_claicmou + jpfiscal->lfIcmOut, 2 )
         ENDIF
         IF jpfiscal->lfIpiVal != 0
            m_claipiba = Round( m_claipiba + jpfiscal->lfIpiBas, 2 )
            m_claipivl = Round( m_claipivl + jpfiscal->lfIpiVal, 2 )
         ENDIF
         IF jpfiscal->lfValCon - jpfiscal->lfIpiBas - jpfiscal->lfIpiOut - jpfiscal->lfIpiVal > 0
            m_claipiis = Round( m_claipiis + ( jpfiscal->lfValCon - jpfiscal->lfIpiBas - jpfiscal->lfIpiOut - jpfiscal->lfIpiVal ), 2 )
         ENDIF
         IF jpfiscal->lfIpiOut != 0
            m_claipiou = Round( m_claipiou + jpfiscal->lfIpiOut, 2 )
         ENDIF
         SKIP
      ENDDO
      IF m_clavlcon != 0
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow,  10, m_Data )
         oPDF:DrawText( oPDF:nRow,  20, m_clavlcon, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  40, m_claicmba, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  60, m_claicmvl, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  80, m_claicmis, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 100, m_claicmou, PicVal(14,2) )
         m_totvlcon = Round( m_totvlcon + m_clavlcon, 2 )
         m_toticmba = Round( m_toticmba + m_claicmba, 2 )
         m_toticmvl = Round( m_toticmvl + m_claicmvl, 2 )
         m_toticmis = Round( m_toticmis + m_claicmis, 2 )
         m_toticmou = Round( m_toticmou + m_claicmou, 2 )
         m_totipiba = Round( m_totipiba + m_claipiba, 2 )
         m_totipivl = Round( m_totipivl + m_claipivl, 2 )
         m_totipiis = Round( m_totipiis + m_claipiis, 2 )
         m_totipiou = Round( m_totipiou + m_claipiou, 2 )
         oPDF:nRow     += 1
      ENDIF
   ENDDO
   oPDF:MaxRowTest()
   oPDF:DrawLine( oPDF:nRow,  0, oPDF:nRow, oPDF:MaxCol() )
   oPDF:nRow += 1
   oPDF:MaxRowTest()
   oPDF:DrawText( oPDF:nRow,   0, "*** ICMS ***" )
   oPDF:DrawText( oPDF:nRow,  20, m_totvlcon, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  40, m_toticmba, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  60, m_toticmvl, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  80, m_toticmis, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow, 100, m_toticmou, PicVal(14,2) )
   oPDF:DrawLine( oPDF:nRow + 1, 0, oPDF:nRow + 1, oPDF:MaxCol() )
   oPDF:nRow += 3
   oPDF:MaxRowTest()
   oPDF:DrawText( oPDF:nRow, 0, "SAIDAS:" )
   oPDF:nRow += 2
   oPDF:MaxRowTest()

   STORE 0 to m_totvlcon, m_toticmba, m_toticmvl, ;
      m_toticmis, m_toticmou
   STORE 0 to m_totipiba, m_totipivl, m_totipiis, m_totipiou

   SELECT jpfiscal
   OrdSetFocus("jpfiscal3")
   SEEK "1" SOFTSEEK
   DO WHILE nKey != K_ESC .AND. jpfiscal->lfTipLan=="1" .AND. ! Eof()
      nKey = Inkey()
      GrafProc()
      IF jpfiscal->lfDatLan > m_dataf .AND. nOpcData == 2
         SKIP
         LOOP
      ENDIF
      IF jpfiscal->lfDatLan < m_datai .AND. nOpcData == 2
         SKIP
         LOOP
      ENDIF
      m_data := Substr( Dtoc( jpfiscal->lfDatLan ), iif( nOpcTotais == 1, 1, 4 ) )
      STORE 0 to m_clavlcon, m_claicmba, m_claicmvl, m_claicmis, m_claicmou, m_claipiba, m_claipivl, m_claipiis, m_claipiou

      DO WHILE nKey != K_ESC .AND. jpfiscal->lfTipLan=="1" .AND. ! Eof()
         IF Substr(Dtoc(jpfiscal->lfDatLan),iif(nOpcTotais==1,1,4)) != m_Data
            EXIT
         ENDIF
         nKey = Inkey()
         GrafProc()
         IF jpfiscal->lfDatLan > m_dataf .AND. nOpcData == 2
            SKIP
            LOOP
         ENDIF
         IF jpfiscal->lfDatLan < m_datai .AND. nOpcData == 2
            SKIP
            LOOP
         ENDIF
         IF ( nOpcGrf == 2 .AND. jpfiscal->lfGRF != "S" ) .OR. ( nOpcGrf == 3 .AND. jpfiscal->lfGRF == "S" )
            SKIP
            LOOP
         ENDIF
         m_clavlcon = Round( m_clavlcon + jpfiscal->lfValCon, 2 )
         IF jpfiscal->lfIcmBas != 0
            m_claicmba = Round( m_claicmba + jpfiscal->lfIcmBas, 2 )
         ENDIF
         IF jpfiscal->lfIcmVal != 0
            m_claicmvl = Round( m_claicmvl + jpfiscal->lfIcmVal, 2 )
         ENDIF
         IF jpfiscal->lfValCon - jpfiscal->lfIcmBas - jpfiscal->lfIcmOut > 0
            m_claicmis = Round( m_claicmis + ( jpfiscal->lfValCon - jpfiscal->lfIcmBas - jpfiscal->lfIcmOut ), 2 )
         ENDIF
         IF jpfiscal->lfIcmOut != 0
            m_claicmou = Round( m_claicmou + jpfiscal->lfIcmOut, 2 )
         ENDIF
         IF jpfiscal->lfIpiVal != 0
            m_claipiba = Round( m_claipiba + jpfiscal->lfIpiBas, 2 )
            m_claipivl = Round( m_claipivl + jpfiscal->lfIpiVal, 2 )
         ENDIF
         IF jpfiscal->lfValCon - jpfiscal->lfIpiBas - jpfiscal->lfIpiOut - jpfiscal->lfIpiVal > 0
            m_claipiis = Round( m_claipiis + ( jpfiscal->lfValCon - jpfiscal->lfIpiBas - jpfiscal->lfIpiOut - jpfiscal->lfIpiVal ), 2 )
         ENDIF
         IF jpfiscal->lfIpiOut != 0
            m_claipiou = Round( m_claipiou + jpfiscal->lfIpiOut, 2 )
         ENDIF
         SKIP
      ENDDO
      IF m_clavlcon != 0
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow,  10, m_data )
         oPDF:DrawText( oPDF:nRow,  20, m_clavlcon, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  40, m_claicmba, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  60, m_claicmvl, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  80, m_claicmis, PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 100, m_claicmou, PicVal(14,2) )
         m_totvlcon = Round( m_totvlcon + m_clavlcon, 2 )
         m_toticmba = Round( m_toticmba + m_claicmba, 2 )
         m_toticmvl = Round( m_toticmvl + m_claicmvl, 2 )
         m_toticmis = Round( m_toticmis + m_claicmis, 2 )
         m_toticmou = Round( m_toticmou + m_claicmou, 2 )
         m_totipiba = Round( m_totipiba + m_claipiba, 2 )
         m_totipivl = Round( m_totipivl + m_claipivl, 2 )
         m_totipiis = Round( m_totipiis + m_claipiis, 2 )
         m_totipiou = Round( m_totipiou + m_claipiou, 2 )
         oPDF:nRow     += 1
      ENDIF
   ENDDO
   oPDF:MaxRowTest()
   oPDF:DrawLine( oPDF:nRow,  0, oPDF:nRow, oPDF:MaxCol() )
   oPDF:nRow += 1
   oPDF:MaxRowTest()
   oPDF:DrawText( oPDF:nRow,   0, "*** ICMS ***" )
   oPDF:DrawText( oPDF:nRow,  20, m_totvlcon, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  40, m_toticmba, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  60, m_toticmvl, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow,  80, m_toticmis, PicVal(14,2) )
   oPDF:DrawText( oPDF:nRow, 100, m_toticmou, PicVal(14,2) )
   oPDF:End()

   RETURN .T.


STATIC FUNCTION imprime()

   LOCAL oPDF, nKey, m_TotVlCon, m_TotIcmBa, m_TotIcmIs, m_TotIcmOu, m_TotIpiIs, m_TotIpiOu
   LOCAL m_TotIpiVl, m_TotIcmVl, m_TotIpiBa, cSQLGroup
   LOCAL cnSQL := ADOClass():New( AppConexao() )

   oPDF := PDFClass():New()
   oPDF:SetType( nOpcPrinterType )
   oPDF:Begin()
   nKey = 0
   oPDF:acHeader := {"","",""}
   oPDF:acHeader[ 1 ] = "RESUMO DA MOVIMENTACAO, POR DATA"
   IF nOpcData == 1
      oPDF:acHeader[ 2 ] = " "
   ELSE
      oPDF:acHeader[ 2 ] = "Periodo: " + Dtoc( dDataInicial ) + " a " + Dtoc( dDataFinal ) + iif( nOpcGrf == 1, "", " " + acTxtGrf[ nOpcGrf ] )
   ENDIF
   oPDF:acHeader[ 3 ] = Space(10) + "--DATA--  ---VALOR CONTABIL--  --BASE DE CALCULO--  -IMPOSTO DEB/CRED.-  --ISENTOS/N.TRIBUT-  -------OUTROS------"
   oPDF:PageHeader()
   oPDF:DrawText( oPDF:nRow, 0, "ENTRADAS:" )
   oPDF:nRow += 2

   WITH OBJECT cnSQL
      cSQLGroup := "LPAD( YEAR( LFDATLAN ), 4, '0' ), LPAD( MONTH( LFDATLAN ), 2, '0' )"
      IF nOpcTotais == 1
         cSQLGroup += ", LPAD( Day( LFDATLAN ), 2, '0' )"
      ENDIF
      cSQLGroup := "CONCAT_WS( '-', " + cSQLGroup + " )"
      :cSQL := "SELECT LFTIPLAN, " + cSQLGroup + " AS DATA, " + ;
         " SUM( LFVALCON ) AS VALCON, SUM( IF( LFICMVAL = 0, 0, LFICMBAS ) ) AS ICMBAS," + ;
         " SUM( LFICMVAL ) AS ICMVAL, SUM( LFICMOUT ) AS ICMOUT," + ;
         " SUM( IF( LFVALCON - LFICMBAS - LFICMOUT > 0, LFVALCON - LFICMBAS - LFICMOUT, 0 ) ) AS ICMISE," + ;
         " SUM( LFIPIVAL ) AS IPIVAL, SUM( IF( LFIPIVAL > 0, LFIPIBAS, 0 ) ) AS IPIBAS," + ;
         " SUM( IF( LFVALCON - LFIPIBAS - LFIPIOUT - LFIPIVAL > 0, LFVALCON - LFIPIBAS - LFIPIOUT - LFIPIVAL, 0 ) ) AS IPIISE," + ;
         " SUM( LFIPIOUT ) AS IPIOUT" + ;
         " FROM JPFISCAL" + ;
         " WHERE LFTIPLAN IN ( '1', '2' )"
      IF nOpcData == 2
         :cSQL += " AND LFDATLAN BETWEEN CAST( " + DateSQL( dDataInicial ) + " AS DATE )" + ;
            " AND CAST( " + DateSQL( dDataFinal ) + " AS DATE )"
      ENDIF
      IF nOpcGrf <> 1
         :cSQL += " AND LFGRF " + iif( nOpcGrf == 2, "=", "<>" ) + " 'S'"
      ENDIF
      :cSQL += " GROUP BY LFTIPLAN, " + cSQLGroup + ;
         " HAVING VALCON > 0" + ;
         " ORDER BY LFTIPLAN DESC, DATA"
      :Execute()

      STORE 0 to m_totvlcon, m_toticmba, m_toticmvl, m_toticmis, m_toticmou
      STORE 0 to m_totipiba, m_totipivl, m_totipiis, m_totipiou

      DO WHILE :String( "LFTIPLAN", 1 ) == "2" .AND. nKey != K_ESC .AND. ! :Eof()
         GrafProc()
         nKey := Inkey()
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow,  10, :String( "DATA" ) )
         oPDF:DrawText( oPDF:nRow,  20, :Number( "VALCON" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  40, :Number( "ICMBAS" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  60, :Number( "ICMVAL" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  80, :Number( "ICMISE" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 100, :Number( "ICMOUT" ), PicVal(14,2) )
         m_totvlcon += :Number( "VALCON" )
         m_toticmba += :Number( "ICMBAS" )
         m_toticmvl += :Number( "ICMVAL" )
         m_toticmis += :Number( "ICMISE" )
         m_toticmou += :Number( "ICMOUT" )
         m_totipiba += :Number( "ICMBAS" )
         m_totipivl += :Number( "IPIVAL" )
         m_totipiis += :Number( "IPIISE" )
         m_totipiou += :Number( "IPIOUT" )
         oPDF:nRow     += 1
         :MoveNext()
      ENDDO
      oPDF:MaxRowTest()
      oPDF:DrawLine( oPDF:nRow,  0, oPDF:nRow, oPDF:MaxCol() )
      oPDF:nRow += 1
      oPDF:MaxRowTest()
      oPDF:DrawText( oPDF:nRow,   0, "*** ICMS ***" )
      oPDF:DrawText( oPDF:nRow,  20, m_totvlcon, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  40, m_toticmba, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  60, m_toticmvl, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  80, m_toticmis, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow, 100, m_toticmou, PicVal(14,2) )
      oPDF:DrawLine( oPDF:nRow + 1, 0, oPDF:nRow + 1, oPDF:MaxCol() )
      oPDF:nRow += 3
      oPDF:MaxRowTest()
      oPDF:DrawText( oPDF:nRow, 0, "SAIDAS:" )
      oPDF:nRow += 2
      oPDF:MaxRowTest()

      STORE 0 to m_totvlcon, m_toticmba, m_toticmvl, m_toticmis, m_toticmou
      STORE 0 to m_totipiba, m_totipivl, m_totipiis, m_totipiou

      DO WHILE :String( "LFTIPLAN", 1 ) == "1" .AND. nKey != K_ESC .AND. ! :Eof()
         nKey := Inkey()
         GrafProc()
         oPDF:MaxRowTest()
         oPDF:DrawText( oPDF:nRow,  10, :String( "DATA" ) )
         oPDF:DrawText( oPDF:nRow,  20, :Number( "VALCON" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  40, :Number( "ICMBAS" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  60, :Number( "ICMVAL" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow,  80, :Number( "ICMISE" ), PicVal(14,2) )
         oPDF:DrawText( oPDF:nRow, 100, :Number( "ICMOUT" ), PicVal(14,2) )
         m_totvlcon += :Number( "VALCON" )
         m_toticmba += :Number( "ICMBAS" )
         m_toticmvl += :Number( "ICMVAL" )
         m_toticmis += :Number( "ICMISE" )
         m_toticmou += :Number( "ICMOUT" )
         m_totipiba += :Number( "IPIBAS" )
         m_totipivl += :Number( "IPIVAL" )
         m_totipiis += :Number( "IPIISE" )
         m_totipiou += :Number( "IPIOUT" )
         oPDF:nRow     += 1
         :MoveNext()
      ENDDO
      oPDF:MaxRowTest()
      oPDF:DrawLine( oPDF:nRow,  0, oPDF:nRow, oPDF:MaxCol() )
      oPDF:nRow += 1
      oPDF:MaxRowTest()
      oPDF:DrawText( oPDF:nRow,   0, "*** ICMS ***" )
      oPDF:DrawText( oPDF:nRow,  20, m_totvlcon, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  40, m_toticmba, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  60, m_toticmvl, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow,  80, m_toticmis, PicVal(14,2) )
      oPDF:DrawText( oPDF:nRow, 100, m_toticmou, PicVal(14,2) )
      :CloseRecordset()
   ENDWITH
   oPDF:End()

   RETURN .T.


Agora é uma soma só, e a precaução ficou por conta disto:
" WHERE LFTIPLAN IN ( '1', '2' )"

Porque?
Porque se tiver um '0' não imprimiria nada, porque não entraria em nenhum dos dois do while.

Meu modo de trabalho

MensagemEnviado: 26 Jul 2020 18:31
por JoséQuintas
Tava olhando esse fiscal, e tem um valor que é sempre calculado.
Foi bom no tempo do DBF, pra economizar espaço, mas é chato pra colocar nos fontes/comandos.
Vou criar o campo, pra facilitar.

Vi que o MySQL tem a opção de um campo calculado, que pode ou não ser armazenado na tabela.
Achei legal a opção mas... acho que isso pode quebrar meu backup, porque o valor vai no backup, e no retorno provavelmente vai dar erro.
Na dúvida, e como SÓ EU uso livros fiscais, vai na base de campo na tabela mesmo, sem cálculo automático.

Tá na reta final de conversão... correr o risco de atrapalhar a conversão agora só por causa disso não compensa.

Meu modo de trabalho

MensagemEnviado: 26 Jul 2020 23:42
por JoséQuintas
Mexendo em muito fonte.
Mas é interessante que, pelo nome dos fontes, dá pra saber em que módulos ando mexendo.

Pasta de d:\cdrom\FONTES\INTEGRA

10/02/2020  10:26             1.473 pcontreddisp.prg
10/02/2020  10:26             1.589 pcontredrenum.prg
10/02/2020  10:26             4.449 pconthistorico.prg
10/02/2020  10:26             3.012 pediimpplaref.prg
10/02/2020  10:26             5.811 pfiscrel0020.prg
10/02/2020  10:26             1.828 ptesteconsultadfe.prg
10/02/2020  10:26             3.558 psetupempresa.prg
10/02/2020  10:26             9.561 ze_pdfbolbradescoclass.prg
10/02/2020  10:26             5.384 ze_ftp.prg
10/02/2020  10:26               240 ze_seguro.prg
10/02/2020  10:26            48.987 ze_pdfboletoclass.prg
10/02/2020  10:26             1.653 ptesqueryc.prg
10/02/2020  10:26               792 pdfestatus.prg
12/02/2020  18:42             2.866 ze_novaversao.prg
14/02/2020  11:19               621 psetupcapicom.prg
19/03/2020  11:43             6.353 ze_webservice.prg
19/03/2020  20:06             2.863 ze_resource.prg
22/03/2020  13:14            17.515 ze_updatedefault.prg
06/05/2020  01:12             2.275 pconttotais.prg
06/05/2020  10:38             5.262 pcontrel0385.prg
17/05/2020  18:13             5.644 pcontlancpad.prg
17/05/2020  18:41             4.409 pcontimpsped.prg
17/05/2020  18:55             7.293 pjpempresa.prg
19/05/2020  11:00             7.775 pcontsetup.prg
03/06/2020  13:25             4.981 ptesdadabe.prg
04/06/2020  13:03             4.566 ze_excel1.prg
04/06/2020  13:03             6.659 ze_excel2.prg
04/06/2020  13:03             6.069 ze_updateexeup.prg
04/06/2020  13:10            11.954 ze_logerr.prg
07/06/2020  16:53             6.359 pcontlancalote.prg
10/06/2020  07:50             9.963 pretitau.prg
10/06/2020  07:50             6.760 pprecombustivel.prg
10/06/2020  07:52             4.215 psitejpa.prg
10/06/2020  07:53             6.862 ptestregiao.prg
10/06/2020  07:54             3.460 ze_helpprint.prg
10/06/2020  07:54             4.606 ze_spedxmllist.prg
10/06/2020  07:54            15.642 ze_SpedXmlMDFE.prg
10/06/2020  07:55             5.797 pjpagenda.prg
10/06/2020  07:55             5.079 pestototarmazem.prg
10/06/2020  07:55             2.334 pediimpanpcnae.prg
10/06/2020  08:05             2.114 ptescep.prg
10/06/2020  08:05             4.181 pnotagerarps.prg
10/06/2020  08:05             8.878 pjptransp.prg
10/06/2020  08:05             4.811 pjpmotori.prg
10/06/2020  08:05             4.617 pjpnfbase.prg
10/06/2020  08:05             4.627 pjpibpt.prg
10/06/2020  08:05             5.872 pjpdecreto.prg
10/06/2020  08:05             3.580 pfiscblocok.prg
10/06/2020  08:05             1.857 pestovalest.prg
10/06/2020  08:05             1.715 pestoentfor.prg
10/06/2020  08:05             1.582 pedixls.prg
10/06/2020  08:05             2.924 pediimpibgecnae.prg
10/06/2020  08:05             2.308 pedixml2.prg
10/06/2020  08:05             2.680 pediimpanploc.prg
10/06/2020  08:05             2.494 pediimpanpage.prg
10/06/2020  08:05             2.661 pediimpanpati.prg
10/06/2020  08:05             2.970 pedi0260.prg
10/06/2020  08:05             4.561 pcontimpexcel.prg
10/06/2020  08:05             7.424 ljptransp.prg
10/06/2020  08:05             3.255 ljpforpag.prg
10/06/2020  08:06             2.794 pediimpanpins.prg
10/06/2020  08:06             1.727 pdfemanif.prg
17/06/2020  10:55             6.463 ptesjson.prg
18/06/2020  17:21             7.053 pjppedidofatura.prg
19/06/2020  12:45            15.541 pjppedidocupom.prg
19/06/2020  12:45             4.213 pjppedidonfe.prg
23/06/2020  15:18             6.708 ze_enviaemail.prg
24/06/2020  17:19            44.621 pdfeserver.prg
27/06/2020  17:58             5.202 ze_SpedCadastroClass.prg
28/06/2020  06:31               697 pdfenaoenc.prg
28/06/2020  20:59            18.347 pjppedidocte.prg
29/06/2020  07:41            31.833 pdfesalva.prg
01/07/2020  19:10             1.240 ptesbase.prg
02/07/2020  09:05             5.854 pfiscrel0070.prg
02/07/2020  09:05             6.688 pfiscrel0010.prg
02/07/2020  09:05            14.113 ljpestoquec.prg
02/07/2020  09:09            17.214 pfiscrel0130.prg
05/07/2020  09:10             8.665 ljpitem.prg
07/07/2020  10:32            26.352 ze_updatee.prg
07/07/2020  10:32            19.656 ze_updatec.prg
07/07/2020  10:32            19.315 ze_updateb.prg
07/07/2020  10:32             2.004 ze_updatea.prg
08/07/2020  21:07             9.587 pcontrel0330.prg
08/07/2020  21:08             4.266 pcontrel0370.prg
08/07/2020  21:08             4.502 pcontrel0470.prg
08/07/2020  21:08            10.303 pcontrel0550.prg
08/07/2020  21:12            12.824 pcontrel0520.prg
08/07/2020  21:15             6.127 pcontsintetica.prg
08/07/2020  21:21            10.260 pcontrel0250.prg
08/07/2020  21:21            11.513 pcontrel0300.prg
08/07/2020  21:21            10.414 pcontrel0320.prg
08/07/2020  21:23             2.639 pcontemitidos.prg
08/07/2020  21:24             7.024 pcontfecha.prg
08/07/2020  21:24             3.361 pcontimpplano.prg
08/07/2020  21:24             9.433 pcontrecalculo.prg
08/07/2020  21:24            10.155 pcontrel0270.prg
08/07/2020  21:24            10.592 pcontrel0310.prg
08/07/2020  21:24             7.926 pcontrel0340.prg
08/07/2020  21:25             4.779 pcontsaldo.prg
08/07/2020  21:25               522 pcont_lucro.prg
09/07/2020  11:46             3.203 pcontnumdia.prg
09/07/2020  13:35             8.390 pcontrel0360.prg
09/07/2020  13:56             8.631 pcontrel0390.prg
09/07/2020  14:57            19.263 pcontrel0380.prg
09/07/2020  14:57            10.912 pcontrel0530.prg
09/07/2020  14:58            18.258 pcontrel0210.prg
09/07/2020  14:58             8.744 pcontrel0010.prg
09/07/2020  15:14            24.039 ze_updateg.prg
12/07/2020  23:39             3.175 pbancorelsaldo.prg
12/07/2020  23:39             6.526 pbancorelextrato.prg
13/07/2020  17:04             5.148 pnotaservico.prg
13/07/2020  17:04             3.561 pnotaxls.prg
13/07/2020  17:09             3.494 pbol0050.prg
13/07/2020  17:09             8.866 pbol0040.prg
13/07/2020  17:09            13.261 pbol0020.prg
13/07/2020  17:09             7.593 ljpimposto.prg
13/07/2020  17:09            19.968 ljpcadastro.prg
13/07/2020  17:10             1.221 pdfegerapdf.prg
13/07/2020  17:10             2.818 pdfeemail.prg
13/07/2020  17:10             2.583 pdfecteinut.prg
13/07/2020  17:10             3.019 pdfectecancel.prg
13/07/2020  17:11             4.456 pfinanrelmaifor.prg
13/07/2020  17:11             4.852 pfinanrelmaicli.prg
13/07/2020  17:11             7.741 pfinanrelfluxo.prg
13/07/2020  17:11             1.759 pedi0270.prg
13/07/2020  17:11             6.555 pdfezipxml.prg
13/07/2020  17:11             2.694 pdfenfeinut.prg
13/07/2020  17:15            14.254 pfiscsaidas.prg
13/07/2020  17:15            15.088 pfiscentradas.prg
13/07/2020  17:15             8.007 pfisccorrecao.prg
13/07/2020  17:15            34.254 pfinanrelreceber.prg
14/07/2020  00:44            22.720 pjpforpag.prg
14/07/2020  13:08            24.975 lbalger.prg
14/07/2020  22:19            60.359 ze_SpedXmlNFE.prg
15/07/2020  11:05            23.568 ze_update2020.prg
16/07/2020  15:34             1.937 pprecancel.prg
16/07/2020  18:20            22.033 ze_updatef.prg
16/07/2020  18:32            25.600 ze_updated.prg
17/07/2020  00:16             6.036 ze_prot.prg
17/07/2020  14:05            32.443 pdfeimporta.prg
17/07/2020  14:05            11.524 ljpfisica.prg
17/07/2020  14:05            19.431 ljpestoqueb.prg
17/07/2020  14:05            28.749 ljpestoquea.prg
17/07/2020  14:06            11.802 pestorelanalise.prg
17/07/2020  14:06             2.147 pestoitemxls.prg
17/07/2020  14:10             2.454 pnotageranfe.prg
17/07/2020  14:10            11.947 pnotaficcliven.prg
17/07/2020  14:10            18.108 pnotaconsprod.prg
17/07/2020  14:10             7.529 pjpvendedor.prg
17/07/2020  14:10             8.948 pjpveiculo.prg
17/07/2020  14:10            70.792 pjppedido.prg
17/07/2020  14:10             5.888 pjpfisicab.prg
17/07/2020  14:10             5.328 pjpfisicaa.prg
17/07/2020  14:10             5.302 pjpcomissao.prg
17/07/2020  14:10            36.621 pjpcadastro.prg
17/07/2020  14:10             6.104 pfiscrel0140.prg
17/07/2020  14:10             8.922 pfiscnotas.prg
17/07/2020  14:13             3.133 ppretabcombreaj.prg
17/07/2020  14:13             8.448 pprereltabmulti.prg
17/07/2020  14:13            10.721 pprereltabcomb.prg
17/07/2020  14:13            10.649 pprehtmltabpre.prg
17/07/2020  14:13             4.251 pnotavervendas.prg
17/07/2020  14:13            14.080 pnotaromaneio.prg
17/07/2020  14:13             5.206 pnotarelvendcli.prg
17/07/2020  14:13            31.155 pnotarelnotas.prg
17/07/2020  14:13             4.648 pnotarelcompcli.prg
17/07/2020  14:13             5.249 pnotarelclivend.prg
17/07/2020  14:13             3.401 pnotaproximas.prg
17/07/2020  14:13             7.373 pprevalperc.prg
17/07/2020  14:16            10.473 pedi0010.prg
17/07/2020  14:16             4.881 pestorecalculo.prg
17/07/2020  14:16            16.049 pjpestoque.prg
17/07/2020  17:16             7.651 ze_updateexedown.prg
19/07/2020  18:25             1.453 pbancoconsolida.prg
20/07/2020  10:51            21.494 ppretabcomb.prg
20/07/2020  16:40            27.435 ljppedido.prg
21/07/2020  19:36            28.337 pnotaplanilha.prg
21/07/2020  19:44             9.840 pnotarelmapa.prg
21/07/2020  19:44            11.633 pnotarelcompmes.prg
21/07/2020  19:44            28.292 pjppedidonota.prg
22/07/2020  14:51            21.005 pjpnotfis.prg
22/07/2020  14:51            25.233 pjpmdfcab.prg
22/07/2020  18:55            20.828 pfinanrelpagar.prg
22/07/2020  18:55            34.443 pfinanedreceber.prg
22/07/2020  18:55            27.430 pfinanedpagar.prg
22/07/2020  18:55            11.167 pfinanbaixaport.prg
22/07/2020  18:55             7.592 pediexpclarcon.prg
22/07/2020  18:55             4.108 pedi0190.prg
22/07/2020  18:55             6.712 pedi0150.prg
22/07/2020  18:55             3.437 pcont_valid.prg
22/07/2020  18:55             6.368 pcontrel0230.prg
22/07/2020  18:55            41.114 pcontlancainclui.prg
22/07/2020  18:55            15.811 pcontlancaedit.prg
22/07/2020  18:55            23.229 pcontfcont.prg
22/07/2020  18:55               504 pcontctaadm.prg
22/07/2020  18:55            14.394 pcontcontas.prg
22/07/2020  18:55            12.588 pbol0030.prg
22/07/2020  18:55             2.024 pauxpisenq.prg
22/07/2020  18:55             1.279 pauxpiscst.prg
22/07/2020  18:55             2.668 pauxorimer.prg
22/07/2020  18:55             1.311 pauxmodfis.prg
22/07/2020  18:55             1.409 pauxipienq.prg
22/07/2020  18:55             1.727 pauxipicst.prg
22/07/2020  18:55             1.770 pauxicmcst.prg
22/07/2020  18:55             4.382 pauxcnae.prg
22/07/2020  18:55             1.319 pauxcarcor.prg
22/07/2020  18:55             4.819 ljplicmov.prg
22/07/2020  18:57             4.091 ppretabela.prg
22/07/2020  18:57             9.436 pprereltabgeral.prg
22/07/2020  18:57            85.082 pjppedidosub.prg
22/07/2020  18:57            14.540 pjplicmov.prg
22/07/2020  18:57            27.534 pjpitem.prg
22/07/2020  18:57            26.149 pjpimposto.prg
22/07/2020  18:57            27.782 pjpanpmov.prg
22/07/2020  18:57            58.105 pfiscsped.prg
23/07/2020  01:15             2.621 pbancografresumo.prg
23/07/2020  01:15             4.130 pbancograficomes.prg
23/07/2020  01:15             6.000 pbancogera.prg
23/07/2020  01:15            11.653 pbancocomparames.prg
23/07/2020  01:15             4.160 pbancoccusto.prg
23/07/2020  01:17             8.155 pbancorelccusto.prg
23/07/2020  01:18             9.277 ze_update.prg
23/07/2020  05:02            12.892 pbancolanca.prg
24/07/2020  15:36             4.883 pcont_func.prg
24/07/2020  17:36            56.221 pcontsped.prg
25/07/2020  02:51             5.635 pcontfiscal.prg
25/07/2020  15:08             1.143 pfisctotais.prg
26/07/2020  03:38            39.191 pfiscsintegra.prg
26/07/2020  16:52            28.664 ze_updateh.prg
26/07/2020  22:14             6.121 pfiscrel0120.prg
26/07/2020  22:14            12.455 pfiscrel0090.prg
26/07/2020  22:14             9.841 pfiscrel0060.prg
26/07/2020  22:21             8.353 pfiscrel0110.prg
26/07/2020  22:21            21.403 pfiscrel0030.prg
26/07/2020  22:25            11.886 pfiscrel0100.prg
26/07/2020  22:25            10.571 pfiscrel0080.prg
26/07/2020  22:25             7.784 pfiscrel0050.prg
26/07/2020  22:28            21.806 pfiscrel0040.prg
             238 arquivo(s)      2.629.844 bytes


O anterior foi o bancário, e o atual é o fiscal, com passagem rápida pelo sped contábil.
Também dá pra ver que, eventualmente desvio do módulo sendo alterado.
E que, dependendo da alteração, faço em vários fontes de uma vez.
No fiscal andei alterando nomes de campos e variáveis em vários módulos, pra ficar "mais padrão", por isso quase tudo com a mesma data/hora.

Meu modo de trabalho

MensagemEnviado: 29 Jul 2020 05:03
por JoséQuintas
10/03/2020 20:34 6.750.512 jpa2020.exe
28/06/2020 06:31 6.544.816 jpa.exe
02/07/2020 07:30 6.544.816 JPA.EXE
05/07/2020 09:48 6.536.192 jpa.exe
29/07/2020 04:53 6.449.152 jpa.exe

Eba !!!!!!

Finalizado fiscal.

Agora sim, só falta o contábil e relacionados, pra liquidar de vez com DBF.

Meu modo de trabalho

MensagemEnviado: 29 Jul 2020 17:31
por JoséQuintas
Na falta de um lugar apropriado....
Uma alteração no contábil, sei lá se vale a pena, mas fiz, pra ficar mais flexível.

STATIC FUNCTION OkGrupo( mplGrupo )

   LOCAL lReturn := .T.

   @ Row(), 35 SAY Pad( iif( mplGrupo == "A", "Ativo", iif( mplGrupo == "P", "Passivo", iif( mplGrupo == "R", "Result.", "" ) ) ), 20 )
   IF ! mplGrupo $ "APR"
      MsgWarning( "Grupo tem que ser (A)tivo, (P)assivo ou (R)esultado!" )
      lReturn := .F.
   ENDIF

   RETURN lReturn


STATIC FUNCTION OkGrupo( mplGrupo )

   LOCAL lReturn := .T., nPos
   LOCAL aGrupoList := { "A:Ativo", "P:Passivo", "R:Resultado" } // "L:Patr.Liq", "C:Compensacao", "O:Outras" }

   nPos := hb_AScan( aGrupoList, { | e | mplGrupo == Left( e, 1 ) } )
   @ Row(), 35 SAY Pad( iif( nPos == 0, "", Substr( aGrupoList[ nPos ], 3 ), 20 )
   IF nPos == 0
      cList := ""
      FOR EACH oElement IN aGrupoList
         cList += oElement + iif( oElement:__EnumIsLast, "", ", " )
      NEXT
      MsgWarning( "Opções disponíveis:" +  cList )
      lReturn := .F.
   ENDIF

   RETURN lReturn


Ficou interessante, talvez dê pra aproveitar a rotina pra muito mais coisas.
A validação é no primeiro caractere, e em caso de erro mostra a lista.

Se criar rotina separada pra isso, dá pra usar pra analítica/sintética, masculino/feminino, sim/não, pessoa física/jurídica, etc. etc. etc.
Ou até exibir um menu com as opções automaticamente.

Meu modo de trabalho

MensagemEnviado: 30 Jul 2020 19:02
por JoséQuintas
Então.... só reforçando aqui:

Meu módulo contábil é o único que está usando DBF.
O módulo contábil e relacionados.

TODO restante já está somente em MySQL.

Durante esse tempo todo, fui alterando de DBF pra MySQL, e os clientes foram atualizando.

Usei o método mais demorado, já que converti usando ADO, o que obriga a reescrever fontes.
Primeiro fiz a gravação dupla, DBF + MySQL ao mesmo tempo, alterando toda parte que fazia gravação em DBF, pra gravar também no MySQL.
Depois alterei para o MySQL ser o principal, usar o campo incremental do MySQL pra fornecer códigos, e gravar igual no DBF.
Depois comecei a alterar todas as leituras, pra serem feitas usando somente MySQL.
E assim que não havia leitura no DBF, apagava o DBF.

Comecei pra valer em dezembro/2019, após ter acesso ao browse usando ADO.
Foram praticamente 7 meses, aprendendo mais SQL conforme ia colocando em prática.

Antes disso, ia fazendo alguma coisa, passei o de imobiliária parcial pra MySQL usando DBFs temporários, por ser de uso em uma única empresa, pra ter mais controle sobre o que acontecia, antes de adotar MySQL geral.
Até vou precisar ajustar melhor, pra ficar compatível com o que tenho usado agora pra MySQL, sem DBFs temporários.

Meu EXE é um só pra TODOS os clientes, significa que TUDO em TODOS os clientes foi pra MySQL.
A exceção é o programa da imobiliária, e os aplicativos em Linux/Flagship
E falta agora a contabilidade e relacionados, que são bem mais simples do que o restante.

Estou muito contente com o resultado.
Tudo correu até mais tranquilo do que eu esperava.

Meu modo de trabalho

MensagemEnviado: 04 Ago 2020 15:19
por JoséQuintas
Erro de hoje:

whatsapp.png


Problema:

erro.png


Coloquei pra salvar a soma de valor de ST, ao invés de base de ST.

Problemas desse tipo.... aí tanto faz se é DBF, SQL... errou salva errado kkkk

Como solução rápida, no cliente usei o HeidiSQL, fiz o select com relacionamento e alterei o conteúdo manualmente.
E já alterei o aplicativo, agora faz certo.

Tava errado no pedido e na nota fiscal, já que o total da nota vém do pedido.

Nota:
Já estava em uso há alguns dias em outros clientes.
Esse não atualizava desde MAIO deste ano.
Foi a primeira nota com substituição tributária, desde a mudança desse fonte pra SQL.

Ou seja: ele estava com DBF antes do almoço, passou pra MySQL depois do almoço, e deu esse erro, porque tava errado mesmo, nenhum outro problema.

Meu modo de trabalho

MensagemEnviado: 04 Ago 2020 15:37
por JoséQuintas
Aproveitando:

Então....

Eu atualizo estruturas automático, tanto DBF quanto MySQL.
Salvar dados de DBF pra MySQL, também automático.
O aplicativo tem atualização automática, é uma opção no menu.

Lembram? fonte fácil, atualização fácil, conversão fácil, tudo fácil.
Isso torna a correção de erros fácil e rápida.

Seria muuuito diferente, se eu tivesse que ir no cliente, pesquisar o problema, levar fontes, utilitários, etc.

Tem que pensar em todo conjunto, tudo que puder facilitar pra gente, melhor.
Cometo erros igual todo mundo, mas essas coisas facilitam fazer manutenção, e por isso tudo foi rápido/fácil.

Meu modo de trabalho

MensagemEnviado: 07 Ago 2020 14:59
por JoséQuintas
Chegou a hora.
Fiz uma cópia do aplicativo como jpa202008.exe como precaução.
Começar a limpar isto agora:

19/06/2020  17:41             7.666 ze_update.prg
26/06/2020  10:37            22.801 ze_update2020.prg
31/03/2020  18:20             2.004 ze_updatea.prg
28/05/2020  01:29            19.315 ze_updateb.prg
28/05/2020  01:34            19.656 ze_updatec.prg
08/06/2020  22:12            25.138 ze_updated.prg
22/03/2020  13:14            17.515 ze_updatedefault.prg
19/06/2020  17:41            26.352 ze_updatee.prg
10/06/2020  07:54             7.647 ze_updateexedown.prg
04/06/2020  13:03             6.069 ze_updateexeup.prg
14/05/2020  14:23            21.976 ze_updatef.prg
19/06/2020  12:35            23.814 ze_updateg.prg
20/05/2020  03:43            20.502 ze_updateh.prg
              13 arquivo(s)        220.455 bytes


Tem criação/atualização de estrutura de DBF e de MySQL.
E transferência de DBF pra MySQL.

Meu modo de trabalho

MensagemEnviado: 07 Ago 2020 15:18
por JoséQuintas
Achei que ia reduzir mais.

07/08/2020  14:22             9.328 ze_update.prg
07/08/2020  15:06            14.480 ze_update2020.prg
01/08/2020  06:36             2.071 ze_updatea.prg
07/08/2020  14:49            17.200 ze_updateb.prg
07/08/2020  15:00            15.497 ze_updatec.prg
07/08/2020  14:55            18.460 ze_updated.prg
30/07/2020  04:09            15.340 ze_updatedefault.prg
07/08/2020  15:03            15.372 ze_updatee.prg
17/07/2020  17:16             7.651 ze_updateexedown.prg
04/06/2020  13:03             6.069 ze_updateexeup.prg
07/08/2020  14:30            13.279 ze_updatef.prg
07/08/2020  14:46            16.967 ze_updateg.prg
07/08/2020  15:05            23.646 ze_updateh.prg
              13 arquivo(s)        175.360 bytes


Exceto por esta rotina, o aplicativo não sabe mais sobre muitos DBFs que já existiram.

STATIC FUNCTION UpdateDeleteOld()

   LOCAL cFile, nIdade, aList := { ;
      "ba_auto", ;
      "ba_grup", ;
      "ba_movi", ;
      "jpbaauto", ;
      "jpbaccusto", ;
      "jpbagrup", ;
      "jpanpage", ;
      "jpanpati", ;
      "jpanpins", ;
      "jpanploc", ;
      "jpbamovi", ;
      "jpanpope", ;
      "jpanppro", ;
      "jpbancario", ;
      "jpbarra", ;
      "jpcadastro", ;
      "jpcarcor", ;
      "jpclista", ;
      "jpcep", ;
      "jpcfop", ;
      "jpcomissao", ;
      "jpcotaca", ;
      "jpcotcli", ;
      "jpcotfor", ;
      "jpcotpro", ;
      "jpcte", ;
      "jpdecret", ;
      "jpdocrel", ;
      "jpedicfg", ;
      "jpestoque", ;
      "jpfinan", ;
      "jpfiscal", ;
      "jpfisica", ;
      "jpforpag", ;
      "jpibpt",   ;
      "jpimposto", ;
      "jpitem", ;
      "jpitped", ;
      "jplicmov", ;
      "jplogsi", ;
      "jpmdfcab", ;
      "jpmdfdet", ;
      "jpmotori", ;
      "jpnfbase", ;
      "jpnfeger", ;
      "jpnfexml", ;
      "jpnotaca", ;
      "jpnotfis", ;
      "jpordbar", ;
      "jpordres", ;
      "jpordser", ;
      "jppedido", ;
      "jppreco", ;
      "jpprehis", ;
      "jppretab", ;
      "jpromcab", ;
      "jpromdet", ;
      "jptexto", ;
      "jptransa", ;
      "jptransp", ;
      "jpveiculo", ;
      "jpvendedor", ;
      "jpvvdem", ;
      "jpvvfin", ;
      "nfbase", ;
      "rastrea" }

   SayScroll( "Verificando velhos/desativados" )

   nIdade := iif( IsMaquinaJPA(), -1, 15 )
   FOR EACH cFile IN aList
      DeleteOldFiles( cFile + ".dbf", nIdade )
      DeleteOldFiles( cFile + ".cdx", nIdade )
   NEXT

   RETURN NIL


Na minha máquina apaga na hora, já nos clientes espera 15 dias após o último uso.
NÃO é rotina nova, então é possível que já tenha apagado na maioria dos clientes.

Pois é...
Rotinas automáticas de atualizar DBF, transferir pra MySQL, etc.
Tudo relacionado a DBF vai acabar sumindo....

NÃO... não terminei ainda.... continua faltando o contábil e relacionados.
Pra esses... nem tem nada de nota fiscal, estoque, etc. pra converter de DBF, é só apagar mesmo.

Meu modo de trabalho

MensagemEnviado: 07 Ago 2020 15:49
por JoséQuintas
JoséQuintas escreveu:NÃO... não terminei ainda.... continua faltando o contábil e relacionados.
Pra esses... nem tem nada de nota fiscal, estoque, etc. pra converter de DBF, é só apagar mesmo.


Pra QUEM USA só contábil, o resto não é usado.
Lembrando que é tudo um único EXE, então o contábil não pode ficar de fora.
TUDO que tenho está num único EXE, tudo é atualizado ao mesmo tempo, seja dbf ou sql, então... no final nenhum fica de fora.

Meu modo de trabalho

MensagemEnviado: 08 Ago 2020 02:51
por Vlademiro
Seus aplicativos estão independentes de banco de dados devido ao ADO, mas vc está usando muita função nativa do MySQL, o que vai te deixar dependente dele. Uma coisa boa que vc poderia fazer é catalogar em algum local quais as funções nativas do MySQL que vc está usando para , se houver necessidade, criar essas funções em outro banco. Bancos como Sqlserver, Oracle e PostgreSQL tem o comando Create function. As vezes a função existe com nome diferente. Talvez compense só criar uma com o mesmo nome em vez de ficar mechendo no seu código e colocando IFs para cada banco.

Meu modo de trabalho

MensagemEnviado: 08 Ago 2020 23:45
por JoséQuintas
Ainda é cedo pra pensar nisso.
Teria que começar a mexer em outro, pra ver quais seriam as diferenças.

Um lado bom é que uso minha classe, então existe a possibilidade de colocar diferenças pra ela tratar.

Por exemplo, entre Excel e MySQL

METHOD TableList() CLASS ADOClass

   LOCAL acTableList := {}, Rs

   IF ".XLS" $ Upper( ::cn:ConnectionString )
      rs := ::cn:openSchema(20) // adSchemaTables
      DO WHILE ! Rs:Eof()
         IF ! "Print_Are" $ rs:Fields( "Table_Name" ):Value .AND. ;
               ! "FilterDatabase" $ rs:Fields( "Table_Name" ):Value
            AAdd( acTableList, "[" + rs:Fields( "Table_Name" ):Value + "]" )
         ENDIF
         rs:MoveNext()
      ENDDO
      rs:Close()
   ELSE
      ::cSQL := "SELECT DISTINCT table_name AS TABELA FROM information_schema.TABLES WHERE table_schema=" + StringSQL( Lower( AppEmpresaApelido() ) )
      ::Execute()
      DO WHILE ! ::Eof()
         AAdd( acTableList, ::Value( "TABELA" ) )
         ::MoveNext()
      ENDDO
      ::CloseRecordset()
   ENDIF

   RETURN acTableList

Meu modo de trabalho

MensagemEnviado: 08 Ago 2020 23:49
por JoséQuintas
No VB também cheguei a ajustar pra MySQL ou ADS/SQL Server/etc.

If InStr(cSql, " TOP 1") <> 0 Then
   If InStr(UCase(cnConexao.ConnectionString), "MYSQL") <> 0 Then
      cSql = Replace(cSql, " TOP 1 ", " ")
      cSql = cSql & " LIMIT 1"
   End If
End If


Isso trocava o SELECT TOP 1 xxxxxx por SELECT xxx LIMIT 1

Pouca experiência com SQL, mas já preparado desde o início pra eventuais diferenças.
Nessa época, testando ADS com DBF mas preparado pra MySQL.

Meu modo de trabalho

MensagemEnviado: 09 Ago 2020 16:12
por JoséQuintas
Reorganizei os "updates", mas ainda vou mexer.

09/08/2020  05:52            11.149 ze_update.prg
09/08/2020  05:20            48.203 ze_update2020.prg
09/08/2020  05:47            22.061 ze_updateDBF.prg
30/07/2020  04:09            15.340 ze_updatedefault.prg
09/08/2020  05:43            60.929 ze_updateSQL.prg


ze_update - é a atualização central, atualiza os arquivos de configuração e log, porque o resto depende deles

ze_updatesql - é o primeiro chamado, confere se as tabelas existem no MySQL, somente quando troca versão, pra não perder tempo no uso normal. De um modo geral é CREATE TABLE IF NOT EXISTS

ze_updatedbf - vou acabar incorporando no update2020, faz atualização nos DBFs que ainda restam
Alteração de estrutura, e compatiblidade com nova estrutura

ze_updatedefault - só pra gravar conteúdo default em certas tabelas

ze_update2020 - esse sim, faz muita coisa
- deixa o conteúdo dos DBFs compatível com a necessidade atual, seja pra uso como DBF ou pra transferir para o MySQL
- atualiza estruturas do MySQL, caso eu tenha alterado, por exemplo, campos caractere pra numérico
- Transfere para MySQL o que tiver que ser transferido
- Faz outros ajustes pra deixar compatível com versão atual

Atualizações antigas, não mais necessárias, é justamente o que comecei a apagar do ze_Update2020.
Por isso transferir a atualização de DBF aqui pra dentro acho correto, porque também vai acabar sendo apagado quando não mais existir DBF.

Acabei lembrando de um cliente que não atualiza há um ano.... pra esse, acaba sendo necessário manter as atualizações MySQL.
DBFs não, porque não fazem falta. Só uso meu aplicativo pra atualizar o outro, mas... precisa ser atualizado junto, pra continuar funcionando a atualizando no futuro. Talvez junte os dois aplicativos... não tem nada a ver um com o outro, mas... evita certas rotinas repetidas, ou ... esquecer de alguma dependência que não pode ser apagada - fontes de rotinas genéricas são compartilhadas nos dois, incluindo a classe ADO.

Meu modo de trabalho

MensagemEnviado: 09 Ago 2020 16:30
por JoséQuintas
Pra curiosidade, a atualização entre versões, de DBF, SQL, e transferência

/*
ZE_UPDATE2020 - Atualizações 2020
*/

#include "inkey.ch"
#include "josequintas.ch"
#include "directry.ch"
#include "sefaz_anpprod.ch"

FUNCTION ze_Update2020()

   LOCAL nVersaoDBF

   nVersaoDBF := AppVersaoDbfAnt()

   SayScroll( "Atualizações 9/9 - atualizações por data" )

   IF nVersaoDBF < 20200101;   Update0100A();  ENDIF
   IF nVersaoDBF < 20200110;   Update0110();   ENDIF
   IF nVersaoDBF < 20200331;   Update03SQL( nVersaoDBF );  ENDIF
   IF nVersaoDBF < 20200430;   Update04SQL( nVersaoDBF );  ENDIF
   IF nVersaoDBF < 20200731;   Update07SQL( nVersaoDBF );  ENDIF
   IF nVersaoDBF < 20200705;   Update0705();   ENDIF
   IF nVersaoDBF < 20200707.2; Update0707();   ENDIF
   IF nVersaoDBF < 20200713;   Update0713();   ENDIF
   IF nVersaoDBF < 20200727.2; Update0727B();  ENDIF
   IF nVersaoDBF < 20200831;   Update08SQL( nVersaoDBF );  ENDIF
   CLOSE DATABASES

   RETURN NIL

STATIC FUNCTION VerificaNumeracao( cArquivo, cCampoChave, cCampoObs )

   LOCAL cId := StrZero( 0, 6 ), aList := {}, nAtual := 0, nTotal, nRecNo, cTmpFile

   SayScroll( "Verificando repetidos em " + cArquivo + ".DBF" )
   IF ! UseSoDbf( cArquivo, .T. )
      QUIT
   ENDIF
   cTmpFile := MyTempFile()
   INDEX ON &cCampoChave TO ( cTmpFile )
   GOTO TOP
   GrafTempo( cArquivo )
   nTotal := LastRec()
   DO WHILE ! Eof()
      GrafTempo( nAtual++, nTotal )
      IF FieldGet( FieldPos( cCampoChave ) ) <= cId .OR. Val( FieldGet( FieldPos( cCampoChave ) ) ) == 0
         AAdd( aList, RecNo() )
      ENDIF
      cId := FieldGet( FieldPos( cCampoChave ) )
      SKIP
   ENDDO
   IF Len( aList ) != 0
      FOR EACH nRecNo IN aList
         Inkey()
         GOTO ( nRecNo )
         cID := FieldGet( FieldPos( cCampoChave ) )
         IF Val( cID ) == 0
            GOTO BOTTOM
            cId := FieldGet( FieldPos( cCampoChave ) )
         ENDIF
         DO WHILE .T.
            SEEK cId
            IF Eof()
               EXIT
            ENDIF
            cId := StrZero( Val( cId ) + 1, 6 )
            Inkey()
         ENDDO
         GOTO ( nRecNo )
         SayScroll( "lançamento " + cArquivo + " de " + SoNumeros( FieldGet( FieldPos( cCampoChave ) ) ) + " para " + cID )
         GravaOcorrencia( cArquivo, cId, "lancamento " + cArquivo + " de " + SoNumeros( FieldGet( FieldPos( cCampoChave ) ) ) + " para " + cID )
         RecLock()
         IF cCampoObs != NIL .AND. ! Empty( cCampoObs )
            FieldPut( FieldPos( cCampoObs ), AllTrim( FieldGet( FieldPos( cCampoObs ) ) ) + " lanc.ant. " + ;
               FieldGet( FieldPos( cCampoChave ) ) )
         ENDIF
         FieldPut( FieldPos( cCampoChave ), cId )
         RecUnlock()
      NEXT
   ENDIF
   CLOSE DATABASES
   fErase( cTmpFile )

   RETURN NIL

STATIC FUNCTION Update0100A()

   IF ! AbreArquivos( "jpsenha" )
      QUIT
   ENDIF
   SayScroll( "2020-01-00-A Nome de módulos" )
   pw_AddModule( "PJPIMPOSTO",   "PLEISIMPOSTO" )
   pw_AddModule( "LJPIMPOSTO",   "PLEISRELIMPOSTO" )
   pw_AddModule( "PJPNOTFIS",    "PNOTACADASTRO" )
   pw_AddModule( "PJPDECRETO",   "PLEISDECRETO" )
   pw_AddModule( "PAUXPRODEP",   "PESTODEPTO" )
   pw_AddModule( "PAUXPROGRU",   "PESTOGRUPO" )
   pw_AddModule( "PAUXPROSEC",   "PESTOSECAO" )
   pw_AddModule( "PAUXPROUNI",   "PLEISPROUNI" )
   pw_AddModule( "PAUXPROLOC",   "PESTOLOCAL" )
   pw_AddModule( "PAUXCFOP",     "PLEISCFOP" )
   pw_AddModule( "PAUXTRIEMP",   "PLEISTRIEMP" )
   pw_AddModule( "PAUXTRICAD",   "PLEISTRICAD" )
   pw_AddModule( "PAUXTRIPRO",   "PLEISTRIPRO" )
   pw_AddModule( "PAUXTRIUF",    "PLEISTRIUF" )
   pw_AddModule( "PAUXICMCST",   "PLEISICMCST" )
   pw_AddModule( "PAUXIPICST",   "PLEISIPICST" )
   pw_AddModule( "PAUXIPIENQ",   "PLEISIPIENQ" )
   pw_AddModule( "PAUXORIMER",   "PLEISORIMER" )
   pw_AddModule( "PAUXPISCST",   "PLEISPISCST" )
   pw_AddModule( "PAUXPISENQ",   "PLEISPISENQ" )
   pw_AddModule( "PAUXCNAE",     "PLEISCNAE" )
   pw_AddModule( "PAUXCARCOR",   "PLEISCORRRECAO" )
   pw_AddModule( "PAUXMODFIS",   "PLEISMODFIS" )
   pw_AddModule( "LJPCADAS",     "LJPCADAS1" )
   pw_AddModule( "LJPPEDIDO",    "LJPPEDI" )
   pw_AddModule( "LJPTRANSP",    "LJPCADAS3" )
   pw_AddModule( "PJPCADAS",     "PJPCADAS1" )
   pw_AddModule( "PJPCADASB",    "PJPCADAS1B" )
   pw_AddModule( "PJPCOMISSAO",  "PJPCOMISS" )
   pw_AddModule( "PJPTRANSP",    "PJPCADAS3" )
   pw_AddModule( "PJPVEICULO",   "PJPVEICUL" )
   pw_AddModule( "PJPVENDEDOR",  "PJPVENDED" )
   pw_AddModule( "LJPESTOQUEA",  "LJPESTOQA" )
   pw_AddModule( "LJPESTOQUEB",  "LJPESTOQB" )
   pw_AddModule( "LJPESTOQUEC",  "LJPESTOQC" )
   pw_AddModule( "LJPESTOQUEA",  "PJPESTOQUEA" )
   pw_AddModule( "LJPESTOQUEB",  "PJPESTOQUEB" )
   pw_AddModule( "LJPESTOQUEC",  "PJPESTOQUEC" )
   pw_AddModule( "LJPCADASTRO",  "LJPCADAS" )
   pw_AddModule( "PJPCADASTRO",  "PJPCADAS" )
   pw_AddModule( "PJPCADASTROB", "PJPCADASB" )
   pw_AddModule( "PJPEMPRESA",   "PJPEMPRE" )
   pw_AddModule( "PJPCIDADE",    "PLEISCIDADE" )
   pw_AddModule( "PJPUF",        "PLEISUF" )
   pw_AddModule( "LJPCIDADE",    "PLEISRELCIDADE" )
   pw_AddModule( "PJPIBPT",      "PLEISIBPT" )
   CLOSE DATABASES

   RETURN NIL

STATIC FUNCTION Update0110()

   LOCAL oElement, cnSQL := ADOClass():New( AppConexao() )
   LOCAL aList := { ;
      ; // combustivel
      { 820101032, "L",  "DIESEL B S10/S50 PRA GERACAO ENERGIA" }, ;
      { 820101026, "L",  "DIESEL B S1800 PRA GERACAO DE ENERGIA" }, ;
      { 820101027, "L",  "DIESEL B S500 PRA GERACAO DE ENERGIA" }, ;
      { 820101017, "L",  "MIST DIESEL MARITMO 98% 2%" }, ;
      { 820101018, "L",  "DIESEL MARITMO 95% 5%" }, ;
      { 820101019, "L",  "DIESEL MARITMO DMB B2" }, ;
      { 820101020, "L",  "DIESEL MARITMO DMB B5" }, ;
      { 820101021, "L",  "DIESEL NAUTICO B2 ESPECIAL PPM ENXOFRE" }, ;
      { 420201001, "L",  "DIESEL MARITMO" }, ;
      { 420201003, "L",  "DMB MDO" }, ;
      { 430101004, "L",  "OLEO COMB TURBINA GERADORA" }, ;
      { 510103001, "KG", "OLEO COMBUSTIVEL 3 OC3" }, ;
      { 510101001, "KG", "OLEO COMBUSTIVEL 1A" }, ;
      { 510101002, "KG", "OLEO COMBUSTIVEL 2A" }, ;
      { 510102001, "KG", "OLEO COMBUSTIVEL B1" }, ;
      { 510102002, "KG", "OLEO COMBUSTIVEL B2" }, ;
      { 510201001, "KG", "OLEO COMBUSTIVEL MARITMO" }, ;
      { 510201003, "KG", "OLEO COMBUSTIVEL MARITMO MISTURA" }, ;
      { 510301003, "KG", "OLEO COMBUSTIVEL GERACAO ELETRICA" }, ;
      { 560101001, "L",  "OLEO DE XISTO" }, ;
      { 820101033, "L",  "OLEO DIESEL S10 ADITIVADO" }, ;
      { 820101034, "L",  "OLEO DIESEL S10 COMUM" }, ;
      { 820101011, "L",  "OLEO DIESEL BS1800 ADITIVADO" }, ;
      { 820101003, "L",  "MIST DIESEL 95% 5%" }, ;
      { 820101013, "L",  "OLEO DIESEL BS500 ADITIVADO" }, ;
      { 820101012, "L",  "OLEO DIESEL BS500 COMUM" }, ;
      { 420202001, "L",  "OLEO DIESEL NAUTICO ESP ENXOFRE 200 PPM" }, ;
      { 510301001, "KG", "OUTROS OLEOS COMBUSTIVEIS" }, ;
      { 410102001, "L",  "QUEROSENE ILUMINANTE" }, ;
      ; // outros
      { 650101004, "KG", "GRAXAS DE CALCIO" }, ;
      { 650101003, "KG", "GRAXAS DE LITIO" }, ;
      { 650101001, "KG", "GRAXAS MINERAIS" }, ;
      { 650101002, "KG", "OUTRAS GRAXAS" }, ;
      { 620502001, "L",  "MOTORES 2 TEMPOS" }, ;
      { 620501002, "L",  "CICLO DIESEL" }, ;
      { 620501001, "L",  "CICLO OTTO" }, ;
      { 620601003, "L",  "CORRENTE DE MOTOSSERRA" }, ;
      { 620601001, "L",  "OLEOS EXTENSORES E PLASTICICANTES" }, ;
      { 620601004, "L",  "OUTROS OLEOS LUBRIF ACABADOS" }, ;
      { 620601002, "L",  "PULVERIZACAO AGRICOLA" }, ;
      { 620401001, "L",  "OLEOS LUBRIF FERROVIARIOS" }, ;
      { 620101002, "L",  "ENGRENAGENS E SISTEMAS CIRCULATORIOS" }, ;
      { 620101007, "L",  "ESTAMPAGEM" }, ;
      { 620101001, "L",  "HIDRAULICO" }, ;
      { 620101004, "L",  "ISOLANTE TIPO A" }, ;
      { 620101005, "L",  "ISOLANTE TIPO B" }, ;
      { 620101008, "L",  "OUTROS OLEOS LUBRIF INDUST" }, ;
      { 620101003, "L",  "PROCESSO" }, ;
      { 620101006, "L",  "TEXTIL AMACIANTE DE FIBRAS" }, ;
      { 620301001, "L",  "OLEOS LUBRIF MARITMOS" }, ;
      { 620201001, "L",  "OLEOS LUBRIF AVIACAO" }, ;
      { 660101001, "L",  "OLEOS LUBRIF PARAF GRAXAS INTERMED" }, ;
      { 620505001, "L",  "OUTROS OLEOS LUBRIF AUTOMOTIVOS" }, ;
      { 620504001, "L",  "TRANSMISSAO AUTOMATICA" }, ;
      { 620503001, "L",  "TRANSMISSOES E SISTEMAS HIDRAULICOS" } }

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-01-10" )

   WITH OBJECT cnSQL
      FOR EACH oElement IN aList
         :QueryCreate()
         :QueryAdd( "IDANPPRO", StrZero( oElement[ 1 ], 9 ) )
         :QueryAdd( "APDESCRICAO", oElement[ 3 ] )
         :QueryAdd( "APUNIDADE", oElement[ 2 ] )
         :QueryAdd( "APISIMP", "S" )
         :QueryExecuteInsert( "JPANPPRO", .T. )
         Inkey()
      NEXT
      FOR EACH oElement IN SEFAZ_ANPPROD
         :QueryCreate()
         :QueryAdd( "IDANPPRO", oElement[ 1 ] )
         :QueryAdd( "APDESCRICAO", oElement[ 2 ] )
         :QueryAdd( "APISIMP", "N" )
         :QueryExecuteInsert( "JPANPPRO", .T. )
         Inkey()
      NEXT
   ENDWITH

   RETURN NIL

STATIC FUNCTION Update03SQL( nVersaoDBF )

   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-03 SQL" )
   WITH OBJECT cnSQL
      IF nVersaoDBF < 20200302
         SayScroll( "2020-03-02 SQL" )
         :ExecuteCmd( "ALTER TABLE JPAGENDA CHANGE COLUMN IDAGENDA IDAGENDA INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPAGE CHANGE COLUMN IDANPAGE IDANPAGE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPATI CHANGE COLUMN IDANPATI IDANPATI INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPINS CHANGE COLUMN IDANPINS IDANPINS INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPLOC CHANGE COLUMN IDANPLOC IDANPLOC INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPOPE CHANGE COLUMN IDANPOPE IDANPOPE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPPRO CHANGE COLUMN IDANPPRO IDANPPRO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPCLISTA CHANGE COLUMN IDCLISTA IDCLISTA INT(11) NOT NULL AUTO_INCREMENT" )
         IF :FieldExists( "CBID", "JPCOMBUSTIVEL" )
            :ExecuteCmd( "ALTER TABLE JPCOMBUSTIVEL DROP COLUMN CBID, DROP PRIMARY KEY" )
            :ExecuteCmd( "ALTER TABLE JPCOMBUSTIVEL ADD COLUMN IDCOMBUSTIVEL INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " ADD PRIMARY KEY ( IDCOMBUSTIVEL )" )
         ENDIF
         :ExecuteCmd( "ALTER TABLE JPCOMBUSTIVEL CHANGE COLUMN IDCOMBUSTIVEL IDCOMBUSTIVEL INT(11) NOT NULL AUTO_INCREMENT FIRST" )
         :ExecuteCmd( "ALTER TABLE JPCOMISSAO CHANGE COLUMN IDCOMISSAO IDCOMISSAO INT(11) NOT NULL AUTO_INCREMENT" )
         IF :FieldExists( "DENUMLAN", "JPDECRETO" )
            SayScroll( "Modificando tabela JPDECRETO" )
            :ExecuteCmd( "ALTER TABLE JPDECRETO DROP COLUMN DEID, DROP PRIMARY KEY, " + ;
               "CHANGE COLUMN DENUMLAN IDDECRETO INT(11) NOT NULL AUTO_INCREMENT, " + ;
               "ADD PRIMARY KEY ( IDDECRETO )" )
         ENDIF
         :ExecuteCmd( "ALTER TABLE JPDECRETO CHANGE COLUMN IDDECRETO IDDECRETO INT(11) NOT NULL AUTO_INCREMENT" )
         IF :IndexExists( "IDXEDICFG", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG DROP INDEX IDXEDICFG" )
         ENDIF
         IF :IndexExists( "IDXEDI", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG DROP INDEX IDXEDI" )
         ENDIF
         IF :IndexExists( "IDXJPA", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG DROP INDEX IDXJPA" )
         ENDIF
         IF :FieldExists( "EDID", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN EDID IDEDICFG INT(11) NOT NULL AUTO_INCREMENT," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDEDICFG )" )
         ENDIF
         IF :FieldExists( "IDREGISTRO", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN IDREGISTRO IDEDICFG INT(11) NOT NULL AUTO_INCREMENT," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDEDICFG )" )
         ENDIF
         IF :FieldExists( "EDNUMLAN", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG DROP COLUMN EDNUMLAN" )
         ENDIF
         IF :FieldExists( "EDCODEDI1", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN EDCODEDI1 EDEMPRESA VARCHAR(20) NOT NULL DEFAULT ''" )
         ENDIF
         IF :FieldExists( "EDCODJPA", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN EDCODJPA EDINTERNO VARCHAR(6) NOT NULL DEFAULT ''" )
         ENDIF
         IF :FieldExists( "EDCODEDI2", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN EDCODEDI2 EDEXTERNO VARCHAR(20) NOT NULL DEFAULT ''" )
         ENDIF
         IF :FieldExists( "EDDESEDI", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN EDDESEDI EDDESCRICAO VARCHAR(50) NOT NULL DEFAULT ''" )
         ENDIF
         IF ! :IndexExists( "IDXINTERNO", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG ADD INDEX IDXINTERNO ( EDTIPO, EDINTERNO )" )
         ENDIF
         IF ! :indexExists( "IDXEMPRESA", "JPEDICFG" )
            :ExecuteCmd( "ALTER TABLE JPEDICFG ADD INDEX IDXEMPRESA ( EDTIPO, EDEMPRESA, EDEXTERNO )" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200312
         SayScroll( "2020-03-12 SQL" )
         IF :FieldExists( "PDDATCAN", "JPPEDIDO" ) // 2020-03-15
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDDATCAN" )
         ENDIF
         IF :FieldExists( "PDMOTCAN", "JPPEDIDO" ) // 2020-03-15
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDMOTCAN" )
         ENDIF
         :ExecuteCmd( "DELETE FROM JPITPED WHERE IPPEDIDO IN ( SELECT IDPEDIDO FROM JPPEDIDO WHERE PDSTATUS = 'C' )" )
         :ExecuteCmd( "DELETE FROM JPPEDIDO WHERE PDSTATUS = 'C'" )
      ENDIF
      IF nVersaoDBF < 20200302
         :ExecuteCmd( "ALTER TABLE JPEMANFE CHANGE COLUMN ENEMANFE ENEMANFE VARCHAR(250) NOT NULL DEFAULT ''" )
         :ExecuteCmd( "ALTER TABLE JPEMANFE CHANGE COLUMN IDEMANFE IDEMANFE INT(11) NOT NULL AUTO_INCREMENT" )
         IF :FieldExists( "ESCLIFOR", "JPESTOQUE" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESCLIFOR ESCADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
         IF :IndexExists( "IDXITEM", "JPESTOQUE" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE DROP INDEX IDXITEM" )
         ENDIF
         IF :FieldExists( "ESITEM", "JPESTOQUE" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESITEM ESPRODUTO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
         IF ! :IndexExists( "IDXPRODUTO", "JPESTOQUE" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE ADD INDEX IDXPRODUTO ( ESPRODUTO, ESTIPLAN, ESDATLAN, IDESTOQUE )" )
         ENDIF
         IF ! :IndexExists( "IDXPRODUTO", "JPESTOQUE" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE ADD INDEX IDXPRODUTO ( ESPRODUTO, ESTIPLAN DESC, ESDATLAN, IDESTOQUE )" )
         ENDIF
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN IDESTOQUE IDESTOQUE INT(11) NOT NULL AUTO_INCREMENT" )
         IF nVersaoDBF < 20200217
            IF :FieldExists( "ESCLIFOR", "JPESTOQUE" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESFILIAL ESFILIAL VARCHAR(6) NOT NULL DEFAULT '000000'" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESTIPLAN ESTIPLAN CHAR(1) NOT NULL DEFAULT '1'" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESTRANSA ESTRANSA VARCHAR(6) NOT NULL DEFAULT '000000'" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESPRODUTO ESPRODUTO VARCHAR(6) NOT NULL DEFAULT '000000'" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESNUMDEP ESNUMDEP CHAR(1) NOT NULL DEFAULT '1'" )
               :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESCCUSTO ESCCUSTO VARCHAR(6) NOT NULL DEFAULT '000000'" )
            ENDIF
         ENDIF
         IF nVersaoDBF < 20200221
            :ExecuteCmd( "UPDATE JPESTOQUE SET ESPEDIDO = 0 WHERE ESPEDIDO = ''" )
            :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESPEDIDO ESPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         ENDIF
         IF :FieldExists( "FSID", "JPFISICA" )
            :ExecuteCmd( "ALTER TABLE JPFISICA CHANGE COLUMN FSID IDFISICA INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDFISICA )" )
         ENDIF
         IF :FieldExists( "IDREGISTRO", "JPFISICA" )
            :ExecuteCmd( "ALTER TABLE JPFISICA CHANGE COLUMN IDREGISTRO IDFISICA INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDFISICA )" )
         ENDIF
         IF :FieldExists( "IBID", "JPIBPT" )
            :ExecuteCmd( "ALTER TABLE JPIBPT DROP COLUMN IBID, DROP PRIMARY KEY" )
         ENDIF
         IF ! :FieldExists( "IDIBPT", "JPIBPT" )
            :ExecuteCmd( "ALTER TABLE JPIBPT ADD COLUMN IDIBPT INT(11) NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY ( IDIBPT )" )
         ENDIF
         :ExecuteCmd( "ALTER TABLE JPIBPT CHANGE COLUMN IDIBPT IDIBPT INT(11) NOT NULL AUTO_INCREMENT FIRST" )
      ENDIF
      IF nVersaoDbf < 20200302
         IF :IndexExists( "IDXNUMLAN", "JPLICMOV" )
            :ExecuteCmd( "ALTER TABLE JPLICMOV DROP INDEX IDXNUMLAN" )
         ENDIF
         IF :FieldExists( "LCNUMLAN", "JPLICMOV" )
            :ExecuteCmd( "ALTER TABLE JPLICMOV DROP COLUMN LCNUMLAN" )
         ENDIF
         IF :FieldExists( "LCID", "JPLICMOV" )
            :ExecuteCmd( "ALTER TABLE JPLICMOV CHANGE COLUMN LCID IDLICMOV INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDLICMOV )" )
         ENDIF
         IF :FieldExists( "IDREGISTRO", "JPLICMOV" )
            :ExecuteCmd( "ALTER TABLE JPLICMOV CHANGE COLUMN IDREGISTRO IDLICMOV INT(11) UNSIGNED NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDLICMOV )" )
         ENDIF
         IF :FieldExists( "NBID", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE CHANGE COLUMN NBID IDNFBASE INT(11) UNSIGNED NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDNFBASE )" )
         ENDIF
         IF :FieldExists( "IDREGISTRO", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE CHANGE COLUMN IDREGISTRO IDNFBASE INT(11) UNSIGNED NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDNFBASE )" )
         ENDIF
         IF :IndexExists( "NOME", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE DROP INDEX NOME" )
         ENDIF
         IF :IndexExists( "ENDERECO", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE DROP INDEX ENDERECO" )
         ENDIF
         IF :IndexExists( "NUMLAN", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE DROP INDEX NUMLAN" )
         ENDIF
         IF :FieldExists( "NBNUMLAN", "JPNFBASE" )
            :ExecuteCmd( "ALTER TABLE JPNFBASE DROP COLUMN NBNUMLAN" )
         ENDIF
         IF :IndexExists( "IDXCADEMI", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP INDEX IDXCADEMI" )
         ENDIF
         IF ! :IndexExists( "IDXPEDIDO", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS ADD INDEX IDXPEDIDO ( NFPEDIDO, IDNOTFIS )" )
         ENDIF
         IF :IndexExists( "NOTA", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP INDEX NOTA" )
         ENDIF
         IF ! :IndexExists( "IDXNOTFIS", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS ADD INDEX IDXNOTFIS ( NFFILIAL, NFNOTFIS )" )
         ENDIF
         IF :FieldExists( "NFCADDES", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS CHANGE COLUMN NFCADDES NFCADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
         IF :FieldExists( "NFAVENUM", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP COLUMN NFAVENUM" )
         ENDIF
         IF :FieldExists( "NFAVEPRO", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP COLUMN NFAVEPRO" )
         ENDIF
         IF :FieldExists( "NFCTE", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP COLUMN NFCTE" )
         ENDIF
         IF :FieldExists( "NFNFE", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS DROP COLUMN NFNFE" )
         ENDIF
         IF ! :IndexExists( "IDXCADASTRO", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS ADD INDEX IDXCADASTRO ( NFCADASTRO, NFDATEMI, IDNOTFIS )" )
         ENDIF
         IF ! :FieldExists( "NFVALDES", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS ADD COLUMN NFVALDES DOUBLE(14,2) NOT NULL DEFAULT '0'" )
         ENDIF
         IF ! :FieldExists( "NFIIVAL", "JPNOTFIS" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS ADD COLUMN NFIIVAL DOUBLE(14,2) NOT NULL DEFAULT '0'" )
         ENDIF
         IF nVersaoDBF < 20200221
            :ExecuteCmd( "UPDATE JPNOTFIS SET NFPEDIDO = 0 WHERE NFPEDIDO IS NULL OR NFPEDIDO < '0' OR NFPEDIDO > '999999'" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS CHANGE COLUMN NFPEDIDO NFPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         ENDIF
         :ExecuteCmd( "UPDATE JPNOTFIS SET NFLEIS = TRIM( NFLEIS )" )
         :ExecuteCmd( "UPDATE JPNOTFIS SET NFLEIS = TRIM( Left( NFLEIS, LENGTH( NFLEIS ) - 1 ) ) WHERE RIGHT( NFLEIS, 1 ) = ','" )
         IF :FieldExists( "PHID", "JPPREHIS" )
            :ExecuteCmd( "ALTER TABLE JPPREHIS CHANGE COLUMN PHID IDPREHIS INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDPREHIS )" )
         ENDIF
         IF :FieldExists( "IDREGISTRO", "JPPREHIS" )
            :ExecuteCmd( "ALTER TABLE JPPREHIS CHANGE COLUMN IDREGISTRO IDPREHIS INT(11) NOT NULL AUTO_INCREMENT FIRST," + ;
               " DROP PRIMARY KEY, ADD PRIMARY KEY ( IDPREHIS )" )
         ENDIF
         IF :FieldExists( "PHCADAS", "JPPREHIS" )
            :ExecuteCmd( "ALTER TABLE JPPREHIS CHANGE COLUMN PHCADAS PHCADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
         IF :FieldExists( "PHITEM", "JPPREHIS" )
            :ExecuteCmd( "ALTER TABLE JPPREHIS CHANGE COLUMN PHITEM PHPRODUTO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200302
         IF :FieldExists( "FICLIFOR", "JPFINAN" )
            :ExecuteCmd( "ALTER TABLE JPFINAN CHANGE COLUMN FICLIFOR FICADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
         IF :IndexExists( "IDXNUMLAN", "JPFINAN" )
            :ExecuteCmd( "ALTER TABLE JPFINAN DROP INDEX IDXNUMLAN" )
         ENDIF
         IF ! :IndexExists( "IDXPEDIDO", "JPFINAN" )
            :ExecuteCmd( "ALTER TABLE JPFINAN ADD INDEX IDXPEDIDO ( FIPEDIDO, IDFINAN )" )
         ENDIF
         IF nVersaoDBF < 20191201
            :ExecuteCmd( "UPDATE JPFINAN SET FIPEDIDO = '0' WHERE FIPEDIDO < '0'" )
            :ExecuteCmd( "ALTER TABLE JPFINAN CHANGE COLUMN FIPEDIDO FIPEDIDO VARCHAR(6) NOT NULL DEFAULT '0'" )
         ENDIF
         IF nVersaoDBF < 20200221
            :ExecuteCmd( "UPDATE JPFINAN SET FIPEDIDO = 0 WHERE FIPEDIDO IS NULL OR FIPEDIDO < '0'" )
            :ExecuteCmd( "ALTER TABLE JPFINAN CHANGE COLUMN FIPEDIDO FIPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200326
         IF :FieldExists( "FIDATPRE", "JPFINAN" )
            :ExecuteCmd( "ALTER TABLE JPFINAN DROP COLUMN FIDATPRE" )
         ENDIF
         IF :FieldExists( "IPQTDEF", "JPITPED" )
            :ExecuteCmd( "ALTER TABLE JPITPED DROP COLUMN IPQTDEF" )
         ENDIF
         IF :FieldExists( "JPVALTAB", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDVALTAB" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200302
         IF :FieldExists( "PDVALCUT", "JPPEDIDO" )
            SayScroll( "Modificando tabela JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDVALCUT" )
         ENDIF
         IF :FieldExists( "PDREACAO", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDREACAO" )
         ENDIF
         IF :FieldExists( "PDPARCEL", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDPARCEL" )
         ENDIF
         IF :FieldExists( "PDDOLAR", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDDOLAR" )
         ENDIF
         IF nVersaoDBF < 20200222
            :ExecuteCmd( "UPDATE JPPEDIDO SET PDPEDREL = 0 WHERE PDPEDREL= ''" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO CHANGE COLUMN PDPEDREL PDPEDREL INT(11) NOT NULL DEFAULT '0'" )
            IF ! :IndexExists( "IDXPEDREL", "JPPEDIDO" )
               :ExecuteCmd( "ALTER TABLE JPPEDIDO ADD INDEX IDXPEDREL ( PDPEDREL )" )
            ENDIF
         ENDIF
         :ExecuteCmd( "UPDATE JPPEDIDO SET PDLEIS = TRIM( PDLEIS )" )
         :ExecuteCmd( "UPDATE JPPEDIDO SET PDLEIS = TRIM( Left( PDLEIS, LENGTH( PDLEIS ) - 1 ) ) WHERE RIGHT( PDLEIS, 1 ) = ','" )
         IF :FieldExists( "PDCLIFOR", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO CHANGE COLUMN PDCLIFOR PDCADASTRO VARCHAR(6) NOT NULL DEFAULT '000000'" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200315
         :ExecuteCmd( "ALTER TABLE JPCIDADE CHANGE COLUMN IDCIDADE IDCIDADE INT(11) NOT NULL AUTO_INCREMENT" )
      ENDIF
   ENDWITH

   RETURN NIL

STATIC FUNCTION Update04SQL( nVersaoDBF )

   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-04 SQL" )
   WITH OBJECT cnSQL
      IF nVersaoDBF < 20200408
         SayScroll( "2020-04-08 SQL" )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESCADASTRO = '0'  WHERE ESCADASTRO IS NULL OR ESCADASTRO < '0' OR ESCADASTRO > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESCADASTRO ESCADASTRO INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESFILIAL = '0' WHERE ESFILIAL IS NULL OR ESFILIAL < '0' OR ESFILIAL > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESFILIAL ESFILIAL INT(11) NOT NULL DEFAULT '0'"  )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESPRODUTO = '0' WHERE ESPRODUTO IS NULL OR ESPRODUTO < '0' OR ESPRODUTO > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESPRODUTO ESPRODUTO INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESTRANSA = '0' WHERE ESTRANSA IS NULL OR ESTRANSA < '0' OR ESTRANSA > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESTRANSA ESTRANSA INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDbf < 20200427
         SayScroll( "2020-04-27 SQL" )
         :ExecuteCmd( "UPDATE JPCOMISSAO SET CMVENDEDOR = '000000' WHERE CMVENDEDOR IS NULL OR CMVENDEDOR = ''" )
         :ExecuteCmd( "UPDATE JPCOMISSAO SET CMPRODEP = '000000' WHERE CMPRODEP IS NULL OR CMPRODEP = ''" )
         :ExecuteCmd( "ALTER TABLE JPCOMISSAO" + ;
            " CHANGE CMVENDEDOR CMVENDEDOR INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE CMPRODEP CMPRODEP INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESCCUSTO = '000000' WHERE ESCCUSTO IS NULL OR ESCCUSTO <  '0' OR ESCCUSTO > '999999'"  )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN ESCCUSTO ESCCUSTO INT(11) NOT NULL DEFAULT '0'"  )
      ENDIF
      IF nVersaoDBF < 20200423
         SayScroll( "2020.04.23 Regras Fiscais numéricas" )
         :ExecuteCmd( "UPDATE JPIMPOSTO SET IMTRANSA = '0' WHERE IMTRANSA IS NULL OR IMTRANSA < '0' OR IMTRANSA > '999999'" )
         :ExecuteCmd( "UPDATE JPIMPOSTO SET IMTRIUF = '0' WHERE IMTRIUF IS NULL OR IMTRIUF < '0' OR IMTRIUF > '999999'" )
         :ExecuteCmd( "UPDATE JPIMPOSTO SET IMTRICAD = '0' WHERE IMTRICAD IS NULL OR IMTRICAD < '0' OR IMTRICAD > '999999'" )
         :ExecuteCmd( "UPDATE JPIMPOSTO SET IMTRIPRO = '0' WHERE IMTRIPRO IS NULL OR IMTRIPRO < '0' OR IMTRIPRO > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPIMPOSTO " + ;
            " CHANGE COLUMN IMTRANSA IMTRANSA INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN IMTRIUF  IMTRIUF INT(11)  NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN IMTRICAD IMTRICAD INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN IMTRIPRO IMTRIPRO INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200408
         SayScroll( "2020.04.08 Atualizando JPFINAN.SQL pra numérico" )
         IF AppEmpresaApelido() == "MARINGA"
            :ExecuteCmd( "DELETE FROM JPFINAN WHERE FIVALOR=0 OR FIDATEMI IS NULL" ) // LIXO MARINGA
         ENDIF
         :ExecuteCmd( "UPDATE JPFINAN SET FIFILIAL='1' WHERE FIFILIAL IS NULL OR FIFILIAL < '0' OR FIFILIAL > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FICADASTRO = '0' WHERE FICADASTRO IS NULL OR FICADASTRO < '0' OR FICADASTRO > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FISACADO = '0' WHERE FISACADO IS NULL OR FISACADO < '0' OR FISACADO > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FICCUSTO = '0' WHERE FICCUSTO IS NULL OR FICCUSTO < '0' OR FICCUSTO > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FIOPERACAO = '0' WHERE FIOPERACAO IS NULL OR FIOPERACAO < '0' OR FIOPERACAO > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FIPORTADOR = '0' WHERE FIPORTADOR IS NULL OR FIPORTADOR < '0' OR FIPORTADOR > '999999'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FIVENDEDOR = '0' WHERE FIVENDEDOR IS NULL OR FIVENDEDOR < '0' OR FIVENDEDOR > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPFINAN " + ;
            "CHANGE COLUMN FIFILIAL FIFILIAL INT(11) NOT NULL DEFAULT '0'," + ;
            "CHANGE COLUMN FICADASTRO FICADASTRO INT(11) NOT NULL DEFAULT '0', " + ;
            "CHANGE COLUMN FISACADO FISACADO INT(11) NOT NULL DEFAULT '0'," + ;
            "CHANGE COLUMN FICCUSTO FICCUSTO INT(11) NOT NULL DEFAULT '0'," + ;
            "CHANGE COLUMN FIOPERACAO FIOPERACAO INT(11) NOT NULL DEFAULT '0'," + ;
            "CHANGE COLUMN FIPORTADOR FIPORTADOR INT(11) NOT NULL DEFAULT '0'," + ;
            "CHANGE COLUMN FIVENDEDOR FIVENDEDOR INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200425
         SayScroll( "2020.04.25 Num.Bancário jpfinan.SQL" )
         :ExecuteCmd( "UPDATE JPFINAN SET FINUMBAN = '000000' WHERE Year( FIDATVEN  ) < 2020" )
      ENDIF
      IF nVersaoDBF < 20200406
         IF :FieldExists( "CDSITFAZ", "JPCADASTRO" )
            :ExecuteCmd( "ALTER TABLE JPCADASTRO DROP COLUMN CDSITFAZ" )
         ENDIF
         IF :FieldExists( "CDSITNFE", "JPCADASTRO" )
            :ExecuteCmd( "ALTER TABLE JPCADASTRO DROP COLUMN CDSITNFE" )
         ENDIF
      ENDIF
      IF nVersaoDBF < 20200427
         :ExecuteCmd( "UPDATE JPCADASTRO SET CDVENDEDOR = '000000' WHERE CDPORTADOR IS NULL OR CDVENDEDOR < '0' OR CDVENDEDOR > '999999'" )
         :ExecuteCmd( "UPDATE JPCADASTRO SET CDPORTADOR = '000000' WHERE CDVENDEDOR IS NULL OR CDPORTADOR < '0' OR CDPORTADOR > '999999'" )
         :ExecuteCmd( "UPDATE JPCADASTRO SET CDTRANSP = '000000' WHERE CDTRANSP IS NULL OR CDTRANSP < '0' OR CDTRANSP > '999999'" )
         :ExecuteCmd( "UPDATE JPCADASTRO SET CDTRICAD = '000000' WHERE CDTRICAD IS NULL OR CDTRICAD < '0' OR CDTRICAD > '999999'" )
         :ExecuteCmd( "UPDATE JPCADASTRO SET CDSTATUS = '000000' WHERE CDSTATUS IS NULL OR CDSTATUS < '0' OR CDSTATUS > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPCADASTRO" + ;
            " CHANGE COLUMN CDVENDEDOR CDVENDEDOR INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN CDPORTADOR CDPORTADOR INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN CDTRANSP CDTRANSP INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN CDTRICAD CDTRICAD INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN CDSTATUS CDSTATUS INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDbf < 20200427
         :ExecuteCmd( "UPDATE JPITEM SET IEPRODEP = '000000' WHERE IEPRODEP IS NULL OR IEPRODEP < '0' OR IEPRODEP > '999999'" )
         :ExecuteCmd( "UPDATE JPITEM SET IEPROSEC = '000000' WHERE IEPROSEC IS NULL OR IEPROSEC < '0' OR IEPROSEC > '999999'" )
         :ExecuteCmd( "UPDATE JPITEM SET IEPROGRU = '000000' WHERE IEPROGRU IS NULL OR IEPROGRU < '0' OR IEPROGRU > '999999'" )
         :ExecuteCmd( "UPDATE JPITEM SET IEPROLOC = '000000' WHERE IEPROLOC IS NULL OR IEPROLOC < '0' OR IEPROLOC > '999999'" )
         :ExecuteCmd( "ALTER TABLE JPITEM" + ;
           " CHANGE IEPRODEP IEPRODEP INT(11) NOT NULL DEFAULT '0'," + ;
           " CHANGE IEPROSEC IEPROSEC INT(11) NOT NULL DEFAULT '0'," + ;
           " CHANGE IEPROGRU IEPROGRU INT(11) NOT NULL DEFAULT '0'," + ;
           " CHANGE IEPROLOC IEPROLOC INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
   ENDWITH

   RETURN NIL

STATIC FUNCTION Update07SQL( nVersaoDBF )

   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   WITH OBJECT cnSQL
      IF nVersaoDBF < 20200716
         IF ! :FieldExists( "PHREAJUSTE", "JPPREHIS" )
            :ExecuteCmd( "ALTER TABLE JPPREHIS ADD COLUMN PHREAJUSTE VARCHAR(1) NOT NULL DEFAULT ''" )
         ENDIF
         :ExecuteCmd( "DELETE FROM JPPREHIS WHERE PHCADASTRO IS NULL OR PHCADASTRO = 0 OR" + ;
            " PHPRODUTO IS NULL OR PHPRODUTO = 0 OR PHFORPAG IS NULL OR PHFORPAG = 0" )
      ENDIF
      IF nVersaoDBF < 20200728
         SayScroll( "2020.07.28 Docto/Parcela não zero JPFINAN.SQL" )
         :ExecuteCmd( "UPDATE JPFINAN SET FIPARCELA = '001' WHERE FIPARCELA < '001'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FINUMDOC = '000000001' WHERE FINUMDOC < '000000001'" )
         :ExecuteCmd( "UPDATE JPFINAN SET FINUMBAN = '0' WHERE FINUMBAN = ''" )
         :ExecuteCmd( "ALTER TABLE JPFINAN" + ;
            " CHANGE COLUMN FIPARCELA FIPARCELA INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN FINUMDOC FINUMDOC INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN FINUMBAN FINUMBAN INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200716
         IF ! :FieldExists( "PCDATA", "JPPRECO" )
            :ExecuteCmd( "ALTER TABLE JPPRECO ADD COLUMN PCDATA DATE NULL" )
         ENDIF
         IF ! :FieldExists( "PCHORA", "JPPRECO" )
            :ExecuteCmd( "ALTER TABLE JPPRECO ADD COLUMN PCHORA VARCHAR(8) NOT NULL DEFAULT ''" )
         ENDIF
         :ExecuteCmd( "DELETE FROM JPPRECO WHERE PCCADASTRO IS NULL OR PCCADASTRO = 0 OR " + ;
            " PCPRODUTO IS NULL OR PCPRODUTO = 0 OR PCFORPAG IS NULL OR PCFORPAG = 0" )
      ENDIF
   ENDWITH

   RETURN NIL

STATIC FUNCTION Update0705()

   SayScroll( "2020-07-05 - Nome de rotina" )
   IF ! AbreArquivos( "jpsenha" )
      QUIT
   ENDIF
   pw_AddModule( "PJPPEDIDO",       "P0600PED" )
   pw_AddModule( "PJPESTOQUE",      "PESTOLANCA1" )
   pw_AddModule( "PJPESTOQUEE",     "PESTOLANCA2" )
   pw_AddModule( "PJPPEDIDOCTE",    "ADMPEDCTE" )
   pw_AddModule( "PJPPEDIDONFE",    "ADMPEDNOT" )
   pw_AddModule( "PJPPEDIDOFATURA", "ADMPEDFAT" )
   pw_AddModule( "PJPPEDIDOCUPOM",  "ADMPEDCUP" )
   pw_AddModule( "PJPMDFCAB",       "PJPMF" )
   pw_AddModule( "PJPPEDIDONOTA",   "P0600NOTA" )
   pw_AddModule( "PJPMDFCAB",       "PJPMDF" )
   CLOSE DATABASES

   RETURN NIL

STATIC FUNCTION Update0707()

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-07-06" )
   SayScroll( "Salvando SQL.JPBANCARIO" )
   VerificaNumeracao( "JPBANCARIO", "IDBANCARIO" )
   CopyDbfToSQL( "JPBANCARIO", .T., .F., .T. )
   SayScroll( "Salvando SQL.JPBACCUSTO" )
   VerificaNumeracao( "JPBACCUSTO", "IDBACCUSTO" )
   CopyDBFToSQL( "JPBACCUSTO", .T., .F., .T. )
   SayScroll( "Salvando SQL.JPBAAUTO" )
   VerificaNumeracao( "JPBAAUTO", "IDBAAUTO" )
   CopyDBFToSQL( "JPBAAUTO", .T., .F., .T. )

   RETURN NIL

STATIC FUNCTION Update0713()

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-07-13 Salvando SQL.JPPRECO" )
   CopyDBFToSQL( "JPPRECO", .T., .F., .T. )

   RETURN NIL

STATIC FUNCTION Update0727B()

   SayScroll( "2020-07-27 Update JPFISCAL.DBF" )
   IF ! UseSoDBF( "jpfiscal", .T. )
      QUIT
   ENDIF
   DO WHILE ! Eof()
      RecLock()
      REPLACE ;
         jpfiscal->lfPedido WITH StrZero( Val( jpfiscal->lfPedido ), 6 ), ;
         jpfiscal->lfFilial WITH StrZero( Val( jpfiscal->lfFilial ), 6 ), ;
         jpfiscal->lfDocIni WITH StrZero( Val( jpfiscal->lfDocIni ), 9 ), ;
         jpfiscal->lfDocFim WITH StrZero( Val( jpfiscal->lfDocFim ), 9 ), ;
         jpfiscal->lfCadastro WITH StrZero( Val( jpfiscal->lfCadastro ), 6 ), ;
         jpfiscal->lfModFis WITH StrZero( Val( jpfiscal->lfModFis ), 2 ), ;
         jpfiscal->lfCCusto WITH StrZero( Val( jpfiscal->lfCCusto ), 6 )
      SKIP
   ENDDO
   CLOSE DATABASES
   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "Salvando SQL.JPFISCAL" )
   VerificaNumeracao( "jpfiscal", "IDFISCAL", "lfObs" )
   CopyDbfToSQL( "JPFISCAL", .T., .F., .T. )

   RETURN NIL

STATIC FUNCTION Update08SQL( nVersaoDBF )

   LOCAL cnSQL := ADOClass():New( AppConexao() )

   IF AppConexao() == NIL
      RETURN NIL
   ENDIF
   SayScroll( "2020-08 SQL" )
   WITH OBJECT cnSQL
      IF nVersaoDBF < 20200801
         SayScroll( "2020-08-01 SQL" )
         :ExecuteCmd( "ALTER TABLE JPAGENDA CHANGE COLUMN IDAGENDA IDAGENDA INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPAGE CHANGE COLUMN IDANPAGE IDANPAGE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPATI CHANGE COLUMN IDANPATI IDANPATI INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPINS CHANGE COLUMN IDANPINS IDANPINS INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPLOC CHANGE COLUMN IDANPLOC IDANPLOC INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPOPE CHANGE COLUMN IDANPOPE IDANPOPE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPANPPRO CHANGE COLUMN IDANPPRO IDANPPRO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPCLISTA CHANGE COLUMN IDCLISTA IDCLISTA INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPCOMBUSTIVEL CHANGE COLUMN IDCOMBUSTIVEL IDCOMBUSTIVEL INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPCOMISSAO CHANGE COLUMN IDCOMISSAO IDCOMISSAO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPDECRETO CHANGE COLUMN IDDECRETO IDDECRETO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPEDICFG CHANGE COLUMN IDEDICFG IDEDICFG INT(11) NOT NULL AUTO_INCREMENT" )
      ENDIF
      IF nVersaoDBF < 20200801
         :ExecuteCmd( "ALTER TABLE JPEMANFE CHANGE COLUMN IDEMANFE IDEMANFE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "UPDATE JPESTOQUE SET ESPEDIDO = 0 WHERE ESPEDIDO=''" )
         :ExecuteCmd( "ALTER TABLE JPESTOQUE CHANGE COLUMN IDESTOQUE IDESTOQUE INT(11) NOT NULL AUTO_INCREMENT," + ;
            "CHANGE COLUMN ESPEDIDO ESPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPFISICA CHANGE COLUMN IDFISICA IDFISICA INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPIBPT CHANGE COLUMN IDIBPT IDIBPT INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPMOTORI CHANGE COLUMN IDMOTORI IDMOTORI INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPFORPAG" + ;
            " CHANGE COLUMN IDFORPAG IDFORPAG INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN FPDE1 FPDE1 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPATE1 FPATE1 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTMES1 FPQTMES1 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTDIA1 FPQTDIA1  INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDIAFIM1 FPDIAFIM1 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDE2 FPDE2 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPATE2 FPATE2 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTMES2 FPQTMES2 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTDIA2 FPQTDIA2 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDIAFIM2 FPDIAFIM2 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDE3 FPDE3 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPATE3 FPATE3 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTMES3 FPQTMES3 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTDIA3 FPQTDIA3 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDIAFIM3 FPDIAFIM3 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDE4 FPDE4 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPATE4 FPATE4 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTMES4 FPQTMES4 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTDIA4 FPQTDIA4 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDIAFIM4 FPDIAFIM4 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDE5 FPDE5 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPATE5 FPATE5 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTMES5 FPQTMES5 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPQTDIA5 FPQTDIA5 INT(3) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN FPDIAFIM5 FPDIAFIM5 INT(3) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200801
         IF :FieldExists( "NFCADTRA", "JPNOTFIS" )
            :ExecuteCmd( "UPDATE JPNOTFIS SET NFCADTRA = '0' WHERE NFCADTRA = ''" )
            :ExecuteCmd( "ALTER TABLE JPNOTFIS CHANGE COLUMN NFCADTRA NFTRANSP INT(11) NOT NULL DEFAULT '0'" )
         ENDIF
         IF ! :FieldExists( "KKMANIF", "JPNFEKEY" )
            :ExecuteCmd( "ALTER TABLE JPNFEKEY ADD COLUMN KKMANIF CHAR(1) NOT NULL DEFAULT 'N'" )
            :ExecuteCmd( "UPDATE JPNFEKEY SET KKMANIF = 'S' WHERE KKDATEMI < '2020-03-01'" )
         ENDIF
         :ExecuteCmd( "ALTER TABLE JPIMPOSTO CHANGE COLUMN IDIMPOSTO IDIMPOSTO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPLICMOV CHANGE COLUMN IDLICMOV IDLICMOV INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPNFBASE CHANGE COLUMN IDNFBASE IDNFBASE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPNOTFIS" + ;
            " CHANGE COLUMN IDNOTFIS IDNOTFIS INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN NFPEDIDO NFPEDIDO INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN NFQTDVOL NFQTDVOL INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN NFTRANSP NFTRANSP INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPPREHIS CHANGE COLUMN IDPREHIS IDPREHIS INT(11) NOT NULL AUTO_INCREMENT" )
      ENDIF
      IF nVersaoDBF < 20200801
         :ExecuteCmd( "ALTER TABLE JPFINAN CHANGE COLUMN FIPEDIDO FIPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPTRANSA CHANGE COLUMN IDTRANSA IDTRANSA INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPVEICULO" + ;
            " CHANGE COLUMN IDVEICULO IDVEICULO INT(11) NOT NULL AUTO_INCREMENT, " + ;
            " CHANGE COLUMN VEPESO VEPESO INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPACTOT VECAPACTOT INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC1 VECAPAC1 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC2 VECAPAC2 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC3 VECAPAC3 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC4 VECAPAC4 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC5 VECAPAC5 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC6 VECAPAC6 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC7 VECAPAC7 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC8 VECAPAC8 INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN VECAPAC9 VECAPAC9 INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPVENDEDOR CHANGE COLUMN IDVENDEDOR IDVENDEDOR INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPFINAN" + ;
            " CHANGE COLUMN IDFINAN IDFINAN INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN FIPEDIDO FIPEDIDO INT(11) NOT NULL DEFAULT '0' " )
         :ExecuteCmd( "ALTER TABLE JPITPED" + ;
            " CHANGE COLUMN IDITPED IDITPED    INT(11) NOT NULL AUTO_INCREMENT, " + ;
            " CHANGE COLUMN IPPEDIDO IPPEDIDO INT(11) NOT NULL DEFAULT '0'," + ;
            " CHANGE COLUMN IPGARANTIA IPGARANTIA INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200801
         :ExecuteCmd( "ALTER TABLE JPCADASTRO CHANGE COLUMN IDCADASTRO IDCADASTRO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPTRANSP CHANGE COLUMN IDTRANSP IDTRANSP   INT(11) NOT NULL AUTO_INCREMENT" )
      ENDIF
      IF nVersaoDBF < 20200801
         IF :FieldExists( "JPVALTAB", "JPPEDIDO" )
            :ExecuteCmd( "ALTER TABLE JPPEDIDO DROP COLUMN PDVALTAB" )
         ENDIF
         :ExecuteCmd( "UPDATE JPPEDIDO SET PDPEDREL = 0 WHERE PDPEDREL=''" )
         :ExecuteCmd( "ALTER TABLE JPPEDIDO CHANGE COLUMN PDPEDREL PDPEDREL INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPITEM CHANGE COLUMN IDPRODUTO IDPRODUTO INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPITEM" + ;
            " CHANGE COLUMN IDPRODUTO IDPRODUTO  INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN IEGTINQTD IEGTINQTD  INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IEGARCOM IEGARCOM   INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IEGARVEN IEGARVEN   INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IEALTURA IEALTURA   INT(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IELARGURA IELARGURA  Int(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IEPROFUND IEPROFUND  Int(11) NOT NULL DEFAULT '0', " + ;
            " CHANGE COLUMN IEQTDCOM IEQTDCOM   INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPPEDIDO CHANGE COLUMN IDPEDIDO IDPEDIDO   INT(11) NOT NULL AUTO_INCREMENT" )
      ENDIF
      IF nVersaoDBF < 20200801
         :ExecuteCmd( "ALTER TABLE JPCIDADE CHANGE COLUMN IDCIDADE IDCIDADE INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPFISCAL" + ;
            " CHANGE COLUMN IDFISCAL IDFISCAL INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN LFPEDIDO LFPEDIDO INT(11) NOT NULL DEFAULT '0'" )
         :ExecuteCmd( "ALTER TABLE JPPRECO CHANGE COLUMN IDPRECO IDPRECO    INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPMDFCAB CHANGE COLUMN IDMDFCAB IDMDFCAB INT(11) NOT NULL AUTO_INCREMENT" )
         :ExecuteCmd( "ALTER TABLE JPMDFDET" + ;
            " CHANGE COLUMN IDMDFDET IDMDFDET INT(11) NOT NULL AUTO_INCREMENT," + ;
            " CHANGE COLUMN MDPESO MDPESO INT(11) NOT NULL DEFAULT '0'" )
      ENDIF
      IF nVersaoDBF < 20200804
         :ExecuteCmd( "ALTER TABLE JPBANCARIO CHANGE COLUMN BAHIST BAHIST VARCHAR(50) NOT NULL DEFAULT ''" )
      ENDIF
   ENDWITH

   RETURN NIL


Tem muito comando SQL nesse fonte.
Por isso apagando coisa antiga, porque começa a ser arriscado deixar essas mudanças no fonte, por causa de atrasadinhos.
Atualizar só UM cliente atrasado nesta semana, pra apagar tudo anterior a JULHO.

Pois é... a tranferência/uso foi só mais uma entre várias etapas.

Agora começa a fase: o que mais o MySQL pode fazer, que o DBF não podia...
É, por exemplo: juntar registros com linhas separadas de observações, porque podem ficar todas juntas no MySQL.
Alterar isso antes, complicaria pra manter compatibilidade com estrutura de DBFs pra transferir.
Uma vez encerrada a transferência em todos os clientes, acabou a necessidade de manter compatibilidade.

Meu modo de trabalho

MensagemEnviado: 11 Ago 2020 12:31
por JoséQuintas
Ainda rodeando a contabilidade.
Primeiro preparando, tirando o atraso de atualizações.
A primeira coisa: trocando nome de arquivo, nome de campos, e até tipo de campo, bem diferente das conversões anteriores.

Esse é o módulo que eu menos atualizava.
Apenas aproveitando a situação, dar uma geral no aplicativo em DBF, deixando preparado pra depois transferir pra MySQL.

STATIC FUNCTION Update0807( nVersaoDBF )

   JPCTHISTOCreateDbf( nVersaoDBF )

   RETURN NIL

STATIC FUNCTION JPCTHISTOCreateDbf( nVersaoDBF )

   LOCAL aStruList := { ;
     { "IDCTHISTO", "N", 6 }, ;
     { "CHCODIGO", "N", 7 }, ;
     { "CHDESCRI", "C", 250 }, ;
     { "CHINFINC", "C", 80 }, ;
     { "CHINFALT", "C", 80 } }

   IF nVersaoDBF < 20200807
      AAdd( aStruList, { "HIHISPAD", "C", 6 } )
      AAdd( aStruList, { "HIDESCRI", "C", 250 } )
      AAdd( aStruList, { "HIINFINC", "C", 80 } )
      AAdd( aStruList, { "HIINFALT", "C", 80 } )
   ENDIF

   SayScroll( "JPCTHISTO.DBF, verificando atualizações" )

   IF ! ValidaStru( "jpcthisto", aStruList )
      MsgStop( "JPCTHISTO não disponível!" )
      QUIT
   ENDIF
   CLOSE DATABASES
   IF nVersaoDBF >= 20200807
      RETURN NIL
   ENDIF
   IF File( "cthisto.dbf" )
      IF ! ValidaStru( "cthisto", aStruList )
         MsgStop( "CTHISTO não disponível" )
         QUIT
      ENDIF
      IF ! UseSoDbf( "jpcthisto", .T. )
         QUIT
      ENDIF
      IF ! UseSoDbf( "cthisto", .T. )
         QUIT
      ENDIF
      SELECT cthisto
      GOTO TOP
      DO WHILE ! Eof()
         SELECT jpcthisto
         RecAppend()
         REPLACE ;
            jpcthisto->idctHisto WITH Int( Val( cthisto->hiHisPad ) / 10 ), ;
            jpcthisto->chCodigo  WITH Val( cthisto->hiHisPad ), ;
            jpcthisto->chDescri  WITH cthisto->hiDescri, ;
            jpcthisto->chInfInc  WITH cthisto->hiInfInc, ;
            jpcthisto->chInfAlt  WITH cthisto->hiInfAlt
         SELECT cthisto
         RecLock()
         DELETE
         RecUnlock()
         SKIP
      ENDDO
      CLOSE DATABASES
      fErase( "cthisto.dbf" )
   ENDIF
   IF ! UseSoDbf( "jpcthisto", .T. )
      RETURN NIL
   ENDIF
   DO WHILE ! Eof()
      RecLock()
      IF Empty( jpcthisto->idCtHisto )
         REPLACE jpcthisto->idctHisto WITH Int( jpcthisto->chCodigo / 10 )
      ENDIF
      IF Empty( jpcthisto->chCodigo )
         REPLACE jpcthisto->chCodigo WITH Val( jpcthisto->hiHisPad )
      ENDIF
      IF Empty( jpcthisto->chDescri )
         REPLACE jpcthisto->chDescri WITH jpcthisto->hiDescri
      ENDIF
      IF Empty( jpcthisto->chInfInc )
         REPLACE jpcthisto->chInfInc WITH jpcthisto->hiInfInc
      ENDIF
      IF Empty( jpcthisto->chInfAlt )
         REPLACE jpcthisto->chInfAlt WITH jpcthisto->hiInfAlt
      ENDIF
      IF jpcthisto->idctHisto == 0
         DELETE
      ENDIF
      SKIP
   ENDDO
   CLOSE DATABASES

   RETURN JPCTHISTOCreateDBF( 20200807 )


Pensando em aproveitar o incremental do MySQL, e gravar um código alternativo com o incremental + dígito de controle
Com certeza isso vai exigir a chave primária no incremental e mais um índice secundário pelo código com dígito de controle, mas tudo bem.
Pois é... por enquanto atualizar o DBF, mas pensando no que vai ser bom para o MySQL depois.

Por enquanto, na minha máquina, essa conversão roda toda vez que entro no aplicativo.
É que é fase de testes, confirmando o comportamento das mudanças do aplicativo, e pensando se altero algo mais.

Seria péssimo mudar de idéia com isso já instalado no cliente, porque complicaria conversões de conversões...

Meu modo de trabalho

MensagemEnviado: 12 Ago 2020 23:15
por JoséQuintas
Só pra constar:

Agora só mexendo com contabilidade, ainda em DBF.

Todo restante funcionando em MySQL sem nenhum problema.

Meu modo de trabalho

MensagemEnviado: 14 Ago 2020 17:05
por JoséQuintas
Voltei à situação de 07/08, parece.... parece que só perdi toda conversão da contabilidade.

Meu modo de trabalho

MensagemEnviado: 16 Ago 2020 14:00
por JoséQuintas
Estou revisando minha atualização de arquivos.
Acho que no passar dos anos, piorei ao invés de melhorar.

O que acontece: temos que considerar 4 situações

Situação 1:
Quando temos campos novos, precisamos dos campos novos e dos antigos ao mesmo tempo
Ok com conversão de arquivo por vez

Situação 2:
Terminada conversão, não precisa mais dos campos antigos, precisamos eliminar os antigos
Ok com conversão de um arquivo por vez

Situação 3:
A conversão pode incluir transferência pra MySQL, neste caso, precisamos eliminar os antigos ANTES de terminar toda conversão
Ok com conversão de um arquivo por vez

Situação 4:
Justamente isso tudo junto, que pode significar parcialmente convertido
Deu algum problema, e precisa recomeçar... lá vém problemas, porque não tem mais o que tinha antes em cada arquivo, só vai ter pra onde parou.

O que eu fazia antes:

Quando era só DBF... eu rodava tudo duas vezes: uma acrescentando em tudo, e depois outra removendo em tudo
Normal... se correu tudo bem com a adição, a remoção não é problema.

Mas... entrou o SQL.
A transferência do MySQL precisa do DBF pronto.
Acabei alterando pra fazer um DBF por vez, tudo que tiver que fazer.

Mas isso tem um risco: se der problema na conversão.... ferrou... porque não tem mais a situação anterior.
Ok, tem o backup, mas para o usuário complica, além do que, se a conversão tem problema, só retornar o backup não resolve.

Os DBFs estão acabando, mas... o contabil vai precisar de conversão ativa por alguns anos.... então vou repensar.

Por enquanto, uma coisa que pensei foi salvar a situação atual de conversão.
E como salvar a conversão parcial?
Se eu tiver as conversões por data.... salvaria a data como sendo data atual.

Exemplo:

Versão atual 2020-08-16, versão dos DBFs 2019-01-01

Ao rodar uma conversão de 2019-02-01, gravaria como versão 2019-02-01
Se der problema ao carregar o EXE, as versões anteriores a 2019-02-01 já não seriam feitas.
Normal, já teriam sido feitas antes, bastaria converter a partir daí.

O lado ruim é que obrigatoriamente iria fazer as conversões na ordem definida.
Às vezes, com o tempo, mais rápido fazer várias de uma vez do que uma de cada vez.
Exemplo: 10 atualizações adicionando e removendo campo. Mais rápido acrescentar todos e remover todos no final, pra fazer logo tudo de uma vez.

Mas... DBF tá acabando? tá sim, mas MySQL também vai precisar, e o contábil vai precisar disso por tempo indeterminado.
Ainda pensando na idéia.
Senão... é recorrer ao backup, tudo bem, o aplicativo obrigatoriamente faz backup.

Agora lembrei porque uso versão de DBF, além da versão de EXE.
É que ao trocar versão de DBF obrigatoriamente faço backup.
Não dá pra fazer isso ao trocar versão de EXE, porque EXE posso trocar várias vezes, sem mexer nos DBFs, e obrigar backup na na troca de EXE seria muita perda de tempo.

Meu modo de trabalho

MensagemEnviado: 16 Ago 2020 14:18
por JoséQuintas
JoséQuintas escreveu:Agora lembrei porque uso versão de DBF, além da versão de EXE.
É que ao trocar versão de DBF obrigatoriamente faço backup.
Não dá pra fazer isso ao trocar versão de EXE, porque EXE posso trocar várias vezes, sem mexer nos DBFs, e obrigar backup na na troca de EXE seria muita perda de tempo.


Ao postar isso me veio outra idéia:
Porque salvar versão de DBF?
Basta a versão do EXE mesmo.

Quem vai precisar estruturas novas é o EXE, pronto, resolvido sobre atualizar.

E o backup?
IF AppVersaoEXEAnt() < '2020.08.16.1437' // versão que mudou estrutura

Só preciso ter no EXE a data em que ocorreu mudança de estrutura, pra forçar o backup.
No cliente, é só manter salva a data do último EXE usado.
Legal, isso pode servir até pra obrigar a atualizar o EXE, coisa que vou acabar precisando fazer, no caso de MySQL remoto.

Pensar mais sobre isso.

Nota: Isso tem a ver com atualização, mas não resolve a questão anterior, apenas elimina a necessidade de salvar um campo a mais.

Meu modo de trabalho

MensagemEnviado: 19 Ago 2020 13:38
por JoséQuintas
Bom... acho que consegui voltar à posição antes da perda de fontes.

Talvez interessante relatar aqui, porque como eu já disse, contabilidade vou primeiro tratar os DBFs antes de começar com MySQL pra ela.

Alterei TUDO, nome de arquivo, nome de campos, e tipo de alguns campos.

No plano de contas tem lá:

código normal: o código tradicional 1.01.01.001.0001-1
código reduzido: o código que vou usar no MySQL como incremental 9999999

E na gravação de lançamentos, só uso o código normal, mesmo permitindo ao usuário usar o código reduzido.
Vou inverter isso: fazer pelo reduzido.

Como sempre, vou fazer passo a passo.

1) Minha primeira alteração vai ser: gravar os dois, o normal e o reduzido.
Neste momento, vão existir registros com e sem código reduzido, mas todos com o código normal.
Tudo bem, tudo continua funcionando, gravar um campo a mais não dá problema com nada.
E vou conferindo se tudo ok.

2) A partir do momento que isso estiver ok, gravação ok, crio a conversão para informações antigas, que estão sem código reduzido.
Neste momento, a base fica correta, tudo preenchido.

3) Com tudo preenchido, começo a alterar os fontes pra se basearem no código reduzido, ao invés do código normal.
Neste momento, tudo ok também, se tá tudo gravado, tanto faz de um ou de outro

4) Uma vez não precisando mais do código normal, posso eliminar isso de vez dos lançamentos.
Lembrando que, se eu precisar do código normal é só pesquisar no plano de contas.
Neste momento, deixa de existir o campo

Então... estou fazendo estas coisas EM DBF.
TALVEZ tenha rotina que em DBF seja melhor manter os dois códigos, pra velocidade.
Sem problemas.... só vou eliminar o campo no final, então vai dar pra tirar essa dúvida durante as alterações.

As coisas estão indo em frente, e contabilidade vai ficar em MySQL também, é isso que importa.

Meu modo de trabalho

MensagemEnviado: 19 Ago 2020 13:58
por JoséQuintas
Não recuperei tudo ainda.
Os fontes não estão usando a estrutura nova do plano de contas.

Não se enganem, tenho que alterar um campo de cada vez em todos os fontes.
Nesse ponto é importante eu ter usado nomes únicos.

Faço primeiro com replace all, para os campos que continuam caractere ou numérico.
Depois, tenho que ver um a um, para os campos que mudaram de caractere pra numérico.

E não é só isso: o campo pode ser atribuído a uma variável, então, em fonte onde isso acontece, tenho que revisar essa variável também.

Esse sim, vai dar erro pra quem atualizar agora, e praticamente em tudo da contabilidade.

Fazer o que.... mágica não existe... já tinha feito isso, fazer tudo de novo, por ter perdido fontes.

Ainda bem que o pessoal da contabilidade não costuma atualizar.....

Meu modo de trabalho

MensagemEnviado: 19 Ago 2020 14:31
por JoséQuintas
Antes que perguntem:

Mas... e salvar no git/github não resolveu? teria resolvido se eu tivesse salvo kkkkk
O plano de contas é aquele arquivo... que ficou com quase 400 campos....
Pois é... a gente só sente falta do backup quando perde....

Só correção: não perdi fontes, perdi alterações em fontes.

O que perdi?
Não sei. acho que só alterações na contabilidade.
Com certeza, se perdi algo mais, alguém vai reclamar e faço de novo.
Ainda bem que salvei tudo antes de começar a mexer na contabilidade... senão... vixe...

Aproveitando.....
A vantagem de deixar fontes padronizados, ou pensar em como localizar mais fácil

Buscando a_tipo

busca1.png


buscando ->a_tipo, afinal, é um campo de arquivo

busca2.png


Primeiro pesquisar o que é certeza, já corrigir, e depois ver o que sobra.
Ganha-se muito tempo com isso, porque o primeiro já é certo, e a parte duvidosa deixa pra depois, já servindo pra conferir o resto.
Parece uma coisa meio idiota, de tão simples, mas a ajuda por estar padronizado é poderosa.
Pode transformar horas de conferência/alteração, em apenas alguns minutos.

Fazer tudo de uma vez, um fonte de cada vez, é perder mais tempo, e ter mais chances de errar.
Nesse caso é melhor fazer um campo de cada vez, em todos os fontes.

Meu modo de trabalho

MensagemEnviado: 19 Ago 2020 14:58
por JoséQuintas
Uma coisa interessante que uso:

Lembram? plano de contas tem 96 meses

Saldo anterior
a_Deb01, a_Cre01
a_Deb02, a_Cre02
etc.

Como calcular o saldo anterior ou atual de um determinado mes?

Que tal isto?
METHOD SaldoAnterior( nNumMes ) CLASS CTCONTAClass

   LOCAL nValor

   nValor := ::SaldoAtual( nNumMes - 1 )

   RETURN nValor


Aqui já cheguei a usar diferente, parecido com o anterior, mas agora é for/next mesmo.

METHOD SaldoAtual( nNumMes ) CLASS CTCONTAClass

   LOCAL nValor, nCont

   nNumMes := Min( Max( nNumMes, 0 ), CONTABIL_MESMAX )
   nValor := jpctconta->plSdAnt
   FOR nCont = 1 TO nNumMes
      nValor += ::SaldoDebito( nCont ) - ::SaldoCredito( nCont )
   NEXT

   RETURN nValor


e o movimento do mes

METHOD SaldoDebito( nNumMes ) CLASS CTCONTAClass

   LOCAL nValor := 0

   DO CASE
   CASE nNumMes < 1 .OR. nNumMes > CONTABIL_MESMAX
      nValor := 0
   OTHERWISE
      nValor := jpctconta->( FieldGet( FieldPos( "A_DEB" + StrZero( nNumMes, 2 ) ) ) )
   ENDCASE

   RETURN nValor


Retirei a parte de conversão em dolar pra postar aqui, pra não complicar.

Em Clipper cheguei a usar com recursividade mesmo, e com os nomes dos campos pra ganhar agilidade, sem macro.

Algo do tipo:
FUNCTION DebitoMes( nMes )
   DO CASE
   CASE nMes < 1; RETURN 0
   CASE nMes == 1; RETURN conta->A_DEB01
   CASE nMes == 2; RETURN conta->A_DEB02
   ....
ENDCASE
RETURN 0


FUNCTION SaldoMes( nMes )
   IF nMes == 0
      RETURN conta->a_SdAnt
   ENDIF
   RETURN SaldoMes( nMes - 1 ) + DebitoMes( nMes ) - CreditoMes( nMes )


Notem que acaba sendo SEM o uso de macro, o que deixou muito mais rápido.
Não lembro se comparei usando for/next.

Já comentei por aqui: por ter testado JOINER, um compilador brasileiro parecido com Clipper, que NÃO tinha macro, acabou que comecei a evitar macros.
A gente sempre encontra uma saída, e fica mais rápido.

Meu modo de trabalho

MensagemEnviado: 19 Ago 2020 15:24
por JoséQuintas
Uma coisa interessante....

Contabilidade poderia ser fechamento cada 1 mes, 2 meses, 3, 4, 6 ou 12

Como fica o cálculo de uma despesa de julho, sendo que cada fechamento zera isso?

Que tal assim:

nMes         := 7 // mes atual, exemplo
nFecha     := 6  // mes fechamento, exemplo
nMesFecha := Round( ( nMes - 1  ) / nFecha ) * nFecha // resultado desta conta = 6, semestral fechou mes 6
nSaldo := Saldo( nMes ) - Saldo( nFecha )


Isso vale pra qualquer um dos 96 meses.
Simples e prático, encerramento de exercício automático, sem precisar interferência do usuário.
E tanto faz qual é o tipo de encerramento.
Qualquer saldo, de qualquer mês, em qualquer situação.
Pra geração de SPED Contábil... maravilha... fácil obter saldo acumulado, encerrado, não encerrado, etc.

Mas é interessante o que uma fórmula ou uma função podem fazer....

É bom pra chamar a atenção em outra coisa:
As linguagens de programação não vém com rotinas prontas pra facilitar tudo que existe.
Cabe a nós criar funções pra facilitar nossa vida.

Isso é mais importante ainda pra LIB gráfica/GUI.
Criar funções direcionadas ao nosso uso ajuda muuuuuito.

Meu modo de trabalho

MensagemEnviado: 21 Ago 2020 23:50
por JoséQuintas
Acabei me contradizendo quanto ao uso do MySQL.

mysql.png


O primeiro uso foi nesse cliente que hoje ocupa 1.2GB, com tabelas acima de 1 milhão de registros.
Aplicativo particular desse cliente, era só pra tomar conta do aplicativo Clipper SUMMER, mas pelo volume de informações, e por ser isolado de todo resto, acabei usando Harbour e foi onde comecei a usar MySQL.
NÃO está totalmente em MySQL, o problema maior é alterar toda a lógica primeiro.

TODO restante é meu aplicativo. Aquele cliente que menciono, que sobrou 2MB em DBF, é esse de tamanho maior de 4.1GB

Tem usuário aqui com arquivos menores, mas tem usuário com arquivos maiores.
Deixei de achar que meus arquivos são grandes, quando mexi em aplicativo Clipper de terceiros, usando ADS Server, com arquivos de mais de 4 milhões de registros, referente a 28 filiais integradas. O backup precisou de alguns DVDs.
Se estão achando esses tamanhos grandes demais, saibam que não são, existem muito maiores por aí.

Duran