Página 32 de 35

Gerar XML

Enviado: 03 Out 2021 17:11
por JoséQuintas
Só de curiosidade...
Só estou pegando o fonte que já existe em Harbour, e transformando em SQL, ajustando necessidades.

Código: Selecionar todos

STATIC FUNCTION NfeBlocoProdutoIcms00( cXml, cnSQL )

   WITH OBJECT cnSQL
      cXml += [<ICMS00>]
      cXml += XmlTag( "orig",  Substr( :String( "IPICMCST" ), 1, 1 ) )
      cXml += XmlTag( "CST",   Substr( :String( "IPICMCST" ), 2, 2 ) )
      cXml += XmlTag( "modBC", "3" ) // base valor da operacao
      cXml += XmlTag( "vBC",   :Number( "IPICMBAS" ), 2 )
      cXml += XmlTag( "pICMS", :Number( "IPICMALI" ), 2 )
      cXml += XmlTag( "vICMS", :Number( "IPICMVAL" ), 2 )
      cXml += [</ICMS00>]
   ENDWITH

   RETURN Nil
No fonte Harbour recebo o recordset ADO.
No fonte SQL, pego diretamente da tabela.

Já poderia acabar com a função do Harbour, substituindo aqui:

Código: Selecionar todos

STATIC FUNCTION NfeBlocoProdutoIcms( cXml, cnSQL, nTotDifValf, nTotRemVal, nTotDesVal )

   LOCAL nCst

   nCst := Val( Substr( :String( "IPICMCST" ), 2 ) )

   WITH OBJECT cnSQL
      cXml += [<ICMS>]
      DO CASE
      CASE nCst == 0;  NfeBlocoProdutoIcms00( @cXml, cnSQL )
Apenas comentário:

Já poderia trocar a função Harbour pela função do SQL.
Com isso, vou migrando a conversão aos poucos, podendo conferir/colocar em prática um bloco de cada vez.

Acho que se fizer tudo numa única função SQL vai ser difícil conferir no caso de erro, seriam trocentas variáveis também.
Um bloco de cada vez, trata poucas variáveis, e facilita.

Talvez... apenas talvez... possa ser usado um BEGIN/END pra cada bloco, permitindo juntar tudo numa única function, e cada BEGIN/END tratando de suas variáveis.
NÃO dá pra colocar function dentro de function, mas dá pra usar IF/CASE, e um BEGIN/END em cada um, com suas próprias variáveis.
Tipo... um BEGIN/END pra IPI com as variáveis do IPI, um BEGIN/END pra ICMS com as variáveis do ICMS, etc.

O tempo vai dizer o que facilita mais...

Gerar XML

Enviado: 03 Out 2021 17:23
por JoséQuintas
Criei mais um método na classe ADO:

Código: Selecionar todos

METHOD ReturnFunction( ... ) CLASS ADOClass

   LOCAL cSQL, aItem, aList := hb_AParams()

   cSQL := aList[ 1 ]
   hb_ADel( aList, 1, .T. )

   cSQL += "("
   FOR EACH aItem IN aList
      cSQL += ValueSQL( aItem )
      cSQL += iif( aItem:__ENumIsLast(), "", "," )
   NEXT
   cSQL += ")"
   ::ReturnSelect( cSQL )

   RETURN Nil
Pra poder usar, por exemplo:

Código: Selecionar todos

WITH OBJECT cnSQL
   cXml += :ReturnFunction( "ze_Xml", 1, 2, 3 )
   cXml += :ReturnFunction( "ze_Xml", 4, 5, 6 )
   cXml += :ReturnFunction( "ze_Xml", 7, 8, 9 )
ENDWITH
Nesta semana também criei classes pra facilitar incluir centenas/milhares de registros por vez.
É a diferença entre demorar 20 minutos ou alguns segundos pra incluir 100.000 registros.

Gerar XML

Enviado: 04 Out 2021 01:10
por JoséQuintas
Não sei porque, mas a rotina falha de vez em quando.
Por mais que tenha feito tratamento pra não acontecer NULL, ele acontece.
A pior parte: chamando fora do PRG, funciona normalmente, aonde de outra forma falhou.

Gerar XML

Enviado: 04 Out 2021 19:58
por JoséQuintas
o último deu mais trabalho

Código: Selecionar todos

