Implement scoped L2 E annual adjustments and asset control with grouped D E manifest
This commit is contained in:
@@ -25595,5 +25595,73 @@
|
||||
"code_status": "E ainda não implementado;dependênciaD agora disponível emQA. Isto não declaraE entregue nem fechaL2."
|
||||
},
|
||||
"active_writes": false
|
||||
},
|
||||
"l2_e_assets_regularizations_qa_20261025": {
|
||||
"status": "L2-E concluído em QA no âmbito demonstrado,comF;D/E preparados para entrega conjunta,sem instalação",
|
||||
"report": "docs/investmoz/simulations/l2-e-assets-regularizations-qa-20261025.json",
|
||||
"joint_manifest": "docs/investmoz/simulations/l2-de-delivery-manifest-20261025.json",
|
||||
"installed": "819ae791",
|
||||
"L1": {
|
||||
"state": "operacional com limitações explícitas",
|
||||
"delivered": 129,
|
||||
"functional_residuals": [
|
||||
"P058",
|
||||
"P084",
|
||||
"P085"
|
||||
],
|
||||
"documentary_residual": "P001 — exclusivamente05/06",
|
||||
"reopened": false
|
||||
},
|
||||
"F": {
|
||||
"exact_example": {
|
||||
"12": "132/25",
|
||||
"13": "4/3",
|
||||
"14": "1000/1",
|
||||
"15": "736/25",
|
||||
"16": "484/3",
|
||||
"17": "9892/75",
|
||||
"18": "0/1"
|
||||
},
|
||||
"presentation_MZN": {
|
||||
"12": "5.28",
|
||||
"13": "1.333333",
|
||||
"14": "1000.00",
|
||||
"15": "29.44",
|
||||
"16": "161.333333",
|
||||
"17": "131.893333",
|
||||
"18": "0.00"
|
||||
},
|
||||
"negative_annual_example": "Base final1/2 contra provisórios34%/67%:delta−4/25MZN,campo13;sem nova dedução.",
|
||||
"C_example": "IVA16,gasóleoC50%→8;pro rata34%→2.72;final67%→5.36;delta2.64,sem reaplicarC.",
|
||||
"unknown": "Cobertura incompleta,denominadorzero,fonte/factos em falta,edição da aquisição original ou resultadoE desactualizado→12/13 e totais afectados indeterminados. Ausência12/13 contraditória exige reconciliação expressa.",
|
||||
"precision": "Fracções exactas;decimais técnicos até6casas,sem regra declarativa presumida."
|
||||
},
|
||||
"validation": {
|
||||
"frontend": "31testes dirigidos:22ABC/D/compras reutilizados e9E;todos passaram.",
|
||||
"API": "1teste/94asserções;gravação→recuperação exacta de modelo_a_snapshot,origensD,históricos,activos e mapas anteriores;DatabaseTransactions rollback,base simulator_fiscal_delta_20261021_test,porta55441.",
|
||||
"types": "types:check passou",
|
||||
"build": "build passou",
|
||||
"UI": "Componente real;denominadorzero mantém totais indeterminados;gravar→recuperar→relatório em dois âmbitos sintéticos;totais e activo conferidos. Provas válidas anteriores reutilizadas.",
|
||||
"proofs": [
|
||||
"outputs/iva-l2-e-20261025/ui-recovered-totals.png",
|
||||
"outputs/iva-l2-e-20261025/ui-recovered-asset.png"
|
||||
],
|
||||
"logs": [
|
||||
"outputs/iva-l2-e-20261025/frontend-tests-final.txt",
|
||||
"outputs/iva-l2-e-20261025/types-final.txt",
|
||||
"outputs/iva-l2-e-20261025/build-final.txt"
|
||||
]
|
||||
},
|
||||
"scope_limits": [
|
||||
"Combinação18(6)+afectação mista continua pendente;sem produto genérico de percentagens.",
|
||||
"E demonstrado:acerto anual22(6),controlo individual dos activos e redução/anulação documentada da dedução pelo adquirente51(4),emMZN/2026. Não equivale a automatização de todas as regularizações.",
|
||||
"Sucessão de nota de crédito e acerto anual na mesma parcela requer reconciliação sequencial própria;não somar dois deltas independentes. Vários documentos rectificativos sobre a mesma parcela ainda não consolidados por este percurso.",
|
||||
"Erros materiais51(6–7),incobráveis/recuperações51(8–9),mudanças de regime e regularizações plurianuais não automatizados. Não se inventa percentagem/prazo de activos. O registo27RCIVA não constitui esse cálculo.",
|
||||
"O motor mensal mantém2026/MZN. Um prazo51(4) que alcance2027 é conservado no relatório;lançamento em2027 permanece fora do âmbito mensal demonstrado.",
|
||||
"Origem de bases reduzidas sem IVA liquidado consolidado mantém14/16/17/18 indeterminados,também quando só existem eventosE no livro do período.",
|
||||
"17/18 são diferenças antes de créditos anteriores;19–27,pagamento/reembolso e restituição de bebidas mantêmL3. Formalidades digitais não automatizadas e cálculo fiscal restante dos ramosL1 limitados continuam identificados.",
|
||||
"Divergência de remissões textuais11/12 versus caixas12/13 mantém-se identificada;P001 continua exclusivamente05/06."
|
||||
],
|
||||
"active_writes": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"status": "preparado para uma entrega conjuntaD/E;nao instalado",
|
||||
"baseline_installed": "819ae791",
|
||||
"baseline_receipt": "855ab066",
|
||||
"accepted_D": "2c2a2f20",
|
||||
"candidate": "commit que contém este manifesto",
|
||||
"scope": [
|
||||
"D afectação real/pro rata provisório/proporções18(6) exclusivas",
|
||||
"E acerto anual22(6),controlo de activos27RCIVA e rectificação demonstrada51(4)",
|
||||
"F sobre parcelas/totais afectados"
|
||||
],
|
||||
"reports": [
|
||||
"docs/investmoz/simulations/l2-d-allocation-qa-20261025.json",
|
||||
"docs/investmoz/simulations/l2-e-assets-regularizations-qa-20261025.json"
|
||||
],
|
||||
"reuse_proofs": true,
|
||||
"fresh_at_delivery": [
|
||||
"Conferir main remoto e compatibilidade/preservação de concorrência",
|
||||
"Autenticar destino e conferir identidade/estado instalado",
|
||||
"Backup fresco e restauro real em destino isolado antes de instalar",
|
||||
"Manifesto do mergeefectivo e instalação pelo processoGit existente",
|
||||
"Conferir apenas delta instalado,com identidades/contextos RLS equivalentes",
|
||||
"Reconciliar código/schema/dados/configuração/permissões e emitir recibo"
|
||||
],
|
||||
"not_performed": "Não houve pushmain,instalação,backup activo ou migração neste incrementoQA.",
|
||||
"limitations": [
|
||||
"Combinação18(6)+afectação mista continua pendente;sem produto genérico de percentagens.",
|
||||
"E demonstrado:acerto anual22(6),controlo individual dos activos e redução/anulação documentada da dedução pelo adquirente51(4),emMZN/2026. Não equivale a automatização de todas as regularizações.",
|
||||
"Sucessão de nota de crédito e acerto anual na mesma parcela requer reconciliação sequencial própria;não somar dois deltas independentes. Vários documentos rectificativos sobre a mesma parcela ainda não consolidados por este percurso.",
|
||||
"Erros materiais51(6–7),incobráveis/recuperações51(8–9),mudanças de regime e regularizações plurianuais não automatizados. Não se inventa percentagem/prazo de activos. O registo27RCIVA não constitui esse cálculo.",
|
||||
"O motor mensal mantém2026/MZN. Um prazo51(4) que alcance2027 é conservado no relatório;lançamento em2027 permanece fora do âmbito mensal demonstrado.",
|
||||
"Origem de bases reduzidas sem IVA liquidado consolidado mantém14/16/17/18 indeterminados,também quando só existem eventosE no livro do período.",
|
||||
"17/18 são diferenças antes de créditos anteriores;19–27,pagamento/reembolso e restituição de bebidas mantêmL3. Formalidades digitais não automatizadas e cálculo fiscal restante dos ramosL1 limitados continuam identificados.",
|
||||
"Divergência de remissões textuais11/12 versus caixas12/13 mantém-se identificada;P001 continua exclusivamente05/06."
|
||||
],
|
||||
"L1": {
|
||||
"state": "operacional com limitações explícitas",
|
||||
"delivered": 129,
|
||||
"functional_residuals": [
|
||||
"P058",
|
||||
"P084",
|
||||
"P085"
|
||||
],
|
||||
"documentary_residual": "P001 — exclusivamente05/06",
|
||||
"reopened": false
|
||||
},
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisitions.ts",
|
||||
"sha256_LF": "2bca130d43808e8e0b773d63591f9711462fc79ba7cc231821ce693d4bd010f9"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-bc.ts",
|
||||
"sha256_LF": "4ce47274449e075e5e22a02f7bc497e4df6925c1d628f1e9cd057748d016012f"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-allocation.ts",
|
||||
"sha256_LF": "df960bab63e89c3b3cea3c61b5c57481194b19d5dd0894830a181cdbe10dc972"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-regularizations.ts",
|
||||
"sha256_LF": "72f432ab11a09ff4a1893ca59c9f19b88d6e709ba59842b087630fd7623e24c9"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "0af55cadc0bf2cd0b88673653c6915fcb36e7f3ad61b34721e44b2865da29d42"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-allocation.tsx",
|
||||
"sha256_LF": "222204ee4c699b2cbddb9cb921a37397f6be313a215905163ee135aa2b26f104"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-regularizations.tsx",
|
||||
"sha256_LF": "9b551a10613410210e5106f8b2de5a9c1d596d1d793a12a4c0c6cae06d5b32e2"
|
||||
}
|
||||
],
|
||||
"compatibility_at_preparation": {
|
||||
"fetched_utc": "2026-10-25T11:21:49.097830+00:00",
|
||||
"origin_main": "819ae791e9f3a7347ec8c804f58e6cc34a22cfaf",
|
||||
"main_is_ancestor": true,
|
||||
"concurrent_main_delta_since_installed": false,
|
||||
"next": "Repetir conferência no momento da entrega;nenhum reset/stash/sobrescrita de alterações concorrentes."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"status": "L2-E concluído em QA no âmbito demonstrado,comF;D/E preparados para entrega conjunta,sem instalação",
|
||||
"matrix_anchor": "AUD-SIM-INT02",
|
||||
"installed_revision": "819ae791",
|
||||
"installed_receipt": "855ab066",
|
||||
"D_accepted_candidate": "2c2a2f20",
|
||||
"L1": {
|
||||
"state": "operacional com limitações explícitas",
|
||||
"delivered": 129,
|
||||
"functional_residuals": [
|
||||
"P058",
|
||||
"P084",
|
||||
"P085"
|
||||
],
|
||||
"documentary_residual": "P001 — exclusivamente05/06",
|
||||
"reopened": false
|
||||
},
|
||||
"source_cotejo": [
|
||||
{
|
||||
"document": "Lei13/2016,CIVA republicado",
|
||||
"file": "docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf",
|
||||
"articles": "22(4–8);51(1/4)",
|
||||
"pdf_pages": [
|
||||
16,
|
||||
21
|
||||
],
|
||||
"printed_pages": [
|
||||
"1426(274)",
|
||||
"1426(279)"
|
||||
],
|
||||
"renders": [
|
||||
"outputs/iva-l2-d-20261025/2016-16.png",
|
||||
"outputs/iva-l2-e-20261025/2016-21.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"document": "Decreto7/2008,de16deAbril,Regulamento do CIVA",
|
||||
"file": "docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf",
|
||||
"article": "27(1–3)",
|
||||
"pdf_page": 5,
|
||||
"printed_page": "103",
|
||||
"render": "outputs/iva-l2-e-20261025/2008-5.png",
|
||||
"scope": "registo individual de activos/data/IVA suportado/deduzido;sem criar regularização plurianual genérica"
|
||||
}
|
||||
],
|
||||
"implemented": {
|
||||
"annual": "Adopção expressa de resultadoD congelado e parcelas originais;reutiliza documento/linha,IVA elegível já limitado porC,quociente aplicado e dedução exacta. Bases finais anuais22(4–5),quociente à centésima superior22(8);delta=final−efectivamente deduzido;último período do ano22(6). Histórico original conservado na revisão seguinte,sem recálculo automático.",
|
||||
"assets": "Reutiliza aquisiçõescampo08 e identifica expressamente bens de investimento importados,conservando campo11. Controlo27RCIVA:data,documento/linha,IVA suportado e deduzido,moeda/versão e natureza efectiva/projectada. Sem amortização ou periodicidade presumida.",
|
||||
"purchase_correction": "51(4):uma parcela original,documento rectificativo do fornecedor,data de recepção,motivo/âmbito,dedução corrigida documentada e manutenção das condições de direito. Redução/anulação a favor do Estado;recepção ou período seguinte. Acréscimo,erro e mudança de direito conservam percursos próprios.",
|
||||
"preservation": "ChavesE compactas de detecção de alterações,não autoridade fiscal;conservam integralmente os dados de origem sem repetição nas chaves. Gravação privada/API e recuperação exactas. Alterações desactualizam o resultado;originais e versões anteriores preservados.",
|
||||
"deduplication": "Identidade por documento/linha e ano para acerto anual,mesmo com revisões diferentes. Não regularizar duas vezes a mesma parcela,nem empilhar nota de crédito e acerto anual sem reconciliação. Origem em falta/versão incoerente não vira zero.",
|
||||
"reuse": "Mantém livros A/B/C/D e mapasL1;novos campos só para bases finais,eventos,documentos e condições em falta. Sem nova aprovação/certificado. E guarda resultado próprio;F é actualizado por acção expressa,sem tocar silenciosamente no resultadoD."
|
||||
},
|
||||
"F": {
|
||||
"exact_example": {
|
||||
"12": "132/25",
|
||||
"13": "4/3",
|
||||
"14": "1000/1",
|
||||
"15": "736/25",
|
||||
"16": "484/3",
|
||||
"17": "9892/75",
|
||||
"18": "0/1"
|
||||
},
|
||||
"presentation_MZN": {
|
||||
"12": "5.28",
|
||||
"13": "1.333333",
|
||||
"14": "1000.00",
|
||||
"15": "29.44",
|
||||
"16": "161.333333",
|
||||
"17": "131.893333",
|
||||
"18": "0.00"
|
||||
},
|
||||
"negative_annual_example": "Base final1/2 contra provisórios34%/67%:delta−4/25MZN,campo13;sem nova dedução.",
|
||||
"C_example": "IVA16,gasóleoC50%→8;pro rata34%→2.72;final67%→5.36;delta2.64,sem reaplicarC.",
|
||||
"unknown": "Cobertura incompleta,denominadorzero,fonte/factos em falta,edição da aquisição original ou resultadoE desactualizado→12/13 e totais afectados indeterminados. Ausência12/13 contraditória exige reconciliação expressa.",
|
||||
"precision": "Fracções exactas;decimais técnicos até6casas,sem regra declarativa presumida."
|
||||
},
|
||||
"validation": {
|
||||
"frontend": "31testes dirigidos:22ABC/D/compras reutilizados e9E;todos passaram.",
|
||||
"API": "1teste/94asserções;gravação→recuperação exacta de modelo_a_snapshot,origensD,históricos,activos e mapas anteriores;DatabaseTransactions rollback,base simulator_fiscal_delta_20261021_test,porta55441.",
|
||||
"types": "types:check passou",
|
||||
"build": "build passou",
|
||||
"UI": "Componente real;denominadorzero mantém totais indeterminados;gravar→recuperar→relatório em dois âmbitos sintéticos;totais e activo conferidos. Provas válidas anteriores reutilizadas.",
|
||||
"proofs": [
|
||||
"outputs/iva-l2-e-20261025/ui-recovered-totals.png",
|
||||
"outputs/iva-l2-e-20261025/ui-recovered-asset.png"
|
||||
],
|
||||
"logs": [
|
||||
"outputs/iva-l2-e-20261025/frontend-tests-final.txt",
|
||||
"outputs/iva-l2-e-20261025/types-final.txt",
|
||||
"outputs/iva-l2-e-20261025/build-final.txt"
|
||||
]
|
||||
},
|
||||
"scope_limits": [
|
||||
"Combinação18(6)+afectação mista continua pendente;sem produto genérico de percentagens.",
|
||||
"E demonstrado:acerto anual22(6),controlo individual dos activos e redução/anulação documentada da dedução pelo adquirente51(4),emMZN/2026. Não equivale a automatização de todas as regularizações.",
|
||||
"Sucessão de nota de crédito e acerto anual na mesma parcela requer reconciliação sequencial própria;não somar dois deltas independentes. Vários documentos rectificativos sobre a mesma parcela ainda não consolidados por este percurso.",
|
||||
"Erros materiais51(6–7),incobráveis/recuperações51(8–9),mudanças de regime e regularizações plurianuais não automatizados. Não se inventa percentagem/prazo de activos. O registo27RCIVA não constitui esse cálculo.",
|
||||
"O motor mensal mantém2026/MZN. Um prazo51(4) que alcance2027 é conservado no relatório;lançamento em2027 permanece fora do âmbito mensal demonstrado.",
|
||||
"Origem de bases reduzidas sem IVA liquidado consolidado mantém14/16/17/18 indeterminados,também quando só existem eventosE no livro do período.",
|
||||
"17/18 são diferenças antes de créditos anteriores;19–27,pagamento/reembolso e restituição de bebidas mantêmL3. Formalidades digitais não automatizadas e cálculo fiscal restante dos ramosL1 limitados continuam identificados.",
|
||||
"Divergência de remissões textuais11/12 versus caixas12/13 mantém-se identificada;P001 continua exclusivamente05/06."
|
||||
],
|
||||
"schema_changes": false,
|
||||
"active_writes": false,
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisitions.ts",
|
||||
"sha256_LF": "2bca130d43808e8e0b773d63591f9711462fc79ba7cc231821ce693d4bd010f9"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-bc.ts",
|
||||
"sha256_LF": "4ce47274449e075e5e22a02f7bc497e4df6925c1d628f1e9cd057748d016012f"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-allocation.ts",
|
||||
"sha256_LF": "df960bab63e89c3b3cea3c61b5c57481194b19d5dd0894830a181cdbe10dc972"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-regularizations.ts",
|
||||
"sha256_LF": "72f432ab11a09ff4a1893ca59c9f19b88d6e709ba59842b087630fd7623e24c9"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "0af55cadc0bf2cd0b88673653c6915fcb36e7f3ad61b34721e44b2865da29d42"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-allocation.tsx",
|
||||
"sha256_LF": "222204ee4c699b2cbddb9cb921a37397f6be313a215905163ee135aa2b26f104"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-regularizations.tsx",
|
||||
"sha256_LF": "9b551a10613410210e5106f8b2de5a9c1d596d1d793a12a4c0c6cae06d5b32e2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,31 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 25/10/2026 — L2-E emQA;D/E preparados conjuntamente,comF
|
||||
|
||||
ABC/F permanece instalado819ae791,recibo855ab066.D aceite emQA2c2a2f20.E concluído emQA **no âmbito abaixo**,sem afirmar automatização de todas as regularizações.L1 conserva129fechos+P058/P084/P085+P001=133,todas as linhas preservadas;P001 apenas05/06.Restituição de bebidas continuaL3.
|
||||
|
||||
Na aquisição existente,«E — activos e regularizações por evento» permite escolher resultado original congelado e parcelas,conservando documento/linha,moeda,mês,dedução exacta e condiçõesC/D. Escolher outra versão é adopção expressa;os restantes dados do evento mantêm-se.Pressupostos de projecção continuam identificados;submissão fiscal ocorre fora do portal.
|
||||
|
||||
**Acerto anual22(6):**indicar bases anuais finais22(4–5),semIVA,incluindo fora de campo e excluindo vendas de activo usado/imobiliárias-financeiras acessórias;confirmar âmbito. Quociente final à centésima superior22(8).Diferença entre IVA elegível antesD,já limitado porC,vezes proporção final,e o deduzido congelado. Último período do ano;campo12 se positiva,campo13 se negativa. Não recalcular nem substituir a dedução original.Não se aplica genericamente a afectação real ou18(6)+mista,que continua pendente.
|
||||
|
||||
**Activos:**controlo individual27RCIVA,Decreto7/2008,BR16PDF5,p103:documento/linha,data,IVA suportado/deduzido e versão. Reutiliza08;numa importação,pode identificar expressamente «Bem de investimento importado»,mantendo11 e condições aduaneiras. O registo de activos não inventa amortização ou regularização plurianual.
|
||||
|
||||
**Rectificação pelo adquirente51(4):**seleccionar uma parcela,documento rectificativo do fornecedor,data de recepção,motivo e dedução corrigida documentada;confirmar manutenção das condições de direito.Reduções/anulações corrigem a dedução a favor do Estado,na recepção ou período seguinte. Acréscimos,erros,mudança de direito e múltiplos documentos sucessivos exigem percurso próprio. O prazo que alcance2027 conserva-se no relatório;motor mensal ainda2026/MZN.
|
||||
|
||||
**Guardar e conferirF:**guardarE;retirar declarações contraditórias de ausência12/13 e confirmar cobertura de todas as regularizações do período;actualizar expressamente o apuramento parcial. F soma fracções exactas,12em15 e13em16,sem dupla dedução. Aquisição editada após a origem congelada pede reconciliação;dados incompletos/resultados desactualizados não viramzero. Evita repetir documento/linha/ano mesmo mudando revisão;acerto anual+nota de crédito na mesma parcela não são somados sem reconciliação sequencial.
|
||||
|
||||
ExemploMZN:12=5,28;13=4/3;14=1000;15=29,44;16=484/3;17=9892/75;18=0. Decimais até6casas são apresentação técnica,sem arredondamento declarativo presumido. Bases reduzidas semIVA liquidado consolidado mantêm14/16/17/18 indeterminados,inclusive quando apenas há eventosE no livro.17/18 antecedem créditos anteriores;19–27/pagamento/reembolso permanecemL3.
|
||||
|
||||
**LimitesE:**erros51(6–7),incobráveis/recuperações51(8–9),mudanças de regime,regularização plurianual e sequência de vários eventos na mesma parcela não estão automatizados. Mantêm fonte/evento/prazo e trabalho fiscal próprios;nenhum fecho silencioso. Divergência textual11/12 versus caixas12/13 conservada,sem reabrirP001.
|
||||
|
||||
31testes dirigidos(22anteriores+9E),API1/94asserções com guardaQA55441/rollback,tipos/build e componente real conferidos. API conserva exactamente o rascunho,origensD,históricos e mapas anteriores. Chaves compactas evitam repetir o conteúdo congelado e mantêm o exemplo dentro do limite existente;nenhum limite de validação foi alargado.
|
||||
|
||||
Manifesto único:docs/investmoz/simulations/l2-de-delivery-manifest-20261025.json.A entrega exige conferência fresca de main/concorrência,autenticação,backup/restauro e delta instalado pelo processoGit existente.Não executados neste incrementoQA.Relatório:docs/investmoz/simulations/l2-e-assets-regularizations-qa-20261025.json.Provas:outputs/iva-l2-e-20261025/ui-recovered-totals.png eui-recovered-asset.png.
|
||||
|
||||
## 25/10/2026 — inventário anterior à implementação/testesE
|
||||
|
||||
ABC/F instalado819ae791,recibo855ab066.L1129fechos/quatroresiduais preservados,sem perdas confirmadas. E reutiliza livro/resultadosD congelados,sem recalcular históricos;inventário de activos e regularizações por evento. Cotejo dirigido22(1–8),Lei13/2016PDF16/p1426(274),e18(6),Lei10/2025PDF13;sem reconstruir motor. Comunicação prévia22(2) é condição da fonte,sem novo aprovador. Regularização anual22(6) segueE.
|
||||
|
||||
## 25/10/2026 — L2-D emQA,comF e dependências deE
|
||||
|
||||
ABC/F entregue819ae791,recibo855ab066.D concluído emQA no âmbito demonstrado,sem instalação nova.L1 operacional com limitações:129entregues+P058/P084/P085+P001=133;nenhuma linhaL1 alterada.LivrosABC e históricos preservados,actuais e comparados integralmente.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
|
||||
import {useState} from 'react';
|
||||
import {newRegularization,regularizationBook,regularizationSources,saveRegularizationBook,recordRegularizations,regularizationReportRows,frozenAssetRows,type RegularizationEvent,type RegularizationBook} from '@/lib/iva-regularizations';
|
||||
export function AcquisitionRegularizations({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}){
|
||||
const[error,setError]=useState('');let b:RegularizationBook;
|
||||
try{b=regularizationBook(draft);}catch(cause){return<p role="alert">{String(cause)}</p>;}
|
||||
const save=(next:RegularizationBook)=>{try{onChange('modelo_a_snapshot',saveRegularizationBook(draft,next));setError('');}catch(cause){setError(String(cause));}};
|
||||
const change=(id:string,patch:Partial<RegularizationEvent>)=>save({...b,events:b.events.map(e=>e.id===id?{...e,...patch}:e)});
|
||||
const sources=regularizationSources(draft);
|
||||
const add=(kind:RegularizationEvent['kind'])=>save({...b,events:[...b.events,newRegularization(crypto.randomUUID(),kind,sources.at(-1)??null)]});
|
||||
const labels={scope:'As parcelas e o âmbito correspondem ao evento,sem estender o direito a outros bens/operações',recorded:'O valor original foi deduzido ou é pressuposto explicitamente assumido nesta simulação',annual:'Bases finais do ano completo22(4),semIVA,incluindo operações fora de campo',exclusions:'Excluí vendas de activo usado e operações imobiliárias/financeiras acessórias22(5)',supplierCorrection:'O fornecedor anulou/reduziu a operação e emitiu o documento rectificativo',rightUnchanged:'O documento conserva o âmbito e as condições de dedução;sem alteração de direito'};
|
||||
return<details className="rounded border p-3"><summary>E — activos e regularizações por evento</summary>
|
||||
<p>Reutiliza resultados congelados de D e documentos existentes. O acerto anual usa o IVA elegível antes de D, já limitado por C, e compara com o deduzido. Uma rectificação da compra tem documento e prazo próprios. Guardar E não altera as deduções originais; depois actualize expressamente o apuramento parcial para conferir F.</p>
|
||||
<p>18.º(6) com afectação mista continua pendente. Não existe regularização plurianual genérica de activos,correcção de erros ou incobráveis automatizada neste percurso. Submissão fiscal fora do InvesteMoz;19–27/restituição em L3.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" onClick={()=>add('annual')}>Preparar acerto anual do pro rata</button>{' '}
|
||||
<button type="button" onClick={()=>add('purchase_correction')}>Preparar rectificação da dedução pelo adquirente</button>
|
||||
{b.events.map((e,index)=><fieldset className="rounded border p-3" key={e.id}><legend>{index+1}. {e.kind==='annual'?'Acerto anual22(6)':'Rectificação51(4)'}</legend>
|
||||
<label>Resultado original congelado<select value={sources.findIndex(s=>JSON.stringify(s)===JSON.stringify(e.source))} onChange={event=>{const source=sources[Number(event.target.value)];if(source)change(e.id,{source:structuredClone(source),rowIds:source.inputs.filter(r=>e.kind!=='annual'||r.d?.method==='pro_rata').map(r=>r.id)});}}><option value={-1}>Seleccione uma versão</option>{sources.map((s,i)=><option key={i} value={i}>Revisão{s.revision} — {s.calculatedAt}</option>)}</select></label>
|
||||
<p>Escolher outra versão adopta expressamente as parcelas dessa versão,conservando os outros dados do evento. Desmarque parcelas fora do âmbito;não pode regularizar a mesma aquisição duas vezes.</p>
|
||||
{e.source?.inputs.map(r=><label className="block" key={r.id}><input type="checkbox" checked={e.rowIds.includes(r.id)} onChange={event=>change(e.id,{rowIds:event.target.checked?[...e.rowIds,r.id]:e.rowIds.filter(id=>id!==r.id)})}/>{r.description||r.id} — {r.documentRef} — {r.period} — {r.origin==='projected'?'Pressuposto de simulação':'Efectivo declarado'}</label>)}
|
||||
<label>Período da regularização<input type="month" value={e.period} min="2026-01" max="2026-12" onChange={v=>change(e.id,{period:v.target.value})}/></label>
|
||||
<label>Documento/fonte do evento<input value={e.reference} onChange={v=>change(e.id,{reference:v.target.value})}/></label>
|
||||
<label>Motivo e âmbito do evento<input value={e.reason} onChange={v=>change(e.id,{reason:v.target.value})}/></label>
|
||||
{e.kind==='annual'?<><label>Operações anuais finais com direito — sem IVA (MZN)<input value={e.numerator} onChange={v=>change(e.id,{numerator:v.target.value})}/></label><label>Total anual final22(4–5) — sem IVA (MZN)<input value={e.denominator} onChange={v=>change(e.id,{denominator:v.target.value})}/></label></>:<><label>Data de recepção do documento rectificativo<input type="date" value={e.receivedDate} onChange={v=>change(e.id,{receivedDate:v.target.value})}/></label><label>Dedução corrigida documentada (MZN)<input value={e.correctedDeductible} onChange={v=>change(e.id,{correctedDeductible:v.target.value})}/></label></>}
|
||||
{Object.entries(labels).filter(([k])=>['scope','recorded',...(e.kind==='annual'?['annual','exclusions']:['supplierCorrection','rightUnchanged'])].includes(k)).map(([k,label])=><label className="block" key={k}>{label}<select value={e.facts[k]??''} onChange={v=>change(e.id,{facts:{...e.facts,[k]:v.target.value as ''|'yes'|'no'}})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>)}
|
||||
<button type="button" onClick={()=>save({...b,events:b.events.filter(row=>row.id!==e.id)})}>Remover evento em preparação (histórico conservado)</button>
|
||||
</fieldset>)}
|
||||
{[...new Set(b.events.map(e=>e.period))].filter(Boolean).map(period=><label className="block" key={period}><input type="checkbox" checked={b.completePeriods.includes(period)} onChange={e=>save({...b,completePeriods:e.target.checked?[...b.completePeriods,period]:b.completePeriods.filter(p=>p!==period)})}/>Reconciliei todas as regularizações deste período — {period};os outros motivos não automatizados foram conferidos.</label>)}
|
||||
<p>Quando existirem eventos E, retire as declarações «Sem operações» nos campos12/13 desse período. Uma cobertura incompleta mantém os totais afectados indeterminados.</p>
|
||||
<button type="button" onClick={()=>{try{save(recordRegularizations(draft,b));}catch(cause){setError(String(cause));}}}>Calcular e guardar regularizações E</button>
|
||||
{[...regularizationReportRows(draft),...frozenAssetRows(draft)].map(([,label,value,source],i)=><p key={i}><strong>{label}</strong>: {value}<small className="block">{source}</small></p>)}
|
||||
</details>;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import {AcquisitionRegularizations} from './acquisition-regularizations';
|
||||
import { AcquisitionOperationLink } from './acquisition-operation-link';
|
||||
import { AcquisitionImportsExclusions } from './acquisition-imports-exclusions';
|
||||
import { AcquisitionAllocationEditor } from './acquisition-allocation';
|
||||
@@ -49,6 +50,7 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
|
||||
<label>Descrição<input className="block rounded border p-2" value={row.description} onChange={e=>change(row.id,{description:e.target.value})}/></label>
|
||||
{row.sourceMonth===null && <label>Reutilizar valor existente, sem presumir que o orçamento é uma factura<select className="block rounded border p-2" value="" onChange={e=>run(()=>{const ref=references.find(r=>r.key===e.target.value);if(ref)change(row.id,{sourceMonth:ref.month,sourcePeriod:ref.period,sourceField:ref.kind,sourceAmount:ref.amount,sourceCurrency:ref.currency,...(ref.kind==='asset'?{}:{period:ref.period})});})}><option value="">Escolher apenas se corresponde a esta aquisição</option>{references.filter(ref=>ref.kind===(row.field==='08'?'asset':row.field==='10'?'operating':'inventory')).map(ref=><option key={ref.key} value={ref.key}>{ref.label}: {ref.amount??'sem informação'} {ref.currency}</option>)}</select></label>}
|
||||
{select(row,'field','Categoria',[['08','08 — Imobilizado'],['09','09 — Existências'],['10','10 — Outros bens e serviços'],...(row.bc?[['11','11 — Importações de bens'] as [string,string]]:[])])}
|
||||
{row.field==='11'&&<label><input type="checkbox" checked={row.investmentAsset??false} onChange={e=>change(row.id,{investmentAsset:e.target.checked})}/>Bem de investimento importado — incluir no controlo de activos;conserva campo11 e condições próprias da importação.</label>}
|
||||
{select(row,'origin','Natureza dos dados',[['projected','Projectados / pressupostos'],['effective','Efectivos declarados']])}
|
||||
<label>Período de dedução<input className="block rounded border p-2" type="month" value={row.period} onChange={e=>change(row.id,{period:e.target.value})}/></label>
|
||||
{row.sourceMonth!==null && <label>O valor de origem corresponde a<select className="block rounded border p-2" value={row.sourceBasis} onChange={e=>{const basis=e.target.value as Acquisition['sourceBasis'];change(row.id,{sourceBasis:basis,...(basis==='net'&&!row.net?{net:row.sourceAmount??''}:basis==='gross'&&!row.gross?{gross:row.sourceAmount??''}:{})});}}><option value="">Por classificar</option><option value="net">Valor sem IVA</option><option value="gross">Valor com IVA</option></select></label>}
|
||||
@@ -72,6 +74,7 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
|
||||
{row.sourceMonth===null && <button type="button" className="mr-3 underline" onClick={()=>save({...book,rows:book.rows.filter(r=>r.id!==row.id)})}>Remover esta aquisição introduzida directamente</button>}
|
||||
{row.sourceMonth!==null && <button type="button" className="underline" onClick={()=>run(()=>{const ref=acquisitionPlanReferences(draft).find(r=>r.month===row.sourceMonth && r.kind===(row.sourceField??'inventory'));if(!ref)throw new Error('Origem não disponível; conserve o registo e reveja o plano.');if(draft.currency!==row.sourceCurrency)throw new Error('Moeda divergente; não adoptar montantes de outra moeda.');change(row.id,{sourceAmount:ref.amount,sourcePeriod:ref.period,...(row.sourceBasis==='net'?{net:ref.amount??'',gross:''}:row.sourceBasis==='gross'?{gross:ref.amount??'',net:''}:{})});})}>Adoptar explicitamente o valor actual do plano (conserva histórico)</button>}
|
||||
</details>)}
|
||||
<AcquisitionRegularizations draft={draft} onChange={onChange}/>
|
||||
{book.periods.map(period=><details key={period.period} className="rounded border p-3"><summary>{period.period} — cobertura das parcelas — {period.complete && period.absent.length===acquisitionExcludedFields.length?'confirmada':'por completar'}</summary>
|
||||
{book.rows.some(r=>r.bc)&&<label className="block"><input type="checkbox" checked={period.importsComplete??false} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,importsComplete:e.target.checked}:p)})}/> Incluí todas as importações de bens/campo11 deste período,mesmo isentas ou sem direito;sem somar novamente IVA de serviços incluídos na base.</label>}
|
||||
<label className="block"><input type="checkbox" checked={period.complete} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,complete:e.target.checked}:p)})}/> Incluí todas as aquisições08–10 deste período, incluindo as sem direito à dedução; uma lista vazia significa que não houve essas aquisições.</label>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {evaluateAcquisitionBC,validAcquisitionBC,acquisitionBCSource,acquisition
|
||||
import { linkedOperationRight, validAcquisitionRightLink, acquisitionRightVersion, acquisitionRightSource, type AcquisitionRightLink, type AcquisitionEligibility } from './iva-acquisition-right.ts';
|
||||
import { acquisitionTreatmentIssue, acquisitionContinuityVersion, formerExplorationSource, type AcquisitionTreatment } from './iva-acquisition-continuity.ts';
|
||||
import { addFractions, decimalFraction, fractionLabel, fractionPresentation, positiveFraction, subtractFractions, type FiscalFraction } from './fiscal-rational.ts';
|
||||
import {regularizationFields,regularizationSourceKey,regularizationReportRows,frozenAssetRows,validateRegularizationStore} from './iva-regularizations.ts';
|
||||
|
||||
export const acquisitionVersion = 'modelo-a-internal-acquisitions/1';
|
||||
export const acquisitionBasis = 'CIVA arts.18(1)(a),18(2) (Lei10/2025),18(3–5),19(1)(a),19(2),20 (Lei22/2022),21(1–3); âmbito interno, afectação exclusiva, mesmo período, sem exclusões ou dedução parcial.';
|
||||
@@ -23,6 +24,7 @@ export type Acquisition = {
|
||||
deductionLink?: AcquisitionRightLink;
|
||||
bc?:AcquisitionBC;
|
||||
d?:AcquisitionAllocation;
|
||||
investmentAsset?:boolean;
|
||||
acquisitionTreatmentOrigin?: {id:'former_exploration_exemption';source:string};
|
||||
};
|
||||
export type AcquisitionResult = {id:string; net:string|null; vat:string|null; gross:string|null; deductible:string|null; reason:string; eligibility?:AcquisitionEligibility;deductibleExact?:FiscalFraction;nonDeductibleExact?:FiscalFraction;allocationCalculation?:{version:string;ratio:FiscalFraction;rawRatio:FiscalFraction;before:FiscalFraction;source:string}};
|
||||
@@ -44,7 +46,9 @@ export function readAcquisitionStore(raw:unknown):Store {
|
||||
if (!raw) return {version:1,books:{}};
|
||||
const value=JSON.parse(String(raw));
|
||||
if (value?.version!==1 || !value.books || typeof value.books!=='object' || Array.isArray(value.books)) throw new Error('Compras fiscais guardadas não recuperáveis; dados preservados.');
|
||||
validateRegularizationStore(value);
|
||||
for(const book of Object.values(value.books) as AcquisitionBook[]) {
|
||||
if(book?.rows?.some(row=>row.investmentAsset!==undefined&&typeof row.investmentAsset!=='boolean'))throw new Error('Classificação do activo não recuperável;originais preservados.');
|
||||
if(book?.rows?.some(row=>row.d!==undefined&&!validAcquisitionAllocation(row.d)))throw new Error('AfectaçãoD não recuperável;originais preservados.');
|
||||
if(book?.rows?.some(row=>row.bc!==undefined&&!validAcquisitionBC(row.bc))||book?.periods?.some(p=>p.importsComplete!==undefined&&typeof p.importsComplete!=='boolean'))throw new Error('CondiçõesB/C ou cobertura de importações inválidas;originais preservados.');
|
||||
if(book?.rightVersion!==undefined&&book.rightVersion!==acquisitionRightVersion)throw new Error('Versão do direito da aquisição não recuperável; original preservado.');
|
||||
@@ -141,7 +145,8 @@ function sourceKey(draft:Record<string,unknown>,book:AcquisitionBook):string {
|
||||
// Change detection only, never an authorization or authenticity token.
|
||||
let hash=14695981039346656037n;for(let i=0;i<raw.length;i++)hash=BigInt.asUintN(64,(hash^BigInt(raw.charCodeAt(i)))*1099511628211n);
|
||||
const operations=(readAcquisitionStore(draft.modelo_a_snapshot) as Store & {salesOperations?:Record<string,unknown>}).salesOperations?.[acquisitionScope(draft)];
|
||||
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}${book.rows.some(r=>r.acquisitionTreatment||r.acquisitionTreatmentOrigin)?`:${acquisitionContinuityVersion}`:''}${book.rightVersion||book.rows.some(r=>r.deductionLink)?`:${acquisitionRightVersion}`:''}${book.rows.some(r=>r.bc)?`:${acquisitionBCSourceKey(draft,book.rows)}`:''}${book.rows.some(r=>r.d)?`:${acquisitionAllocationVersion}:${allocationSourceKey(draft,book.rows)}`:''}`;
|
||||
const e=regularizationSourceKey(draft);
|
||||
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}${book.rows.some(r=>r.acquisitionTreatment||r.acquisitionTreatmentOrigin)?`:${acquisitionContinuityVersion}`:''}${book.rightVersion||book.rows.some(r=>r.deductionLink)?`:${acquisitionRightVersion}`:''}${book.rows.some(r=>r.bc)?`:${acquisitionBCSourceKey(draft,book.rows)}`:''}${book.rows.some(r=>r.d)?`:${acquisitionAllocationVersion}:${allocationSourceKey(draft,book.rows)}`:''}${e?`:E:${e}`:''}`;
|
||||
}
|
||||
function location(draft:Record<string,unknown>):string {
|
||||
const labels=draft.selection_labels as Record<string,unknown>|undefined;
|
||||
@@ -206,7 +211,13 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
|
||||
}
|
||||
if(!input?.complete) reasons.push('Aquisições08–10: confirme que todas as compras do período foram incluídas, mesmo quando não existam.');
|
||||
for(const row of book.rows.filter(r=>r.period===period)) {const r=rows.find(x=>x.id===row.id)!;if(r.deductible===null) reasons.push(`${row.description||row.id}: ${r.reason}`);}
|
||||
for(const field of excluded) if(fields[field]===null) reasons.push(`Campo${field}: fora deste lote, sem declaração expressa de inexistência de operações.`);
|
||||
const adjustments=regularizationFields(draft,period,book.rows);
|
||||
if(adjustments){
|
||||
if(input?.absent.some(f=>f==='12'||f==='13'))throw Error('Existem regularizaçõesE neste período. Retire a declaração de ausência12/13 e confirme a coberturaE,sem apagar o histórico.');
|
||||
for(const f of ['12','13'])fields[f]=adjustments.fields[f]?fractionPresentation(adjustments.fields[f]!).decimal:null;
|
||||
reasons.push(...adjustments.reasons);
|
||||
}
|
||||
for(const field of excluded) if(fields[field]===null) reasons.push(`Campo${field}: sem apuramento correspondente ou declaração expressa de inexistência de operações.`);
|
||||
fields['14']=sum(['01','03','05','06','07'].map(f=>fields[f]));
|
||||
fields['15']=sum(['08','09','10','11','12'].map(f=>fields[f]));
|
||||
fields['16']=sum(['02','04','13'].map(f=>fields[f]));
|
||||
@@ -214,14 +225,15 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
|
||||
fields['17']=state===null||taxpayer===null?null:money(state>taxpayer?state-taxpayer:0n);
|
||||
fields['18']=state===null||taxpayer===null?null:money(taxpayer>state?taxpayer-state:0n);
|
||||
const classified=salesOperationsCurrent(draft,operations)?operations.result?.periods.find(p=>p.period===period):null;
|
||||
if(classified?.exactFields||book.rows.some(r=>r.bc||r.d)) {
|
||||
if(classified?.exactFields||book.rows.some(r=>r.bc||r.d)||adjustments) {
|
||||
const exactFields:Record<string,FiscalFraction|null>={};
|
||||
for(const f of ['01','02','03','04','05','06','07','08','09','10','11','12','13'])exactFields[f]=classified?.exactFields?.[f]??(fields[f]===null?null:decimalFraction(fields[f]!));
|
||||
for(const f of ['08','09','10','11'])if(fields[f]!==null){const matching=book.rows.filter(r=>r.period===period&&r.field===f).map(r=>rows.find(v=>v.id===r.id)!);exactFields[f]=addFractions(matching.map(r=>r.deductibleExact??decimalFraction(r.deductible!)));}
|
||||
if(adjustments)for(const f of ['12','13'])exactFields[f]=adjustments.fields[f];
|
||||
for(const [f,parts] of [['14',['01','03','05','06','07']],['15',['08','09','10','11','12']],['16',['02','04','13']]] as const)exactFields[f]=parts.some(k=>exactFields[k]===null)?null:addFractions(parts.map(k=>exactFields[k]!));
|
||||
exactFields['17']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['16'],exactFields['15'])):null;
|
||||
exactFields['18']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['15'],exactFields['16'])):null;
|
||||
if(book.rows.some(r=>r.d)&&allocationHasUnconsolidatedOutputs(draft,period)){
|
||||
if((book.rows.some(r=>r.d)||adjustments)&&allocationHasUnconsolidatedOutputs(draft,period)){
|
||||
for(const f of ['14','16','17','18'])exactFields[f]=null;
|
||||
reasons.push('Origem18(6) identifica base/proporção,sem IVA liquidado consolidado no livro de operações. Não presumir impostozero nem duplicar a base:totais14/16/17/18 permanecem indeterminados;dedução15 conserva apuramento próprio.');
|
||||
}
|
||||
@@ -263,6 +275,6 @@ export function acquisitionReportRows(draft:Record<string,unknown>):Array<[strin
|
||||
if(period.exactFields)out.push(['IVA mensal',`Modelo A — ${period.period} — precisão exacta`,Object.entries(period.exactFields).map(([f,v])=>`${f}: ${v?fractionLabel(v):'Indeterminado'}`).join('; '),'Fracções em MZN; apresentação decimal técnica, sem regra declarativa presumida.']);
|
||||
if(period.reasons.length)out.push(['IVA mensal',`Modelo A — ${period.period} — pendências`,period.reasons.join(' '),result.sources]);
|
||||
}
|
||||
return out;
|
||||
return [...out,...regularizationReportRows(draft),...frozenAssetRows(draft)];
|
||||
} catch {return [['IVA mensal','Compras — Modelo A','Não foi possível recuperar os dados guardados','Preservados, sem substituição por zero']];}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import type {AcquisitionCalculation,Acquisition,AcquisitionResult} from './iva-acquisitions.ts';
|
||||
import {addFractions,decimalFraction,fraction,fractionLabel,fractionPresentation,subtractFractions,positiveFraction,type FiscalFraction} from './fiscal-rational.ts';
|
||||
|
||||
export const regularizationSource='L2-E: CIVA22(4–8),Lei13/2016 PDF16/p1426(274),acerto anual no último período do ano;CIVA51(1/4),PDF21/p1426(279),rectificação da dedução pelo adquirente até ao fim do período seguinte à recepção. Decreto7/2008,RCIVA27,BR16 PDF5/p103,registo individual de activos: data,IVA suportado e deduzido. Caixas ModeloA12/13;divergência de remissões textuais11/12 conservada,sem alterar P001/05–06.';
|
||||
type Choice=''|'yes'|'no';
|
||||
export type RegularizationEvent={id:string;kind:'annual'|'purchase_correction';period:string;source:AcquisitionCalculation|null;rowIds:string[];numerator:string;denominator:string;reference:string;receivedDate:string;correctedDeductible:string;reason:string;facts:Record<string,Choice>};
|
||||
export type RegularizationResult={id:string;period:string;status:'determined'|'indeterminate';reason:string;delta:FiscalFraction|null;rawRatio?:FiscalFraction;ratio?:FiscalFraction;details:string;source:string};
|
||||
type Calculation={version:1;calculatedAt:string;key:string;events:RegularizationResult[]};
|
||||
export type RegularizationBook={version:1;events:RegularizationEvent[];completePeriods:string[];result?:Calculation;history?:Calculation[]};
|
||||
type FrozenLine={input:Acquisition;result:AcquisitionResult};
|
||||
const scope=(draft:Record<string,unknown>)=>String(draft.context_opportunity||'general');
|
||||
function store(draft:Record<string,unknown>):Record<string,unknown>{const s=JSON.parse(String(draft.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books)throw Error('Origem fiscal não recuperável;conserve os originais.');return s;}
|
||||
export function validateRegularizationStore(s:Record<string,unknown>):void{
|
||||
if(s.regularizations===undefined)return;
|
||||
if(!s.regularizations||typeof s.regularizations!=='object'||Array.isArray(s.regularizations))throw Error('Mapa de regularizações não recuperável;originais preservados.');
|
||||
for(const value of Object.values(s.regularizations))assertRegularizationBook(value as RegularizationBook);
|
||||
}
|
||||
function assertRegularizationBook(value:RegularizationBook):void{
|
||||
const badSource=(s:AcquisitionCalculation|null)=>s!==null&&(!s||typeof s!=='object'||!Array.isArray(s.inputs)||!Array.isArray(s.rows)||!Array.isArray(s.periods)||!Number.isInteger(s.revision)||!['currency','opportunity','calculatedAt','sourceKey'].every(k=>typeof s[k as keyof AcquisitionCalculation]==='string'));
|
||||
if(!value||value.version!==1||!Array.isArray(value.events)||!Array.isArray(value.completePeriods)||value.completePeriods.some(p=>typeof p!=='string')||value.history!==undefined&&!Array.isArray(value.history)||value.events.some(e=>!e||!['annual','purchase_correction'].includes(e.kind)||!['id','period','numerator','denominator','reference','receivedDate','correctedDeductible','reason'].every(k=>typeof e[k as keyof RegularizationEvent]==='string')||!Array.isArray(e.rowIds)||e.rowIds.some(id=>typeof id!=='string')||!e.facts||typeof e.facts!=='object'||Array.isArray(e.facts)||Object.values(e.facts).some(v=>!['','yes','no'].includes(v))||badSource(e.source)))throw Error('Regularizações não recuperáveis;originais preservados,sem substituir por zero.');
|
||||
if(value.result&&(!Array.isArray(value.result.events)||value.result.version!==1||typeof value.result.key!=='string'||typeof value.result.calculatedAt!=='string'||value.result.events.some(e=>!e||typeof e.id!=='string'||typeof e.period!=='string'||!['determined','indeterminate'].includes(e.status)||e.delta!==null&&(!e.delta||! /^-?\d{1,80}$/.test(e.delta.numerator)||!/^\d{1,80}$/.test(e.delta.denominator)||BigInt(e.delta.denominator)===0n))))throw Error('Resultado de regularizações não recuperável;originais preservados.');
|
||||
}
|
||||
export function regularizationBook(draft:Record<string,unknown>):RegularizationBook{
|
||||
const value=(store(draft).regularizations as Record<string,RegularizationBook>|undefined)?.[scope(draft)];
|
||||
if(!value)return{version:1,events:[],completePeriods:[]};
|
||||
assertRegularizationBook(value);
|
||||
return value;
|
||||
}
|
||||
export function saveRegularizationBook(draft:Record<string,unknown>,book:RegularizationBook):string{const s=store(draft);return JSON.stringify({...s,regularizations:{...(s.regularizations as object??{}),[scope(draft)]:book}});}
|
||||
export function regularizationSources(draft:Record<string,unknown>):AcquisitionCalculation[]{const b=(store(draft).books as Record<string,{result?:AcquisitionCalculation;history?:AcquisitionCalculation[]}>)[scope(draft)];return b?[...(b.history??[]),...(b.result?[b.result]:[])].filter((v,i,a)=>a.findIndex(x=>JSON.stringify(x)===JSON.stringify(v))===i):[];}
|
||||
export function newRegularization(id:string,kind:RegularizationEvent['kind'],source:AcquisitionCalculation|null):RegularizationEvent{return{id,kind,period:source?source.inputs[0]?.period.slice(0,4)+'-12':'',source:source?structuredClone(source):null,rowIds:source?.inputs.filter(r=>kind==='annual'?r.d?.method==='pro_rata':true).map(r=>r.id)??[],numerator:'',denominator:'',reference:'',receivedDate:'',correctedDeductible:'',reason:'',facts:{}};}
|
||||
function frozenLines(e:RegularizationEvent):FrozenLine[]{return e.rowIds.map(id=>({input:e.source!.inputs.find(r=>r.id===id)!,result:e.source!.rows.find(r=>r.id===id)!}));}
|
||||
function sourceAvailable(draft:Record<string,unknown>,e:RegularizationEvent):boolean{return !!e.source&®ularizationSources(draft).some(s=>JSON.stringify(s)===JSON.stringify(e.source));}
|
||||
function multiply(a:FiscalFraction,b:FiscalFraction){return fraction(BigInt(a.numerator)*BigInt(b.numerator),BigInt(a.denominator)*BigInt(b.denominator));}
|
||||
const validDate=(s:string)=>/^2026-\d{2}-\d{2}$/.test(s)&&!Number.isNaN(Date.parse(s))&&new Date(s).toISOString().slice(0,10)===s;
|
||||
function identity(e:RegularizationEvent,id:string):string{const r=e.source?.inputs.find(r=>r.id===id);return JSON.stringify([e.kind,r?.supplier.trim().toLowerCase(),r?.documentType,r?.documentRef.trim().toLowerCase(),r?.documentDate,r?.documentLine.trim(),e.kind==='annual'?e.period.slice(0,4):'']);}
|
||||
export function evaluateRegularization(draft:Record<string,unknown>,e:RegularizationEvent):RegularizationResult{
|
||||
const stop=(reason:string):RegularizationResult=>({id:e.id,period:e.period,status:'indeterminate',reason,delta:null,details:'Histórico preservado;sem lançamento automático.',source:regularizationSource});
|
||||
if(!/^2026-(0[1-9]|1[0-2])$/.test(e.period)||draft.currency!=='MZN'||e.source?.currency!=='MZN'||e.source.opportunity!==scope(draft)||!sourceAvailable(draft,e))return stop('Âmbito2026/MZN e resultado original congelado na mesma oportunidade por identificar;uma edição actual não substitui o resultado original.');
|
||||
if(!e.rowIds.length||new Set(e.rowIds).size!==e.rowIds.length)return stop('Seleccione parcelas originais distintas.');
|
||||
const lines=frozenLines(e);if(lines.some(l=>!l.input||!l.result||l.result.deductibleExact===undefined||l.result.eligibility?.status!=='eligible'||!l.input.documentRef.trim()))return stop('Parcela original sem dedução exacta/elegível/documento;não presumir dedução inicial.');
|
||||
if(!e.reference.trim()||!e.reason.trim()||e.facts.scope!=='yes'||e.facts.recorded!=='yes')return stop('Identifique documento/fonte,motivo,âmbito e montante originalmente deduzido (efectivo ou pressuposto da projecção).');
|
||||
let delta:FiscalFraction,details:string,rawRatio:FiscalFraction|undefined,ratio:FiscalFraction|undefined;
|
||||
if(e.kind==='annual'){
|
||||
if(!e.period.endsWith('-12')||lines.some(l=>l.input.period.slice(0,4)!==e.period.slice(0,4)||l.input.d?.method!=='pro_rata'||!l.result.allocationCalculation))return stop('Acerto22(6):apenas pro rata provisório do mesmo ano,no último período;afectação real e18(6) têm regras próprias.');
|
||||
if(e.facts.annual!=='yes'||e.facts.exclusions!=='yes')return stop('Confirme bases efectivas anuais22(4),semIVA,incluindo fora de campo,e exclusões22(5);não usar apenas vendas do mês.');
|
||||
if(!/^\d{1,15}(\.\d{1,6})?$/.test(e.numerator)||!/^\d{1,15}(\.\d{1,6})?$/.test(e.denominator))return stop('Bases anuais finais em falta ou inválidas.');
|
||||
const n=decimalFraction(e.numerator),d=decimalFraction(e.denominator);if(BigInt(d.numerator)===0n)return stop('Denominador anual zero;sem percentagem por defeito.');
|
||||
rawRatio=fraction(BigInt(n.numerator)*BigInt(d.denominator),BigInt(n.denominator)*BigInt(d.numerator));if(BigInt(rawRatio.numerator)>BigInt(rawRatio.denominator))return stop('Numerador anual excede denominador.');
|
||||
ratio=fraction((BigInt(rawRatio.numerator)*100n+BigInt(rawRatio.denominator)-1n)/BigInt(rawRatio.denominator),100n);
|
||||
delta=addFractions(lines.map(l=>subtractFractions(multiply(l.result.allocationCalculation!.before,ratio!),l.result.deductibleExact!)));
|
||||
details=lines.map(l=>`${l.input.documentRef}/linha${l.input.documentLine||'única'}:IVA elegível antesD${fractionLabel(l.result.allocationCalculation!.before)},deduzido${fractionLabel(l.result.deductibleExact!)},final${fractionLabel(multiply(l.result.allocationCalculation!.before,ratio!))}`).join('; ')+`;quociente final${fractionLabel(rawRatio)};aplicado${fractionLabel(ratio)}`;
|
||||
}else{
|
||||
if(lines.length!==1||!validDate(e.receivedDate))return stop('Rectificação51(4):seleccione uma linha e a data de recepção do documento rectificativo.');
|
||||
const received=e.receivedDate.slice(0,7),next=new Date(e.receivedDate+'T00:00:00Z');next.setUTCMonth(next.getUTCMonth()+1,1);const last=next.toISOString().slice(0,7);
|
||||
if(e.receivedDate<lines[0].input.documentDate||e.period<received||e.period>last||e.period<lines[0].input.period)return stop('Período fora da recepção/seguinte51(4),ou anterior à dedução original;sem alteração retroactiva.');
|
||||
if(e.facts.supplierCorrection!=='yes'||e.facts.rightUnchanged!=='yes')return stop('Confirme rectificação pelo fornecedor e manutenção do âmbito/condições da dedução. Mudança de direito exige enquadramento próprio.');
|
||||
let corrected:FiscalFraction;try{corrected=decimalFraction(e.correctedDeductible);}catch{return stop('Indique a dedução corrigida documentada,sem taxa ou dedução presumida.');}
|
||||
if(BigInt(subtractFractions(corrected,lines[0].result.deductibleExact!).numerator)>0n)return stop('Este percurso51(4) trata anulação/redução da dedução pelo adquirente;acréscimos ou erros têm fundamento/prazo próprios.');
|
||||
delta=subtractFractions(corrected,lines[0].result.deductibleExact!);details=`${lines[0].input.documentRef};deduzido${fractionLabel(lines[0].result.deductibleExact!)};corrigido${fractionLabel(corrected)};recebido${e.receivedDate};limite${last}`;
|
||||
}
|
||||
const sign=BigInt(delta.numerator);return{id:e.id,period:e.period,status:'determined',delta,...(rawRatio&&ratio?{rawRatio,ratio}:{}),reason:sign>0n?'A favor do sujeito passivo — campo12':sign<0n?'A favor do Estado — campo13':'Sem diferença;evento conservado,sem nova dedução',details:`${details};revisão original${e.source.revision}/${e.source.calculatedAt};${lines.some(l=>l.input.origin==='projected')?'Pressuposto de simulação;sem declaração fiscal submetida':'Dados efectivos declarados;submissão fora do portal'};${e.reference};${e.reason}`,source:regularizationSource};
|
||||
}
|
||||
// Change detection only. Keep frozen source values, without embedding them again in every calculation key.
|
||||
function fingerprint(value:unknown):string{const raw=JSON.stringify(value);let hash=14695981039346656037n;for(let i=0;i<raw.length;i++)hash=BigInt.asUintN(64,(hash^BigInt(raw.charCodeAt(i)))*1099511628211n);return`${raw.length}:${hash.toString(16)}`;}
|
||||
function key(book:RegularizationBook){return fingerprint([book.version,book.events,book.completePeriods]);}
|
||||
export function recordRegularizations(draft:Record<string,unknown>,book:RegularizationBook,now=new Date().toISOString()):RegularizationBook{
|
||||
if(new Set(book.events.map(e=>e.id)).size!==book.events.length)throw Error('Evento repetido;sem dupla regularização.');
|
||||
const identities=book.events.flatMap(e=>e.rowIds.map(id=>identity(e,id)));if(new Set(identities).size!==identities.length)throw Error('Mesma aquisição/ano ou documento já incluído noutra regularização deste motivo;consolide o evento original.');
|
||||
// Mixing an annual correction and a credit note on the same deduction needs sequencing, not two independent deltas.
|
||||
const seen=new Map<string,string>();for(const e of book.events)for(const id of e.rowIds){const k=identity({...e,kind:'purchase_correction'},id),prior=seen.get(k);if(prior&&prior!==e.kind)throw Error('Acerto anual e documento rectificativo sobre a mesma parcela exigem reconciliação sequencial;sem dupla correcção.');seen.set(k,e.kind);}
|
||||
const result:Calculation={version:1,calculatedAt:now,key:key(book),events:book.events.map(e=>evaluateRegularization(draft,e))};return{...book,...(book.result?{history:[...(book.history??[]),structuredClone(book.result)]}:{}),result};
|
||||
}
|
||||
export function regularizationsCurrent(draft:Record<string,unknown>,b:RegularizationBook):boolean{return !!b.result&&b.result.version===1&&b.result.key===key(b)&&b.result.events.length===b.events.length&&b.events.every(e=>sourceAvailable(draft,e)&&b.result!.events.filter(r=>r.id===e.id&&r.period===e.period).length===1);}
|
||||
export function regularizationFields(draft:Record<string,unknown>,period:string,currentRows?:Acquisition[]):{fields:Record<string,FiscalFraction|null>;reasons:string[]}|null{
|
||||
const b=regularizationBook(draft);if(!b.events.some(e=>e.period===period)&&!b.completePeriods.includes(period))return null;
|
||||
const events=b.result?.events.filter(e=>e.period===period)??[];
|
||||
if(currentRows&&b.events.some(e=>e.period===period&&e.source&&frozenLines(e).some(l=>l.input&¤tRows.some(r=>r.id===l.input.id&&JSON.stringify(r)!==JSON.stringify(l.input)))))return{fields:{'12':null,'13':null},reasons:['A aquisição actual difere da parcela congelada usada emE;reconcilie a diferença antes de somar a regularização,sem recalcular o histórico.']};
|
||||
if(!b.completePeriods.includes(period)||!regularizationsCurrent(draft,b)||events.some(e=>!e.delta))return{fields:{'12':null,'13':null},reasons:['RegularizaçõesE:complete/reconcilie o período e guarde o cálculo correspondente;sem zero presumido nem uso de resultado histórico.']};
|
||||
return{fields:{'12':addFractions(events.map(e=>positiveFraction(e.delta!))),'13':addFractions(events.map(e=>positiveFraction(fraction(-BigInt(e.delta!.numerator),BigInt(e.delta!.denominator)))) )},reasons:['E:apenas eventos documentados declarados completos;12/13 separados,sem recalcular aquisições históricas.']};
|
||||
}
|
||||
export function regularizationSourceKey(draft:Record<string,unknown>):string{const b=regularizationBook(draft);const reduced=(store(draft).regulatedReducedBases as Record<string,{rows:unknown[]}>|undefined)?.[scope(draft)]?.rows??[];return b.events.length||b.completePeriods.length?fingerprint([b,regularizationsCurrent(draft,b),reduced]):'';}
|
||||
export function regularizationReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{const b=regularizationBook(draft);if(!b.events.length)return[];return[['IVA mensal','RegularizaçõesE',regularizationsCurrent(draft,b)?'Resultado correspondente às entradas':'Histórico preservado;rever entradas/origem',regularizationSource],...(b.result?.events??[]).map(e=>['IVA mensal',`Regularização ${e.id} — ${e.period}`,`${e.status==='determined'?e.reason:'Indeterminado: '+e.reason};delta${e.delta?fractionLabel(e.delta):'indeterminado'}`,e.details+';'+e.source] as [string,string,string,string])];}
|
||||
export function frozenAssetRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{const sources=regularizationSources(draft),seen=new Set<string>(),out:Array<[string,string,string,string]>=[];for(const s of [...sources].reverse())for(const r of s.inputs.filter(r=>r.field==='08'||r.investmentAsset===true)){const k=JSON.stringify([r.supplier,r.documentRef,r.documentDate,r.documentLine]);if(seen.has(k))continue;seen.add(k);const v=s.rows.find(v=>v.id===r.id);out.push(['IVA mensal',`Activo — ${r.description||r.id}`,`${r.documentRef};linha${r.documentLine||'única'};aquisição${r.documentDate};IVA suportado${v?.vat??'indeterminado'};IVA deduzido${v?.deductibleExact?fractionLabel(v.deductibleExact):v?.deductible??'indeterminado'}`,`RCIVA27,Decreto7/2008 PDF5/p103;${s.currency};revisão${s.revision}/${s.calculatedAt};${r.origin==='projected'?'Pressuposto de simulação':'Dados efectivos declarados'};sem amortização fiscal ou regularização plurianual presumidas.`]);}return out;}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace Tests\Feature;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
final class IvaRegularizationDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_frozen_d_adjustments_assets_totals_and_history_recover_without_changing_other_books(): void
|
||||
{
|
||||
$target=DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test',$target->name);
|
||||
$this->assertSame(55441,(int)$target->port);
|
||||
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);
|
||||
$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));
|
||||
$draft=json_decode(file_get_contents(base_path('outputs/iva-l2-e-20261025/calculated.json')),true,flags:JSON_THROW_ON_ERROR);
|
||||
$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>[],'economic_snapshot'=>$draft],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');
|
||||
$recovered=$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame($draft['modelo_a_snapshot'],$recovered['modelo_a_snapshot']);
|
||||
$s=json_decode($recovered['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);
|
||||
$e=$s['regularizations']['opportunity:370'];
|
||||
$this->assertCount(2,$e['events']);$this->assertCount(1,$e['history']);
|
||||
$this->assertSame(['numerator'=>'132','denominator'=>'25'],$e['result']['events'][0]['delta']);
|
||||
$this->assertSame(['numerator'=>'4','denominator'=>'3'],$s['books']['opportunity:370']['result']['periods'][0]['exactFields']['13']);
|
||||
foreach(['12'=>'5.28','13'=>'1.333333','15'=>'29.44','16'=>'161.333333','17'=>'131.893333','18'=>'0.00'] as $field=>$value)$this->assertSame($value,$s['books']['opportunity:370']['result']['periods'][0]['fields'][(string)$field]);
|
||||
$frozen=json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-l2e-preserved.json')),true,flags:JSON_THROW_ON_ERROR);
|
||||
$prior=json_decode($frozen['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);
|
||||
$this->assertSame($prior['books']['opportunity:370']['result'],$s['books']['opportunity:370']['history'][array_key_last($s['books']['opportunity:370']['history'])]);
|
||||
$this->assertSame($prior['books']['opportunity:370']['result'],$e['events'][0]['source']);
|
||||
foreach($prior as $key=>$map){if($key==='version')continue;foreach($map as $context=>$original){if($key==='books'&&$context==='opportunity:370')continue;$this->assertSame($original,$s[$key][$context]);}}
|
||||
$this->assertSame('08',$s['books']['opportunity:372']['rows'][0]['field']);
|
||||
$this->assertSame(['numerator'=>'16','denominator'=>'3'],$s['books']['opportunity:372']['result']['rows'][0]['deductibleExact']);
|
||||
file_put_contents(base_path('outputs/iva-l2-e-20261025/recovered.json'),json_encode($recovered,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import {newRegularization,evaluateRegularization,recordRegularizations,saveRegularizationBook,regularizationBook,regularizationFields,regularizationsCurrent,frozenAssetRows} from '../../resources/js/lib/iva-regularizations.ts';
|
||||
import {acquisitionBook,recordAcquisitions,saveAcquisitionBook,acquisitionCurrent,acquisitionReportRows,readAcquisitionStore} from '../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {recordSales,saveSalesBook} from '../../resources/js/lib/iva-operations.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l2e-preserved.json',import.meta.url),'utf8'));
|
||||
export function regularizationFixture(){
|
||||
const draft=structuredClone(frozen);draft.context_opportunity='opportunity:370';draft.project_title='SYNTHETIC QA — L2-E activos e regularizações';
|
||||
const source=structuredClone(acquisitionBook(draft).result);
|
||||
const annual={...newRegularization('annual-2026','annual',source),numerator:'2',denominator:'3',reference:'SYNTHETIC QA contas anuais2026',reason:'SYNTHETIC QA âmbito anual da entidade',facts:{scope:'yes',recorded:'yes',annual:'yes',exclusions:'yes'}};
|
||||
const correction={...newRegularization('credit-note','purchase_correction',source),rowIds:['actual'],receivedDate:'2026-12-15',correctedDeductible:'4',reference:'SYNTHETIC QA nota de créditoNC1',reason:'SYNTHETIC QA redução da aquisição',facts:{scope:'yes',recorded:'yes',supplierCorrection:'yes',rightUnchanged:'yes'}};
|
||||
return{draft,annual,correction,book:{version:1,events:[annual,correction],completePeriods:['2026-12']}};
|
||||
}
|
||||
test('annual adjustment uses frozen C-limited input and actually deducted D,ceil final ratio and both signs',()=>{
|
||||
const{draft,annual}=regularizationFixture();let r=evaluateRegularization(draft,annual);assert.equal(r.status,'determined');assert.deepEqual(r.rawRatio,{numerator:'2',denominator:'3'});assert.deepEqual(r.ratio,{numerator:'67',denominator:'100'});assert.deepEqual(r.delta,{numerator:'132',denominator:'25'});assert.match(r.reason,/campo12/);
|
||||
r=evaluateRegularization(draft,{...annual,numerator:'1',denominator:'2'});assert.deepEqual(r.delta,{numerator:'-4',denominator:'25'});assert.match(r.reason,/campo13/);
|
||||
for(const patch of [{period:'2026-11'},{denominator:'0'},{numerator:'4'},{reference:''},{rowIds:['actual']},{rowIds:['missing']},{facts:{...annual.facts,annual:''}},{facts:{...annual.facts,recorded:''}}])assert.equal(evaluateRegularization(draft,{...annual,...patch}).delta,null,JSON.stringify(patch));
|
||||
const altered=structuredClone(draft);const book=acquisitionBook(altered);book.rows[1].d.numerator='3';altered.modelo_a_snapshot=saveAcquisitionBook(altered,book);assert.deepEqual(evaluateRegularization(altered,annual).delta,{numerator:'132',denominator:'25'},'current edits never recalculate historical deduction');
|
||||
const fabricated=structuredClone(annual);fabricated.source.rows[1].deductibleExact.numerator='0';assert.equal(evaluateRegularization(draft,fabricated).delta,null);
|
||||
});
|
||||
test('credit note keeps exact historical fraction,receipt deadline and source conditions;unsupported increase stays unknown',()=>{
|
||||
const{draft,correction}=regularizationFixture();const r=evaluateRegularization(draft,correction);assert.deepEqual(r.delta,{numerator:'-4',denominator:'3'});assert.match(r.reason,/campo13/);
|
||||
for(const patch of [{period:'2026-11'},{receivedDate:'2026-02-30'},{receivedDate:'2026-10-10'},{correctedDeductible:'6'},{correctedDeductible:''},{facts:{...correction.facts,supplierCorrection:''}},{rowIds:['actual','diesel']}])assert.equal(evaluateRegularization(draft,{...correction,...patch}).delta,null,JSON.stringify(patch));
|
||||
assert.deepEqual(evaluateRegularization(draft,{...correction,correctedDeductible:'0'}).delta,{numerator:'-16',denominator:'3'});
|
||||
});
|
||||
test('F uses exact 12/13 once,requires coverage and detects stale E without overwriting D history or other opportunities',()=>{
|
||||
let{draft,book}=regularizationFixture();const before=JSON.parse(draft.modelo_a_snapshot);book=recordRegularizations(draft,book);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);
|
||||
const acquired=acquisitionBook(draft);assert.equal(acquisitionCurrent(draft,acquired),false);assert.throws(()=>recordAcquisitions(draft,acquired),/ausência12\/13/);
|
||||
acquired.periods[0].absent=acquired.periods[0].absent.filter(f=>!['12','13'].includes(f));const adjusted=recordAcquisitions(draft,acquired);draft.modelo_a_snapshot=saveAcquisitionBook(draft,adjusted);
|
||||
const p=adjusted.result.periods[0];assert.deepEqual(['12','13','15','16','17','18'].map(f=>p.fields[f]),['5.28','1.333333','29.44','161.333333','131.893333','0.00']);assert.deepEqual(p.exactFields['13'],{numerator:'4',denominator:'3'});assert.ok(acquisitionCurrent(draft,adjusted));assert.ok(regularizationsCurrent(draft,book));
|
||||
assert.deepEqual(adjusted.history.at(-1),before.books['opportunity:370'].result);const after=JSON.parse(draft.modelo_a_snapshot);for(const[k,map]of Object.entries(before)){if(k==='version')continue;for(const[id,v]of Object.entries(map)){if(k==='books'&&id==='opportunity:370')continue;assert.deepEqual(after[k][id],v,k+id);}}
|
||||
for(const id of ['opportunity:368','opportunity:369','opportunity:371'])assert.ok(acquisitionCurrent({...draft,context_opportunity:id},after.books[id]));
|
||||
const incomplete={...book,completePeriods:[]};assert.equal(regularizationFields({...draft,modelo_a_snapshot:saveRegularizationBook(draft,incomplete)},'2026-12').fields['12'],null);
|
||||
const divergent=structuredClone(adjusted);divergent.rows[1].d.numerator='2';assert.equal(recordAcquisitions(draft,divergent).result.periods[0].fields['15'],null,'changed original deduction must be reconciled before F');
|
||||
const changed=structuredClone(book);changed.events[0].numerator='1';draft.modelo_a_snapshot=saveRegularizationBook(draft,changed);assert.equal(acquisitionCurrent(draft,adjusted),false);assert.equal(regularizationFields(draft,'2026-12').fields['13'],null);assert.deepEqual(changed.result,book.result);
|
||||
});
|
||||
test('F retains reduced-output uncertainty even when only E events remain in the current period book',()=>{
|
||||
let{draft,book}=regularizationFixture();book=recordRegularizations(draft,book);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);const acquisitions={...acquisitionBook(draft),rows:[],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:[]}]};const first=recordAcquisitions(draft,acquisitions);draft.modelo_a_snapshot=saveAcquisitionBook(draft,first);assert.equal(first.result.periods[0].fields['15'],'5.28');
|
||||
const s=JSON.parse(draft.modelo_a_snapshot);s.regulatedReducedBases['opportunity:370']=structuredClone(s.regulatedReducedBases['opportunity:371']);draft.modelo_a_snapshot=JSON.stringify(s);assert.equal(acquisitionCurrent(draft,first),false);const result=recordAcquisitions(draft,first).result;assert.equal(result.periods[0].fields['15'],'5.28');for(const f of ['14','16','17','18'])assert.equal(result.periods[0].fields[f],null,f);
|
||||
});
|
||||
test('annual adjustment composes C exclusion before D exactly once,including a projected investment asset',()=>{
|
||||
let{draft}=regularizationFixture();let b=acquisitionBook(draft);const diesel=structuredClone(b.rows[2]);diesel.id='diesel-pro-rata';diesel.field='08';diesel.documentRef='SYNTHETIC QA activo gasóleo';diesel.d.method='pro_rata';
|
||||
b=recordAcquisitions(draft,{...b,rows:[diesel],result:undefined,history:undefined});draft.modelo_a_snapshot=saveAcquisitionBook(draft,b);const e={...newRegularization('annual-diesel','annual',b.result),numerator:'2',denominator:'3',reference:'SYNTHETIC QA anual',reason:'SYNTHETIC QA âmbito',facts:{scope:'yes',recorded:'yes',annual:'yes',exclusions:'yes'}};
|
||||
assert.deepEqual(b.result.rows[0].allocationCalculation.before,{numerator:'8',denominator:'1'});assert.deepEqual(evaluateRegularization(draft,e).delta,{numerator:'66',denominator:'25'});
|
||||
});
|
||||
test('deduplication includes original document/line across revisions and rejects unsequenced annual plus credit note',()=>{
|
||||
const{draft,book,annual,correction}=regularizationFixture();assert.throws(()=>recordRegularizations(draft,{...book,events:[annual,{...annual,id:'duplicate'}]}),/Mesma aquisição/);
|
||||
assert.throws(()=>recordRegularizations(draft,{...book,events:[correction,{...correction,id:'other'}]}),/Mesma aquisição/);
|
||||
assert.throws(()=>recordRegularizations(draft,{...book,events:[annual,{...correction,rowIds:['pro-rata']}]}),/sequencial/);
|
||||
});
|
||||
test('investment imports retain customs field11 while entering the frozen asset control register',()=>{
|
||||
const{draft}=regularizationFixture();let b=acquisitionBook(draft);b.rows[3].investmentAsset=true;b=recordAcquisitions(draft,b);draft.modelo_a_snapshot=saveAcquisitionBook(draft,b);assert.equal(b.result.periods[0].fields['11'],'10.72');const register=JSON.stringify(frozenAssetRows(draft));assert.match(register,/import-due/);assert.match(register,/268\/25/);assert.equal(b.rows[3].field,'11');
|
||||
});
|
||||
test('unrecoverable E source or fraction is visible and a missing calculated event never turns into zero',()=>{
|
||||
const{draft,book}=regularizationFixture();const saved=recordRegularizations(draft,book);const incomplete=structuredClone(saved);incomplete.result.events=[];const altered={...draft,modelo_a_snapshot:saveRegularizationBook(draft,incomplete)};assert.equal(regularizationFields(altered,'2026-12').fields['12'],null);
|
||||
for(const mutate of [b=>b.events[0].source={},b=>b.result.events[0].delta.denominator='0',b=>b.events[0].facts.scope='maybe']){const bad=structuredClone(saved);mutate(bad);assert.throws(()=>readAcquisitionStore(saveRegularizationBook(draft,bad)),/Regulariza|regulariza/);}
|
||||
});
|
||||
test('asset register reuses frozen acquisition values and E save/recovery/report preserves source,history and prior maps',()=>{
|
||||
let{draft,book}=regularizationFixture();book=recordRegularizations(draft,book);const first=structuredClone(book.result);book=recordRegularizations(draft,book);assert.deepEqual(book.history,[first]);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);
|
||||
let acquired=acquisitionBook(draft);acquired.periods[0].absent=acquired.periods[0].absent.filter(f=>!['12','13'].includes(f));acquired=recordAcquisitions(draft,acquired);draft.modelo_a_snapshot=saveAcquisitionBook(draft,acquired);
|
||||
const assetDraft={...draft,context_opportunity:'opportunity:372'};const s=JSON.parse(draft.modelo_a_snapshot);assetDraft.modelo_a_snapshot=saveSalesBook(assetDraft,recordSales(assetDraft,structuredClone(s.salesOperations['opportunity:370'])));
|
||||
const asset=structuredClone(acquired.rows[0]);asset.id='asset-control';asset.field='08';asset.description='SYNTHETIC QA activo de investimento';asset.documentRef='SYNTHETIC QA activo1';
|
||||
const assetBook=recordAcquisitions(assetDraft,{currency:'MZN',taxpayerRegistered:'yes',rows:[asset],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:['12','13']}]});assetDraft.modelo_a_snapshot=saveAcquisitionBook(assetDraft,assetBook);
|
||||
const report=JSON.stringify(frozenAssetRows(assetDraft));for(const token of ['activo1','2026-12-10','16/3','RCIVA27','IVA suportado16'])assert.ok(report.includes(token),token);
|
||||
const recovered=JSON.parse(JSON.stringify({...assetDraft,context_opportunity:'opportunity:370'}));assert.deepEqual(regularizationBook(recovered),book);const text=JSON.stringify(acquisitionReportRows(recovered));for(const token of ['Regularização annual-2026','5.28','4/3','131.893333','Pressuposto','22(4–8)'])assert.ok(text.includes(token),token);
|
||||
if(process.env.IVA_L2E_CALCULATED)fs.writeFileSync(process.env.IVA_L2E_CALCULATED,JSON.stringify(recovered));
|
||||
});
|
||||
Reference in New Issue
Block a user