feat(portal): deliver ordinary national credit fraction and education continuity
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,5 +1,29 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 10/11/2026 — L8: fracção nacional ordinária demonstrada até ao relatório em QA
|
||||
|
||||
Pendência funcional encerrada **em QA no recorte ordinário**: opção explícita de derivar a fracção nacional do crédito internacional da colecta L6 corrente e da parcela líquida estrangeira correspondida à matéria colectável. Fonte aceite reutilizada: CIRPC65(1)(a/b),65(2),Lei34/2007,PDF18/p768-(72),e ordem66/RIRPC21. Não se exige nova validação da fonte oficial. A fórmula técnica `colecta antes de créditos × parcela líquida / matéria colectável` fica limitada à colecta normal corrente, base positiva, inclusão documentada, mesmo titular/exercício/país e afectação líquida identificada. O portal não inventa nem reparte custos/perdas/deduções; valores acima da base ou parcelas por país que em conjunto excedam a base impedem o saldo dependente.
|
||||
|
||||
O modo documentado anterior continua por omissão nos livros existentes. Limite/suporte manuais ficam conservados quando se escolhe derivação, mas não são utilizados como limite activo nesse modo. Nova correspondência fica ligada à chave da colecta L6; origem alterada exige nova conferência. Método inválido é recusado. Os resultados antigos congelados não são reescritos. Convenção/aplicabilidade/limite continuam documentados por caso; efeitos L7 e outros regimes/períodos/moedas conservam correspondência própria. Não há consumo de crédito oficial, pagamento ou submissão fiscal.
|
||||
|
||||
Percurso demonstrado: cálculo → gravação/actualização/GET pelos endpoints privados existentes → recuperação com snapshot íntegro → recálculo corrente → HTML e relatório financeiro do dossier. Exemplo exclusivamente sintético:base4400/colecta1408/parcela100 produz fracção32;imposto estrangeiro100 limitado32,PEC200/PPC500 deixam676MZN de saldo simulado. Parcela0,01 produz fracção exacta2/625,sem arredondamento fiscal presumido. Correspondência em falta deixa resultado indeterminado. LimiteCDT6 continua a reduzir a dedução e a produzir702 no cenário documentado. Provas em `outputs/irpc-national-fraction-20261110/recovered-reports.json`,HTMLs/relatórios no mesmo directório.
|
||||
|
||||
Seis casos frontend novos (cinco iniciais e um adicional de afectação por países),um caso API/23 asserções apenas do delta, com transacção revertida na QA existente `proactive_investment_20261027_test:55441`,actor sintético existente1021. Sem novas bases,concessões,migrations ou alterações aos documentos/históricos anteriores. Tipos e lint dirigidos aprovados; compilação QA final acompanhada no log próprio. Provas aceites de IVA/IRPC/PEC/transporte/económico não repetidas. Reprodução dirigida: `node --test tests/frontend/irpc-national-fraction.test.mjs`; gerador de amostras `tools/irpc-national-fraction-fixtures.mjs`; writer QA `php tools/irpc-national-fraction-qa.php test`; conferência dos relatórios recuperados `node tools/irpc-national-fraction-recovered.mjs`.
|
||||
|
||||
**L8 continua parcial; fiscal não se limita a L8.** Transporte ordinário e crédito económico já entregues em569a1cd7 preservados. Resíduos L1–L7 e lotes L9–L16 estão explicitados no saldo por módulo existente, sem presumir fecho integral a partir de uma entrada ou taxa. Novo delta L8 e continuidade de Educação são compatíveis (sem schema/rotas/writers novos), agrupados em `outputs/entrega-educacao-l8-20261110.zip`, com manifesto/fontes/patch/reversão contra ff726e57; ainda não instalados. Tipos/lint/Pint/compilação QA final aprovados. O índice de trabalho e o pacote anterior ficam preservados; conferência em índices temporários verifica aplicação e reversão, sem instalar ficheiros. Homologação mantém ff726e57; conferência visual permanece separada. O próximo trabalho é outra pendência funcional do saldo, não repetir esta prova.
|
||||
|
||||
## 10/11/2026 — execução de conclusão integral após instalação ff726e57
|
||||
|
||||
Entrega de fontes até `ff726e57` instalada e verificada por HTTPS na homologação; inclui S08 e os restantes ajustes do pacote selado. Disponibilidade de código não é uma nova atribuição real nem prova de todos os percursos privados. Recibo `outputs/reviewed-install-20261110/verification.json`: revisão servida, 44 hashes do delta, configuração preservada, zero migrations/importações e release anterior conservada. As anotações antigas «sem instalação» continuam históricas, superadas neste âmbito. Conferência visual S08 permanece no registo próprio, sem reinstalação ou bloqueio transversal.
|
||||
|
||||
Saldo actual por módulo/percurso reconciliado na visão consolidada existente e em 35 IDs da matriz, sem criar novas linhas ou apagar estados/critérios anteriores. Os 3.555 registos incluem cláusulas/campos sobrepostos, não 3.555 funcionalidades independentes. Restantes células preservadas; recibos aceites reutilizados, sem nova auditoria geral.
|
||||
|
||||
**Utilização avançada nesta passagem — Educação:** no detalhe privado de uma declaração, preparar o fundamento da revisão e consultar Anterior/Seguinte no histórico deixa de exigir abandonar a revisão. As ligações conservam estado/posição; resposta nova actualiza a declaração mas não adopta silenciosamente a versão analisada. Um envio sem confirmação continua recuperável com a chave original. Alterar declaração/tipo/actor mantém a chave de isolamento do componente. Sair para outra página continua protegido pela guarda existente. Não houve novo endpoint, decisão automática, writer ou efeito na base.
|
||||
|
||||
Quatro verificações novas de comportamento em `tests/frontend/education-review.test.mjs`, seleccionadas por `history paging`, aprovadas. Tipos globais, lint dirigido e compilação QA concluídos com código 0. O build QA existente foi actualizado, sem novo ambiente ou dados. Logs `storage/logs/education-history-continuity-20261110-{types,build}.log`. Sem repetir testes aceites ou suites PHP. O delta é local e ainda não instalado remotamente; não há prova de navegador nem persistência durável do motivo entre sessões. Saldo no módulo: essa retoma durável, jornadas externas com representação/vínculos documentados e conferência visual. O proprietário já indicou que não existe cadastro/documento aprovado desses vínculos; isso bloqueia apenas os ramos externos dependentes.
|
||||
|
||||
Próximo trabalho funcional na ordem preservada: saldo fiscal L8 com fontes aceites, seguido de modalidades de Imobiliário/Bens de consumo/franquias e Estatística/IA, sem reabrir os fechos aceites. Não há exclusão de funcionalidades previstas nem conclusão global presumida.
|
||||
|
||||
## 10/11/2026 — preparação conjunta de contador e aliases
|
||||
|
||||
[Entrega conjunta](../v2/auditoria-portal/entrega-conjunta-contador-aliases-2026-11-10.md) preparada contra `main`/`origin/main` em `2209b838`, com patch único, reversão e manifesto. Oito casos novos/56 asserções conferem contexto sem base de dados nos dois aliases, GET/HEAD e com/sem barra final. Hashes das fontes e do build privado do contador continuam iguais ao recibo aceite; provas anteriores reutilizadas, não repetidas.
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
## Continuação integral — saldo e execução em 10/11/2026
|
||||
|
||||
Actualização L8 desta passagem: recorte ordinário de derivação da fracção nacional implementado e demonstrado em QA até gravação/recuperação/relatório pelo writer privado existente. Base4400/colecta1408/parcela100 → fracção32 → saldo simulado676,com limites/provas próprios; caso exacto2/625 e correspondência em falta demonstrados. Nenhuma determinação automática de CDT,repartição de custos/perdas ou consumo oficial. Modo documentado e históricos preservados. Delta agrupável com Educação,ainda sem instalação. Fonte e percurso no início do manual; provas `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
|
||||
|
||||
Correcção do saldo global: preservar explicitamente resíduos L1–L7 e L9–L16 (INSS,IRPS mensal/anual,Selo,direitos,ICE,SISA,IPRA),conforme tabela actualizada na visão existente. Transporte/crédito económico entregues em569a1cd7 não são trabalho ausente. Nenhum lote agregado é fechado por número de testes;fecho comunicado nesta passagem é apenas a fracção nacional ordinária em QA.
|
||||
|
||||
Entrega agrupada de fontes: `outputs/entrega-educacao-l8-20261110.zip`, base ff726e57, manifesto e patch/reversão conferidos em índices temporários; Educação e L8 sem alterações de schema/rotas/writers. Tipos, lint, Pint e compilação QA finais aprovados. Sem instalação/publicação nova, sem alterar o pacote selado anterior ou o índice de trabalho. Homologação permanece ff726e57.
|
||||
|
||||
Instalação `ff726e57` concluída e verificada na homologação: contador/aliases, contactos, pré-dossier, curadoria, sitemap e S08 incluídos. As pendências de instalação dessas entregas nas entradas antigas abaixo são históricas, não trabalho a repetir. Prova: `outputs/reviewed-install-20261110/verification.json`. Conferência visual S08 conserva cinco pontos pendentes no mesmo registo; não bloqueia trabalhos independentes.
|
||||
|
||||
Saldo por percursos no início de [visao-consolidada-modulos.md](visao-consolidada-modulos.md), separado em implementação, instalação/conferência e dependência concreta. Matriz existente reconciliada em 35 IDs agregados, conservando 3.555 linhas, estados, critérios originais e todas as células fora das três colunas actualizadas. Não se converteu falta de prova em falta de implementação nem se criou novo inventário.
|
||||
|
||||
Primeiro trabalho executado nesta passagem: `AUD-EDU-01/03`, continuidade da revisão de procura/capacidade ao consultar histórico. A paginação normal obrigava a abandonar a preparação para navegar; os dois sentidos passam a conservar o estado e a posição, no mesmo detalhe. Chave por declaração/tipo/actor continua a separar contextos. Quatro verificações novas aprovadas: fundamento conservado, tentativa incerta repetida com a mesma chave, versão nova exige comparação e actor diferente não recebe o fundamento anterior. Tipos globais e lint dos dois ficheiros alterados aprovados. Nenhum teste anterior reexecutado; nenhuma alteração de writer, schema, permissões, dados ou histórico. Delta novo local, sem instalação remota ou prova visual presumida; não confundir com `ff726e57` já instalada.
|
||||
|
||||
Compilação QA do delta concluída com código 0; apenas `public/build-qa-opportunities` actualizado para a instalação local existente. Logs de tipos/build: `storage/logs/education-history-continuity-20261110-{types,build}.log`. Build servido de produção, ZIP selado, dados e cluster QA preservados. Não foi feita instalação remota deste delta.
|
||||
|
||||
Saldo de Educação: retoma durável da preparação de revisão, jornadas externas/representação sem cadastro aprovado e conferência visual. O delta resolve a perda de continuidade dentro da mesma página, não persistência entre sessões. Próximo trabalho na ordem acordada: continuar a funcionalidade fiscal L8 com fonte executável; preservar IVA/IRPC/PEC e exportações aceites, bloquear apenas os casos cuja regra/prova específica falta. O portal inteiro não fica concluído por este incremento.
|
||||
|
||||
## Entrega conjunta preparada em 10/11/2026
|
||||
|
||||
[Contador e aliases na mesma entrega](entrega-conjunta-contador-aliases-2026-11-10.md): base `main` e `origin/main` actualizadas e coincidentes em `2209b838`; pacote de fontes com manifesto, patch conjunto e inverso, conferidos num índice temporário. Oito verificações novas de contexto/rotas, 56 asserções; provas aceites do contador e aliases reutilizadas, sem repetir suites anteriores. Sem instalação, push, dados de negócio, configuração ou build servido alterados.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,58 @@
|
||||
## Saldo operacional reconciliado — 10/11/2026, após `ff726e57`
|
||||
|
||||
### Actualização fiscal: recorte L8 demonstrado em QA; saldo L1–L16 preservado
|
||||
|
||||
**Pendência encerrada nesta passagem em QA:** derivação proporcional da fracção nacional do crédito internacional no caso L6 ordinário corrente, com parcela líquida já incluída e correspondida à matéria colectável. Percurso cálculo → gravação/recuperação pela API privada existente → recálculo → HTML/relatório do dossier demonstrado. Não é fecho integral de L8, determinação automática de CDT ou repartição de custos/perdas. Delta agrupável com a continuidade de Educação, ainda não instalado na homologação, que mantém `ff726e57`.
|
||||
|
||||
O resumo anterior centrado em L8 era incompleto. Conservam-se explicitamente os seguintes resíduos e lotes; os recibos posteriores identificados neste manual prevalecem sobre anotações antigas de ausência/instalação pendente. Nenhuma estimativa histórica ou contagem de testes representa conclusão do módulo.
|
||||
|
||||
| Lote fiscal | Saldo efectivo preservado |
|
||||
| --- | --- |
|
||||
| L1 — enquadramento IVA | Casos residuais de P058 e P001 fora dos tratamentos já entregues, incluindo o incremento P001 `d1551dab`/`63147312`. P084/P085 e restantes fechos operacionais aceites não são reabertos. |
|
||||
| L2 — dedução/regularizações IVA | Art.18(6)/afectação mista, regularizações sucessivas/plurianuais, erros/incobráveis e variantes fora dos recortes entregues. |
|
||||
| L3 — saldo/Modelo A | Sequência/reversões de deduções, apuramentos próprios de fornecimentos/margem/viagens/eventos e formalidades C/D/E não cobertas; entregas posteriores de restituição preservadas. |
|
||||
| L4 — ISPC | Operações ocasionais, aquisições acima do limite e formulários/instruções/regulamentação de 2026 aplicáveis; apuramento habitual entregue preservado. |
|
||||
| L5 — retenções IRPC | Casos documentais/AT e variantes de dispensa/CDT, juros/rectificações/guias não encerrados pelos recibos. Taxas gerais/especiais, câmbio e retenções sofridas já entregues conservados. |
|
||||
| L6 — IRPC normal | Grupos de código e documentais discriminados no manual: perdas/deduções, períodos/moedas/representação e casos fora do recorte ordinário demonstrado. Colecta ordinária utilizável não é IRPC integral. |
|
||||
| L7 — tratamentos específicos IRPC | Famílias/combinações/mistos/transporte e documentos por caso ainda abertos. Cumulações e ramos específicos entregues preservados. |
|
||||
| L8 — liquidação IRPC | Derivação nacional ordinária encerrada em QA nesta passagem. Restam correspondência CDT automática, inclusão/correcção económica e quota automáticas, art.64(3/4)/benefícios, obrigações PPC/PEC, juros/guias, autónomos/períodos/moedas/M20M22 e casos transitórios/especiais. Transporte ordinário de créditos/PEC já entregue em `569a1cd7` não é tratado como ausente. |
|
||||
| L9 — INSS | Percurso contributivo por trabalhador/vínculo/base/período e recuperação/declaração; referências de taxas e massa salarial não são fecho integral. |
|
||||
| L10 — IRPS mensal | Tabela mensal do período, condições pessoais aplicáveis, remunerações/benefícios, retenção e líquido ligados a L9; preservar o caso anteriormente demonstrado, sem o extrapolar. |
|
||||
| L11 — IRPS anual/outras categorias | Categorias, englobamento/tratamento próprio, deduções, retenções, saldo e formulários; não copiar regras IRPC. |
|
||||
| L12 — Selo | Verbas por operação/contrato, incidência/base/taxa/isenção/obrigação com fonte e período; não aplicar exemplo genérico a todas as facturas. |
|
||||
| L13 — direitos aduaneiros | Código pautal/origem/valor/despesas/câmbio/benefícios e percurso documental; não inferir código pela descrição nem benefício pela localização. |
|
||||
| L14 — ICE | Produtos/operações, base/unidade e tributação específica/ad valorem/mista, período e ligações pertinentes; produção interna não depende de calcular direitos aduaneiros. |
|
||||
| L15 — SISA | Delimitação legal/autárquica, transmissão onerosa/direitos, incidência/isenções/base/período e percurso; DUAT não é propriedade da terra. |
|
||||
| L16 — IPRA/encargos imobiliários | Posse/uso/valor patrimonial, autarquia/exercício/isenções e separação de impostos e taxas administrativas; preço de compra não substitui valor patrimonial. |
|
||||
|
||||
Não foram identificados recibos de fecho integral de L9–L16 nesta reconciliação dirigida. Isso conserva o saldo; não apaga código, fontes ou casos parciais anteriores. Dúvidas/fontes em falta afectam apenas os casos dependentes. Detalhes e prova L8 na entrada actual do manual existente e em `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
|
||||
|
||||
Esta é a actualização da visão e da matriz existentes, não um novo inventário. A matriz conserva 3.555 linhas sobrepostas de requisitos, cláusulas e campos; a contagem não representa funcionalidades independentes nem trabalho por implementar. Preservam-se todos os fechos e provas aceites. As secções datadas de 18/10 abaixo são históricas e não reabrem implementações posteriores.
|
||||
|
||||
Instalação de fontes até `ff726e57` concluída na homologação, incluindo S08, contador, aliases, contactos, pré-dossier, curadoria e sitemap. Prova: `outputs/reviewed-install-20261110/verification.json`, revisão HTTPS, 44 hashes do delta, configuração preservada, zero migrations/importações. Não reinstalar esse pacote. Código instalado não comprova por si só cada flag, ledger de instalação modular, canal externo ou percurso privado em uso real; as habilitações anteriores e as suas provas mantêm-se. Um módulo não é declarado desactivado apenas por falta de nova observação.
|
||||
|
||||
| Módulo / percurso utilizável a completar | Implementação que falta | Instalação e conferência | Dependência concreta / critério de conclusão |
|
||||
| --- | --- | --- | --- |
|
||||
| Descoberta, oportunidades e conteúdos públicos → interesse/dossier | Completar cláusulas e modalidades especializadas ainda abertas; não refazer cadastro, revisão, publicação, pesquisa ou curadoria entregues. | Fontes até ff726e57 instaladas; conferir apresentação/percursos ainda sem prova visual. | Dados efectivos de cada anúncio, divulgação e representação existentes. Fecho: localizar uma ficha, preparar/publicar pelo workflow aplicável e continuar no mesmo dossier sem perder contexto. |
|
||||
| Territórios, mapa, âncoras/clusters → selecção contextual | Consumidores territoriais e correspondências ainda não cobertos; preservar mapa único, filtros e observações quantitativas entregues. | Não reabrir instalação dos deltas incluídos; conferir consumidores sem prova, sem repetir percursos aceites. | Geometrias, coordenadas, associações e fontes reais em falta por item. Fecho: pesquisar/seleccionar/guardar/recuperar a referência correcta, sem inventar equivalências ou pontos. |
|
||||
| Balcão, percurso adaptativo e S08 → pedido/resultado | Restantes consumidores e parametrização por âmbito, obrigações/registo/licenciamento sustentados; núcleo de dossier, pedido, histórico e fecho pelo dono preservado. | S08 instalada. Conferência restante: #S08, estado, botão, gravação/recuperação, janela estreita. | Requisitos de cada serviço e actos externos próprios; bloquear só esse serviço/caso quando faltarem. Fecho: preparar/submeter/acompanhar/recuperar resultado no dossier certo. |
|
||||
| CRM, responsáveis e tarefas → acompanhamento | Restantes formulários/consumidores e preparações não confirmadas; não recriar conversão, próxima acção, edição e preparação de tarefas já demonstradas. | Fontes instaladas; restantes variantes por perfil e uso visual a conferir. | Designações/representação do caso e integrações externas apenas onde usadas. Fecho: converter uma vez, atribuir, acompanhar e retomar sem duplicar tarefa/interacção. |
|
||||
| Biblioteca, documentos, scanner e suporte → consulta privada | Cobertura do acervo e consumidores/variantes ainda não individualizados; preservar importação idempotente, originais, scanner, revogação e incidentes demonstrados. | Não reinstalar consulta pública/editor aceites; conferir anexos e consumidores pendentes. Operação de scanner/worker é distinta de código. | Bytes/fontes autorizados e disponibilidade efectiva dos serviços quando necessários. Fecho: importar/consultar/partilhar/revogar com original, autoria e histórico íntegros. |
|
||||
| Simulador fiscal/contributivo → cálculo/rascunho/relatório | Resíduos L1–L7, restante L8 e L9–L16 conforme tabela acima. Fracção nacional ordinária agora demonstrada em QA; transporte/crédito económico já entregues preservados. Não limitar saldo fiscal a L8. | Fontes até ff726e57 instaladas; delta de fracção nacional ainda QA. Conferir apenas novos consumidores/deltas. | Diploma/período/limite/prova/CDT concretos faltantes bloqueiam só o cálculo dependente. Fecho por ramo: cenário calculado, guardado, recuperado e exportado com fonte/versão/valores coerentes. |
|
||||
| Negócios, empresas e duplicados → ficha/publicação/comparação | Consolidação rastreável de entidades inequívocas, outros formatos e variantes contratuais ainda abertos. Pesquisa de semelhantes por raio/sector/classificação e consulta dos lotes já existem. | Fontes instaladas; visual/variantes ainda não observadas separados. | Pontos/fontes/classificações e prova do mandato por caso; não fundir apenas por nome. Fecho: localizar/importar/reconciliar, publicar e comparar sem perder originais/relações. |
|
||||
| Fundos e alertas → referência/avaliação informativa/acompanhamento | Vínculos restantes da referência ao cadastro e canais/fontes externas; catálogo, editor, versões, critérios e gerador local já implementados. | Código instalado; confirmar operação do agendador/canal apenas no âmbito configurado e observação visual restante. | Fontes/prazos efectivos e credencial/destinatário do canal concreto. Fecho: consultar uma chamada, responder aos critérios, guardar e receber o aviso configurado sem decisão de crédito presumida. |
|
||||
| Forma societária e preparação da empresa → modelo | Casos especiais; ingestão/versionamento/vínculo dos sete modelos exactos e derivadas; actividade/licenciamento integrado e percursos de assinatura/submissão/registo ainda não cobertos. | Comparador e preparação factual têm código instalado; não confundir ficha factual com contrato. Conferência visual pendente. | Exemplares exactos dos modelos e intervenientes externos de assinatura/registo. Fecho: comparar com fontes, preparar a forma escolhida e recuperar o modelo/derivada versionados; ausência de um modelo não bloqueia o comparador. |
|
||||
| Capitalização e BVM → orientação/referência no dossier | Casos especiais e procedimentos CVM ainda não codificados; os guiões base de capitalização e cinco percursos BVM já existem. | Fontes instaladas; visual/login dos recortes pendentes. | Regulamento anual de montantes, preçário/encargos e condições da contraparte aplicáveis. Fecho: seleccionar percurso, perceber requisitos/fontes/indeterminações e recuperar a preparação no dossier. |
|
||||
| Checklists e formulários → versão publicada/submissão | Outros consumidores e parametrização transversal de limites, formatos/mensagens e camadas documentais por fonte/âmbito. Editor, vigência, tipos, anexos e selector sem UUID manual já existem. | Fontes instaladas; habilitação nominal quando exigida pelo instalador existente, sem a presumir ausente; visual restante. | Texto/requisito/contexto publicado por administrador autorizado. Fecho: publicar v2, novas submissões usam v2 e respostas v1 continuam recuperáveis. |
|
||||
| Educação → declaração/revisão/histórico | Retoma durável da preparação de revisão e jornadas externas de estudantes/formadoras/empregadores ainda abertas. **Avançado nesta passagem:** paginação do histórico preserva a revisão em preparação e o envio incerto, mantendo comparação/actor. | Delta novo local, ainda não instalado; quatro verificações novas, sem repetir provas anteriores. Conferência visual separada. | Não existe cadastro/documento aprovado dos vínculos externos, conforme decisão já registada; bloqueia só esses vínculos. Fecho interno: preparar/submeter, outro gestor rever e consultar histórico sem perder preparação ou duplicar decisão. |
|
||||
| Bolsa/parcerias → candidatura/sala/acordo/marco | Variantes e consumidores não cobertos; preparação durável dos passos ainda só em memória. Editores, candidaturas, acordos, resumos e históricos entregues não são ausência funcional. | Fontes instaladas; conferência de partilha/revogação e restantes variantes por actor pendente. | Participantes/documentos autorizados no caso; não inferir autorização de oportunidade a partir da sala. Fecho: candidatura, colaboração, acordo/marco e revogação sem perda de original/histórico. |
|
||||
| Perfis, gestão, indicadores e painéis → operação por papel | Métricas/fontes/denominadores e restantes filas por perfil; contextos de país em consumidores ainda abertos. Preservar perfis, revogação, suporte e editores entregues. | Código instalado; atribuições reais não são criadas por instalação; observação por papel separada. | Designações operacionais e fontes dos indicadores concretos. Fecho: acção útil permitida/negada pelo papel, evento contado com âmbito/período explícitos e distinção zero/sem dados. |
|
||||
| Acesso offline, línguas e IA → consumidores previstos | Consulta offline de fichas já existe; edição/sincronização e idiomas adicionais não concluídos; IA não substitui os percursos determinísticos. | Código entregue preservado; abertura sem rede, apresentação e novos deltas por conferir. | Línguas/traduções aprovadas e fornecedor/credencial/âmbito de IA quando necessários; só bloqueiam esses casos. Fecho por consumidor, sem prometer cobertura geral offline ou inteligência não implementada. |
|
||||
|
||||
**Ordem de continuação preservada:** primeiro defeitos que impeçam usar/concluir percursos existentes (estabilização); depois simulador fiscal delimitado, Imobiliário, Bens de consumo/franquias, Estatística/painéis e IA, conforme ordem já registada. A correcção da continuidade do histórico de Educação pertence à estabilização de um percurso existente, não cria uma frente paralela. Próximo trabalho nessa ordem: continuar o saldo fiscal L8 executável com as fontes aceites; faltas de prova/CDT/regulamento bloqueiam apenas os casos afectados. A conferência visual S08 não suspende essa implementação.
|
||||
|
||||
O portal inteiro permanece em conclusão, sem excluir requisitos por inferência. A matriz conserva os critérios originais e as provas por ID; este quadro organiza o trabalho por percursos, sem promover módulos agregados a concluídos por código instalado ou por um teste parcial.
|
||||
|
||||
<!-- saldo-atual-20261018 -->
|
||||
## Saldo atual por requisito em 18 de outubro de 2026
|
||||
|
||||
|
||||
@@ -116,6 +116,15 @@ export function SimulationIrpcInternational({
|
||||
<fieldset className="space-y-3 rounded border p-3">
|
||||
<legend>Crédito por dupla tributação internacional</legend>
|
||||
<p>{internationalCreditSource}</p>
|
||||
{c.positions.some((p) => p.nationalMethod === 'proportional') && (
|
||||
<p>
|
||||
No modo de derivação ordinária abaixo, o portal calcula a
|
||||
fracção nacional a partir da colecta e matéria colectável L6
|
||||
correntes e da parcela líquida correspondida. Mantêm-se as
|
||||
restantes condições e limites do modo documentado; não
|
||||
determina a convenção aplicável.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
Reúna os comprovativos de cada país numa posição por exercício.
|
||||
Indique a fracção nacional e a convenção conforme o suporte do
|
||||
@@ -220,6 +229,59 @@ export function SimulationIrpcInternational({
|
||||
onChange={(country) => patch({ country })}
|
||||
allowLegacyValue
|
||||
/>
|
||||
<label className="block">
|
||||
Fracção nacional
|
||||
<select
|
||||
aria-label={
|
||||
'Internacional ' +
|
||||
(index + 1) +
|
||||
' — Método da fracção nacional'
|
||||
}
|
||||
value={p.nationalMethod ?? 'documented'}
|
||||
onChange={(e) =>
|
||||
patch({
|
||||
nationalMethod: e.target
|
||||
.value as InternationalPosition['nationalMethod'],
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="documented">
|
||||
Limite documentado
|
||||
</option>
|
||||
<option value="proportional">
|
||||
Derivar da colecta L6 ordinária
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
{p.nationalMethod === 'proportional' && (
|
||||
<label className="block">
|
||||
Correspondência da parcela líquida estrangeira
|
||||
<textarea
|
||||
aria-label={
|
||||
'Internacional ' +
|
||||
(index + 1) +
|
||||
' — Correspondência da parcela líquida'
|
||||
}
|
||||
value={p.nationalBasisReference ?? ''}
|
||||
onChange={(e) =>
|
||||
patch({
|
||||
nationalBasisReference:
|
||||
e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="block">
|
||||
Indique abaixo a parcela líquida já incluída
|
||||
na matéria colectável, após os custos,
|
||||
perdas e deduções correspondentes. A fracção
|
||||
será colecta L6 × parcela líquida / matéria
|
||||
colectável L6, antes de créditos. O portal
|
||||
não reparte custos ou perdas nem determina
|
||||
convenções. O limite manual anterior é
|
||||
conservado, mas não usado neste modo.
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(
|
||||
[
|
||||
['subjectNuit', 'NUIT titular'],
|
||||
@@ -238,23 +300,33 @@ export function SimulationIrpcInternational({
|
||||
'Suporte da fracção nacional e exercício',
|
||||
],
|
||||
] as const
|
||||
).map(([key, label]) => (
|
||||
<label key={key} className="block">
|
||||
{label}
|
||||
<input
|
||||
aria-label={
|
||||
'Internacional ' +
|
||||
(index + 1) +
|
||||
' — ' +
|
||||
label
|
||||
}
|
||||
value={p[key]}
|
||||
onChange={(e) =>
|
||||
patch({ [key]: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
)
|
||||
.filter(
|
||||
([key]) =>
|
||||
p.nationalMethod !== 'proportional' ||
|
||||
(key !== 'nationalLimit' &&
|
||||
key !== 'nationalLimitReference'),
|
||||
)
|
||||
.map(([key, label]) => (
|
||||
<label key={key} className="block">
|
||||
{key === 'incomeAmount' &&
|
||||
p.nationalMethod === 'proportional'
|
||||
? 'Parcela líquida estrangeira incluída — MZN'
|
||||
: label}
|
||||
<input
|
||||
aria-label={
|
||||
'Internacional ' +
|
||||
(index + 1) +
|
||||
' — ' +
|
||||
label
|
||||
}
|
||||
value={p[key]}
|
||||
onChange={(e) =>
|
||||
patch({ [key]: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
<label className="block">
|
||||
Rendimento incluído na matéria colectável
|
||||
<select
|
||||
|
||||
@@ -1154,12 +1154,28 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
||||
b.subjectNuit,
|
||||
b.year,
|
||||
b.assessmentKey,
|
||||
{
|
||||
ordinary:
|
||||
assessment?.kind === 'normal' &&
|
||||
assessment.current &&
|
||||
assessment.key === b.assessmentKey,
|
||||
taxableBase: assessment?.record?.model.taxableBase ?? null,
|
||||
colecta: assessment?.colecta ?? null,
|
||||
},
|
||||
);
|
||||
pending.push(
|
||||
...m.pending.map((e) => 'País ' + p.country + ': ' + e),
|
||||
);
|
||||
return m;
|
||||
}) ?? [];
|
||||
const derivedNational = positions.flatMap((p) =>
|
||||
p.nationalDerivation ? [p.nationalDerivation.income] : [],
|
||||
);
|
||||
const currentBase = assessment?.record?.model.taxableBase;
|
||||
if (currentBase && cmp(addFractions(derivedNational), currentBase) > 0n)
|
||||
pending.push(
|
||||
'As parcelas estrangeiras com fracção derivada excedem em conjunto a matéria colectável corrente; reconcilie a afectação por país.',
|
||||
);
|
||||
const economicRows =
|
||||
b.economicCredits?.rows.map((r) => {
|
||||
const issues = claims
|
||||
@@ -1384,7 +1400,9 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
||||
: []),
|
||||
...(b.internationalCredits
|
||||
? [
|
||||
'Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. ' +
|
||||
(positions.some((p) => p.nationalDerivation)
|
||||
? 'Crédito internacional do próprio exercício:fracção nacional derivada no caso L6 ordinário com parcela líquida correspondida;CDT e montantes MZN documentados;sem repartição presumida de custos/perdas,transporte ou consumo oficial. '
|
||||
: 'Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. ') +
|
||||
(b.economicCredits
|
||||
? 'Benefícios positivos ficam separados;crédito económico no detalhe próprio.'
|
||||
: 'Créditos económicos/benefícios positivos ficam separados.'),
|
||||
@@ -1630,6 +1648,21 @@ export function annualReportRows(
|
||||
show(p.paid) +
|
||||
'; fracção nacional: ' +
|
||||
show(p.nationalLimit) +
|
||||
(p.nationalDerivation
|
||||
? '; derivação L6: colecta ' +
|
||||
show(p.nationalDerivation.colecta) +
|
||||
' × parcela líquida ' +
|
||||
show(p.nationalDerivation.income) +
|
||||
' / matéria colectável ' +
|
||||
show(
|
||||
p.nationalDerivation
|
||||
.taxableBase,
|
||||
) +
|
||||
'; exacta ' +
|
||||
p.nationalLimit?.numerator +
|
||||
'/' +
|
||||
p.nationalLimit?.denominator
|
||||
: '; limite documentado') +
|
||||
'; limite CDT: ' +
|
||||
(p.input.treaty === 'no'
|
||||
? 'Não aplicável documentado'
|
||||
@@ -1646,7 +1679,15 @@ export function annualReportRows(
|
||||
p.pending.join(' | '),
|
||||
p.input.incomeReference +
|
||||
'; ' +
|
||||
p.input.nationalLimitReference +
|
||||
(p.nationalDerivation
|
||||
? 'Limite manual anterior conservado, não utilizado nesta derivação'
|
||||
: p.input.nationalLimitReference) +
|
||||
(p.nationalDerivation
|
||||
? '; correspondência: ' +
|
||||
p.nationalDerivation.reference +
|
||||
'; ' +
|
||||
p.nationalDerivation.version
|
||||
: '') +
|
||||
'; ' +
|
||||
p.input.treatyReference +
|
||||
'; ' +
|
||||
|
||||
@@ -38,6 +38,8 @@ export type InternationalPosition = {
|
||||
incomeReference: string;
|
||||
nationalLimit: string;
|
||||
nationalLimitReference: string;
|
||||
nationalMethod?: 'documented' | 'proportional';
|
||||
nationalBasisReference?: string;
|
||||
treaty: '' | 'yes' | 'no';
|
||||
treatyLimit: string;
|
||||
treatyReference: string;
|
||||
@@ -149,6 +151,10 @@ export function validateInternational(c: InternationalCredits) {
|
||||
) ||
|
||||
!yesNo.includes(p.incomeIncluded) ||
|
||||
!yesNo.includes(p.treaty) ||
|
||||
(p.nationalMethod !== undefined &&
|
||||
!['documented', 'proportional'].includes(p.nationalMethod)) ||
|
||||
(p.nationalBasisReference !== undefined &&
|
||||
typeof p.nationalBasisReference !== 'string') ||
|
||||
!evidenceValid(p.previousUse) ||
|
||||
!Array.isArray(p.payments)
|
||||
)
|
||||
@@ -278,6 +284,11 @@ export function internationalPosition(
|
||||
subjectNuit: string,
|
||||
year: string,
|
||||
assessmentKey: string,
|
||||
nationalBasis?: {
|
||||
ordinary: boolean;
|
||||
taxableBase: FiscalFraction | null;
|
||||
colecta: FiscalFraction | null;
|
||||
},
|
||||
) {
|
||||
const pending = [...proofIssues];
|
||||
let nationalLimit: FiscalFraction | null = null,
|
||||
@@ -286,6 +297,13 @@ export function internationalPosition(
|
||||
eligible: FiscalFraction | null = null,
|
||||
available: FiscalFraction | null = null,
|
||||
aboveLimit: FiscalFraction | null = null;
|
||||
let nationalDerivation: {
|
||||
version: string;
|
||||
income: FiscalFraction;
|
||||
taxableBase: FiscalFraction;
|
||||
colecta: FiscalFraction;
|
||||
reference: string;
|
||||
} | null = null;
|
||||
try {
|
||||
if (
|
||||
!p.id.trim() ||
|
||||
@@ -311,8 +329,46 @@ export function internationalPosition(
|
||||
'Falta inclusão documentada do rendimento estrangeiro na matéria colectável.',
|
||||
);
|
||||
const income = creditMoney(p.incomeAmount);
|
||||
nationalLimit = creditLimit(p.nationalLimit);
|
||||
if (!p.nationalLimitReference.trim())
|
||||
if (p.nationalMethod === 'proportional') {
|
||||
const basis = nationalBasis;
|
||||
if (
|
||||
!basis?.ordinary ||
|
||||
!basis.taxableBase ||
|
||||
!basis.colecta ||
|
||||
creditCompare(basis.taxableBase, zero()) <= 0n ||
|
||||
creditCompare(basis.colecta, zero()) < 0n
|
||||
)
|
||||
throw Error(
|
||||
'Derivação da fracção nacional exige matéria colectável positiva e colecta L6 ordinária corrente; efeitos L7 e casos especiais conservam correspondência própria.',
|
||||
);
|
||||
if (!p.nationalBasisReference?.trim())
|
||||
throw Error(
|
||||
'Identifique a correspondência da parcela líquida estrangeira na matéria colectável, incluindo custos/perdas/deduções aplicáveis; o portal não reparte esses valores por presunção.',
|
||||
);
|
||||
if (creditCompare(income, basis.taxableBase) > 0n)
|
||||
throw Error(
|
||||
'Parcela estrangeira excede a matéria colectável corrente; reconcilie custos, perdas e deduções antes de derivar a fracção.',
|
||||
);
|
||||
nationalLimit = fraction(
|
||||
BigInt(basis.colecta.numerator) *
|
||||
BigInt(income.numerator) *
|
||||
BigInt(basis.taxableBase.denominator),
|
||||
BigInt(basis.colecta.denominator) *
|
||||
BigInt(income.denominator) *
|
||||
BigInt(basis.taxableBase.numerator),
|
||||
);
|
||||
nationalDerivation = {
|
||||
version: 'IRPC-national-fraction/1',
|
||||
income,
|
||||
taxableBase: structuredClone(basis.taxableBase),
|
||||
colecta: structuredClone(basis.colecta),
|
||||
reference: p.nationalBasisReference,
|
||||
};
|
||||
} else nationalLimit = creditLimit(p.nationalLimit);
|
||||
if (
|
||||
p.nationalMethod !== 'proportional' &&
|
||||
!p.nationalLimitReference.trim()
|
||||
)
|
||||
throw Error(
|
||||
'Fracção nacional exige suporte próprio; não é calculada nem presumida pelo portal.',
|
||||
);
|
||||
@@ -363,6 +419,7 @@ export function internationalPosition(
|
||||
country: p.country,
|
||||
paid,
|
||||
nationalLimit,
|
||||
...(nationalDerivation ? { nationalDerivation } : {}),
|
||||
treatyLimit,
|
||||
previousUse,
|
||||
eligible,
|
||||
@@ -403,7 +460,11 @@ export function internationalDeduction(
|
||||
: null,
|
||||
}
|
||||
: {}),
|
||||
source: internationalCreditSource,
|
||||
source:
|
||||
internationalCreditSource +
|
||||
(positions.some((p) => p.nationalDerivation)
|
||||
? ' Modo IRPC-national-fraction/1: no caso L6 ordinário corrente com parcela líquida documentada e correspondida, fracção = colecta antes de créditos × parcela líquida estrangeira / matéria colectável. Não efectua repartição de custos/perdas ou qualificação automática de CDT.'
|
||||
: ''),
|
||||
countryAllocationCreated: false,
|
||||
carryCreated: false,
|
||||
officialBalancesConsumed: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import EducationReview from '@/components/education-review';
|
||||
import type { EducationReviewProps } from '@/lib/education-review';
|
||||
import { Management, Pagination } from '../management/shared';
|
||||
import { Management } from '../management/shared';
|
||||
import type { Paginated } from '../management/shared';
|
||||
|
||||
type ReviewEntry = {
|
||||
@@ -146,7 +146,36 @@ export default function Declaration(
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
<Pagination page={props.history} />
|
||||
<nav
|
||||
aria-label="Paginação do histórico de revisão"
|
||||
className="flex flex-wrap items-center gap-4"
|
||||
>
|
||||
{props.history.prev_page_url && (
|
||||
<Link
|
||||
className="rounded border p-2"
|
||||
href={props.history.prev_page_url}
|
||||
preserveState
|
||||
preserveScroll
|
||||
>
|
||||
Anterior
|
||||
</Link>
|
||||
)}
|
||||
<span>
|
||||
{props.history.total} registos · Página{' '}
|
||||
{props.history.current_page} de{' '}
|
||||
{props.history.last_page}
|
||||
</span>
|
||||
{props.history.next_page_url && (
|
||||
<Link
|
||||
className="rounded border p-2"
|
||||
href={props.history.next_page_url}
|
||||
preserveState
|
||||
preserveScroll
|
||||
>
|
||||
Seguinte
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
</section>
|
||||
)}
|
||||
</Management>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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 IrpcNationalFractionDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_derived_national_fraction_round_trips_through_the_existing_private_draft_writer(): void
|
||||
{
|
||||
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('proactive_investment_20261027_test', $target->name);
|
||||
$this->assertSame(55441, (int) $target->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$this->actingAs(User::findOrFail(1021));
|
||||
$id = null;
|
||||
$etag = null;
|
||||
foreach (['ordinary', 'exact', 'missing'] as $name) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-national-fraction-20261110/'.$name.'.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$personal = ['person_type' => $snapshot['person_type']];
|
||||
unset($snapshot['person_type']);
|
||||
$payload = ['personal_snapshot' => $personal, 'economic_snapshot' => $snapshot];
|
||||
if ($id === null) {
|
||||
$id = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
|
||||
} else {
|
||||
$this->putJson('/api/v1/simulation-drafts/'.$id, $payload, ['If-Match' => $etag])->assertOk();
|
||||
}
|
||||
$response = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();
|
||||
$etag = $response->headers->get('ETag');
|
||||
$back = $response->json('data.economic_snapshot');
|
||||
$this->assertSame($snapshot['modelo_a_snapshot'], $back['modelo_a_snapshot']);
|
||||
$this->assertSame($snapshot['business_plan_snapshot'], $back['business_plan_snapshot']);
|
||||
$book = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$snapshot['context_opportunity']];
|
||||
$this->assertSame('proportional', $book['internationalCredits']['positions'][0]['nationalMethod']);
|
||||
$model = $book['result']['model'];
|
||||
if ($name === 'missing') {
|
||||
$this->assertSame('indeterminate', $model['status']);
|
||||
$this->assertNull($model['arithmeticBalance']);
|
||||
} else {
|
||||
$this->assertSame(['numerator' => $name === 'ordinary' ? '32' : '2', 'denominator' => $name === 'ordinary' ? '1' : '625'], $model['internationalCredit']['positions'][0]['nationalLimit']);
|
||||
$this->assertSame('IRPC-national-fraction/1', $model['internationalCredit']['positions'][0]['nationalDerivation']['version']);
|
||||
}
|
||||
file_put_contents(base_path('outputs/irpc-national-fraction-20261110/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,69 @@ import assert from 'node:assert/strict';
|
||||
import { harness, props, text } from './helpers/education-review.mjs';
|
||||
const form = 'education-review';
|
||||
|
||||
function historyPage(page = 1) {
|
||||
return {
|
||||
data: [], total: 24, current_page: page, last_page: 2,
|
||||
prev_page_url: page === 2 ? '/management/education/demands/one?scope=review&history_page=1' : null,
|
||||
next_page_url: page === 1 ? '/management/education/demands/one?scope=review&history_page=2' : null,
|
||||
};
|
||||
}
|
||||
|
||||
test('history paging keeps the prepared review and does not ask to discard it', () => {
|
||||
const h = harness(props({ history: historyPage() }));
|
||||
h.change(form, reason, 'SYNTHETIC reasoning still being prepared.');
|
||||
const next = h.all().find(node => node.type === 'a' && text(node) === 'Seguinte');
|
||||
assert.equal(next.props.preserveState, true);
|
||||
assert.equal(next.props.preserveScroll, true);
|
||||
assert.match(next.props.href, /scope=review&history_page=2$/);
|
||||
assert.equal(h.leave(next.props.preserveState), false);
|
||||
assert.equal(h.confirmCalls, 0);
|
||||
h.render({ ...h.props, history: historyPage(2) });
|
||||
assert.equal(h.field(form, reason).props.value, 'SYNTHETIC reasoning still being prepared.');
|
||||
const previous = h.all().find(node => node.type === 'a' && text(node) === 'Anterior');
|
||||
assert.equal(previous.props.preserveState, true);
|
||||
assert.equal(previous.props.preserveScroll, true);
|
||||
assert.equal(h.sent.length, 0);
|
||||
assert.equal(h.leave(), true); // Leaving the declaration still protects unsaved work.
|
||||
});
|
||||
|
||||
test('history paging keeps an uncertain attempt recoverable under the same command key', () => {
|
||||
const h = harness(props({ history: historyPage() }));
|
||||
const request = prepared(h);
|
||||
h.fail(request);
|
||||
h.render({ ...h.props, history: historyPage(2) });
|
||||
const retry = h.submit(form);
|
||||
assert.deepEqual(retry.data, request.data);
|
||||
assert.equal(h.sent.length, 2); // One explicit retry; paging itself sends no decision.
|
||||
});
|
||||
|
||||
test('history paging cannot silently approve a newer declaration version', () => {
|
||||
const h = harness(props({ history: historyPage() }));
|
||||
h.change(form, reason, 'SYNTHETIC review prepared against version one.');
|
||||
h.render({
|
||||
...h.props, history: historyPage(2),
|
||||
declaration: { ...h.props.declaration, version_no: 2, quantity: 31 },
|
||||
});
|
||||
h.submit(form);
|
||||
assert.equal(h.sent.length, 0);
|
||||
assert.equal(h.field(form, reason).props.value, 'SYNTHETIC review prepared against version one.');
|
||||
assert.match(text(h.tree), /A declaração mudou/);
|
||||
h.click('Confirmar a leitura desta versão');
|
||||
const request = h.submit(form);
|
||||
assert.equal(request.data.version_no, 2);
|
||||
});
|
||||
|
||||
test('history paging with a different actor remounts instead of disclosing the prepared reason', () => {
|
||||
const h = harness(props({ history: historyPage() }));
|
||||
h.change(form, reason, 'SYNTHETIC private reasoning of the previous actor.');
|
||||
h.render({
|
||||
...h.props, actorId: 3, history: historyPage(2),
|
||||
declaration: { ...h.props.declaration, review_context: { actor_id: 3, commands: [] } },
|
||||
});
|
||||
assert.equal(h.field(form, reason).props.value, '');
|
||||
assert.equal(h.sent.length, 0);
|
||||
});
|
||||
|
||||
test('the private detail renders preserved reasons, unavailable entries and contextual history pagination', () => {
|
||||
const h = harness(
|
||||
props({
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { internationalCase } from './helpers/irpc-international-case.mjs';
|
||||
import {
|
||||
calculateAnnual,
|
||||
annualBook,
|
||||
annualCurrent,
|
||||
recordAnnual,
|
||||
saveAnnualBook,
|
||||
annualHtml,
|
||||
annualReportRows,
|
||||
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
||||
|
||||
export function nationalCase(income = '100') {
|
||||
const c = internationalCase('100');
|
||||
Object.assign(c.b.internationalCredits.positions[0], {
|
||||
nationalMethod: 'proportional',
|
||||
incomeAmount: income,
|
||||
nationalBasisReference:
|
||||
'SYNTHETIC QA: net taxable foreign component, costs/losses/deductions allocated and included in this L6 base; not a real tax determination',
|
||||
});
|
||||
return c;
|
||||
}
|
||||
const position = (c) => c.b.internationalCredits.positions[0];
|
||||
|
||||
void test('national fraction aggregate cannot allocate more than the current base across countries', () => {
|
||||
const c = nationalCase('3000');
|
||||
const second = structuredClone(position(c));
|
||||
Object.assign(second, {
|
||||
id: 'country-pt',
|
||||
country: 'PT',
|
||||
qualificationCountry: 'PT',
|
||||
});
|
||||
Object.assign(second.payments[0], {
|
||||
id: 'tax-pt',
|
||||
document: 'SYNTHETIC foreign-tax-pt',
|
||||
});
|
||||
c.b.internationalCredits.positions.push(second);
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.equal(m.arithmeticBalance, null);
|
||||
assert.ok(m.pending.some((message) => message.includes('em conjunto')));
|
||||
});
|
||||
|
||||
void test('national fraction derives 1408 x 100 / 4400 before credits and retains the manual entry', () => {
|
||||
const c = nationalCase();
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
const p = m.internationalCredit.positions[0];
|
||||
assert.deepEqual(p.nationalLimit, { numerator: '32', denominator: '1' });
|
||||
assert.equal(p.input.nationalLimit, '8');
|
||||
assert.equal(
|
||||
p.nationalDerivation.reference,
|
||||
position(c).nationalBasisReference,
|
||||
);
|
||||
assert.deepEqual(m.payable, { numerator: '676', denominator: '1' });
|
||||
assert.equal(m.internationalCredit.officialBalancesConsumed, false);
|
||||
});
|
||||
|
||||
void test('national fraction calculation survives serialization, retrieval and both existing report renderers', () => {
|
||||
const c = nationalCase('0.01');
|
||||
const historical = structuredClone(c.b.result);
|
||||
const b = recordAnnual(c.d, c.b, '2026-11-10T13:00:00+02:00');
|
||||
const saved = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) };
|
||||
const back = JSON.parse(JSON.stringify(saved));
|
||||
const recovered = annualBook(back);
|
||||
assert.equal(annualCurrent(back, recovered), true);
|
||||
assert.deepEqual(
|
||||
recovered.result.model.internationalCredit.positions[0].nationalLimit,
|
||||
{ numerator: '2', denominator: '625' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
calculateAnnual(back, recovered).internationalCredit,
|
||||
recovered.result.model.internationalCredit,
|
||||
);
|
||||
assert.deepEqual(c.b.result, historical);
|
||||
assert.match(annualHtml(back), /2\/625/);
|
||||
assert.match(
|
||||
JSON.stringify(annualReportRows(back)),
|
||||
/IRPC-national-fraction\/1/,
|
||||
);
|
||||
assert.match(
|
||||
JSON.stringify(
|
||||
createProjectDossierReport(back, {
|
||||
selectedSections: new Set(['finance']),
|
||||
generatedAt: '2026-11-10T13:00:00+02:00',
|
||||
}),
|
||||
),
|
||||
/2\/625/,
|
||||
);
|
||||
});
|
||||
|
||||
void test('national fraction leaves a case indeterminate without net-income correspondence or with too much income', () => {
|
||||
for (const patch of [
|
||||
{ nationalBasisReference: '' },
|
||||
{ incomeAmount: '4400.01' },
|
||||
{ nationalMethod: 'unknown' },
|
||||
]) {
|
||||
const c = nationalCase();
|
||||
Object.assign(position(c), patch);
|
||||
if (patch.nationalMethod)
|
||||
assert.throws(() => calculateAnnual(c.d, c.b));
|
||||
else {
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.equal(m.status, 'indeterminate');
|
||||
assert.equal(m.arithmeticBalance, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void test('national fraction rejects stale assessment binding and still applies a documented treaty ceiling', () => {
|
||||
const c = nationalCase();
|
||||
Object.assign(position(c), {
|
||||
treaty: 'yes',
|
||||
treatyLimit: '6',
|
||||
treatyReference:
|
||||
'SYNTHETIC QA case-specific ceiling, no actual treaty assertion',
|
||||
});
|
||||
assert.deepEqual(calculateAnnual(c.d, c.b).payable, {
|
||||
numerator: '702',
|
||||
denominator: '1',
|
||||
});
|
||||
position(c).assessmentKey = 'obsolete';
|
||||
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
||||
});
|
||||
|
||||
void test('national fraction keeps historical documented mode unchanged when the optional method is absent', () => {
|
||||
const c = internationalCase();
|
||||
const original = calculateAnnual(c.d, c.b);
|
||||
position(c).nationalMethod = 'documented';
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(
|
||||
m.internationalCredit.deducted,
|
||||
original.internationalCredit.deducted,
|
||||
);
|
||||
assert.deepEqual(m.payable, { numerator: '700', denominator: '1' });
|
||||
assert.equal(
|
||||
m.internationalCredit.positions[0].nationalDerivation,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import fs from 'node:fs';
|
||||
import { internationalCase } from '../tests/frontend/helpers/irpc-international-case.mjs';
|
||||
import { recordAnnual, saveAnnualBook } from '../resources/js/lib/irpc-annual-settlement.ts';
|
||||
const out = new URL('../outputs/irpc-national-fraction-20261110/', import.meta.url);
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
for (const [name, amount, reference] of [
|
||||
['ordinary', '100', 'SYNTHETIC QA: net foreign taxable component included in this ordinary base after corresponding costs/losses/deductions'],
|
||||
['exact', '0.01', 'SYNTHETIC QA: exact net component correspondence'],
|
||||
['missing', '100', ''],
|
||||
]) {
|
||||
const c = internationalCase('100');
|
||||
Object.assign(c.b.internationalCredits.positions[0], { nationalMethod: 'proportional', incomeAmount: amount, nationalBasisReference: reference });
|
||||
const book = recordAnnual(c.d, c.b, '2026-11-10T13:00:00+02:00');
|
||||
const snapshot = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, book) };
|
||||
fs.writeFileSync(new URL(name + '.json', out), JSON.stringify(snapshot));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
if (($argv[1] ?? '') !== 'test' || basename($root) !== 'portal-investidor') {
|
||||
throw new RuntimeException('Explicit local test operation required.');
|
||||
}
|
||||
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:'.base64_encode(str_repeat('Q', 32)),
|
||||
'APP_CONFIG_CACHE' => $root.'/outputs/irpc-national-fraction-20261110/no-config.php',
|
||||
'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
|
||||
'DB_DATABASE' => 'proactive_investment_20261027_test', 'DB_USERNAME' => 'investmoz_test', 'DB_PASSWORD' => '',
|
||||
'DB_URL' => '', 'DATABASE_URL' => '', 'CACHE_STORE' => 'array', 'SESSION_DRIVER' => 'array',
|
||||
'QUEUE_CONNECTION' => 'sync', 'MAIL_MAILER' => 'array', 'SUITECRM_ENABLED' => 'false'] as $key => $value) {
|
||||
putenv($key.'='.$value);
|
||||
$_ENV[$key] = $_SERVER[$key] = $value;
|
||||
}
|
||||
$pdo = new PDO('pgsql:host=127.0.0.1;port=55441;dbname=proactive_investment_20261027_test', 'investmoz_test', '');
|
||||
$identity = $pdo->query('select current_database() as name,inet_server_port() as port')->fetch(PDO::FETCH_ASSOC);
|
||||
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
|
||||
throw new RuntimeException('Isolated QA identity required.');
|
||||
}
|
||||
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/IrpcNationalFractionDraftTest.php'];
|
||||
$argc = count($argv);
|
||||
$_SERVER['argv'] = $argv;
|
||||
$_SERVER['argc'] = $argc;
|
||||
require $root.'/vendor/autoload.php';
|
||||
require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
@@ -0,0 +1,25 @@
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { annualBook, annualCurrent, calculateAnnual, annualHtml, annualReportRows } from '../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
|
||||
const out = new URL('../outputs/irpc-national-fraction-20261110/', import.meta.url);
|
||||
const cases = [];
|
||||
for (const name of ['ordinary', 'exact', 'missing']) {
|
||||
const d = JSON.parse(fs.readFileSync(new URL('recovered-' + name + '.json', out), 'utf8'));
|
||||
const b = annualBook(d), m = b.result.model, fresh = calculateAnnual(d, b);
|
||||
assert.equal(annualCurrent(d, b), true);
|
||||
assert.deepEqual(fresh.internationalCredit, m.internationalCredit);
|
||||
assert.deepEqual(fresh.arithmeticBalance, m.arithmeticBalance);
|
||||
const html = annualHtml(d);
|
||||
const rows = annualReportRows(d);
|
||||
const dossier = createProjectDossierReport(d, { selectedSections: new Set(['finance']), generatedAt: '2026-11-10T13:00:00+02:00' });
|
||||
if (name !== 'missing') {
|
||||
assert.match(html, /derivação L6/);
|
||||
assert.match(JSON.stringify(dossier), /IRPC-national-fraction\/1/);
|
||||
} else assert.equal(m.arithmeticBalance, null);
|
||||
fs.writeFileSync(new URL('report-' + name + '.html', out), html);
|
||||
fs.writeFileSync(new URL('dossier-' + name + '.json', out), JSON.stringify(dossier));
|
||||
cases.push({ name, status: m.status, nationalLimit: m.internationalCredit.positions[0].nationalLimit, balance: m.arithmeticBalance, apiRecovered: true, current: true, html: true, dossier: true, reportRows: rows.length });
|
||||
}
|
||||
fs.writeFileSync(new URL('recovered-reports.json', out), JSON.stringify({ synthetic: true, activeDatabaseChanged: false, cases }, null, 2));
|
||||
console.log(JSON.stringify(cases));
|
||||
Reference in New Issue
Block a user