ELSEIF cCst = '900' THEN

   SET cXml := CONCAT( '<ICMSSN900>',
      ze_XmlTag( 'orig',   cOrigem ),
      ze_XmlTag( 'CSOSN',  cCst ),
      IF( nIcmVal = 0, '', ze_XmlTag( 'modBC', '3' ) ),
      IF( nIcmVal = 0 OR nIcmBas = 0, '', ze_XmlTag( 'vBC', CONCAT( nIcmBas, '' ) ) ),
      IF( nIcmVal = 0 OR nIcmRed = 0, '', ze_XmlTag( 'pRedBC', CONCAT( nIcmRed, '' ) ) ),
      IF( nIcmVal = 0 OR nIcmAli = 0, '', ze_XmlTag( 'pICMS', CONCAT( nIcmAli, '' ) ) ),
      IF( nIcmVal = 0, '', ze_XmlTag( 'vICMS', CONCAT( nIcmVal, '' ) ) ),
      IF( nSubVal = 0, '', ze_XmlTag( 'modBCST', '4' ) ),
      IF( nSubVal = 0 OR nSubIva = 0, '', ze_XmlTag( 'pMVAST', CONCAT( nSubIva, '' ) ) ),
      IF( nSubVal = 0 OR nSubRed = 0, '', ze_XmlTag( 'pRedBCST', CONCAT( nSubRed, '' ) ) ),
      IF( nSubVal = 0 OR nSubBas = 0, '', ze_XmlTag( 'vBCST', CONCAT( nSubBas, '' ) ) ),
      IF( nSubVal = 0 OR nSubAli = 0, '', ze_XmlTag( 'pICMSST', CONCAT( nSubAli, '' ) ) ),
      IF( nSubVal = 0, '', ze_XmlTag( 'vICMSST', CONCAT( nSubVal, '' ) ) ),
      IF( nIcsVal = 0, '', ze_XmlTag( 'pCredSN',     CONCAT( nIcsAli, '' ) ) ),
      IF( nIcsVal = 0, '', ze_XmlTag( 'vCredICMSSN', CONCAT( nIcsVal, '' ) ) ),
      '</ICMSSN900>' );

END IF;

Gerar XML

Enviado: 04 Out 2021 20:03
por JoséQuintas
Agora que percebi...

Minha funçãozinha de criar XML dos tempos do Clipper, passou pelo Harbour, e agora tá no SQL !!!

Pra que complicar kkkk

Quase terminando o bloco completo de ICMS, com todas as variações...
Já dá pra apagar da linha 558 até 861, mais de 300 linhas do fonte Harbour. (Em MySQL a function ficou com 258 linhas de ponta a ponta )
Esperar um teste prático disso primeiro.

Gerar XML

Enviado: 04 Out 2021 20:11
por JoséQuintas
Alguém já tinha feito isso?
Ou sou o primeiro a pensar em fazer isso?

Da mesma forma que no Clipper/Harbour, é relativamente simples, sempre a mesma coisa, mas com certeza dá trabalho, porque tem muita informação.

Meu modo de trabalho

Enviado: 05 Out 2021 11:21
por JoséQuintas
nfeantes.png
nfedepois.png
O fonte está perdendo a posição de ser um dos maiores fontes do aplicativo.

Meu modo de trabalho

Enviado: 06 Out 2021 10:00
por JoséQuintas
Autorizei as primeiras notas hoje, usando rotinas do SQL !!!
Foi de primeira.

Código: Selecionar todos

05/10/2021  11:11               118 ze_XmlNfeProdutoArmamento.sql
05/10/2021  12:59             1.670 ze_XmlNfeProdutoCofins.sql
05/10/2021  11:13               845 ze_XmlNfeProdutoCombustivel.sql
05/10/2021  09:19            11.712 ze_XmlNfeProdutoIcms.sql
05/10/2021  11:12               109 ze_XmlNfeProdutoII.sql
05/10/2021  11:12               116 ze_XmlNfeProdutoImporta.sql
05/10/2021  09:19             1.158 ze_XmlNfeProdutoipi.sql
05/10/2021  11:12               209 ze_XmlNfeProdutoISS.sql
05/10/2021  11:12               120 ze_XmlNfeProdutoMedicamento.sql
05/10/2021  12:53             1.619 ze_XmlNfeProdutoPis.sql
05/10/2021  11:12               116 ze_XmlNfeProdutoVeiculo.sql
09/09/2021  17:17               740 ze_XmlNode.sql
03/10/2021  23:45               332 ze_XmlTag.sql

