Prepare L3 D refund lifecycle and grouped C D QA delivery with preserved credits
This commit is contained in:
@@ -83,8 +83,9 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
'economic_snapshot.published_iva_scope' => ['nullable', 'string', 'in:yes,no'],
|
||||
'economic_snapshot.monthly_iva_settings' => ['nullable', 'string', 'max:2000'],
|
||||
'economic_snapshot.monthly_iva_snapshot' => ['nullable', 'string', 'max:60000'],
|
||||
// Preserve operation books and explicit history as the draft grows.
|
||||
'economic_snapshot.modelo_a_snapshot' => ['nullable', 'string', 'json', 'max:4000000'],
|
||||
// Preserve accepted books and explicit history through the L3 refund increment.
|
||||
// This is a bounded transport choice, not a business limit on fiscal records.
|
||||
'economic_snapshot.modelo_a_snapshot' => ['nullable', 'string', 'json', 'max:6000000'],
|
||||
'economic_snapshot.project_team_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_execution_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_risk_responses_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
|
||||
@@ -26775,5 +26775,90 @@
|
||||
],
|
||||
"schema_changes": false,
|
||||
"active_writes": false
|
||||
},
|
||||
"l3_d_qa_20261025": {
|
||||
"status": "L3-D concluído em QA no âmbito operacional demonstrado;preparado comC,sem instalação",
|
||||
"matrix_anchor": "AUD-SIM-INT02",
|
||||
"installed_revision": "109cb0fe",
|
||||
"installed_receipt": "3aa851a4",
|
||||
"C_QA_preserved": "b7a745e6",
|
||||
"implemented": {
|
||||
"allocation": "Reutiliza25de resultadoAB congelado e afectação explícita:26=25−27. Parcela27positiva,não excede25;apenas26 segue para o próximo mês por adopção expressa. Origem antiga/histórica não financia um novo pedido se a afectação actual mudou. Reportes dependentes indeterminados precisam de reconciliação;reporte efectivo já usado em declaração posterior não é reafectado retroactivamente.",
|
||||
"lifecycle": "Preparação→apresentado fora doInvesteMoz→decisão pendente/integral/parcial/indeferida→recebimentos identificados. Referência/data/entidade e âmbito do montante da decisão preservados. Pedido não cria reconhecimento;decisão não cria recebimento.",
|
||||
"basis": "21(6):quatro meses desde período de início e crédito superior100000;21(7a):superior500000;21(7b):cessação identificada. Origem/data nascimento/suporte e créditos mais antigos declarados.21(15):limite10anos com datas fornecidas. Compatibilidade de factos não equivale a concessão. Decisão externa documentada é registada independentemente de fundamento ainda não automatizado,com divergência visível.",
|
||||
"no_duplicate": "Um pedido por parcela/âmbito recuperado;mesma origem não repetida por nova versão ou após apagar preparação actual. Parcela apresentada/decidida/recebida bloqueia retorno silencioso a26. Recibos repetidos,anteriores à decisão ou montantes incompatíveis exigem reconciliação;sem criar caixa/crédito20 por inferência.",
|
||||
"F": "800000de crédito=200000reporte+600000pedido;550000reconhecidos;450000recebidos;100000aguardam recebimento;50000não deferidos. Valores distintos na gravação/recuperação/relatório. Fracções exactas:799681/3de crédito−200000de pedido=199681/3de reporte.",
|
||||
"preservation": "Mesmo snapshot/API e relatório existentes;ivaRefunds acrescido sem alterar mapas anteriores. Cada cálculo congela entradas/resultados e acrescenta histórico. C continua actual eAB72/30/42 intacto.",
|
||||
"technical_limit": "Escolha técnica identificada:limite de transporte do snapshot4M→6Mcaracteres;amostra preservada4055677caracteres excedia o anterior. Sem truncar/apagar históricos;novo limite mantido e rejeição acima dele demonstrada."
|
||||
},
|
||||
"source_cotejo": {
|
||||
"CIVA2016": "Lei13/2016 PDF15–16,p1426(273–274),art21(5/6/7),fundamentos e reporte.",
|
||||
"CIVA2022": "Lei22/2022 PDF3–4,p2252(27–28),art21.",
|
||||
"CIVA2025": "Lei10/2025,BR248suplemento1,PDF13/p2180(13),21(7a),revogação21(8),150dias21(10),suspensões21(13),10anos21(15).",
|
||||
"exemplar": "VAT Return.pdf SHA2569c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741,p2/quadro11. Remissãoart20 no formulário conservada como divergência,sem a transpor para regime2026.",
|
||||
"regulation": "78/2017,30/2022,52/2025 identificados;formalidades não automatizadas,sem transformar pista/resumo em parâmetro.",
|
||||
"bounded_lookup": [
|
||||
"https://www.at.gov.mz/legislacao/fiscal",
|
||||
"https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-suplemento-n%C2%BA-3-de-281217-pag-2602-9-16-br-n%C2%BA-202-boletim-da-rep%C3%BAblica"
|
||||
],
|
||||
"all17originals_unchanged": true
|
||||
},
|
||||
"validation": {
|
||||
"frontend": "33dirigidos passaram:9AB+11C+13D;incluem limites estritos,fracções,duplicação,transporte efectivo já consumido,decisão independente da avaliação automática e preservação completa.",
|
||||
"API": "1teste/109asserções passou;simulator_fiscal_delta_20261021_test/55441,DatabaseTransactions rollback;modelo_a_snapshot recuperado byte a byte. Teste do limite6M:1teste/5asserções,rejeição acima do limite.",
|
||||
"types": "npm.cmd run types:check passou",
|
||||
"build": "npm.cmd run build passou",
|
||||
"UI": "Componente real:recebimento excedente assinala divergência;repor valores600000/550000/450000/100000;gravação→recuperação→relatório conferidos. Todos os contextos anterioresAPI/UI idênticos;exemploD mantém mesmos valores.",
|
||||
"proof": "outputs/iva-l3-d-20261025/ui-recovered-refund.png",
|
||||
"logs": [
|
||||
"outputs/iva-l3-d-20261025/frontend-final.txt",
|
||||
"outputs/iva-l3-d-20261025/api-final.txt",
|
||||
"outputs/iva-l3-d-20261025/api-boundary.txt",
|
||||
"outputs/iva-l3-d-20261025/types-final.txt",
|
||||
"outputs/iva-l3-d-20261025/build-final.txt"
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"Âmbito demonstrado2026/MZN,primeira declaração em prazo com parcela expressa27 e saldo26;D não submete pedido nem executa recebimento bancário. C conserva o seu âmbito demonstrado,sem cobertura fiscal integral.",
|
||||
"Fundamentos21(6),21(7a) e21(7b) avaliados com factos e origem declarados/documentados. Composição cronológica completa dos créditos/extractos regulamentares não gerada automaticamente;ordem dos mais antigos confirmada expressamente,sem conta corrente centralAT ou novos aprovadores.",
|
||||
"Regulamento78/2017,30/2022 e52/2025 identificados;exemplares não localizados no acervo17PDF nem obtidos na consulta delimitadaAT/INM. Formulários,anexos,garantias,compensações,formalidades,suspensões e calendário completo não automatizados. PropostaCTA/resumos não activam regras. Decisão externa documentada pode ser registada sem a avaliação automática do fundamento se tornar autoridade adicional.",
|
||||
"150dias é prazo textual de reembolso devido,não previsão automática de caixa;10anos de21(15) avaliado pelas datas declaradas,com divergência visível. Obrigação50%/12meses revogada porLei10/2025,não aplicada.",
|
||||
"Controlo de duplicação sobre livros e históricos recuperados neste rascunho/âmbitos efectivos. Não é reconciliação global entre rascunhos privados diferentes. Projecções de oportunidades diferentes não são consumos fiscais efectivos.",
|
||||
"Afetação de preparação não altera declaração apresentada. Reporte efectivo já usado numa declaração posterior não pode ser reafectado retroactivamente;usar saldo disponível reconciliado. Pedido apresentado/decisão/recebimento preservam a parcela,inclusive após remoção da preparação actual. Indeferimento/parte não deferida não libertam crédito automaticamente;retorno exige procedimento documentado e reconciliação,sem criar20automaticamente.",
|
||||
"Recebimento é registo declarado;divergências de valor/data/documento ficam identificadas,sem execução bancária,sincronização silenciosa de caixa ou eliminação do documento.",
|
||||
"Erestituição de bebidas não demonstrado nesta ronda epermanece pendente,sem bloquearC/D. L1mantém129fechos+P058/P084/P085+P001=133;L2mantém18(6)+mista,sucessivas/plurianuais,erros/incobráveis. Estatísticas/IA adiadas."
|
||||
],
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "app/Http/Requests/StoreSimulationFormDraftRequest.php",
|
||||
"sha256_LF": "61c79fd1735f54d8d1d964d521ec28613184e363dd07a95d619932ba30e9de68"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-refund.ts",
|
||||
"sha256_LF": "a2f4c0567a0633ad4926c4b57d44c167eae3fc2d9ceb167ce000ffd945ba0bbb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-refund.tsx",
|
||||
"sha256_LF": "72b0706f2d338f4a06a28f0f2a2fa1a29bbeff343b919727fa34465881ed6622"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-closure.ts",
|
||||
"sha256_LF": "0a916c7e36bb76b853c65839aa18e597bc617efe7c4a003126e44eab4243a2cd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-closure.tsx",
|
||||
"sha256_LF": "321883d3fcf6d761310c896ae9b0536f2b6a802f54c052462692931e14723cc3"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "ed0563fb4f39b1d71310e7b28a2d2aa570fae14ba97a14d55159f3fe96c42901"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier.ts",
|
||||
"sha256_LF": "27893e0c9c393a217ece3401ab3e9f839aabdb26f1b62a1130399c2b8666d40d"
|
||||
}
|
||||
],
|
||||
"schema_changes": false,
|
||||
"active_writes": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"status": "QA_prepared_not_installed",
|
||||
"status": "included_in_prepared_CD_group_not_installed",
|
||||
"candidate": "commit que contém este manifesto",
|
||||
"baseline_installed": "109cb0fe9e1530dd09b8277a422ae9c5e68e0674",
|
||||
"baseline_receipt": "3aa851a4",
|
||||
@@ -41,5 +41,6 @@
|
||||
"backup/restauro autenticados antes da instalação",
|
||||
"Git existente,conferência só do delta eprovasQA reutilizadas"
|
||||
],
|
||||
"active_writes": false
|
||||
"active_writes": false,
|
||||
"group_manifest": "docs/investmoz/simulations/l3-cd-prepared-delivery-20261025.json"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"status": "L3_CD_prepared_for_grouped_delivery_not_installed",
|
||||
"candidate": "commit que contém este manifesto",
|
||||
"baseline_installed": "109cb0fe9e1530dd09b8277a422ae9c5e68e0674",
|
||||
"baseline_receipt": "3aa851a4",
|
||||
"accepted_C_candidate": "b7a745e6",
|
||||
"reports": [
|
||||
"docs/investmoz/simulations/l3-c-substitution-qa-20261025.json",
|
||||
"docs/investmoz/simulations/l3-d-refund-qa-20261025.json"
|
||||
],
|
||||
"coverage": "Csubstituições eDreembolso no âmbito operacional demonstrado;formalidades externas,E e residuaisL1/L2 permanecem explícitos",
|
||||
"compatibility_at_preparation": {
|
||||
"origin_main": "109cb0fe9e1530dd09b8277a422ae9c5e68e0674",
|
||||
"main_is_ancestor": true,
|
||||
"checked_utc": "2026-10-25T12:46:36.801875+00:00"
|
||||
},
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "app/Http/Requests/StoreSimulationFormDraftRequest.php",
|
||||
"sha256_LF": "61c79fd1735f54d8d1d964d521ec28613184e363dd07a95d619932ba30e9de68"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-refund.ts",
|
||||
"sha256_LF": "a2f4c0567a0633ad4926c4b57d44c167eae3fc2d9ceb167ce000ffd945ba0bbb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-refund.tsx",
|
||||
"sha256_LF": "72b0706f2d338f4a06a28f0f2a2fa1a29bbeff343b919727fa34465881ed6622"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-closure.ts",
|
||||
"sha256_LF": "0a916c7e36bb76b853c65839aa18e597bc617efe7c4a003126e44eab4243a2cd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-closure.tsx",
|
||||
"sha256_LF": "321883d3fcf6d761310c896ae9b0536f2b6a802f54c052462692931e14723cc3"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "ed0563fb4f39b1d71310e7b28a2d2aa570fae14ba97a14d55159f3fe96c42901"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier.ts",
|
||||
"sha256_LF": "27893e0c9c393a217ece3401ab3e9f839aabdb26f1b62a1130399c2b8666d40d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-substitution.ts",
|
||||
"sha256_LF": "0ea914bf952ff053f7e6912705f80526aaed5a8b9f4b3bd5a27f737c4aef3977"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-substitution.tsx",
|
||||
"sha256_LF": "6a61f59bcbd493ca45e60a14fe3c67375f581581f9cbe882426fdbffa98399c2"
|
||||
}
|
||||
],
|
||||
"limits": [
|
||||
"Âmbito demonstrado2026/MZN,primeira declaração em prazo com parcela expressa27 e saldo26;D não submete pedido nem executa recebimento bancário. C conserva o seu âmbito demonstrado,sem cobertura fiscal integral.",
|
||||
"Fundamentos21(6),21(7a) e21(7b) avaliados com factos e origem declarados/documentados. Composição cronológica completa dos créditos/extractos regulamentares não gerada automaticamente;ordem dos mais antigos confirmada expressamente,sem conta corrente centralAT ou novos aprovadores.",
|
||||
"Regulamento78/2017,30/2022 e52/2025 identificados;exemplares não localizados no acervo17PDF nem obtidos na consulta delimitadaAT/INM. Formulários,anexos,garantias,compensações,formalidades,suspensões e calendário completo não automatizados. PropostaCTA/resumos não activam regras. Decisão externa documentada pode ser registada sem a avaliação automática do fundamento se tornar autoridade adicional.",
|
||||
"150dias é prazo textual de reembolso devido,não previsão automática de caixa;10anos de21(15) avaliado pelas datas declaradas,com divergência visível. Obrigação50%/12meses revogada porLei10/2025,não aplicada.",
|
||||
"Controlo de duplicação sobre livros e históricos recuperados neste rascunho/âmbitos efectivos. Não é reconciliação global entre rascunhos privados diferentes. Projecções de oportunidades diferentes não são consumos fiscais efectivos.",
|
||||
"Afetação de preparação não altera declaração apresentada. Reporte efectivo já usado numa declaração posterior não pode ser reafectado retroactivamente;usar saldo disponível reconciliado. Pedido apresentado/decisão/recebimento preservam a parcela,inclusive após remoção da preparação actual. Indeferimento/parte não deferida não libertam crédito automaticamente;retorno exige procedimento documentado e reconciliação,sem criar20automaticamente.",
|
||||
"Recebimento é registo declarado;divergências de valor/data/documento ficam identificadas,sem execução bancária,sincronização silenciosa de caixa ou eliminação do documento.",
|
||||
"Erestituição de bebidas não demonstrado nesta ronda epermanece pendente,sem bloquearC/D. L1mantém129fechos+P058/P084/P085+P001=133;L2mantém18(6)+mista,sucessivas/plurianuais,erros/incobráveis. Estatísticas/IA adiadas."
|
||||
],
|
||||
"required_at_delivery": [
|
||||
"fetchmain fresco e reconciliação de concorrência",
|
||||
"backup novo/restauro isolado autenticados antes de instalar",
|
||||
"uma entregaGit do conjuntoC/D,sem migrações/importações",
|
||||
"conferir só delta instalado e reutilizar provas válidas",
|
||||
"manterE e residuaisL1/L2 na mesma matriz"
|
||||
],
|
||||
"active_writes": false
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"status": "L3-D concluído em QA no âmbito operacional demonstrado;preparado comC,sem instalação",
|
||||
"matrix_anchor": "AUD-SIM-INT02",
|
||||
"installed_revision": "109cb0fe",
|
||||
"installed_receipt": "3aa851a4",
|
||||
"C_QA_preserved": "b7a745e6",
|
||||
"implemented": {
|
||||
"allocation": "Reutiliza25de resultadoAB congelado e afectação explícita:26=25−27. Parcela27positiva,não excede25;apenas26 segue para o próximo mês por adopção expressa. Origem antiga/histórica não financia um novo pedido se a afectação actual mudou. Reportes dependentes indeterminados precisam de reconciliação;reporte efectivo já usado em declaração posterior não é reafectado retroactivamente.",
|
||||
"lifecycle": "Preparação→apresentado fora doInvesteMoz→decisão pendente/integral/parcial/indeferida→recebimentos identificados. Referência/data/entidade e âmbito do montante da decisão preservados. Pedido não cria reconhecimento;decisão não cria recebimento.",
|
||||
"basis": "21(6):quatro meses desde período de início e crédito superior100000;21(7a):superior500000;21(7b):cessação identificada. Origem/data nascimento/suporte e créditos mais antigos declarados.21(15):limite10anos com datas fornecidas. Compatibilidade de factos não equivale a concessão. Decisão externa documentada é registada independentemente de fundamento ainda não automatizado,com divergência visível.",
|
||||
"no_duplicate": "Um pedido por parcela/âmbito recuperado;mesma origem não repetida por nova versão ou após apagar preparação actual. Parcela apresentada/decidida/recebida bloqueia retorno silencioso a26. Recibos repetidos,anteriores à decisão ou montantes incompatíveis exigem reconciliação;sem criar caixa/crédito20 por inferência.",
|
||||
"F": "800000de crédito=200000reporte+600000pedido;550000reconhecidos;450000recebidos;100000aguardam recebimento;50000não deferidos. Valores distintos na gravação/recuperação/relatório. Fracções exactas:799681/3de crédito−200000de pedido=199681/3de reporte.",
|
||||
"preservation": "Mesmo snapshot/API e relatório existentes;ivaRefunds acrescido sem alterar mapas anteriores. Cada cálculo congela entradas/resultados e acrescenta histórico. C continua actual eAB72/30/42 intacto.",
|
||||
"technical_limit": "Escolha técnica identificada:limite de transporte do snapshot4M→6Mcaracteres;amostra preservada4055677caracteres excedia o anterior. Sem truncar/apagar históricos;novo limite mantido e rejeição acima dele demonstrada."
|
||||
},
|
||||
"source_cotejo": {
|
||||
"CIVA2016": "Lei13/2016 PDF15–16,p1426(273–274),art21(5/6/7),fundamentos e reporte.",
|
||||
"CIVA2022": "Lei22/2022 PDF3–4,p2252(27–28),art21.",
|
||||
"CIVA2025": "Lei10/2025,BR248suplemento1,PDF13/p2180(13),21(7a),revogação21(8),150dias21(10),suspensões21(13),10anos21(15).",
|
||||
"exemplar": "VAT Return.pdf SHA2569c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741,p2/quadro11. Remissãoart20 no formulário conservada como divergência,sem a transpor para regime2026.",
|
||||
"regulation": "78/2017,30/2022,52/2025 identificados;formalidades não automatizadas,sem transformar pista/resumo em parâmetro.",
|
||||
"bounded_lookup": [
|
||||
"https://www.at.gov.mz/legislacao/fiscal",
|
||||
"https://www.inm.gov.mz/pt-br/content/conselho-de-ministros-suplemento-n%C2%BA-3-de-281217-pag-2602-9-16-br-n%C2%BA-202-boletim-da-rep%C3%BAblica"
|
||||
],
|
||||
"all17originals_unchanged": true
|
||||
},
|
||||
"validation": {
|
||||
"frontend": "33dirigidos passaram:9AB+11C+13D;incluem limites estritos,fracções,duplicação,transporte efectivo já consumido,decisão independente da avaliação automática e preservação completa.",
|
||||
"API": "1teste/109asserções passou;simulator_fiscal_delta_20261021_test/55441,DatabaseTransactions rollback;modelo_a_snapshot recuperado byte a byte. Teste do limite6M:1teste/5asserções,rejeição acima do limite.",
|
||||
"types": "npm.cmd run types:check passou",
|
||||
"build": "npm.cmd run build passou",
|
||||
"UI": "Componente real:recebimento excedente assinala divergência;repor valores600000/550000/450000/100000;gravação→recuperação→relatório conferidos. Todos os contextos anterioresAPI/UI idênticos;exemploD mantém mesmos valores.",
|
||||
"proof": "outputs/iva-l3-d-20261025/ui-recovered-refund.png",
|
||||
"logs": [
|
||||
"outputs/iva-l3-d-20261025/frontend-final.txt",
|
||||
"outputs/iva-l3-d-20261025/api-final.txt",
|
||||
"outputs/iva-l3-d-20261025/api-boundary.txt",
|
||||
"outputs/iva-l3-d-20261025/types-final.txt",
|
||||
"outputs/iva-l3-d-20261025/build-final.txt"
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"Âmbito demonstrado2026/MZN,primeira declaração em prazo com parcela expressa27 e saldo26;D não submete pedido nem executa recebimento bancário. C conserva o seu âmbito demonstrado,sem cobertura fiscal integral.",
|
||||
"Fundamentos21(6),21(7a) e21(7b) avaliados com factos e origem declarados/documentados. Composição cronológica completa dos créditos/extractos regulamentares não gerada automaticamente;ordem dos mais antigos confirmada expressamente,sem conta corrente centralAT ou novos aprovadores.",
|
||||
"Regulamento78/2017,30/2022 e52/2025 identificados;exemplares não localizados no acervo17PDF nem obtidos na consulta delimitadaAT/INM. Formulários,anexos,garantias,compensações,formalidades,suspensões e calendário completo não automatizados. PropostaCTA/resumos não activam regras. Decisão externa documentada pode ser registada sem a avaliação automática do fundamento se tornar autoridade adicional.",
|
||||
"150dias é prazo textual de reembolso devido,não previsão automática de caixa;10anos de21(15) avaliado pelas datas declaradas,com divergência visível. Obrigação50%/12meses revogada porLei10/2025,não aplicada.",
|
||||
"Controlo de duplicação sobre livros e históricos recuperados neste rascunho/âmbitos efectivos. Não é reconciliação global entre rascunhos privados diferentes. Projecções de oportunidades diferentes não são consumos fiscais efectivos.",
|
||||
"Afetação de preparação não altera declaração apresentada. Reporte efectivo já usado numa declaração posterior não pode ser reafectado retroactivamente;usar saldo disponível reconciliado. Pedido apresentado/decisão/recebimento preservam a parcela,inclusive após remoção da preparação actual. Indeferimento/parte não deferida não libertam crédito automaticamente;retorno exige procedimento documentado e reconciliação,sem criar20automaticamente.",
|
||||
"Recebimento é registo declarado;divergências de valor/data/documento ficam identificadas,sem execução bancária,sincronização silenciosa de caixa ou eliminação do documento.",
|
||||
"Erestituição de bebidas não demonstrado nesta ronda epermanece pendente,sem bloquearC/D. L1mantém129fechos+P058/P084/P085+P001=133;L2mantém18(6)+mista,sucessivas/plurianuais,erros/incobráveis. Estatísticas/IA adiadas."
|
||||
],
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "app/Http/Requests/StoreSimulationFormDraftRequest.php",
|
||||
"sha256_LF": "61c79fd1735f54d8d1d964d521ec28613184e363dd07a95d619932ba30e9de68"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-refund.ts",
|
||||
"sha256_LF": "a2f4c0567a0633ad4926c4b57d44c167eae3fc2d9ceb167ce000ffd945ba0bbb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-refund.tsx",
|
||||
"sha256_LF": "72b0706f2d338f4a06a28f0f2a2fa1a29bbeff343b919727fa34465881ed6622"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-closure.ts",
|
||||
"sha256_LF": "0a916c7e36bb76b853c65839aa18e597bc617efe7c4a003126e44eab4243a2cd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-closure.tsx",
|
||||
"sha256_LF": "321883d3fcf6d761310c896ae9b0536f2b6a802f54c052462692931e14723cc3"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "ed0563fb4f39b1d71310e7b28a2d2aa570fae14ba97a14d55159f3fe96c42901"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier.ts",
|
||||
"sha256_LF": "27893e0c9c393a217ece3401ab3e9f839aabdb26f1b62a1130399c2b8666d40d"
|
||||
}
|
||||
],
|
||||
"schema_changes": false,
|
||||
"active_writes": false
|
||||
}
|
||||
@@ -1,5 +1,28 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 25/10/2026 — L3-D emQA operacional;C/D preparado para entrega conjunta
|
||||
|
||||
Homologação109cb0fe,recibo3aa851a4;C QAb7a745e6 preservado. Sem instalação,migração/importação ou escrita activa. L3 permanece aberto:formalidades de submissão eErestituição de bebidas pendentes;L1129fechos+P058/P084/P085+P001=133 eL2três famílias pendentes mantidos. Estatísticas/IAadiadas.
|
||||
|
||||
No fecho existente,escolher «Destinar parcela ao pedido27 — acompanhar emD» e indicar montante. Calcular afectação explícita25=26+27;27positiva e não superior25. Apenas26pode seguir para o mês seguinte. Origem/reportes dependentes alterados exigem adopção expressa e reconciliação;reporte efectivo já utilizado em declaração posterior não é reafectado retroactivamente. Afetação preparada não altera uma declaração apresentada.
|
||||
|
||||
Abrir «L3-D — pedido,decisão e recebimento do reembolso». Reutiliza afectação congelada actual de27,âmbito,período e natureza. Pedir apenas fundamento/origem/data nascimento,suporte e referência/data em falta.21(6):quatro meses e crédito superior100000;21(7a):superior500000 e considerar créditos mais antigos;21(7b):cessação identificada. A origem e ordem são factos declarados/documentados,não conta correnteAT reconstruída nem extracto regulamentar automático.21(15)usa datas declaradas para limite10anos. Não aplicar antiga obrigação50%/12meses,revogada pelaLei10/2025.
|
||||
|
||||
Preparação→pedido apresentado fora doInvesteMoz→decisão pendente/integral/parcial/indeferida→recebimento identificado. Pedido não gera decisão;decisão não gera caixa. Guardar número/data/entidade da decisão e respectivo montante;registar recibos/data/montante separadamente. A avaliação automática do fundamento fica independente da decisão externa documentada:fundamento desconhecido/não automatizado não cria aprovador adicional nem impede registar documento recebido. Divergência fica explícita;sem presumir direito numa preparação sem fundamento.
|
||||
|
||||
Mesma parcela não pode sustentar dois pedidos. Depois de pedido apresentado/decisão/recebimento,a parcela continua reservada,inclusive ao remover a preparação actual;histórico preservado. Indeferimento e parte não deferida não libertam crédito automaticamente:retorno exige procedimento documentado e reconciliação,sem criar20porinferência. Duplicações,recebimentos sem decisão/data ou acima do reconhecido ficam indeterminados;documentos conservados. Sem executar banco ou alterar caixa silenciosamente.
|
||||
|
||||
F acompanhaD:800000MZNdecrédito=200000reporte+600000pedido;550000reconhecidos;450000recebidos;100000aguardam recebimento;50000não deferidos. Relatório guarda cada contexto e as mesmas referências/valores. Fracção exacta799681/3menos200000depedido=199681/3de reporte. Gravar→recuperar→relatório conferido no componente real;AB72/30/42 eC intactos.
|
||||
|
||||
Cotejo:Lei13/2016 PDF15–16;Lei22/2022 PDF3–4;Lei10/2025 PDF13,art21;exemplarModeloA quadro11. Remissãoart20 do formulário conservada como divergência. Regulamentos78/2017,30/2022 e52/2025 identificados,mas respectivos exemplares não obtidos no acervo/consulta delimitadaAT/INM nesta ronda;propostas/resumos não activam regras. Formulários,anexos,garantias,compensações,suspensões e calendário completos continuam não automatizados e externos.150dias não constitui previsão de caixa;E não foi demonstrado nesta ronda e não bloqueiaC/D.
|
||||
|
||||
Escolha técnica delimitada:transporte do snapshot4M→6Mcaracteres para preservar todos os livros/históricos aceites ao acrescentarD;sem truncagem. API109asserções recupera exactamente a amostra;teste5asserções mantém rejeição acima6M.33dirigidosAB/C/D,tipos/build eUI passaram. Prova:outputs/iva-l3-d-20261025/ui-recovered-refund.png. Relatório:docs/investmoz/simulations/l3-d-refund-qa-20261025.json;manifesto conjunto:docs/investmoz/simulations/l3-cd-prepared-delivery-20261025.json.
|
||||
|
||||
|
||||
## 25/10/2026 — inventário anterior aL3-D
|
||||
|
||||
AB instalado109cb0fe/3aa851a4 eC QAb7a745e6 preservados,sem perdas confirmadas. L1129fechos/quatroresiduais,L2três famílias eErestituição de bebidas mantidos. D reutiliza crédito congelado,separando26reporte/27pedido,pedido/decisão/recebimento. Cotejo limitadoCIVA21 comredacção2025 eModeloA11;regra50%/12meses revogada,não activar. Formalidades regulamentares externas não automatizadas,não reinventar aprovadores/certificados.
|
||||
|
||||
## 25/10/2026 — L3-C emQA no âmbito demonstrado;homologaçãoAB preservada
|
||||
|
||||
AB entregue109cb0fe,recibo3aa851a4;72calculados/30pagos registados/42porliquidar intactos. C não está instalado. L3 continua aberto,Dreembolso geral eErestituição de bebidas pendentes;L1 mantém129fechos+P058/P084/P085+P001=133 eL2 mantém18(6)+mista,sucessivas/plurianuais,erros/incobráveis. Estatísticas/IAadiadas.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ export function AcquisitionClosure({draft,onChange}:{draft:Record<string,unknown
|
||||
const choice=(r:ClosureInput,key:'firstDeclaration'|'creditsComplete',label:string)=><label className="block">{label}<select value={r[key]} onChange={e=>change(r.id,{[key]:e.target.value})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>;
|
||||
return<details className="rounded border p-3"><summary>L3-A/B — crédito,fecho e pagamentos por período</summary>
|
||||
<p>Reutiliza resultados congelados de L2. Crédito anterior,saldo calculado,pagamento registado e transporte são distintos. Identifique o âmbito fiscal da entidade,sem consolidar oportunidades implicitamente.</p>
|
||||
<p>Primeira declaração em prazo,2026/MZN e reporte integral. SubstituiçõesC,reembolsoD e restituição de bebidasE continuam próprios. Prazo/data são declarados,não calculados pelo portal. Uma simulação não submete declarações nem efectua pagamentos.</p>
|
||||
<p>Primeira declaração em prazo,2026/MZN e reporte integral ou afectação de parcela ao pedidoD. SubstituiçõesC,acompanhamento de reembolsoD e restituição de bebidasE continuam próprios. A parcela preparada para pedido é separada do reporte,sem concessão automática. Prazo/data são declarados,não calculados pelo portal. Uma simulação não submete declarações nem efectua pagamentos.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" onClick={()=>save({...b,rows:[...b.rows,newClosure(crypto.randomUUID(),sources.length===1?sources[0]:null,entities.length===1?entities[0]:String(draft.fiscal_entity_ref??draft.organization_name??''))]})}>Preparar fecho de outro período</button>
|
||||
{b.rows.map(r=><fieldset className="rounded border p-3" key={r.id}><legend>Fecho — {r.period||'período por escolher'}</legend>
|
||||
@@ -35,8 +35,10 @@ export function AcquisitionClosure({draft,onChange}:{draft:Record<string,unknown
|
||||
<label>Juros23<select value={r.interest} onChange={e=>change(r.id,{interest:e.target.value as ClosureInput['interest']})}><option value="">Indeterminados</option><option value="none">Sem juros aplicáveis — motivo identificado</option><option value="documented">Montante identificado,sem cálculo de taxa pelo portal</option></select></label>
|
||||
{r.interest==='documented'&&<label>Montante de juros (MZN)<input value={r.interestAmount} onChange={e=>change(r.id,{interestAmount:e.target.value})}/></label>}
|
||||
{r.interest&&<label>Fonte/motivo dos juros ou da sua inexistência<input value={r.interestRef} onChange={e=>change(r.id,{interestRef:e.target.value})}/></label>}
|
||||
<label>Destino do crédito25<select value={r.destination} onChange={e=>change(r.id,{destination:e.target.value as ClosureInput['destination']})}><option value="">Por escolher</option><option value="report">Reportar integralmente26;sem pedido27 neste percurso</option><option value="refund">Preparar reembolso — percursoD pendente</option></select></label>
|
||||
<label>Destino do crédito25<select value={r.destination} onChange={e=>change(r.id,{destination:e.target.value as ClosureInput['destination']})}><option value="">Por escolher</option><option value="report">Reportar integralmente26;sem pedido27 neste percurso</option><option value="refund">Destinar parcela ao pedido27 — acompanhar emD</option></select></label>
|
||||
{r.destination==='refund'&&<label>Parcela do cr?dito destinada ao pedido27 (MZN)<input value={r.refundAmount??''} onChange={e=>change(r.id,{refundAmount:e.target.value})}/></label>}
|
||||
<h5>Pagamentos registados,distintos do saldo calculado</h5>
|
||||
{r.destination==='refund'&&<label>Parcela do crédito destinada ao pedido27 (MZN)<input value={r.refundAmount??''} onChange={e=>change(r.id,{refundAmount:e.target.value})}/></label>}
|
||||
{r.payments.map(p=><fieldset className="rounded border p-2" key={p.id}>{(['reference','date','amount'] as const).map((k,i)=><label key={k}>{['Referência do pagamento','Data do pagamento','Montante do pagamento (MZN)'][i]}<input type={k==='date'?'date':'text'} value={p[k]} onChange={e=>change(r.id,{payments:r.payments.map(v=>v.id===p.id?{...v,[k]:e.target.value}:v)})}/></label>)}<button type="button" onClick={()=>change(r.id,{payments:r.payments.filter(v=>v.id!==p.id)})}>Remover pagamento desta preparação</button></fieldset>)}
|
||||
<button type="button" onClick={()=>change(r.id,{payments:[...r.payments,{id:crypto.randomUUID(),reference:'',date:'',amount:''}]})}>Registar referência de pagamento</button>
|
||||
<button type="button" onClick={()=>save({...b,rows:b.rows.filter(v=>v.id!==r.id)})}>Remover fecho em preparação (histórico conservado)</button></fieldset>)}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {useState} from 'react';
|
||||
import {refundBook,refundSources,newRefund,saveRefundBook,recordRefund,refundReportRows,type RefundBook,type RefundInput} from '@/lib/iva-refund';
|
||||
export function AcquisitionRefund({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}){
|
||||
const[error,setError]=useState('');let b:RefundBook;try{b=refundBook(draft);}catch(e){return<p role="alert">{String(e)}</p>;}
|
||||
const save=(book:RefundBook)=>{try{onChange('modelo_a_snapshot',saveRefundBook(draft,book));setError('');}catch(e){setError(String(e));}};
|
||||
const change=(id:string,p:Partial<RefundInput>)=>save({...b,rows:b.rows.map(r=>r.id===id?{...r,...p}:r)}),sources=refundSources(draft);
|
||||
const choice=(r:RefundInput,k:'factsConfirmed'|'oldestFirst',label:string)=><label>{label}<select value={r[k]} onChange={e=>change(r.id,{[k]:e.target.value})}><option value="">Por confirmar</option><option value="yes">Sim — documentado ou pressuposto explícito</option><option value="no">Não</option></select></label>;
|
||||
return<details className="rounded border p-3"><summary>L3-D — pedido,decisão e recebimento do reembolso</summary>
|
||||
<p>Primeiro,no fecho do período,escolha a parcela destinada ao pedido27 e calcule a afectação:25=26+27. Apenas26 continua em reporte. Reutilize a afectação abaixo;crédito já transportado não é uma nova parcela disponível.</p>
|
||||
<p>Preparar ou apresentar o pedido não é deferimento;deferimento não é recebimento. Submissão,formulários regulamentares,suspensões e pagamentos bancários ocorrem fora doInvesteMoz. O prazo legal não é previsão de caixa. Não se aplica a antiga obrigação50%/12meses,revogada em2026.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" onClick={()=>save({...b,rows:[...b.rows,newRefund(crypto.randomUUID(),sources.length===1?sources[0]:null)]})}>Preparar pedido de reembolso geral</button>
|
||||
{b.rows.map(r=><fieldset className="rounded border p-3" key={r.id}><legend>Pedido de reembolso — {r.source?.period||'período por escolher'}</legend>
|
||||
<label>Afectação congelada do crédito27<select value={sources.findIndex(s=>s.allocationKey===r.source?.allocationKey)} onChange={e=>{const s=sources[Number(e.target.value)];if(s)change(r.id,{source:structuredClone(s)});}}><option value={-1}>Escolha a parcela actual</option>{sources.map((s,i)=><option key={s.allocationKey} value={i}>{s.period} — {s.entityRef} — fecho{s.closureId}</option>)}</select></label>
|
||||
<label>Fundamento do pedido<select value={r.ground} onChange={e=>change(r.id,{ground:e.target.value as RefundInput['ground']})}><option value="">Por identificar</option><option value="four_months">21(6) — quatro meses e crédito superior100000MZN</option><option value="high_credit">21(7a) — crédito superior500000MZN</option><option value="cessation">21(7b) — cessação de actividade</option></select></label>
|
||||
<label>Período de início do excesso de crédito<input type="month" value={r.originPeriod} onChange={e=>change(r.id,{originPeriod:e.target.value})}/></label>
|
||||
<label>Data de nascimento do direito à dedução<input type="date" value={r.entitlementDate} onChange={e=>change(r.id,{entitlementDate:e.target.value})}/></label>
|
||||
{r.ground==='cessation'&&<label>Data da cessação de actividade<input type="date" value={r.cessationDate} onChange={e=>change(r.id,{cessationDate:e.target.value})}/></label>}
|
||||
<label>Suporte dos factos e origem do crédito<textarea value={r.groundRef} onChange={e=>change(r.id,{groundRef:e.target.value})}/></label>
|
||||
{choice(r,'factsConfirmed','Factos do fundamento identificados no suporte')}{choice(r,'oldestFirst','Considerei sequencialmente os créditos mais antigos')}
|
||||
<label>Referência do pedido ou preparação<input value={r.requestReference} onChange={e=>change(r.id,{requestReference:e.target.value})}/></label>
|
||||
<label>Data do pedido ou prevista<input type="date" value={r.requestDate} onChange={e=>change(r.id,{requestDate:e.target.value})}/></label>
|
||||
<label>Situação do pedido<select value={r.stage} onChange={e=>change(r.id,{stage:e.target.value as RefundInput['stage']})}><option value="preparation">Em preparação — sem submissão</option><option value="submitted">Apresentado fora doInvesteMoz — registo declarado</option></select></label>
|
||||
<label>Decisão recebida<select value={r.decision} onChange={e=>change(r.id,{decision:e.target.value as RefundInput['decision']})}><option value="pending">Ainda não identificada</option><option value="approved">Deferimento integral</option><option value="partial">Deferimento parcial</option><option value="rejected">Indeferimento</option></select></label>
|
||||
{r.decision!=='pending'&&<><label>Número ou referência da decisão<input value={r.decisionReference} onChange={e=>change(r.id,{decisionReference:e.target.value})}/></label><label>Data da decisão<input type="date" value={r.decisionDate} onChange={e=>change(r.id,{decisionDate:e.target.value})}/></label><label>Entidade emitente da decisão<input value={r.decisionIssuer} onChange={e=>change(r.id,{decisionIssuer:e.target.value})}/></label><label>Montante reconhecido na decisão (MZN)<input value={r.approvedAmount} onChange={e=>change(r.id,{approvedAmount:e.target.value})}/></label></>}
|
||||
<h5>Recebimentos registados</h5>
|
||||
{r.receipts.map(p=><fieldset key={p.id}><legend>Recebimento de reembolso</legend>{(['reference','date','amount'] as const).map(k=><label key={k}>{k==='reference'?'Referência do recebimento':k==='date'?'Data do recebimento':'Montante recebido (MZN)'}<input type={k==='date'?'date':'text'} value={p[k]} onChange={e=>change(r.id,{receipts:r.receipts.map(v=>v.id===p.id?{...v,[k]:e.target.value}:v)})}/></label>)}<button type="button" onClick={()=>change(r.id,{receipts:r.receipts.filter(v=>v.id!==p.id)})}>Remover recebimento desta preparação</button></fieldset>)}
|
||||
<button type="button" onClick={()=>change(r.id,{receipts:[...r.receipts,{id:crypto.randomUUID(),reference:'',date:'',amount:''}]})}>Registar recebimento identificado</button>
|
||||
<button type="button" onClick={()=>save({...b,rows:b.rows.filter(v=>v.id!==r.id)})}>Remover pedido desta preparação</button>
|
||||
</fieldset>)}
|
||||
{b.rows.length>0&&<button type="button" onClick={()=>{try{save(recordRefund(draft,b));}catch(e){setError(String(e));}}}>Calcular e guardar acompanhamento D</button>}
|
||||
{refundReportRows(draft).map((r,i)=><p key={i}><strong>{r[1]}:</strong> {r[2]}<br/>{r[3]}</p>)}
|
||||
</details>;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import {AcquisitionClosure} from './acquisition-closure';
|
||||
import {AcquisitionSubstitution} from './acquisition-substitution';
|
||||
import {AcquisitionRefund} from './acquisition-refund';
|
||||
import {AcquisitionRegularizations} from './acquisition-regularizations';
|
||||
import { AcquisitionOperationLink } from './acquisition-operation-link';
|
||||
import { AcquisitionImportsExclusions } from './acquisition-imports-exclusions';
|
||||
@@ -87,6 +88,7 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
|
||||
<p>O apuramento01–18 conserva o seu resultado antes dos créditos. O fechoA/B abaixo reutiliza a versão congelada,sem ordem de pagamento,submissãoAT ou promessa de reembolso.</p>
|
||||
<AcquisitionClosure draft={draft} onChange={onChange}/>
|
||||
<AcquisitionSubstitution draft={draft} onChange={onChange}/>
|
||||
<AcquisitionRefund draft={draft} onChange={onChange}/>
|
||||
{book.result&&<><p role="status">{acquisitionCurrent(draft,book)?'Resultado correspondente às entradas guardadas.':'Entradas ou origem alteradas: resultado histórico preservado; actualizar o cálculo.'}</p><dl>{acquisitionReportRows(draft).map(([,label,value,origin],i)=><div key={i}><dt className="font-semibold">{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ type Choice=''|'yes'|'no';
|
||||
export type ClosureSource={revision:number;calculatedAt:string;fingerprint:string;opportunity:string;currency:string;period:string;location:string;projected:boolean;debit:FiscalFraction|null;credit:FiscalFraction|null};
|
||||
export type ClosureCredit={id:string;kind:'opening'|'communication'|'previous';period:string;reference:string;date:string;amount:string;previousId:string;carryKey:string};
|
||||
export type ClosurePayment={id:string;reference:string;date:string;amount:string};
|
||||
export type ClosureInput={id:string;period:string;entityRef:string;origin:'projected'|'effective';source:ClosureSource|null;firstDeclaration:Choice;filing:''|'timely'|'late';submissionDate:string;filingRef:string;creditsComplete:Choice;credits:ClosureCredit[];interest:''|'none'|'documented';interestAmount:string;interestRef:string;destination:''|'report'|'refund';payments:ClosurePayment[]};
|
||||
export type ClosureInput={id:string;period:string;entityRef:string;origin:'projected'|'effective';source:ClosureSource|null;firstDeclaration:Choice;filing:''|'timely'|'late';submissionDate:string;filingRef:string;creditsComplete:Choice;credits:ClosureCredit[];interest:''|'none'|'documented';interestAmount:string;interestRef:string;destination:''|'report'|'refund';refundAmount?:string;payments:ClosurePayment[]};
|
||||
export type ClosureResult={id:string;period:string;entityRef:string;origin:ClosureInput['origin'];status:'determined'|'indeterminate';reason:string;inputKey:string;financialKey:string;carryKey:string;fields:Record<string,FiscalFraction|null>;paid:FiscalFraction|null;outstanding:FiscalFraction|null;overpayment:FiscalFraction|null;source:string};
|
||||
export type ClosureBook={version:1;rows:ClosureInput[];result?:{calculatedAt:string;key:string;inputs:ClosureInput[];rows:ClosureResult[]};history?:Array<NonNullable<ClosureBook['result']>>};
|
||||
const scope=(d:Record<string,unknown>)=>String(d.context_opportunity||'general');
|
||||
@@ -18,7 +18,7 @@ function maps(d:Record<string,unknown>):Record<string,ClosureBook>{const v=store
|
||||
function validate(b:ClosureBook):void{
|
||||
if(!b||b.version!==1||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history)||b.result!==undefined&&(!Array.isArray(b.result.rows)||!Array.isArray(b.result.inputs)||typeof b.result.key!=='string'))throw Error('FechoL3 não recuperável;originais preservados.');
|
||||
if(b.result?.rows.some(r=>!r||typeof r.id!=='string'||!r.fields||typeof r.fields!=='object'||!['determined','indeterminate'].includes(r.status)||Object.values(r.fields).some(v=>v!==null&&(!v||!/^\d{1,80}$/.test(v.numerator)||!/^\d{1,80}$/.test(v.denominator)||BigInt(v.denominator)===0n))))throw Error('ResultadoL3 não recuperável;originais preservados.');
|
||||
for(const r of b.rows){if(!r||!['id','period','entityRef','submissionDate','filingRef','interestAmount','interestRef'].every(k=>typeof r[k as keyof ClosureInput]==='string')||!['projected','effective'].includes(r.origin)||!['','yes','no'].includes(r.firstDeclaration)||!['','yes','no'].includes(r.creditsComplete)||!['','timely','late'].includes(r.filing)||!['','none','documented'].includes(r.interest)||!['','report','refund'].includes(r.destination)||!Array.isArray(r.credits)||!Array.isArray(r.payments)||r.credits.some(c=>!c||!['opening','communication','previous'].includes(c.kind)||!['id','period','reference','date','amount','previousId','carryKey'].every(k=>typeof c[k as keyof ClosureCredit]==='string'))||r.payments.some(p=>!p||!['id','reference','date','amount'].every(k=>typeof p[k as keyof ClosurePayment]==='string'))||r.source!==null&&(!r.source||!Number.isInteger(r.source.revision)||!['calculatedAt','fingerprint','opportunity','currency','period','location'].every(k=>typeof r.source![k as keyof ClosureSource]==='string')))throw Error('EntradasL3 não recuperáveis;originais preservados,sem substituir por zero.');}
|
||||
for(const r of b.rows){if(!r||!['id','period','entityRef','submissionDate','filingRef','interestAmount','interestRef'].every(k=>typeof r[k as keyof ClosureInput]==='string')||!['projected','effective'].includes(r.origin)||!['','yes','no'].includes(r.firstDeclaration)||!['','yes','no'].includes(r.creditsComplete)||!['','timely','late'].includes(r.filing)||!['','none','documented'].includes(r.interest)||!['','report','refund'].includes(r.destination)||r.refundAmount!==undefined&&typeof r.refundAmount!=='string'||!Array.isArray(r.credits)||!Array.isArray(r.payments)||r.credits.some(c=>!c||!['opening','communication','previous'].includes(c.kind)||!['id','period','reference','date','amount','previousId','carryKey'].every(k=>typeof c[k as keyof ClosureCredit]==='string'))||r.payments.some(p=>!p||!['id','reference','date','amount'].every(k=>typeof p[k as keyof ClosurePayment]==='string'))||r.source!==null&&(!r.source||!Number.isInteger(r.source.revision)||!['calculatedAt','fingerprint','opportunity','currency','period','location'].every(k=>typeof r.source![k as keyof ClosureSource]==='string')))throw Error('EntradasL3 não recuperáveis;originais preservados,sem substituir por zero.');}
|
||||
}
|
||||
export function closureBook(d:Record<string,unknown>):ClosureBook{const b=maps(d)[scope(d)]??{version:1,rows:[]};validate(b);return b;}
|
||||
export function saveClosureBook(d:Record<string,unknown>,b:ClosureBook):string{validate(b);const s=store(d);return JSON.stringify({...s,ivaClosures:{...maps(d),[scope(d)]:b}});}
|
||||
@@ -38,6 +38,16 @@ function substitutionPayments(d:Record<string,unknown>):string[]{const m=store(d
|
||||
function bookKey(d:Record<string,unknown>,b:ClosureBook){const sources=closureSources(d);const values:unknown[]=[b.rows,b.rows.map(r=>sources.some(s=>JSON.stringify(s)===JSON.stringify(r.source))),otherUses(d,b).map(r=>[normalize(r.entityRef),r.origin,r.period,r.credits.map(c=>creditIdentity(r,c)),r.payments.map(p=>paymentIdentity(r,p))])];const reserved=substitutionPayments(d),conflicts=b.rows.flatMap(r=>r.payments.map(p=>paymentIdentity(r,p))).filter(k=>reserved.includes(k));if(conflicts.length)values.push(conflicts);return closureFingerprint(values);}
|
||||
export function closureCurrent(d:Record<string,unknown>,b:ClosureBook):boolean{return !!b.result&&b.result.key===bookKey(d,b)&&b.result.rows.length===b.rows.length&&b.rows.every(r=>b.result!.rows.filter(x=>x.id===r.id&&x.inputKey===inputKey(r)).length===1);}
|
||||
export function adoptPreviousCredit(c:ClosureCredit,previous:ClosureResult):ClosureCredit{if(previous.status!=='determined'||!previous.fields['26'])throw Error('Fecho anterior indeterminado;sem crédito presumido.');return{...c,kind:'previous',period:previous.period,reference:`Fecho ${previous.id}`,previousId:previous.id,carryKey:previous.carryKey,amount:fractionPresentation(previous.fields['26']).decimal,date:''};}
|
||||
function refundLockConflict(d:Record<string,unknown>,row:ClosureInput,fields:Record<string,FiscalFraction|null>):boolean{
|
||||
const books=store(d).ivaRefunds as Record<string,{rows:Array<{source:{closureId:string;entityRef:string;origin:string;period:string;credit:FiscalFraction;carry:FiscalFraction;requested:FiscalFraction}|null;stage:string;decision:string;receipts:unknown[]}>;result?:{inputs:Array<{source:{closureId:string;entityRef:string;origin:string;period:string;credit:FiscalFraction;carry:FiscalFraction;requested:FiscalFraction}|null;stage:string;decision:string;receipts:unknown[]}>};history?:Array<{inputs:Array<{source:{closureId:string;entityRef:string;origin:string;period:string;credit:FiscalFraction;carry:FiscalFraction;requested:FiscalFraction}|null;stage:string;decision:string;receipts:unknown[]}>}>}>|undefined;
|
||||
return Object.entries(books??{}).some(([context,book])=>[...book.rows,...[...(book.history??[]),...(book.result?[book.result]:[])].flatMap(c=>c.inputs)].some(r=>r.source&&(context===scope(d)||r.source.origin==='effective')&&r.source.closureId===row.id&&normalize(r.source.entityRef)===normalize(row.entityRef)&&r.source.origin===row.origin&&r.source.period===row.period&&(r.stage==='submitted'||r.decision!=='pending'||r.receipts.length>0)&&[['25',r.source.credit],['26',r.source.carry],['27',r.source.requested]].some(([f,v])=>JSON.stringify(fields[f as string])!==JSON.stringify(v))));
|
||||
}
|
||||
function effectiveCarryAlreadyUsed(d:Record<string,unknown>,row:ClosureInput,fields:Record<string,FiscalFraction|null>):boolean{
|
||||
if(row.origin!=='effective'||row.destination!=='refund')return false;
|
||||
const books=maps(d),own=books[scope(d)];if(!own)return false;
|
||||
const prior=[...(own.history??[]),...(own.result?[own.result]:[])].flatMap(c=>c.rows).filter(r=>r.id===row.id&&r.origin==='effective'&&r.fields['26']&&fields['26']&&BigInt(subtractFractions(r.fields['26'],fields['26']).numerator)>0n);
|
||||
return prior.some(p=>Object.values(books).some(b=>[...(b.history??[]),...(b.result?[b.result]:[])].some(c=>c.inputs.some(input=>input.origin==='effective'&&normalize(input.entityRef)===normalize(row.entityRef)&&input.period>row.period&&input.credits.some(credit=>credit.kind==='previous'&&credit.previousId===p.id&&credit.carryKey===p.carryKey)&&c.rows.some(r=>r.id===input.id&&r.status==='determined')))));
|
||||
}
|
||||
export function recordClosure(d:Record<string,unknown>,b:ClosureBook,now=new Date().toISOString()):ClosureBook{
|
||||
validate(b);if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identidade de fecho repetida.');
|
||||
const contexts=b.rows.map(r=>JSON.stringify([normalize(r.entityRef),r.origin,r.period]));if(new Set(contexts).size!==contexts.length)throw Error('Mesmo âmbito/período repetido neste cenário;sem duplicar o fecho.');
|
||||
@@ -78,21 +88,29 @@ export function recordClosure(d:Record<string,unknown>,b:ClosureBook,now=new Dat
|
||||
else if(row.interest==='documented'&&row.interestRef.trim()){try{fields['23']=decimalFraction(row.interestAmount);}catch{/* Preserve unknown. */}}
|
||||
fields['24']=fields['23']?addFractions([fields['22'],fields['23']]):null;
|
||||
if(row.destination==='report'){fields['26']=fields['25'];fields['27']=zero();}
|
||||
if(row.destination!=='report'){stop('Escolha reporte integral para o percursoA/B;pedido de reembolso/alocação27 segueL3-D e não é presumido.');continue;}
|
||||
else if(row.destination==='refund'){
|
||||
let amount:FiscalFraction;try{amount=decimalFraction(row.refundAmount??'');}catch{stop('Identifique a parcela destinada ao pedido;sem montante presumido.');continue;}
|
||||
if(BigInt(amount.numerator)===0n||BigInt(subtractFractions(fields['25'],amount).numerator)<0n){stop('Parcela do pedido deve ser positiva e não exceder o crédito25;sem dupla utilização.');continue;}
|
||||
fields['27']=amount;fields['26']=subtractFractions(fields['25'],amount);r.source+=' D:afectação expressa26+27=25,sem concessão/recebimento;CIVA21 eModeloA11.';
|
||||
}else{stop('Escolha reporte integral ou parcela para preparação de reembolsoD;sem destino presumido.');continue;}
|
||||
if(refundLockConflict(d,row,fields)){stop('Crédito ligado a pedido apresentado/decisão/recebimento;conserve a afectação e reconcilie o procedimento,sem o voltar a reportar ou alterar silenciosamente.');continue;}
|
||||
if(effectiveCarryAlreadyUsed(d,row,fields)){stop('Reporte efectivo já utilizado numa declaração posterior recuperada;prepare o pedido sobre o saldo disponível do período reconciliado,sem reafectar retroactivamente crédito consumido.');continue;}
|
||||
if(!fields['24']){stop('Juros23 por identificar:indique montante documentado/pressuposto ou inexistência expressa com motivo;sem taxa presumida.');continue;}
|
||||
r.status='determined';r.carryKey=closureFingerprint([r.id,r.financialKey,fields['26'],scope(d)]);
|
||||
const payments:FiscalFraction[]=[];
|
||||
for(const p of row.payments){if(!p.reference.trim()||!validDate(p.date)||(puses.get(paymentIdentity(row,p))??0)>1||substitutionPayments(d).includes(paymentIdentity(row,p))){invalid='Pagamento sem referência/data ou repetido noutro período/declaração;conserve o documento e reconcilie a imputação.';break;}try{payments.push(decimalFraction(p.amount));}catch{invalid='Montante de pagamento por identificar;sem baixa presumida.';break;}}
|
||||
if(invalid){stop(invalid);continue;}
|
||||
r.paid=addFractions(payments);r.outstanding=positiveFraction(subtractFractions(fields['24'],r.paid));r.overpayment=positiveFraction(subtractFractions(r.paid,fields['24']));
|
||||
r.status='determined';r.carryKey=closureFingerprint([r.id,r.financialKey,fields['26'],scope(d)]);r.reason='Saldo calculado, pagamentos registados e crédito transportado distintos. Pagamento não altera22/25/26;excedente pago não cria crédito20 sem comunicação.21 não aplicável à primeira declaração;27zero pela opção expressa de reporte integral. '+(row.origin==='projected'?'Pressupostos de simulação,sem declaração ou pagamento efectuados pelo portal.':'Dados efectivos declarados;registo não certifica cobrança nem submissãoAT.');results.push(r);
|
||||
r.status='determined';r.carryKey=closureFingerprint([r.id,r.financialKey,fields['26'],scope(d)]);r.reason='Saldo calculado, pagamentos registados e crédito transportado distintos. Pagamento não altera22/25/26;excedente pago não cria crédito20 sem comunicação.21 não aplicável à primeira declaração;'+(row.destination==='refund'?'26/27 separados pela afectação;27 é parcela destinada a pedidoD,sem concessão nem recebimento. ':'27zero pela opção expressa de reporte integral. ')+(row.origin==='projected'?'Pressupostos de simulação,sem declaração ou pagamento efectuados pelo portal.':'Dados efectivos declarados;registo não certifica cobrança nem submissãoAT.');results.push(r);
|
||||
}
|
||||
const result={calculatedAt:now,key:bookKey(d,b),inputs:structuredClone(b.rows),rows:results};return{...b,...(b.result?{history:[...(b.history??[]),structuredClone(b.result)]}:{}),result};
|
||||
}
|
||||
export function closureReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{
|
||||
try{
|
||||
const b=closureBook(d);if(!b.rows.length)return[];
|
||||
const out:Array<[string,string,string,string]>=[['IVA mensal','L3-A/B — fecho por período',closureCurrent(d,b)?'Resultado correspondente às entradas':'Histórico preservado;rever entradas/origens antes de utilizar',closureSource]];
|
||||
const hasRefund=b.result?.rows.some(r=>r.fields['27']&&BigInt(r.fields['27'].numerator)>0n);
|
||||
const description=hasRefund?closureSource.replace('Sem substituição21,reembolso27 ou restituição13(2).','ExtensãoD:26+27=25;27é parcela destinada a pedido,não decisão/recebimento. Substituição21 e restituição13(2) mantêm percursos próprios.'):closureSource;
|
||||
const out:Array<[string,string,string,string]>=[['IVA mensal','L3-A/B — fecho por período',closureCurrent(d,b)?'Resultado correspondente às entradas':'Histórico preservado;rever entradas/origens antes de utilizar',description]];
|
||||
for(const r of b.result?.rows??[]){
|
||||
const input=b.result?.inputs.find(x=>x.id===r.id),source=input?.source;
|
||||
const context=`${r.entityRef};${scope(d)};MZN;${r.origin==='projected'?'Pressuposto de simulação':'Dados efectivos declarados'};versão guardada${b.result!.calculatedAt}`;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {closureBook,closureCurrent,closureFingerprint} from './iva-closure.ts';
|
||||
import {addFractions,subtractFractions,positiveFraction,decimalFraction,fraction,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
|
||||
|
||||
export const refundSource='L3-D:CIVA21(5/6/7),Lei13/2016 PDF15–16,p1426(273–274);Lei22/2022 PDF3–4;Lei10/2025 PDF13/p2180(13),21(7a/8revogado/10/13/15). ModeloA/VAT Return9c0b99a8,p2/quadro11:26+27=25. Pedido,decisão e recebimento separados. Formalidades doRegulamento78/2017/30/2022/52/2025 não automatizadas;sem concessão automática.';
|
||||
type Choice=''|'yes'|'no';
|
||||
export type RefundSource={closureId:string;period:string;entityRef:string;origin:'projected'|'effective';allocationKey:string;calculatedAt:string;credit:FiscalFraction;carry:FiscalFraction;requested:FiscalFraction};
|
||||
export type RefundReceipt={id:string;reference:string;date:string;amount:string};
|
||||
export type RefundInput={id:string;source:RefundSource|null;ground:''|'four_months'|'high_credit'|'cessation';originPeriod:string;entitlementDate:string;cessationDate:string;groundRef:string;factsConfirmed:Choice;oldestFirst:Choice;stage:'preparation'|'submitted';requestReference:string;requestDate:string;decision:'pending'|'approved'|'partial'|'rejected';decisionReference:string;decisionDate:string;decisionIssuer:string;approvedAmount:string;receipts:RefundReceipt[]};
|
||||
export type RefundResult={id:string;status:'determined'|'indeterminate';reason:string;inputKey:string;eligibility:'compatible'|'not_demonstrated'|'incompatible';requested:FiscalFraction|null;reported:FiscalFraction|null;approved:FiscalFraction|null;received:FiscalFraction|null;awaitingReceipt:FiscalFraction|null;notGranted:FiscalFraction|null;source:string};
|
||||
export type RefundCalculation={calculatedAt:string;key:string;inputs:RefundInput[];rows:RefundResult[]};
|
||||
export type RefundBook={version:1;rows:RefundInput[];result?:RefundCalculation;history?:RefundCalculation[]};
|
||||
const scope=(d:Record<string,unknown>)=>String(d.context_opportunity||'general'),norm=(s:string)=>s.trim().toLocaleLowerCase(),zero=()=>fraction(0n);
|
||||
const date=(s:string)=>/^20\d{2}-(0[1-9]|1[0-2])-\d{2}$/.test(s)&&!Number.isNaN(Date.parse(s))&&new Date(s).toISOString().slice(0,10)===s;
|
||||
const period=(s:string)=>/^20\d{2}-(0[1-9]|1[0-2])$/.test(s);
|
||||
const months=(a:string,b:string)=>(Number(b.slice(0,4))-Number(a.slice(0,4)))*12+Number(b.slice(5))-Number(a.slice(5));
|
||||
function store(d:Record<string,unknown>):Record<string,unknown>{const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books)throw Error('Livros não recuperáveis;originais preservados.');return s;}
|
||||
function maps(d:Record<string,unknown>):Record<string,RefundBook>{const m=store(d).ivaRefunds;if(m===undefined)return{};if(!m||typeof m!=='object'||Array.isArray(m))throw Error('Mapa de reembolsos não recuperável;sem o substituir.');return m as Record<string,RefundBook>;}
|
||||
function validate(b:RefundBook):void{if(!b||b.version!==1||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history)||b.result!==undefined&&(!Array.isArray(b.result.rows)||!Array.isArray(b.result.inputs)))throw Error('Livro de reembolsos não recuperável;originais preservados.');for(const r of b.rows)if(!r||!['id','originPeriod','entitlementDate','cessationDate','groundRef','requestReference','requestDate','decisionReference','decisionDate','decisionIssuer','approvedAmount'].every(k=>typeof r[k as keyof RefundInput]==='string')||!['','four_months','high_credit','cessation'].includes(r.ground)||!['','yes','no'].includes(r.factsConfirmed)||!['','yes','no'].includes(r.oldestFirst)||!['preparation','submitted'].includes(r.stage)||!['pending','approved','partial','rejected'].includes(r.decision)||!Array.isArray(r.receipts)||r.receipts.some(p=>!p||!['id','reference','date','amount'].every(k=>typeof p[k as keyof RefundReceipt]==='string')))throw Error('EntradasD não recuperáveis;sem montantes presumidos.');}
|
||||
export function refundBook(d:Record<string,unknown>):RefundBook{const b=maps(d)[scope(d)]??{version:1,rows:[]};validate(b);return b;}
|
||||
export function saveRefundBook(d:Record<string,unknown>,b:RefundBook):string{validate(b);const s=store(d);return JSON.stringify({...s,ivaRefunds:{...maps(d),[scope(d)]:b}});}
|
||||
export function refundSources(d:Record<string,unknown>):RefundSource[]{const b=closureBook(d);if(!closureCurrent(d,b))return[];return(b.result?.rows??[]).flatMap(r=>{const p=r.fields['27'],c=r.fields['25'],carry=r.fields['26'];if(r.status!=='determined'||!p||!c||!carry||BigInt(p.numerator)<=0n)return[];const dependentUnreconciled=b.rows.some(input=>input.credits.some(credit=>credit.kind==='previous'&&credit.previousId===r.id)&&!b.result!.rows.some(result=>result.id===input.id&&result.status==='determined'));if(dependentUnreconciled)return[];return[{closureId:r.id,period:r.period,entityRef:r.entityRef,origin:r.origin,allocationKey:closureFingerprint([r.id,r.entityRef,r.origin,r.period,c,carry,p]),calculatedAt:b.result!.calculatedAt,credit:c,carry,requested:p}];});}
|
||||
export function newRefund(id:string,source:RefundSource|null):RefundInput{return{id,source:source?structuredClone(source):null,ground:'',originPeriod:'',entitlementDate:'',cessationDate:'',groundRef:'',factsConfirmed:'',oldestFirst:'',stage:'preparation',requestReference:'',requestDate:'',decision:'pending',decisionReference:'',decisionDate:'',decisionIssuer:'',approvedAmount:'',receipts:[]};}
|
||||
// A new calculation timestamp alone does not change an identical allocation.
|
||||
function available(d:Record<string,unknown>,r:RefundInput):boolean{return !!r.source&&refundSources(d).some(s=>s.allocationKey===r.source!.allocationKey&&s.closureId===r.source!.closureId&&s.entityRef===r.source!.entityRef&&s.origin===r.source!.origin&&s.period===r.source!.period&&JSON.stringify([s.credit,s.carry,s.requested])===JSON.stringify([r.source!.credit,r.source!.carry,r.source!.requested]));}
|
||||
function identity(r:RefundInput):string{return JSON.stringify([norm(r.source?.entityRef??''),r.source?.origin,r.source?.period,r.source?.closureId]);}
|
||||
function uses(d:Record<string,unknown>,b:RefundBook):RefundInput[]{return[...b.rows,...Object.entries(maps(d)).filter(([id])=>id!==scope(d)).flatMap(([,v])=>{validate(v);return v.rows.filter(r=>r.source?.origin==='effective');})];}
|
||||
function receiptIdentity(r:RefundInput,p:RefundReceipt):string{return JSON.stringify([norm(r.source?.entityRef??''),r.source?.origin,norm(p.reference)]);}
|
||||
function past(d:Record<string,unknown>,b:RefundBook):RefundInput[]{return Object.entries({...maps(d),[scope(d)]:b}).flatMap(([context,v])=>[...(v.history??[]),...(v.result?[v.result]:[])].flatMap(c=>c.inputs.filter(r=>context===scope(d)||r.source?.origin==='effective')));}
|
||||
function key(d:Record<string,unknown>,b:RefundBook):string{const all=uses(d,b),old=past(d,b);return closureFingerprint([b.rows,b.rows.map(r=>available(d,r)),all.map(r=>[r.id,identity(r),r.receipts.map(p=>receiptIdentity(r,p))]),b.rows.map(r=>old.filter(v=>v.id!==r.id&&(v.stage==='submitted'||v.decision!=='pending'||v.receipts.length)).some(v=>identity(v)===identity(r)||r.receipts.some(p=>v.receipts.some(q=>receiptIdentity(v,q)===receiptIdentity(r,p)))))]);}
|
||||
export function refundCurrent(d:Record<string,unknown>,b:RefundBook):boolean{return!!b.result&&b.result.key===key(d,b)&&b.result.rows.length===b.rows.length&&b.rows.every(r=>b.result!.rows.some(v=>v.id===r.id&&v.inputKey===closureFingerprint(r)));}
|
||||
export function recordRefund(d:Record<string,unknown>,b:RefundBook,now=new Date().toISOString()):RefundBook{
|
||||
validate(b);if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identidade de pedido repetida.');const all=uses(d,b),old=past(d,b);
|
||||
const rows=b.rows.map(row=>{
|
||||
const r:RefundResult={id:row.id,status:'indeterminate',reason:'',inputKey:closureFingerprint(row),eligibility:'not_demonstrated',requested:null,reported:null,approved:null,received:null,awaitingReceipt:null,notGranted:null,source:refundSource};const stop=(reason:string)=>({...r,reason});const s=row.source;
|
||||
if(!s||!available(d,row)||d.currency!=='MZN'||!/^2026-(0[1-9]|1[0-2])$/.test(s.period))return stop('Escolha afectação26/27 actual e recuperável do mesmo período2026/MZN;crédito já reportado não é pedido disponível.');
|
||||
if(all.filter(v=>identity(v)===identity(row)).length>1||old.some(v=>v.id!==row.id&&identity(v)===identity(row)&&(v.stage==='submitted'||v.decision!=='pending'||v.receipts.length)))return stop('Parcela já ligada a outro pedido no âmbito recuperado;sem pedir ou consumir duas vezes o mesmo crédito.');
|
||||
r.requested=s.requested;r.reported=s.carry;
|
||||
let eligibilityReason='';
|
||||
if(!row.ground||!period(row.originPeriod)||row.originPeriod>s.period||!date(row.entitlementDate)||row.entitlementDate.slice(0,7)!==row.originPeriod||!row.groundRef.trim()||row.factsConfirmed!=='yes'||row.oldestFirst!=='yes')eligibilityReason='Fundamento/origem/nascimento do crédito,suporte ou consideração dos créditos mais antigos ainda não demonstrados;sem direito presumido.';
|
||||
if(!date(row.requestDate)||row.requestDate.slice(0,7)<s.period||!row.requestReference.trim())return stop('Identifique referência e data do pedido ou preparação;separar da decisão.');
|
||||
if(!eligibilityReason){
|
||||
const lastYear=Number(row.entitlementDate.slice(0,4))+10,lastMonth=Number(row.entitlementDate.slice(5,7)),lastDay=Math.min(Number(row.entitlementDate.slice(8)),new Date(Date.UTC(lastYear,lastMonth,0)).getUTCDate());const anniversary=`${lastYear}-${String(lastMonth).padStart(2,'0')}-${String(lastDay).padStart(2,'0')}`;
|
||||
const above=(n:bigint)=>BigInt(subtractFractions(s.credit,fraction(n)).numerator)>0n;
|
||||
if(row.requestDate<row.entitlementDate||row.requestDate>anniversary){r.eligibility='incompatible';eligibilityReason='Datas declaradas incompatíveis com nascimento do direito/limite10anos de21(15),Lei10/2025;conservar documentos e identificar eventual excepção.';}
|
||||
else if(row.ground==='four_months'&&(months(row.originPeriod,s.period)<4||!above(100000n))||row.ground==='high_credit'&&!above(500000n)||row.ground==='cessation'&&(!date(row.cessationDate)||row.cessationDate>row.requestDate)){r.eligibility='incompatible';eligibilityReason='Factos declarados não demonstram21(6),quatro meses/crédito superior100000;21(7a),superior500000;ou21(7b),cessação identificada.';}
|
||||
else r.eligibility='compatible';
|
||||
}
|
||||
if(row.stage==='preparation'&&(row.decision!=='pending'||row.receipts.length))return stop('Preparação não é pedido apresentado;registe apresentação externa antes de decisão/recebimento.');
|
||||
if(row.decision==='pending'){
|
||||
if(row.receipts.length)return stop('Recebimento sem decisão identificada;conservar registo sem o presumir reconhecido.');
|
||||
if(eligibilityReason)return stop(eligibilityReason+' Pedido/estado conservados;formalidades regulamentares não automatizadas.');
|
||||
r.status='determined';r.reason=row.stage==='preparation'?'Pedido em preparação;parcela separada do reporte,sem submissão,decisão ou recebimento.':'Pedido apresentado fora doInvesteMoz;decisão e recebimento ainda não identificados.';return r;
|
||||
}
|
||||
if(!row.decisionReference.trim()||!row.decisionIssuer.trim()||!date(row.decisionDate)||row.decisionDate<row.requestDate)return stop('Identifique decisão recebida,número/data e entidade emitente;pedido não equivale a deferimento.');
|
||||
let approved:FiscalFraction;try{approved=decimalFraction(row.approvedAmount);}catch{return stop('Montante reconhecido por identificar;indeferimento exigezero expresso,sem transformar desconhecido emzero.');}
|
||||
if(BigInt(subtractFractions(approved,s.requested).numerator)>0n||row.decision==='approved'&&JSON.stringify(approved)!==JSON.stringify(s.requested)||row.decision==='rejected'&&BigInt(approved.numerator)!==0n||row.decision==='partial'&&(BigInt(approved.numerator)<=0n||BigInt(subtractFractions(s.requested,approved).numerator)<=0n))return stop('Montante reconhecido incompatível com decisão/pedido;conservar o âmbito exacto,sem generalizar o deferimento.');
|
||||
r.approved=approved;r.notGranted=subtractFractions(s.requested,approved);const receipts:FiscalFraction[]=[];
|
||||
for(const p of row.receipts){const id=receiptIdentity(row,p);if(!p.reference.trim()||!date(p.date)||p.date<row.decisionDate||all.flatMap(v=>v.receipts.filter(q=>receiptIdentity(v,q)===id)).length>1||old.some(v=>v.id!==row.id&&v.receipts.some(q=>receiptIdentity(v,q)===id)))return stop('Recebimento sem referência/data posterior à decisão ou já registado noutro pedido;sem duplicar caixa.');try{const v=decimalFraction(p.amount);if(BigInt(v.numerator)<=0n)return stop('Recebimento exige montante positivo identificado.');receipts.push(v);}catch{return stop('Montante recebido por identificar;decisão não cria recebimento.');}}
|
||||
r.received=addFractions(receipts);if(BigInt(subtractFractions(r.received,approved).numerator)>0n)return stop('Recebimentos excedem o montante reconhecido;reconcilie sem criar crédito ou caixa adicional.');r.awaitingReceipt=positiveFraction(subtractFractions(approved,r.received));r.status='determined';r.reason=(eligibilityReason?'Decisão externa documentada registada independentemente da avaliação automática do fundamento: '+eligibilityReason+' ':'')+'Pedido,decisão e recebimento separados;parcela27 não é reutilizada em26. Parte não deferida/indeferimento não libertam crédito automaticamente;eventual retorno exige procedimento documentado e reconciliação,sem duplicar20. Recebimento é registo declarado,sem execução bancária ou alteração silenciosa de caixa. Formalidades e suspensões não automatizadas;150dias não são promessa de recebimento.';return r;
|
||||
});
|
||||
const result={calculatedAt:now,key:key(d,b),inputs:structuredClone(b.rows),rows};return{...b,...(b.result?{history:[...(b.history??[]),structuredClone(b.result)]}:{}),result};
|
||||
}
|
||||
export function refundReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=refundBook(d);if(!b.rows.length)return[];const out:Array<[string,string,string,string]>=[['IVA mensal','L3-D — reembolso geral',refundCurrent(d,b)?'Resultado correspondente às entradas':'Histórico preservado;rever entradas/afectação',refundSource]],fmt=(v:FiscalFraction|null|undefined)=>v?fractionPresentation(v).decimal:'Indeterminado';for(const r of b.result?.rows??[]){const input=b.result!.inputs.find(v=>v.id===r.id),s=input?.source;out.push(['IVA mensal',`Reembolso — ${s?.period??'por identificar'}`,`Fundamento:${r.eligibility==='compatible'?'Compatível com factos declarados':r.eligibility==='incompatible'?'Divergência nos factos — ver suporte':'Não demonstrado automaticamente'};Pedido27:${fmt(r.requested)};reporte26 separado:${fmt(r.reported)};reconhecido:${fmt(r.approved)};recebido:${fmt(r.received)};aguarda recebimento:${fmt(r.awaitingReceipt)};não deferido:${fmt(r.notGranted)}`,`${s?.entityRef??''};${scope(d)};MZN;${s?.origin==='effective'?'Dados efectivos declarados':'Pressuposto de simulação'};${input?.stage==='submitted'?'Apresentado fora doInvesteMoz':'Em preparação'};pedido ${input?.requestReference??''},${input?.requestDate??''};origem${input?.originPeriod??''},nascimento${input?.entitlementDate??''};${input?.groundRef??''};${r.reason}`]);out.push(['IVA mensal',`Decisão e recebimento — ${s?.period??''}`,`Decisão:${input?.decision==='approved'?'Deferido':input?.decision==='partial'?'Deferimento parcial':input?.decision==='rejected'?'Indeferido':'Pendente'};documento${input?.decisionReference??''};${input?.decisionDate??''};${input?.decisionIssuer??''};recebimentos:${input?.receipts.map(p=>`${p.reference};${p.date};${p.amount}MZN`).join(' | ')||'nenhum registado'}`,`Afectação do fecho${s?.closureId??''},versão${s?.calculatedAt??''};25:${fmt(s?.credit)}=26:${fmt(s?.carry)}+27:${fmt(s?.requested)};âmbito apenas desta parcela;versão guardada${b.result!.calculatedAt};${refundSource}`]);}return out;}catch{return[['IVA mensal','L3-D','Pedido não recuperável','Originais preservados;sem concessão/recebimento presumidos.']];}}
|
||||
@@ -17,6 +17,7 @@ import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
|
||||
import { acquisitionReportRows } from './iva-acquisitions.ts';
|
||||
import {closureReportRows} from './iva-closure.ts';
|
||||
import {substitutionReportRows} from './iva-substitution.ts';
|
||||
import {refundReportRows} from './iva-refund.ts';
|
||||
import { importExemptionReportRows } from './iva-import-exemptions.ts';
|
||||
import { publicBaseReportRows } from './iva-public-authority-base.ts';
|
||||
import { commissionBaseReportRows } from './iva-commission-base.ts';
|
||||
@@ -650,6 +651,7 @@ export function buildProjectDossierRows(
|
||||
rows.push(...acquisitionReportRows(draft));
|
||||
rows.push(...closureReportRows(draft));
|
||||
rows.push(...substitutionReportRows(draft));
|
||||
rows.push(...refundReportRows(draft));
|
||||
rows.push(...importExemptionReportRows(draft));
|
||||
rows.push(...publicBaseReportRows(draft));
|
||||
rows.push(...commissionBaseReportRows(draft));
|
||||
|
||||
@@ -18,7 +18,7 @@ final class IvaImportSuppliesDraftTest extends TestCase
|
||||
$this->assertSame(55441, (int) $target->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$this->actingAs(User::factory()->create(['email_verified_at' => now()]));
|
||||
$snapshot = json_encode(['padding' => str_repeat('x', 4000000)], JSON_THROW_ON_ERROR);
|
||||
$snapshot = json_encode(['padding' => str_repeat('x', 6000000)], JSON_THROW_ON_ERROR);
|
||||
$this->postJson('/api/v1/simulation-drafts', ['personal_snapshot' => [], 'economic_snapshot' => ['modelo_a_snapshot' => $snapshot]], ['Idempotency-Key' => (string) Str::uuid()])
|
||||
->assertUnprocessable()->assertJsonValidationErrors('economic_snapshot.modelo_a_snapshot');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 IvaRefundDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_refund_recovers_allocation_request_decision_receipts_history_and_all_prior_maps(): 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-l3-d-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);$b=$s['ivaRefunds']['opportunity:374'];$this->assertCount(1,$b['rows']);$this->assertCount(1,$b['history']);
|
||||
$r=$b['result']['rows'][0];$this->assertSame('determined',$r['status']);$this->assertSame('compatible',$r['eligibility']);
|
||||
foreach(['requested'=>'600000','reported'=>'200000','approved'=>'550000','received'=>'450000','awaitingReceipt'=>'100000','notGranted'=>'50000']as $f=>$v)$this->assertSame(['numerator'=>$v,'denominator'=>'1'],$r[$f]);
|
||||
$input=$b['result']['inputs'][0];$this->assertSame('SYNTHETIC QA pedidoR1',$input['requestReference']);$this->assertSame('2026-02-15',$input['requestDate']);$this->assertSame('partial',$input['decision']);$this->assertSame('SYNTHETIC QA decisãoD1',$input['decisionReference']);$this->assertCount(2,$input['receipts']);
|
||||
$allocation=$s['ivaClosures']['opportunity:374']['result']['rows'][0];foreach(['25'=>'800000','26'=>'200000','27'=>'600000']as $f=>$v)$this->assertSame(['numerator'=>$v,'denominator'=>'1'],$allocation['fields'][(string)$f]);
|
||||
$accepted=json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-l3d-preserved.json')),true,flags:JSON_THROW_ON_ERROR);$accepted=json_decode($accepted['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);
|
||||
foreach($accepted as $key=>$map){if($key==='version')continue;foreach($map as $context=>$original)$this->assertSame($original,$s[$key][$context]);}
|
||||
$frozen=json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-l3ab-preserved.json')),true,flags:JSON_THROW_ON_ERROR);
|
||||
foreach(json_decode($frozen['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR)as $key=>$map){if($key==='version')continue;foreach($map as $context=>$original)$this->assertSame($original,$s[$key][$context]);}
|
||||
$old=$s['ivaClosures']['opportunity:373']['result']['rows'][2];$this->assertSame(['numerator'=>'72','denominator'=>'1'],$old['fields']['24']);$this->assertSame(['numerator'=>'30','denominator'=>'1'],$old['paid']);$this->assertSame(['numerator'=>'42','denominator'=>'1'],$old['outstanding']);
|
||||
file_put_contents(base_path('outputs/iva-l3-d-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,31 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import {recordSales,saveSalesBook} from '../../resources/js/lib/iva-operations.ts';
|
||||
import {recordAcquisitions,saveAcquisitionBook,acquisitionBook} from '../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {closureBook,closureSources,newClosure,recordClosure,saveClosureBook,adoptPreviousCredit,newClosureCredit,closureCurrent} from '../../resources/js/lib/iva-closure.ts';
|
||||
import {substitutionBook,substitutionCurrent} from '../../resources/js/lib/iva-substitution.ts';
|
||||
import {refundBook,refundSources,newRefund,recordRefund,saveRefundBook,refundCurrent,refundReportRows} from '../../resources/js/lib/iva-refund.ts';
|
||||
const seed=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3d-preserved.json',import.meta.url),'utf8')),older=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3ab-preserved.json',import.meta.url),'utf8'));
|
||||
function fixture(){const d=structuredClone(seed),base=JSON.parse(older.modelo_a_snapshot),s=JSON.parse(d.modelo_a_snapshot);for(const[k,v]of Object.entries(base))if(k!=='version')s[k]={...v,...s[k]};d.modelo_a_snapshot=JSON.stringify(s);d.context_opportunity='opportunity:374';
|
||||
const sales=structuredClone(s.salesOperations['opportunity:373']);d.modelo_a_snapshot=saveSalesBook(d,recordSales(d,sales));const b=structuredClone(s.books['opportunity:373']);b.rows[0].net='5001000';b.rows[0].vat='800160';b.rows[0].gross='5801160';delete b.result;delete b.history;d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,b,'2026-10-25T13:00:00Z'));
|
||||
const row={...newClosure('refund-Jan',closureSources(d).find(s=>s.period==='2026-01'),'SYNTHETIC QA entidade374'),firstDeclaration:'yes',filing:'timely',submissionDate:'2026-02-15',filingRef:'SYNTHETIC QA prazo declarado',creditsComplete:'yes',interest:'none',interestRef:'SYNTHETIC QA sem juros',destination:'refund',refundAmount:'600000'};
|
||||
const ab=recordClosure(d,{version:1,rows:[row]},'2026-10-25T13:01:00Z');d.modelo_a_snapshot=saveClosureBook(d,ab);assert.equal(ab.result.rows[0].status,'determined',ab.result.rows[0].reason);
|
||||
const input={...newRefund('refund1',refundSources(d)[0]),ground:'high_credit',originPeriod:'2026-01',entitlementDate:'2026-01-10',groundRef:'SYNTHETIC QA extractos/origem800000 e créditos mais antigos',factsConfirmed:'yes',oldestFirst:'yes',requestReference:'SYNTHETIC QA pedidoR1',requestDate:'2026-02-15',stage:'submitted',decision:'partial',decisionReference:'SYNTHETIC QA decisãoD1',decisionDate:'2026-03-01',decisionIssuer:'SYNTHETIC QA entidade emitente declarada',approvedAmount:'550000',receipts:[{id:'receipt1',reference:'SYNTHETIC QA recebimentoR1a',date:'2026-03-05',amount:'200000'},{id:'receipt2',reference:'SYNTHETIC QA recebimentoR1b',date:'2026-03-10',amount:'250000'}]};return{d,b:{version:1,rows:[input]}};
|
||||
}
|
||||
const n=v=>v&&`${v.numerator}/${v.denominator}`;
|
||||
test('25 is allocated once between26 and27;requested,approved,received and waiting remain distinct',()=>{const{d,b}=fixture(),ab=closureBook(d).result.rows[0];assert.equal(n(ab.fields['25']),'800000/1');assert.equal(n(ab.fields['26']),'200000/1');assert.equal(n(ab.fields['27']),'600000/1');const r=recordRefund(d,b).result.rows[0];assert.equal(r.status,'determined',r.reason);assert.equal(r.eligibility,'compatible');assert.equal(n(r.requested),'600000/1');assert.equal(n(r.reported),'200000/1');assert.equal(n(r.approved),'550000/1');assert.equal(n(r.received),'450000/1');assert.equal(n(r.awaitingReceipt),'100000/1');assert.equal(n(r.notGranted),'50000/1');});
|
||||
test('preparation,presented pending,approval and rejection do not automatically create receipt or release credit',()=>{const{d,b}=fixture();for(const stage of ['preparation','submitted']){const v=structuredClone(b);Object.assign(v.rows[0],{stage,decision:'pending',receipts:[]});const r=recordRefund(d,v).result.rows[0];assert.equal(r.status,'determined');assert.equal(r.approved,null);assert.equal(r.received,null);}
|
||||
const full=structuredClone(b);Object.assign(full.rows[0],{decision:'approved',approvedAmount:'600000',receipts:[]});let r=recordRefund(d,full).result.rows[0];assert.equal(n(r.received),'0/1');assert.equal(n(r.awaitingReceipt),'600000/1');const rejected=structuredClone(b);Object.assign(rejected.rows[0],{decision:'rejected',approvedAmount:'0',receipts:[]});r=recordRefund(d,rejected).result.rows[0];assert.equal(r.status,'determined');assert.equal(n(r.notGranted),'600000/1');assert.equal(n(closureBook(d).result.rows[0].fields['26']),'200000/1');});
|
||||
test('source,request and decision details are required;unknown never becomes entitlement or cash',()=>{const{d,b}=fixture();for(const patch of [{source:null},{requestReference:''},{requestDate:''},{decisionReference:''},{decisionDate:'2026-02-01'},{decisionIssuer:''},{approvedAmount:''},{stage:'preparation'}]){const v=structuredClone(b);Object.assign(v.rows[0],patch);assert.equal(recordRefund(d,v).result.rows[0].status,'indeterminate',JSON.stringify(patch));}
|
||||
const forged=structuredClone(b);forged.rows[0].source.requested={numerator:'700000',denominator:'1'};assert.equal(recordRefund(d,forged).result.rows[0].requested,null);});
|
||||
test('a documented external decision is recorded without turning an unimplemented/unknown basis into a new approval gate',()=>{const{d,b}=fixture();for(const patch of [{ground:''},{factsConfirmed:''},{oldestFirst:''},{entitlementDate:''},{groundRef:''}]){const v=structuredClone(b);Object.assign(v.rows[0],patch);const r=recordRefund(d,v).result.rows[0];assert.equal(r.status,'determined',r.reason);assert.equal(r.eligibility,'not_demonstrated');assert.equal(n(r.approved),'550000/1');assert.equal(n(r.received),'450000/1');v.rows[0].decision='pending';v.rows[0].receipts=[];const pending=recordRefund(d,v).result.rows[0];assert.equal(pending.status,'indeterminate');assert.equal(pending.approved,null);}});
|
||||
test('current four-month/high-credit conditions and ten-year limit are applied without obsolete50percent rule',()=>{const{d,b}=fixture();const v=structuredClone(b);Object.assign(v.rows[0],{ground:'four_months',originPeriod:'2025-09',entitlementDate:'2025-09-10'});assert.equal(recordRefund(d,v).result.rows[0].eligibility,'compatible');v.rows[0].originPeriod='2025-10';v.rows[0].entitlementDate='2025-10-10';assert.equal(recordRefund(d,v).result.rows[0].eligibility,'incompatible');v.rows[0].originPeriod='2015-01';v.rows[0].entitlementDate='2015-01-10';assert.equal(recordRefund(d,v).result.rows[0].eligibility,'incompatible');
|
||||
const cessation=structuredClone(b);Object.assign(cessation.rows[0],{ground:'cessation',cessationDate:'2026-01-31'});assert.equal(recordRefund(d,cessation).result.rows[0].eligibility,'compatible');cessation.rows[0].cessationDate='2026-03-01';assert.equal(recordRefund(d,cessation).result.rows[0].eligibility,'incompatible');});
|
||||
test('thresholds are strictly greater;fractional credit remains exact after allocation',()=>{for(const [vat,request,ground,expected]of [['500160','100000','high_credit','incompatible'],['100160','50000','four_months','incompatible'],['500161','100000','high_credit','compatible']]){const{d,b}=fixture(),acq=acquisitionBook(d);acq.rows[0].vat=vat;acq.rows[0].gross=String(Number(acq.rows[0].net)+Number(vat));d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,acq));const ab=closureBook(d);ab.rows[0].source=closureSources(d).filter(s=>s.period==='2026-01').at(-1);ab.rows[0].refundAmount=request;d.modelo_a_snapshot=saveClosureBook(d,recordClosure(d,ab));Object.assign(b.rows[0],{source:refundSources(d)[0],ground,originPeriod:'2025-09',entitlementDate:'2025-09-10',stage:'preparation',decision:'pending',receipts:[]});assert.equal(recordRefund(d,b).result.rows[0].eligibility,expected);}
|
||||
const{d}=fixture(),acq=acquisitionBook(d);acq.rows[0].vat='800161';acq.rows[0].gross='5801161';acq.rows[0].d.numerator='1';acq.rows[0].d.denominator='3';d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,acq));const ab=closureBook(d);ab.rows[0].source=closureSources(d).filter(s=>s.period==='2026-01').at(-1);ab.rows[0].refundAmount='200000';const result=recordClosure(d,ab);assert.equal(n(result.result.rows[0].fields['25']),'799681/3');assert.equal(n(result.result.rows[0].fields['26']),'199681/3');assert.equal(n(result.result.rows[0].fields['27']),'200000/1');});
|
||||
test('request/receipt duplication,over-receipt and decisions beyond request are refused',()=>{const{d,b}=fixture();const copy=structuredClone(b);copy.rows.push({...structuredClone(copy.rows[0]),id:'copy'});assert.ok(recordRefund(d,copy).result.rows.every(r=>r.requested===null));for(const patch of [{approvedAmount:'600001'},{decision:'approved',approvedAmount:'550000'},{decision:'rejected',approvedAmount:'1'},{decision:'pending'}]){const v=structuredClone(b);Object.assign(v.rows[0],patch);assert.equal(recordRefund(d,v).result.rows[0].status,'indeterminate');}const receipt=structuredClone(b);receipt.rows[0].receipts.push({...receipt.rows[0].receipts[0],id:'copy'});assert.equal(recordRefund(d,receipt).result.rows[0].received,null);const over=structuredClone(b);over.rows[0].receipts[0].amount='400000';assert.equal(recordRefund(d,over).result.rows[0].status,'indeterminate');});
|
||||
test('submitted refund locks the allocation even after current preparation is removed;histories are not erased',()=>{const{d,b}=fixture();const book=recordRefund(d,b);d.modelo_a_snapshot=saveRefundBook(d,book);const ab=closureBook(d);ab.rows[0].destination='report';assert.match(recordClosure(d,ab).result.rows[0].reason,/Crédito ligado/);const removed=recordRefund(d,{...book,rows:[]});d.modelo_a_snapshot=saveRefundBook(d,removed);assert.match(recordClosure(d,ab).result.rows[0].reason,/Crédito ligado/);assert.ok(removed.history.length);});
|
||||
test('only residual26 travels to next month;old whole-credit carry requires explicit adoption',()=>{const{d,b}=fixture();let ab=closureBook(d);const full=structuredClone(ab);full.rows[0].destination='report';const before=recordClosure(d,full);const feb={...newClosure('refund-Feb',closureSources(d).find(s=>s.period==='2026-02'),'SYNTHETIC QA entidade374'),firstDeclaration:'yes',filing:'timely',submissionDate:'2026-03-15',filingRef:'SYNTHETIC QA prazo',creditsComplete:'yes',interest:'none',interestRef:'SYNTHETIC QA sem juros',destination:'report',credits:[adoptPreviousCredit(newClosureCredit('carry','previous'),before.result.rows[0])]};ab=recordClosure(d,{...ab,rows:[...ab.rows,feb]});assert.equal(ab.result.rows[1].status,'indeterminate');d.modelo_a_snapshot=saveClosureBook(d,ab);assert.deepEqual(refundSources(d),[]);ab.rows[1].credits[0]=adoptPreviousCredit(ab.rows[1].credits[0],ab.result.rows[0]);ab=recordClosure(d,ab);assert.equal(n(ab.result.rows[1].fields['19']),'200000/1');assert.equal(n(ab.result.rows[1].fields['26']),'199920/1');d.modelo_a_snapshot=saveClosureBook(d,ab);assert.equal(recordRefund(d,b).result.rows[0].status,'determined');});
|
||||
test('effective carry already used in a later declaration cannot be reallocated retroactively for a refund',()=>{const{d}=fixture(),acq=acquisitionBook(d);acq.rows[0].origin='effective';d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,acq));let ab=closureBook(d);Object.assign(ab.rows[0],{origin:'effective',source:closureSources(d).filter(s=>s.period==='2026-01').at(-1),destination:'report'});ab=recordClosure(d,ab);assert.equal(ab.result.rows[0].status,'determined');const feb={...structuredClone(ab.rows[0]),id:'actual-Feb',period:'2026-02',source:closureSources(d).filter(s=>s.period==='2026-02').at(-1),submissionDate:'2026-03-15',credits:[adoptPreviousCredit(newClosureCredit('actual-carry','previous'),ab.result.rows[0])]};ab=recordClosure(d,{...ab,rows:[...ab.rows,feb]});assert.equal(ab.result.rows[1].status,'determined');d.modelo_a_snapshot=saveClosureBook(d,ab);ab.rows[0].destination='refund';ab.rows[0].refundAmount='600000';const changed=recordClosure(d,ab);assert.match(changed.result.rows[0].reason,/Reporte efectivo já utilizado/);assert.equal(changed.result.rows[0].status,'indeterminate');});
|
||||
test('unsubmitted allocation may be changed explicitly,but old reported credit cannot fund the old request',()=>{const{d,b}=fixture();b.rows[0].stage='preparation';b.rows[0].decision='pending';b.rows[0].receipts=[];let book=recordRefund(d,b);d.modelo_a_snapshot=saveRefundBook(d,book);const ab=closureBook(d);ab.rows[0].destination='report';d.modelo_a_snapshot=saveClosureBook(d,recordClosure(d,ab));assert.equal(refundCurrent(d,book),false);assert.equal(recordRefund(d,book).result.rows[0].requested,null);});
|
||||
test('save/recover preserves all prior L1/L2/AB/C maps and frozen report;recalculation appends history',()=>{const{d,b}=fixture(),before=JSON.parse(d.modelo_a_snapshot);let book=recordRefund(d,b);d.modelo_a_snapshot=saveRefundBook(d,book);const after=JSON.parse(d.modelo_a_snapshot);for(const[k,v]of Object.entries(before))assert.deepEqual(after[k],v,k);assert.ok(refundCurrent(d,refundBook(d)));const cDraft={...d,context_opportunity:'opportunity:373'};assert.ok(substitutionCurrent(cDraft,substitutionBook(cDraft)));assert.ok(closureCurrent(cDraft,closureBook(cDraft)));book.rows[0].requestReference='deliberate edit';assert.equal(refundCurrent(d,book),false);d.modelo_a_snapshot=saveRefundBook(d,book);assert.doesNotMatch(refundReportRows(d).map(r=>r.join(' ')).join(' '),/deliberate edit/);const prior=structuredClone(book.result);book=recordRefund(d,book);assert.deepEqual(book.history.at(-1),prior);});
|
||||
test('export synthetic frozen refund reconciliation for guardedAPI and actual component proof',()=>{const{d,b}=fixture();const first=recordRefund(d,b,'2026-10-25T13:02:00Z'),book=recordRefund(d,first,'2026-10-25T13:03:00Z');d.modelo_a_snapshot=saveRefundBook(d,book);assert.ok(refundCurrent(d,book));if(process.env.IVA_L3D_CALCULATED)fs.writeFileSync(process.env.IVA_L3D_CALCULATED,JSON.stringify(d));});
|
||||
Reference in New Issue
Block a user