Reconcile P001 fields 05 06 07 and single-count totals in QA
This commit is contained in:
@@ -27242,5 +27242,20 @@
|
||||
"installed_original": "e38aa74920412e545213b35b7585c847a0b8a969",
|
||||
"preserved_in_current_installed_E": "7ad187f9f0745245268e3ed5317547a96b4700f3",
|
||||
"report": "docs/investmoz/simulations/l1-p084-p085-tempo-operational-closure-20261025.json"
|
||||
},
|
||||
"l1_P001_correspondence_QA_20261025": {
|
||||
"status": "P001_correspondence_reconciled_QA_not_installed",
|
||||
"report": "docs/investmoz/simulations/p001-treatment-reconciliation-qa-20261025.json",
|
||||
"counts": {
|
||||
"delivered": 131,
|
||||
"QA_completed": [
|
||||
"P001"
|
||||
],
|
||||
"functional_remaining": [
|
||||
"P058"
|
||||
],
|
||||
"total": 133
|
||||
},
|
||||
"P084_P085": "closed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"No central AT/cross-dossier account reconciliation beyond recovered maps. The unused part does not obtain a deduction automatically.",
|
||||
"Supply13(2),margin/travel/event VAT and integrations that remain indeterminate need their own rule,base,data and implementation;purchaseVAT16 is never substituted for outputVAT.",
|
||||
"Formalities remain delimited as above;HTML preparations do not certify current electronic edition or replace external submission.",
|
||||
"L1:P058 andP00105/06;L2:18(6)+mixed,successive/pluriennial,errors/baddebts remain in original matrix.",
|
||||
"L1:P058 funcional;P001 correspondênciaQA,não instalada;L2:18(6)+mixed,successive/pluriennial,errors/baddebts remain in original matrix.",
|
||||
"Tempo request→decision→form procedure preserved;statistics/AI remain deferred."
|
||||
],
|
||||
"schema_changes": 0,
|
||||
@@ -157,6 +157,20 @@
|
||||
}
|
||||
],
|
||||
"scope_limit": "A prevenção de duplicação cobre os livros/históricos recuperados. Reconciliação central com aAT ou todos os dossiers não foi demonstrada;isto é limite técnico registado,não nova condição organizacional de fecho.",
|
||||
"outside_L3": "L1conserva131/133:P058eP001residuais;P084/P085fechados no âmbitoTempo.L2conservaosresiduais próprios.Estatísticas/IAadiadas."
|
||||
"outside_L3": "L1:131entregues+P001QA+P058funcional=133;P084/P085fechados. L2residuaispróprios;estatísticas/IAadiadas."
|
||||
},
|
||||
"P001_followup": {
|
||||
"status": "P001_correspondence_reconciled_QA_not_installed",
|
||||
"report": "docs/investmoz/simulations/p001-treatment-reconciliation-qa-20261025.json",
|
||||
"counts": {
|
||||
"delivered": 131,
|
||||
"QA_completed": [
|
||||
"P001"
|
||||
],
|
||||
"functional_remaining": [
|
||||
"P058"
|
||||
],
|
||||
"total": 133
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"status": "P001_correspondence_reconciled_QA_not_installed",
|
||||
"origin": "Tempo nesta sessão25/10/2026 conferiuVATReturn(1).pdf,quadro8:identificação05/06/07 e14=01+03+05+06+07. Não é necessária pesquisa de outro modelo.",
|
||||
"scope": "Reconciliar os tratamentos já implementados com a correspondência do exemplar;05/06separados eoperação única sem soma duplicada em14. Não classificar automaticamente operação estrangeira como06.",
|
||||
"source_trace": {
|
||||
"user_named": "VAT Return(1).pdf",
|
||||
"accessible_local": {
|
||||
"path": "\\\\Mac\\Home\\Documents\\TEMPO\\Portal Tempo\\Servicos Tempo\\Modelos\\VAT Return.pdf",
|
||||
"bytes": 285475,
|
||||
"sha256": "fbbbd2ac437da1a0fc335e5e5bb32df150e5819d0c831e05d80cabe044b8a652"
|
||||
},
|
||||
"original_DOCX": "Novo Modelo A.docx62c198e9,imagem incorporada7ae7f02e,preservados",
|
||||
"identity_note": "User namesVATReturn(1).pdf;accessible authorized Models folder containsVATReturn.pdf. Record the actual local filename/hash independently;accepted user correspondence is not blocked by filename."
|
||||
},
|
||||
"catalogue_reconciled": {
|
||||
"count": 109,
|
||||
"field_counts": {
|
||||
"05": 47,
|
||||
"indeterminate": 9,
|
||||
"07": 45,
|
||||
"01": 4,
|
||||
"03": 4
|
||||
},
|
||||
"inventory": "outputs\\p001-reconciliation-20261025\\treatment-field-inventory.json",
|
||||
"field06": "Nenhum tratamento do catálogo actual atribuído06;nenhuma regra nova foi inventada. Linhas estrangeiras/desconhecidas mantêm classificação indeterminada. Zero apenas com período completo sem linhas06.",
|
||||
"other_consumers": "A execução mensal geral01/02 e a classificaçãoL1 são reutilizadas/substituídas conforme o consumidor existente,não somadas novamente. L2lê os campos classificados e soma14uma vez."
|
||||
},
|
||||
"changes": [
|
||||
"Texto/interface ecorrespondência05/06/07 alinhados com identificação aceite;removida a alegação de que falta outro modelo.",
|
||||
"06deixa de receberzero literal;é agregado exclusivamente como01/03/05/07pelas linhas do campo,com guarda de identidade duplicada.",
|
||||
"Campo14mantém01+03+05+06+07,sem repetição;operações desconhecidas não viram06nemzero.",
|
||||
"Aviso de falta01/02não é apresentado quando existe apuramentoL1actual com01/02conhecidos,mesmo sem aquisições;camadas não somadas em duplicado.",
|
||||
"Históricos/entradas deliberadas preservados;nenhuma reclassificação de snapshots antigos."
|
||||
],
|
||||
"example_MZN": {
|
||||
"01": 100,
|
||||
"03": 200,
|
||||
"05": 300,
|
||||
"06": 0,
|
||||
"07": 400,
|
||||
"14": 1000
|
||||
},
|
||||
"verification": {
|
||||
"frontend_tests": 23,
|
||||
"new_targeted_cases": 8,
|
||||
"types": "passed",
|
||||
"build": "passed;exit0",
|
||||
"UI": "Componentes reais;05=300/06=0/07=400/14=1000;gravar→recuperar→relatório confirmados;sem aviso legado falso de01/02emfalta.",
|
||||
"UI_proof": "outputs\\p001-reconciliation-20261025\\ui-recovered-fields-total14.png",
|
||||
"snapshots_UI_recovered_equal": true,
|
||||
"private_API": "Contrato epermissões existentes reutilizados;sem mudança de formato/endpoint ou nova rondaAPI. Teste desta passagem:aritmética,livros,gravação/recuperação locais ecomponentes reais.",
|
||||
"sources_unchanged": 37,
|
||||
"prior_131_closures_preserved": true,
|
||||
"P084_P085": "closed;not reopened"
|
||||
},
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "resources/js/lib/iva-operations.ts",
|
||||
"sha256_LF": "e25872a7d321f475ee77f63051b379722fac89469773021ae1fee43cd2cdff5c"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisitions.ts",
|
||||
"sha256_LF": "639c666a9813229dca0f95cd591c1e6b4edc12538da0f0a63eda4c4e3a79b524"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-modelo-a.ts",
|
||||
"sha256_LF": "1ae10ac574cc0dcf2c5f6d2cda1aefa221f6aa717086971aecbfbf30553d0823"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-operations.tsx",
|
||||
"sha256_LF": "f7a2dfd16ed7fea33a4d15398f07fced7e8db46dcf07cd1bb1d85552ebe23d40"
|
||||
}
|
||||
],
|
||||
"delivery": "Preparar entregaGit compatível;comparação fresca,backup/restauro econferência apenas do delta antes de instalar;sem declarar disponível agora.",
|
||||
"L1_counts": {
|
||||
"delivered": 131,
|
||||
"QA_completed": [
|
||||
"P001"
|
||||
],
|
||||
"functional_remaining": [
|
||||
"P058"
|
||||
],
|
||||
"total": 133
|
||||
},
|
||||
"limits": [
|
||||
"Conclusão limitada àcorrespondência/separação dos tratamentos existentes;nenhuma classificação fiscal positiva06foi inventada ou demonstrada por uma sonda mecânica de agregação.",
|
||||
"Identificação do formulário não resolve bases/taxas/cálculos indeterminados de outros percursos;mantêm os seus âmbitosL2/L3.",
|
||||
"P058continua com os três casos residuais;P084/P085mantêm fecho operacionalTempo.",
|
||||
"E7ad187f9entregue;b189d0faemQA;L3aberto;estatísticas/IAadiadas."
|
||||
],
|
||||
"active_writes": 0,
|
||||
"schema_changes": 0,
|
||||
"new_source_search": false
|
||||
}
|
||||
@@ -5718,3 +5718,19 @@ SaldoL3:entregar o incrementoQA pelo processo existente;implementar regularizaç
|
||||
P001:mostrados directamente os campos do original recebido NovoModeloA.docx,hash62c198e9,imagem incorporada7ae7f02e. Campo05:"Vendas isentas com direito à dedução". Campo06:"Op.do n.º1,al.b)doart.º18doCIVA",agrupado com07sob"Isentas". Recorte:outputs/l3-saldo-p001-20261025/campos-05-06-exemplar-recebido.png. Pergunta específica àTempo:que operações pretende inscrever no06,distintas das do05? P001permanece pendente desse esclarecimento;original/remissão não foram corrigidos nem substituídos por outra edição. Nenhuma nova pesquisa jurídica,teste,importação ou alteração de dados activos.
|
||||
|
||||
L1mantém131/133;P058três casos residuais eP00105/06. P084/P085permanecem fechados no âmbito operacionalTempo,sem nova pesquisa. ResiduaisL2 eEstatísticas/IAadiadas preservados.
|
||||
|
||||
## 25/10/2026 — inventário anterior à reconciliação funcional deP001
|
||||
|
||||
Tempo conferiuVATReturn(1).pdf e disposição doquadro8:05vendas isentas comdireito;06operações18(1)b;07operações semdireito;14=01+03+05+06+07. A identificação já está esclarecida;prossegue apenas reconciliação dos tratamentos existentes,separação05/06 eausência de duplicação. Não procurar outro modelo nem presumir estrangeiro=06.131fechos anteriores,incluindoP084/P085no procedimentoTempo,foram inventariados epreservados antes dos novos testes. Fontes/anexos acessíveis inventariados emoutputs/p001-reconciliation-20261025/preservation-before-tests.json. E7ad187f9 eQAb189d0fa preservados;L3aberto;sem alteraçõesactivas.
|
||||
|
||||
## 25/10/2026 — P001 reconciliado emQA no catálogo existente;entrega pendente
|
||||
|
||||
A conferência comunicada pelaTempo doVATReturn(1).pdf esclarece05/06/07e14. Não falta procurar outro modelo. Catálogo actual inventariado integralmente:109tratamentos,47atribuídos05,45atribuídos07,4atribuídos01,4atribuídos03e9semcampo/classificação determinada;nenhumatribuído06. Fundamentos/condições próprios mantidos. Outros livros de classificação/base/eventos não são somados automaticamente como operações06. A localização estrangeira continua análise própria,semcampo06presumido.
|
||||
|
||||
Correcção incremental:06soma apenas as suas linhas,como01/03/05/07,emvezdezeroimposto no código;cada identidade pertence no máximo aumcampo de base. Repetir a mesma identidade05/06ouduasvezesno05érecusado.14continua01+03+05+06+07,semrepetição.06sóézeroquandooperíodoestácompletoesemlinhas06;operaçãoaindadesconhecidaouperíodoincompletofaz06/14permaneceremindeterminados. Não foi inventado tratamento fiscal positivo06. Uma sonda mecânica de agregação não é apresentada como esse tratamento.
|
||||
|
||||
O consumidorL2reutilizaaclassificaçãoactual,sem somar novamente aexecuçãogeral01/02. Corrigido aviso legado que dizia faltar01/02quandoL1jáosdetermina,mesmo semaquisições. Snapshots/históricos aceites não foram reescritos;edições deliberadas conservam resultados anteriores e exigem nova versão.
|
||||
|
||||
Prova:23testesfrontend(8novosP001+15dirigidosanteriores),TypeScript ebuildexit0. Componentesreais:01=100/03=200/05=300/06=0/07=400e14=1000MZN;gravar→recuperar→relatórioconfirmamcontagemúnica. CasoIVAincluído5%mantém14exacto18800/21;desconhecidosnãoviramzero. Contrato/APIprivadosexistentesreutilizados;nenhumnovoformatoouendpointnemnovarondaAPI. Fontes/anexosaccessibleisinventariadosmantêmhashes. Recibo:docs/investmoz/simulations/p001-treatment-reconciliation-qa-20261025.json.
|
||||
|
||||
P001concluídoemQAapenasnoâmbitodacorrespondênciadostratamentosexistentes;entregaGitpendente,nãodisponívelnoactivo. SaldoL1:131entregues+1QA(P001)+1funcional(P058)=133. P084/P085mantêmfechooperacionalTempo,semreabrirpesquisa. P058mantémostrêscasosresiduais. L2/L3limitaçõesprópriaspreservadas;E7ad187f9entregueeb189d0faQA;estatísticas/IAadiadas.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,7 +23,7 @@ export function SimulationOperations({draft,onChange}:{draft:Record<string,unkno
|
||||
<h4 className="font-semibold">Operações de IVA — enquadramento das vendas</h4>
|
||||
<p>Oportunidade {String(draft.context_opportunity||'entrada geral')}; moeda original {book.currency||'por indicar'}. Cobertura2026/MZN. Reutilize as vendas e complete apenas o enquadramento. Este bloco classifica as operações; não substitui nem soma novamente a execução mensal geral acima.</p>
|
||||
<p>Uma empresa pode ter operações com tratamentos diferentes. Os valores são projecções sob pressupostos declarados, sem certificar facturas, reconhecer créditos ou submeter uma declaração à AT.</p>
|
||||
<details><summary>Correspondência do campo06 e limites do formulário</summary><p>{modeloA06Correspondence}</p></details>
|
||||
<details><summary>Correspondência dos campos05/06/07 e contagem única</summary><p>{modeloA06Correspondence}</p></details>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save(reuseSales(draft,book)))}>Reutilizar vendas e períodos existentes</button>
|
||||
<button type="button" className="ml-3 underline" disabled={!book.periods.length} onClick={()=>run(()=>save({...book,rows:[...book.rows,newSalesOperation(crypto.randomUUID(),book.periods[0].period,book.currency)]}))}>Acrescentar operação em falta</button>
|
||||
@@ -98,7 +98,7 @@ export function SimulationOperations({draft,onChange}:{draft:Record<string,unkno
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save({...book,rows:book.rows.filter(r=>r.id!==row.id),periods:book.periods.map(p=>p.period===row.period?{...p,complete:false}:p)}))}>Remover esta linha do preenchimento (resultado anterior conservado)</button>
|
||||
</details>;})}
|
||||
{book.periods.map(p=><label className="block" key={p.period}><input type="checkbox" checked={p.complete} onChange={e=>run(()=>save({...book,periods:book.periods.map(v=>v.period===p.period?{...v,complete:e.target.checked}:v)}))}/> {p.period}: incluí todas as operações deste período; lista vazia significa ausência expressamente confirmada.</label>)}
|
||||
<p>Limites: outras isenções/bases especiais e correspondência do campo06 permanecem por delimitar. IVA incluído a5% conserva a fracção exacta e apresenta um decimal a6casas; a aproximação de apresentação não constitui regra declarativa. O desconhecido nunca é assumido como zero.</p>
|
||||
<p>A correspondência05/06/07 está identificada no exemplar. Tratamentos ainda não implementados conservam o seu fundamento por determinar;localização estrangeira não determina06. IVA incluído a5% conserva a fracção exacta e apresenta um decimal a6casas; a aproximação de apresentação não constitui regra declarativa. O desconhecido nunca é assumido como zero.</p>
|
||||
<button type="button" disabled={!book.periods.length} className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50" onClick={()=>run(()=>save(recordSales(draft,book)))}>Calcular e guardar operações IVA</button>
|
||||
{book.result&&<><p role="status">{salesCurrent(draft,book)?'Resultado correspondente às entradas.':'Resultado histórico conservado; reveja as alterações antes de recalcular.'}</p><dl>{salesReportRows(draft).map(([,label,value,origin],index)=><div className="break-inside-avoid border-t py-2" key={index}><dt className="font-semibold">{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
|
||||
</section>;
|
||||
|
||||
@@ -189,7 +189,7 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
|
||||
if(input?.absent.includes('05') && (declaredAmount('exports')||declaredAmount('sales_zero_rate'))) throw new Error('Existem exportações ou vendas de outro enquadramento. Não confirme ausência no campo05 sem resolver a classificação.');
|
||||
const sales=salesCurrent?projection?.rows?.find(r=>r.date.slice(0,7)===period):null;
|
||||
fields['01']=sales?.net_sales??null;fields['02']=sales?.vat??null;
|
||||
if((!sales || sales.net_sales===null || sales.vat===null)&&!(book.rows.some(r=>r.deductionLink||r.d)&&salesOperationsCurrent(draft,salesBook(draft))&&salesBook(draft).result?.periods.some(p=>p.period===period))) reasons.push('01/02: vendas ou IVA mensal em falta, execução incompleta ou desactualizada.');
|
||||
if((!sales || sales.net_sales===null || sales.vat===null)&&!(salesOperationsCurrent(draft,salesBook(draft))&&salesBook(draft).result?.periods.some(p=>p.period===period&&p.fields['01']!=null&&p.fields['02']!=null))) reasons.push('01/02: vendas ou IVA mensal em falta, execução incompleta ou desactualizada.');
|
||||
for(const field of excluded) fields[field]=input?.absent.includes(field)?'0.00':null;
|
||||
const operations=salesBook(draft);
|
||||
if(operations.rows.length || operations.result) {
|
||||
|
||||
@@ -15,9 +15,9 @@ export const modeloAFields: ModeloAField[] = [
|
||||
{field:'02',purpose:'IVA à taxa geral',origin:'Resultado mensal congelado no servidor',calculation:'Campo01 × taxa da versão publicada /100',basis:'CIVA art.17(1); mesma regra/versionamento do consumidor',status:'Implementado; componente do apuramento'},
|
||||
{field:'03',purpose:'Base de operações específicas à taxa reduzida',origin:'Operações separadas e enquadradas; ainda não recolhidas',calculation:'Base das operações elegíveis',basis:'Exemplar e CIVA art.17-A, aditado pela Lei22/2022; enquadramento a cadastrar',status:pending},
|
||||
{field:'04',purpose:'IVA à taxa reduzida',origin:'Campo03 e futura versão fundamentada',calculation:'Campo03 × taxa reduzida aplicável; não activa pelo exemplar',basis:'CIVA art.17-A; âmbito/versão temporal a integrar',status:pending},
|
||||
{field:'05',purpose:'Vendas isentas com direito à dedução',origin:'Vendas classificadas por operação e fundamento',calculation:'Soma das bases classificadas; sem taxa presumida',basis:'Exemplar; CIVA arts.9–14 e 18–20 a consolidar por operação',status:pending},
|
||||
{field:'06',purpose:'Operações da alínea b), n.º1, art.18, conforme exemplar',origin:'Operações classificadas com referência verificada',calculation:'Soma separada; não inferir do nome do sector',basis:'Remissão do exemplar ao art.18(1)(b); cotejo do âmbito necessário',status:pending},
|
||||
{field:'07',purpose:'Isentas sem direito à dedução',origin:'Vendas classificadas e uso das aquisições',calculation:'Soma das bases elegíveis',basis:'Exemplar; incidência/isenções e exclusões de dedução a fundamentar',status:pending},
|
||||
{field:'05',purpose:'Vendas isentas com direito à dedução',origin:'Operações com tratamento próprio identificado no catálogo',calculation:'Soma exclusiva das bases classificadas05; sem repetir06',basis:'Correspondência do exemplar confirmada pelaTempo25/10/2026;fundamento próprio de cada tratamento preservado',status:'Correspondência reconciliada no catálogo existente;condições fiscais por operação mantidas'},
|
||||
{field:'06',purpose:'Operações do n.º1,alínea b),do artigo18.ºdoCIVA',origin:'Linhas com correspondência própria identificada;nenhum tratamento actual atribuído06',calculation:'Soma exclusiva de06;sem repetir05 e sem inferir de localização estrangeira',basis:'Rótulo e posição no exemplar confirmados pelaTempo25/10/2026;sem corrigir a remissão',status:'Identificado e separado;desconhecido não vira06 nem zero;período completo sem linhas06 permitezero'},
|
||||
{field:'07',purpose:'Operações que não conferem direito à dedução',origin:'Vendas classificadas e uso das aquisições',calculation:'Soma das bases elegíveis',basis:'Exemplar; incidência/isenções e exclusões de dedução a fundamentar',status:pending},
|
||||
{field:'08',purpose:'IVA dedutível — imobilizado',origin:'Aquisições/facturas de activos, não o valor global do investimento',calculation:'Apenas imposto elegível documentado',basis:'CIVA arts.18–21; art.18(2), Lei10/2025: documentos legais e fornecedor inscrito',status:pending},
|
||||
{field:'09',purpose:'IVA dedutível — existências',origin:'Compras projectadas reutilizáveis; falta IVA, documento e elegibilidade',calculation:'IVA elegível das existências, não compras × taxa geral',basis:'CIVA arts.18–21; condições/exclusões, afectação e período',status:pending},
|
||||
{field:'10',purpose:'IVA dedutível — outros bens/serviços',origin:'Aquisições discriminadas; despesas agregadas não provam IVA',calculation:'IVA elegível; excluir operações sem direito',basis:'CIVA arts.18–21; art.19(2), Lei10/2025, inclui exclusão das operações art.17-A',status:pending},
|
||||
|
||||
@@ -22,7 +22,7 @@ const source2016 = 'CIVA republicado Lei13/2016, BR156, 10.º suplemento, PDF9
|
||||
const source2025 = 'Lei10/2025, BR248, suplemento1, PDF14–15 (2180(14)–(15)); arts.1/2/3/5; efeitos01/01/2026';
|
||||
const healthGoodsSource = 'CIVA republicado Lei13/2016, PDF9, p.1426(267), art.9(1)(b–f); PDF15, p.1426(273), art.19(1); Lei22/2022 art.1, PDF1, p.2252(25), mantém9(1)(b–f). Âmbito técnico2026/MZN';
|
||||
const internalOperation = 'Operação interna localizada em Moçambique; não é importação nem operação localizada no estrangeiro';
|
||||
export const modeloA06Correspondence = 'Exemplar recebido:06 é base de operações isentas, com remissão18(1)(b); nessa lei18(1)(b) trata IVA de importações, enquanto19(1)(b) enumera operações com direito. O mesmo exemplar já tem05 para vendas isentas com direito. O formulário antigo da AT usa03 para a remissão e06 para existências. Não há correspondência unívoca nem instrução aplicável que separe05/06 no acervo conferido: não atribuir06 automaticamente a operações no estrangeiro ou importar instruções antigas por número.';
|
||||
export const modeloA06Correspondence = 'Correspondência confirmada pela Tempo em25/10/2026 no exemplar VAT Return(1).pdf:05 — Vendas isentas com direito à dedução;06 — Operações do n.º1,alínea b),do artigo18.ºdoCIVA;07 — Operações que não conferem direito à dedução. Campo14=01+03+05+06+07,cada operação contada uma única vez no seu campo. O catálogo actual identifica os seus tratamentos por fundamento e destino exclusivo;nenhum está atribuído ao06. Operação no estrangeiro não significa06:enquadramento desconhecido permanece indeterminado. O06 só soma linhas que tenham correspondência própria identificada;zero exige período completo sem essas linhas. Originais e resultados históricos preservados.';
|
||||
export type OperationTreatment = {id:string; label:string; field:string|null; rate:number|null; article:string; source:string; conditions:string[];logicVersion?:string};
|
||||
const nonprofitConditions = [
|
||||
'Não distribui nem disponibiliza lucros; os corpos gerentes não têm interesse directo ou indirecto nos resultados, por si ou interposta pessoa',
|
||||
@@ -132,6 +132,21 @@ export type SalesOperation = {id:string;period:string;description:string;establi
|
||||
export type SalesBook = {currency:string;rows:SalesOperation[];periods:Array<{period:string;complete:boolean}>; result?:SalesResult; history?:SalesResult[]};
|
||||
export type SalesLineResult = {id:string;field:string|null;net:string|null;vat:string|null;gross:string|null;rate:number|null;status:'calculated'|'exempt'|'indeterminate';reason:string;article:string;source:string;exact?:{net:FiscalFraction;vat:FiscalFraction;gross:FiscalFraction}};
|
||||
export type SalesResult = {version:string;revision:number;calculatedAt:string;opportunity:string;location:string;currency:string;key:string;inputs:SalesOperation[];rows:SalesLineResult[];periods:Array<{period:string;fields:Record<string,string|null>;exactFields?:Record<string,FiscalFraction|null>;reasons:string[]}>;rounding:string};
|
||||
export const salesBaseFields = ['01','03','05','06','07'] as const;
|
||||
/** One result identity belongs to at most one base field; this does not classify a new tax treatment. */
|
||||
export function salesBaseMembership(rows:readonly SalesLineResult[]):Record<typeof salesBaseFields[number],SalesLineResult[]> {
|
||||
const groups:Record<typeof salesBaseFields[number],SalesLineResult[]>={'01':[],'03':[],'05':[],'06':[],'07':[]};
|
||||
const seen=new Set<string>();
|
||||
for(const row of rows){
|
||||
if(seen.has(row.id))throw new Error('Mesma operação repetida no apuramento; não somar em05 e06 nem duas vezes no mesmo campo.');
|
||||
seen.add(row.id);
|
||||
if(row.field===null)continue;
|
||||
if(!salesBaseFields.includes(row.field as typeof salesBaseFields[number]))throw new Error('Campo de base sem correspondência identificada no exemplar; conservar a operação antes de a somar.');
|
||||
groups[row.field as typeof salesBaseFields[number]].push(row);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
type Store = ReturnType<typeof readAcquisitionStore> & {salesOperations?:Record<string,SalesBook>};
|
||||
export function newSalesOperation(id:string,period:string,currency:string):SalesOperation {return {id,period,description:'',establishment:'',amount:'',basis:'',treatment:'other',confirmed:[],evidence:'',sourceAmount:null,sourcePeriod:period,sourceCurrency:currency,source:'Introdução directa; projecção, não declaração submetida'};}
|
||||
export function continueAnnexIOperation(row:SalesOperation,targetId:string):SalesOperation {
|
||||
@@ -244,9 +259,9 @@ export function calculateSales(draft:Record<string,unknown>,book:SalesBook,now=n
|
||||
const periods=book.periods.map(p=>{
|
||||
const matching=book.rows.map((r,i)=>({input:r,result:rows[i]})).filter(r=>r.input.period===p.period),fields:Record<string,string|null>={},exactFields:Record<string,FiscalFraction|null>={},reasons=matching.filter(r=>r.result.status==='indeterminate').map(r=>`${r.input.description||r.input.id}: ${r.result.reason}`);
|
||||
const known=p.complete&&!matching.some(r=>r.result.status==='indeterminate');
|
||||
for(const f of ['01','03','05','07'])fields[f]=known?sum(matching.filter(r=>r.result.field===f).map(r=>r.result.net)):null;
|
||||
const membership=salesBaseMembership(matching.map(r=>r.result));
|
||||
for(const f of salesBaseFields)fields[f]=known?sum(membership[f].map(r=>r.net)):null;
|
||||
for(const [f,base] of [['02','01'],['04','03']])fields[f]=known?sum(matching.filter(r=>r.result.field===base).map(r=>r.result.vat)):null;
|
||||
fields['06']=known?'0.00':null;
|
||||
if(matching.some(r=>r.result.exact)) {
|
||||
for(const [f,base,part] of [['01','01','net'],['02','01','vat'],['03','03','net'],['04','03','vat'],['05','05','net'],['06','06','net'],['07','07','net']] as const) {
|
||||
exactFields[f]=known?addFractions(matching.filter(r=>r.result.field===base).map(r=>r.result.exact?.[part]??decimalFraction(r.result[part]!))):null;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import {operationTreatments,newSalesOperation,calculateSales,recordSales,salesBaseFields,salesBaseMembership,salesBook,salesCurrent,salesReportRows,saveSalesBook,modeloA06Correspondence} from '../../resources/js/lib/iva-operations.ts';
|
||||
import {calculateAcquisitions,recordAcquisitions,saveAcquisitionBook,acquisitionBook,acquisitionCurrent} from '../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {modeloAFields} from '../../resources/js/lib/iva-modelo-a.ts';
|
||||
const seed=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3e-preserved.json',import.meta.url),'utf8'));
|
||||
function fixture(){const d=structuredClone(seed);d.context_opportunity='opportunity:375';d.project_title='SYNTHETIC QA — P001 correspondência05/06';const choices=[['general','100'],['private_health','200'],['export_goods','300'],['housing_rent','400']];const b={currency:'MZN',rows:choices.map(([treatment,amount],index)=>{const rule=operationTreatments.find(t=>t.id===treatment);return{...newSalesOperation(`p001-${index}`,'2026-01','MZN'),description:`SYNTHETIC QA ${treatment}`,establishment:'SYNTHETIC QA estabelecimento',amount,basis:'net',treatment,confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA pressupostos do tratamento,sem declaração submetida'};}),periods:[{period:'2026-01',complete:true}]};const a={currency:'MZN',taxpayerRegistered:'yes',rows:[],periods:[{period:'2026-01',complete:true,absent:['11','12','13']}]};return{d,b,a};}
|
||||
function recorded(){const f=fixture();f.b=recordSales(f.d,f.b,'2026-10-25T16:00:00Z');f.d.modelo_a_snapshot=saveSalesBook(f.d,f.b);f.a=recordAcquisitions(f.d,f.a,'2026-10-25T16:00:01Z');f.d.modelo_a_snapshot=saveAcquisitionBook(f.d,f.a);return f;}
|
||||
test('whole current catalogue has exclusive field mappings;05and07 keep treatment references;no foreign=06 or invented06 treatment',()=>{assert.equal(new Set(operationTreatments.map(t=>t.id)).size,operationTreatments.length);assert.ok(operationTreatments.filter(t=>t.field==='05').length>0);assert.ok(operationTreatments.filter(t=>t.field==='07').length>0);assert.equal(operationTreatments.filter(t=>t.field==='06').length,0);for(const rule of operationTreatments){assert.ok(rule.field===null||salesBaseFields.includes(rule.field),rule.id);if(rule.field==='05'||rule.field==='07'){assert.ok(rule.source);assert.ok(rule.article);}}assert.equal(operationTreatments.find(t=>t.id==='foreign_operation').field,null);assert.equal(operationTreatments.find(t=>t.id==='foreign_operation').rate,null);});
|
||||
test('implemented classified operations feed05or07once and14sums01+03+05+06+07 without duplicating the export300',()=>{const{d,b,a}=recorded(),p=b.result.periods[0].fields;assert.deepEqual(Object.fromEntries(['01','03','05','06','07'].map(f=>[f,p[f]])),{'01':'100.00','03':'200.00','05':'300.00','06':'0.00','07':'400.00'});assert.equal(a.result.periods[0].fields['14'],'1000.00');assert.doesNotMatch(a.result.periods[0].reasons.join(' '),/01\/02: vendas ou IVA mensal em falta/);const membership=salesBaseMembership(b.result.rows);assert.equal(membership['05'][0].id,'p001-2');assert.equal(membership['06'].length,0);assert.equal(Object.values(membership).flat().length,b.rows.length);assert.ok(salesCurrent(d,salesBook(d)));assert.ok(acquisitionCurrent(d,acquisitionBook(d)));});
|
||||
test('mechanical aggregation rejects one operation in05and06or twice in05;distinct06probe is not a new tax classification',()=>{const{b}=recorded(),r=b.result.rows.find(r=>r.field==='05');assert.throws(()=>salesBaseMembership([r,{...r,field:'06'}]),/Mesma operação/);assert.throws(()=>salesBaseMembership([r,{...r}]),/Mesma operação/);const grouped=salesBaseMembership([r,{...r,id:'aggregation-only-06',field:'06',net:'50.00'}]);assert.deepEqual(grouped['05'].map(r=>r.net),['300.00']);assert.deepEqual(grouped['06'].map(r=>r.net),['50.00']);const f=fixture();f.b.rows.push({...f.b.rows[2],treatment:'foreign_operation'});assert.throws(()=>calculateSales(f.d,f.b),/duplicados/);});
|
||||
test('foreign or unknown operation never becomes06orzero;declared absence cannot override that unknown in14',()=>{for(const treatment of ['foreign_operation','other']){const{d,b,a}=fixture();b.rows[2].treatment=treatment;const value=recordSales(d,b);assert.equal(value.result.rows[2].field,null);assert.equal(value.result.periods[0].fields['06'],null);d.modelo_a_snapshot=saveSalesBook(d,value);a.periods[0].absent.push('06');assert.equal(calculateAcquisitions(d,a).periods[0].fields['06'],null);assert.equal(calculateAcquisitions(d,a).periods[0].fields['14'],null);}});
|
||||
test('06zero needs complete classified scope;incomplete scope stays unknown;exact included5percent remains exact in14',()=>{const{d,b,a}=fixture();b.periods[0].complete=false;assert.equal(calculateSales(d,b).periods[0].fields['06'],null);b.periods[0].complete=true;b.rows[1].basis='included';b.rows[1].amount='100';const calc=recordSales(d,b);d.modelo_a_snapshot=saveSalesBook(d,calc);const totals=calculateAcquisitions(d,a);assert.deepEqual(totals.periods[0].exactFields['14'],{numerator:'18800',denominator:'21'});assert.deepEqual(calc.result.periods[0].exactFields['06'],{numerator:'0',denominator:'1'});assert.equal(totals.periods[0].fields['14'],'895.238095');});
|
||||
test('deliberate edits do not rewrite05/06history;save/recovery preserves every prior map and report uses frozen inputs',()=>{let{d,b,a}=recorded();const prior=JSON.parse(seed.modelo_a_snapshot),first=structuredClone(b.result);b.rows[2].description='Deliberate edit after calculation';d.modelo_a_snapshot=saveSalesBook(d,b);assert.equal(salesCurrent(d,salesBook(d)),false);assert.doesNotMatch(salesReportRows(d).flat().join(' '),/Deliberate edit after calculation/);b=recordSales(d,b);assert.deepEqual(b.history.at(-1),first);d.modelo_a_snapshot=saveSalesBook(d,b);const recovered=JSON.parse(JSON.stringify(d));assert.deepEqual(salesBook(recovered),b);const current=JSON.parse(recovered.modelo_a_snapshot);for(const[k,v]of Object.entries(prior))assert.deepEqual(k==='version'?current[k]:Object.fromEntries(Object.keys(v).map(key=>[key,current[k][key]])),v,k);assert.equal(a.result.periods[0].fields['14'],'1000.00');});
|
||||
test('confirmed form labels replace the generic model-search blocker without changing foreign semantics',()=>{assert.match(modeloA06Correspondence,/Correspondência confirmada pela Tempo/);assert.match(modeloA06Correspondence,/Campo14=01\+03\+05\+06\+07/);assert.doesNotMatch(modeloA06Correspondence,/Não há correspondência unívoca|formulário antigo/);assert.equal(modeloAFields.find(f=>f.field==='05').purpose,'Vendas isentas com direito à dedução');assert.equal(modeloAFields.find(f=>f.field==='07').purpose,'Operações que não conferem direito à dedução');assert.match(modeloAFields.find(f=>f.field==='06').purpose,/artigo18/);});
|
||||
test('export guarded API and component proof plus full current treatment-to-field inventory',()=>{const{d,b,a}=recorded();assert.equal(b.result.periods[0].fields['05'],'300.00');assert.equal(a.result.periods[0].fields['14'],'1000.00');if(process.env.P001_RECONCILIATION_OUTPUT){fs.writeFileSync(process.env.P001_RECONCILIATION_OUTPUT,JSON.stringify(d));fs.writeFileSync(process.env.P001_RECONCILIATION_OUTPUT.replace('calculated.json','treatment-field-inventory.json'),JSON.stringify({origin:'User-confirmed received form;implemented catalogue only',labels:Object.fromEntries(modeloAFields.filter(f=>['05','06','07','14'].includes(f.field)).map(f=>[f.field,f.purpose])),treatments:operationTreatments.map(({id,label,field,article,source})=>({id,label,field,article,source})),field06_treatments:[],limit:'No new positive06tax classification inferred;mechanical duplicate guards separately tested.'},null,2));}});
|
||||
Reference in New Issue
Block a user