Meu modo de trabalho

Enviado: 06 Out 2021 17:57
por JoséQuintas
Uma curiosidade, do meu uso de SQL, de antes e agora.

Código: Selecionar todos

      :cSQL := "SELECT NFNOTFIS, JPCADASTRO.CDNOME AS NOME, JPCADASTRO.CDCNPJ AS CNPJ," + ;
         " JPCADASTRO.CDINSEST AS INSEST, JPCADASTRO.CDENDERECO AS ENDERECO," + ;
         " JPCADASTRO.CDNUMERO AS NUMERO, JPCADASTRO.CDCOMPL AS COMPL," + ;
         " JPCADASTRO.CDBAIRRO AS BAIRRO, JPCADASTRO.CDCIDADE AS CIDADE," + ;
         " JPCADASTRO.CDUF AS UF, JPCADASTRO.CDTELEFONE AS TELEFONE," + ;
         " JPCADASTRO.CDCEP AS CEP" + ;
         " FROM JPNOTFIS" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPNOTFIS.NFCADASTRO" + ;
         " WHERE JPNOTFIS.IDNOTFIS = " + NumberSQL( nIdNotFis )
Alteração 1: Como meus campos são únicos, não uso mais o "alias"
Alteração 2: Fazer um join só por causa de um código.... chato demais, e obriga a colocar pelo menos um campo da tabela de notas
Alteração 3: pra Stored Function

Código: Selecionar todos

SELECT CDNOME, CDCNPJ, CDINSEST, CDENDERECO, CDNUMERO, CDCOMPL,
CDBAIRRO, CDCIDADE, CDUF, CDTELEFONE, CDCEP
FROM JPCADASTRO
WHERE IDCADASTRO = ( SELECT NFCADASTRO FROM JPNOTFIS WHERE IDNOTFIS = nIdNotFis )
INTO cNome, cCnpj, cInsEst, cEndereco, cNumero, cCompl, cBairro, cCidade, cUF, cTelefone, cCep;
Bem mais legível, mesmo se não fosse stored function

Como procurei deixar as rotinas o mais independentes possíveis, no que se refere a informação/variáveis, passar pra SQL está relativamente prático.
Estou aproveitando pra revisar/atualizar meu uso de SQL.

Meu modo de trabalho

Enviado: 07 Out 2021 15:33
por JoséQuintas
O bloco de informações adicionais vai dar trabalho, muita informação.
Só começo dele:

Código: Selecionar todos

STATIC FUNCTION NfeBlocoInfAdicionais( cXml, nIdNotFis, mInfAdicContingencia )

   LOCAL mInfAdic, mInfAdicTmp, mLei
   LOCAL cnSQL  := ADOLocal()
   LOCAL cnSQL2 := ADOLocal()

   WITH OBJECT cnSQL
      :cSQL := "SELECT LPAD( NFPEDIDO, 6, '0' ) AS PEDIDO, NFLEIS, NFICSBAS, NFICSALI, NFICSVAL," + ;
         " NFSUBBAS, NFCADASTRO, NFCFOP, NFOBS," + ;
         " JPTABFINPOR.FINPORNOME, SUM( IPDIFVALF ) AS DIFVALF, SUM( IPDIFVALI ) AS DIFVALI," + ;
         " JPPEDIDO.PDPEDREL, JPPEDIDO.PDPEDCLI, JPPEDIDO.PDCONTATO, " + ;
         " TRIM( CONCAT_WS( ' ', JPCADASTRO.CDCEPENT, JPCADASTRO.CDENDENT, JPCADASTRO.CDNUMENT, " + ;
         " JPCADASTRO.CDCOMENT, JPCADASTRO.CDBAIENT, JPCADASTRO.CDCIDENT," + ;
         " JPCADASTRO.CDUFENT ) ) AS ENDENT," + ;
         " IF( JPCADASTRO.CDENDERECO = JPCADASTRO.CDENDCOB,'',TRIM( CONCAT_WS( ' ', JPCADASTRO.CDENDCOB, " + ;
         " JPCADASTRO.CDNUMCOB, JPCADASTRO.CDCOMCOB, JPCADASTRO.CDBAICOB, " + ;
         " JPCADASTRO.CDCIDCOB, JPCADASTRO.CDUFCOB ) ) ) AS ENDCOB," + ;
         " JPCADASTRO.CDMAPA" + ;
         " FROM JPNOTFIS" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPNOTFIS.NFPEDIDO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPNOTFIS.NFCADASTRO" + ;
         " LEFT JOIN JPTABFINPOR ON JPTABFINPOR.IDFINPOR = JPCADASTRO.CDPORTADOR" + ;
         " LEFT JOIN JPITPED ON IPPEDIDO = IDPEDIDO" + ;
         " WHERE IDNOTFIS=" + NumberSQL( nIdNotFis )
      :Execute()
Vou fazer por partes, então, criei a function, inicialmente vazia:

Código: Selecionar todos

CREATE FUNCTION ze_XmlNfeInfAdic( nIdNotFis INT(11) )
RETURNS VARCHAR(1000)

BEGIN

DECLARE cXml VARCHAR(1000) DEFAULT '';

RETURN cXml;

END
E o fonte atual em Harbour, passa a começar assim:

Código: Selecionar todos

STATIC FUNCTION NfeBlocoInfAdicionais( cXml, nIdNotFis, mInfAdicContingencia )

   LOCAL mInfAdic, mInfAdicTmp, mLei
   LOCAL cnSQL  := ADOLocal()
   LOCAL cnSQL2 := ADOLocal()

   WITH OBJECT cnSQL
   mInfAdic := :ReturnFunction( "ze_XmlNfeInfAdic", nIdNotFis )
Agora vou transferindo uma parte de cada vez das informações adicionais para o SQL, até terminar.
E vou fazendo uso de tudo, mesmo incompleto, uma parte sendo feita em SQL e outra no Harbour.

Meu modo de trabalho

Enviado: 07 Out 2021 17:05
por JoséQuintas
nfleis.png
Uia....
A lista de números vira tudo um texto, usando o próprio SQL !!!

Tava aqui pensando...
Tantos preocupados com a linguagem de programação do futuro.... que não viram isso do passado kkkkk

E se tudo isso existe há mais de 20 anos.... estou moderno ou estou antiquado? kkkkk

Só sei que estou gostando do resultado, e tá tudo funcionando até agora.

Nota: eu já disse que observação pra nota de combustível parece uma novela.... é texto que não acaba mais...
Por enquanto aí só tem uma parte.

Meu modo de trabalho

Enviado: 07 Out 2021 18:30
por JoséQuintas
Vai ficar muito grande, por isso fazendo por partes.

Código: Selecionar todos

CREATE FUNCTION ze_XmlNfeInfAdic( nIdNotFis INT(11) )
RETURNS VARCHAR(1000)

BEGIN

DECLARE cXml VARCHAR(10000) DEFAULT '';

/* número do pedido */

BEGIN

   DECLARE nIdPedido INT(11);

   SET nIdPedido := ( SELECT COALESCE( NFPEDIDO, 0 ) FROM JPNOTFIS WHERE IDNOTFIS = nIdNotFis );

   IF nIdPedido != 0 THEN
      SET cXml := CONCAT( cXml, 'PEDIDO ', nIdPedido );
   END IF;

END;


/* texto de lei */

BEGIN

   DECLARE cTxtDecreto VARCHAR(1000);
   DECLARE nCursorEOF INT(11) DEFAULT 0;
   DECLARE SP_CURSOR CURSOR FOR
      SELECT
      DETEXTO
      FROM JPTABDECRETO
      WHERE INSTR(
         ( SELECT NFLEIS FROM JPNOTFIS WHERE IDNOTFIS = nIdNotFis ),
         LPAD( IDDECRETO, 6, '0' ) ) != 0;
   DECLARE CONTINUE HANDLER FOR NOT FOUND SET nCursorEOF = 1;

   OPEN SP_CURSOR;

   THIS:WHILE nCursorEof != 1 DO
      FETCH SP_CURSOR INTO cTxtDecreto;
      IF INSTR( cXml, cTxtDecreto ) = 0 THEN
         SET cXml := CONCAT( cXml, IF( LENGTH( cXml ) = 0, '', ' ' ), cTxtDecreto );
      END IF;
   END WHILE;

   CLOSE SP_CURSOR;

END;

RETURN cXml;

END
Separando em blocos de BEGIN/END, assim separo as variáveis envolvidas em cada bloco.

Pelo que entendi da documentação do MySQL, é como ter procedures dentro de procedures, e as variáveis ficam visíveis em cada bloco.
Ou.... pra ficar no que conhecemos.... é como cada BEGIN/END ter suas variáveis LOCAIS.
E a variável cXML é PRIVATE, conhecida em todos os BEGIN/END.

Acho melhor do que declarar um porrilhão de variáveis no início, e perder o controle sobre o que está sendo usado ou não.
Colocando em cada bloco o que cada um usa, fica melhor de visualizar.
Achei que isso ajuda mais, se é certo ou errado, se gasta memória ou não, não faço idéia.

Por enquanto é fazer, isso de economizar memória ou tempo fica pra depois.
infadic.png
Agora tem o número do pedido, além das outras mensagens.
E assim vai indo a conversão das informações adicionais do XML.

INFELIZMENTE... corre o risco de ter caracteres inválidos, que hoje trato no Harbour, mas vamos em frente, e veremos o que acontece.
NÃO estou pensando nisso mas.... de repente... dá pra pegar em UTF-8 diretamente do MySQL !!!!

Meu modo de trabalho

Enviado: 07 Out 2021 20:05
por JoséQuintas
Antes, enquanto eu processava os produtos, eu controlava se existia CST 60, 10 e 70, pra acrescentar observação.
Vejam se não ficou mais simples.

Código: Selecionar todos

/* CST 60 */

BEGIN

   DECLARE cTmp VARCHAR(300);

   IF ( SELECT COUNT(*) FROM JPITPED WHERE SUBSTR( IPICMCST, 2 ) = '60'
      AND IPPEDIDO = ( SELECT NFPEDIDO FROM JPNOTFIS WHERE IDNOTFIS = nIdNotFis ) ) != 0 THEN
      
      SET cTmp := CONCAT( 'CST 60: RECOLHIMENTO DO ICMS POR SUBSTITUICAO TRIBUTARIA',
         ' ARTIGO 313 DO RICMS/SP. O DESTINATARIO DEVERA ESCRITURAR O DOCUMENTO FISCAL',
         ' NOS TERMOS DO ARTIGO 278 DO RICMS.' );
         
      IF INSTR( cXml, cTmp ) = 0 THEN
         SET cXml := TRIM( CONCAT( cXml, ' ', cTmp ) );
      END IF;
   END IF;

END;
Se existir algum produto com CST 60... vai a observação.
Não depende mais de nenhum processamento anterior.

Só comentário:

Não posso converter o bloco de XML do produto, sem antes eliminar as dependências dele.
Por isso estou convertendo informações adicionais antes de converter o resto do bloco de produtos.
Essa parte, por exemplo, já elimina uma dependência.
Quando o bloco de produtos estiver "limpo", sem fornecer informação pra outros blocos, aí sim ele pode ser convertido.

Meu modo de trabalho

Enviado: 07 Out 2021 20:23
por JoséQuintas
Mais dependências:

Código: Selecionar todos

STATIC FUNCTION NfeBlocoInfAdicionais( cXml, nIdNotFis, mInfAdicContingencia )

   LOCAL mInfAdic, mInfAdicTmp, mLei
   LOCAL cnSQL  := ADOLocal()
   LOCAL cnSQL2 := ADOLocal()

   WITH OBJECT cnSQL
      mInfAdic := :ReturnFunction( "ze_XmlNfeInfAdicionais", nIdNotFis )
      :cSQL := "SELECT LPAD( NFPEDIDO, 6, '0' ) AS PEDIDO," + ;
         " NFSUBBAS, NFCADASTRO, NFCFOP, NFOBS," + ;
         " JPTABFINPOR.FINPORNOME, " + ;
         " JPPEDIDO.PDPEDREL, JPPEDIDO.PDPEDCLI, JPPEDIDO.PDCONTATO, " + ;
         " TRIM( CONCAT_WS( ' ', JPCADASTRO.CDCEPENT, JPCADASTRO.CDENDENT, JPCADASTRO.CDNUMENT, " + ;
         " JPCADASTRO.CDCOMENT, JPCADASTRO.CDBAIENT, JPCADASTRO.CDCIDENT," + ;
         " JPCADASTRO.CDUFENT ) ) AS ENDENT," + ;
         " IF( JPCADASTRO.CDENDERECO = JPCADASTRO.CDENDCOB,'',TRIM( CONCAT_WS( ' ', JPCADASTRO.CDENDCOB, " + ;
         " JPCADASTRO.CDNUMCOB, JPCADASTRO.CDCOMCOB, JPCADASTRO.CDBAICOB, " + ;
         " JPCADASTRO.CDCIDCOB, JPCADASTRO.CDUFCOB ) ) ) AS ENDCOB," + ;
         " JPCADASTRO.CDMAPA" + ;
         " FROM JPNOTFIS" + ;
         " LEFT JOIN JPPEDIDO ON JPPEDIDO.IDPEDIDO = JPNOTFIS.NFPEDIDO" + ;
         " LEFT JOIN JPCADASTRO ON JPCADASTRO.IDCADASTRO = JPNOTFIS.NFCADASTRO" + ;
         " LEFT JOIN JPTABFINPOR ON JPTABFINPOR.IDFINPOR = JPCADASTRO.CDPORTADOR" + ;
         " LEFT JOIN JPITPED ON IPPEDIDO = IDPEDIDO" + ;
         " WHERE IDNOTFIS=" + NumberSQL( nIdNotFis )
      :Execute()
      IF mInfAdicONU3082
         mInfAdicTmp := "ONU 3082 (SUBSTANCIA QUE APRESENTA RISCOS PARA O MEIO AMBIENTE, LIQUIDA, N.E. OLEO COMBUSTIVEL) " + ;
            " CLASSE DE RISCO 9 (SUBSTANCIAS E ARTIGOS PERIGOSOS DIVERSOS), " + ;
            " EMBALAGEM III (BAIXO RISCO)." // "BPF (1A) "
         IF ! mInfAdicTmp $ mInfAdic
            mInfAdic += "; " + mInfAdicTmp
         ENDIF
      ENDIF
      IF mInfAdicONU1202
         mInfAdicTmp := "MISTURA DIESEL/BIODIESEL ONU 1202 (OLEO DIESEL) CLASSE DE RISCO 3 (LIQUIDO INFLAMAVEL) EMBALAGEM III (BAIXO RISCO)."
         IF ! mInfAdicTmp $ mInfAdic
            mInfAdic += "; " + mInfAdicTmp
         ENDIF
      ENDIF
      // resolução ANP 5.232 de 2016
      IF mInfAdicONU3082 .OR. mInfAdicONU1202
         mInfAdicTmp := "DECLARO QUE OS PRODUTOS PERIGOSOS ESTAO ADEQUADAMENTE CLASSIFICADOS, EMBALADOS, " + ;
            "IDENTIFICADOS, E ESTIVADOS PARA SUPORTAR OS RISCOS DAS OPERACOES DE TRANSPORTE E QUE ATENDEM AS EXIGENCIAS " + ;
            "DA REGULAMENTACAO"
         IF ! mInfAdicTmp $ mInfAdic
            mInfAdic += "; " + mInfAdicTmp
         ENDIF
      ENDIF
 
mInfAdicOnu1202, mInfAdicOnu3082 vém do processamento de produtos.
Isso pode ser cadastrado nas leis pra vir automático, mas como desde 2008 tá assim, fazem mais de 12 anos, vou manter, assim mesmo que o usuário esqueça, vai continuar aparecendo automático.

Tem multa, se não colocar que o diesel está EMBALADO.
Aquele mesmo diesel dos postos de gasolina, que sai da bomba... tem que colocar na nota que ele está EMBALADO, senão tem multa.
Que embalagem é essa? nenhuma... mas a lei manda colocar que tá embalado.

Meu modo de trabalho

Enviado: 07 Out 2021 20:29
por JoséQuintas
xmlnfe.png
Uia
O fonte Harbour de 61kb, o segundo maior fonte de todos, agora com 30kb e diminuindo.
Até esqueci da contabilidade, que não funciona mais... kkk

Tentar me empolgar menos com esse e voltar pra contabilidade.

É por isso que procuro mexer nos fontes pensando em sempre deixar em funcionamento.
Interrompo agora, tudo que já fiz FICA EM FUNCIONAMENTO, e continuo depois.

Aqui não existe "próxima versão", só existe a versão atual.
Ou o fonte funciona e já fica em uso, ou vai pro lixo.

A exceção foi a contabilidade, que converti toda base de dados, sem ter alterado os programas...
A versão atual da contabilidade NÃO FUNCIONA, mas não vai pro lixo.