Clarify L1 residual closure criteria and add partial returnee baggage QA
This commit is contained in:
@@ -24409,5 +24409,18 @@
|
||||
"activation": "Joint Git fast-forward/webhook/installer;no migrations/content imports;accepted QA proofs reused;installed delta only",
|
||||
"deduction_calculation": false,
|
||||
"automatic_historical_recalculation": false
|
||||
},
|
||||
"l1_residuals_baggage_20261025": {
|
||||
"report": "docs/investmoz/simulations/iva-l1-residuals-baggage-qa-20261025.json",
|
||||
"delivered": 128,
|
||||
"pending": [
|
||||
"P047",
|
||||
"P058",
|
||||
"P084",
|
||||
"P085",
|
||||
"P001"
|
||||
],
|
||||
"partial_QA_only": true,
|
||||
"homologation_unchanged": "e38aa749"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"base": "9019f766",
|
||||
"homologation_unchanged": "e38aa749",
|
||||
"counts": {
|
||||
"delivered": 128,
|
||||
"complete_QA": 0,
|
||||
"functional_pending": 4,
|
||||
"documentary_P001": 1,
|
||||
"total": 133
|
||||
},
|
||||
"scope": "QA parcial de P058; sem alterações no activo; sem encerramento de ramo",
|
||||
"preserved_delivered_ids": [
|
||||
"AUD-SIM-L1-P002",
|
||||
"AUD-SIM-L1-P003",
|
||||
"AUD-SIM-L1-P004",
|
||||
"AUD-SIM-L1-P005",
|
||||
"AUD-SIM-L1-P006",
|
||||
"AUD-SIM-L1-P007",
|
||||
"AUD-SIM-L1-P008",
|
||||
"AUD-SIM-L1-P009",
|
||||
"AUD-SIM-L1-P010",
|
||||
"AUD-SIM-L1-P011",
|
||||
"AUD-SIM-L1-P012",
|
||||
"AUD-SIM-L1-P013",
|
||||
"AUD-SIM-L1-P014",
|
||||
"AUD-SIM-L1-P015",
|
||||
"AUD-SIM-L1-P016",
|
||||
"AUD-SIM-L1-P017",
|
||||
"AUD-SIM-L1-P018",
|
||||
"AUD-SIM-L1-P019",
|
||||
"AUD-SIM-L1-P020",
|
||||
"AUD-SIM-L1-P021",
|
||||
"AUD-SIM-L1-P022",
|
||||
"AUD-SIM-L1-P023",
|
||||
"AUD-SIM-L1-P024",
|
||||
"AUD-SIM-L1-P025",
|
||||
"AUD-SIM-L1-P026",
|
||||
"AUD-SIM-L1-P027",
|
||||
"AUD-SIM-L1-P028",
|
||||
"AUD-SIM-L1-P029",
|
||||
"AUD-SIM-L1-P030",
|
||||
"AUD-SIM-L1-P031",
|
||||
"AUD-SIM-L1-P032",
|
||||
"AUD-SIM-L1-P033",
|
||||
"AUD-SIM-L1-P034",
|
||||
"AUD-SIM-L1-P035",
|
||||
"AUD-SIM-L1-P036",
|
||||
"AUD-SIM-L1-P037",
|
||||
"AUD-SIM-L1-P038",
|
||||
"AUD-SIM-L1-P039",
|
||||
"AUD-SIM-L1-P040",
|
||||
"AUD-SIM-L1-P041",
|
||||
"AUD-SIM-L1-P042",
|
||||
"AUD-SIM-L1-P043",
|
||||
"AUD-SIM-L1-P044",
|
||||
"AUD-SIM-L1-P045",
|
||||
"AUD-SIM-L1-P046",
|
||||
"AUD-SIM-L1-P048",
|
||||
"AUD-SIM-L1-P049",
|
||||
"AUD-SIM-L1-P050",
|
||||
"AUD-SIM-L1-P051",
|
||||
"AUD-SIM-L1-P052",
|
||||
"AUD-SIM-L1-P053",
|
||||
"AUD-SIM-L1-P054",
|
||||
"AUD-SIM-L1-P055",
|
||||
"AUD-SIM-L1-P056",
|
||||
"AUD-SIM-L1-P057",
|
||||
"AUD-SIM-L1-P059",
|
||||
"AUD-SIM-L1-P060",
|
||||
"AUD-SIM-L1-P061",
|
||||
"AUD-SIM-L1-P062",
|
||||
"AUD-SIM-L1-P063",
|
||||
"AUD-SIM-L1-P064",
|
||||
"AUD-SIM-L1-P065",
|
||||
"AUD-SIM-L1-P066",
|
||||
"AUD-SIM-L1-P067",
|
||||
"AUD-SIM-L1-P068",
|
||||
"AUD-SIM-L1-P069",
|
||||
"AUD-SIM-L1-P070",
|
||||
"AUD-SIM-L1-P071",
|
||||
"AUD-SIM-L1-P072",
|
||||
"AUD-SIM-L1-P073",
|
||||
"AUD-SIM-L1-P074",
|
||||
"AUD-SIM-L1-P075",
|
||||
"AUD-SIM-L1-P076",
|
||||
"AUD-SIM-L1-P077",
|
||||
"AUD-SIM-L1-P078",
|
||||
"AUD-SIM-L1-P079",
|
||||
"AUD-SIM-L1-P080",
|
||||
"AUD-SIM-L1-P081",
|
||||
"AUD-SIM-L1-P082",
|
||||
"AUD-SIM-L1-P083",
|
||||
"AUD-SIM-L1-P086",
|
||||
"AUD-SIM-L1-P087",
|
||||
"AUD-SIM-L1-P088",
|
||||
"AUD-SIM-L1-P089",
|
||||
"AUD-SIM-L1-P090",
|
||||
"AUD-SIM-L1-P091",
|
||||
"AUD-SIM-L1-P092",
|
||||
"AUD-SIM-L1-P093",
|
||||
"AUD-SIM-L1-P094",
|
||||
"AUD-SIM-L1-P095",
|
||||
"AUD-SIM-L1-P096",
|
||||
"AUD-SIM-L1-P097",
|
||||
"AUD-SIM-L1-P098",
|
||||
"AUD-SIM-L1-P099",
|
||||
"AUD-SIM-L1-P100",
|
||||
"AUD-SIM-L1-P101",
|
||||
"AUD-SIM-L1-P102",
|
||||
"AUD-SIM-L1-P103",
|
||||
"AUD-SIM-L1-P104",
|
||||
"AUD-SIM-L1-P105",
|
||||
"AUD-SIM-L1-P106",
|
||||
"AUD-SIM-L1-P107",
|
||||
"AUD-SIM-L1-P108",
|
||||
"AUD-SIM-L1-P109",
|
||||
"AUD-SIM-L1-P110",
|
||||
"AUD-SIM-L1-P111",
|
||||
"AUD-SIM-L1-P112",
|
||||
"AUD-SIM-L1-P113",
|
||||
"AUD-SIM-L1-P114",
|
||||
"AUD-SIM-L1-P115",
|
||||
"AUD-SIM-L1-P116",
|
||||
"AUD-SIM-L1-P117",
|
||||
"AUD-SIM-L1-P118",
|
||||
"AUD-SIM-L1-P119",
|
||||
"AUD-SIM-L1-P120",
|
||||
"AUD-SIM-L1-P121",
|
||||
"AUD-SIM-L1-P122",
|
||||
"AUD-SIM-L1-P123",
|
||||
"AUD-SIM-L1-P124",
|
||||
"AUD-SIM-L1-P125",
|
||||
"AUD-SIM-L1-P126",
|
||||
"AUD-SIM-L1-P127",
|
||||
"AUD-SIM-L1-P128",
|
||||
"AUD-SIM-L1-P129",
|
||||
"AUD-SIM-L1-P130",
|
||||
"AUD-SIM-L1-P131",
|
||||
"AUD-SIM-L1-P132",
|
||||
"AUD-SIM-L1-P133"
|
||||
],
|
||||
"matrix_changed_rows": [
|
||||
"AUD-SIM-L1-P001",
|
||||
"AUD-SIM-L1-P047",
|
||||
"AUD-SIM-L1-P058",
|
||||
"AUD-SIM-L1-P084",
|
||||
"AUD-SIM-L1-P085"
|
||||
],
|
||||
"residuals": {
|
||||
"P047": {
|
||||
"impediment": "Ainda não há implementação específica12(1)b. As quatro remissões estão identificadas;IPP22 integral e alterações2025/2026 já obtidas. Faltam condições executáveis e sua correspondência por família (partidos7/91+14/92,mecenato4/94,veículos oficiais diplomáticos/consulares,IPP),distinguindo isenção integral de direitos,redução e pedido.",
|
||||
"close": "Percursos por família sustentados nos textos aplicáveis,com bens/beneficiário/operação/período,condições e âmbito do benefício aduaneiro demonstrados;isençãoIVA só na hipótese12(1)b correspondente;desconhecido não zero;gravação,recuperação,relatório eQA por ramo,seguida de entrega compatível.",
|
||||
"next": "Começar pelos itensIPP22/QuadroV já cotejados;obter/cotejar os trechos específicos das outras três famílias sem condicionar o avanço independente."
|
||||
},
|
||||
"P058": {
|
||||
"impediment": "Veículos já entregues; quatro percursos de bagagem dos artigos 57(2)c/d, 57(3) e 61 agora demonstrados em QA, ainda não instalados. Restam a remissão incoerente de 57(2)b para o próprio n.º 2, o caso de propriedade exactamente 180 dias e a delimitação do artigo 63(2) (arma de caça e até 100 cartuchos) no âmbito dos beneficiários de CIVA 12(2)a. Não falta o Decreto 37/2023 integral.",
|
||||
"close": "Delimitar e implementar o âmbito residual por categoria e bens; resolver por referência expressa os parâmetros ambíguos ou conservar uma limitação de âmbito expressamente aceite. Demonstrar condições, prazos, cálculo/não aplicação, gravação, recuperação e relatório, sem aplicar exigências de veículos à bagagem; entregar o conjunto compatível. O novo QA parcial não fecha P058.",
|
||||
"next": "Preparar os quatro percursos de bagagem já testados para entrega compatível; cotejar separadamente 57(2)b, propriedade de 180 dias e a correspondência de 63(2), sem presumir isenção."
|
||||
},
|
||||
"P084": {
|
||||
"impediment": "Procedimento operacionalTempo e acompanhamento pedido→apresentado→decisão com despacho→formulário estão definidos,implementados eentregues. O gerador actual produz pedido/preparação textual ou associa exemplar preenchido;falta correspondência concreta do formulário por aquisição interna/importação e,quando demonstrada,preenchimento/exportação dos seus campos oficiais. ModeloI.1/CBF não demonstra sozinho essa correspondência.",
|
||||
"close": "Demonstrar formulário/versão aplicável (ou instrução que dispense modelo específico) em cada cenário,com origem identificada;reutilização de entidade,justificação,bens,operação e despacho no exemplar/saída adequados;conservar âmbito e históricos,submissão externa explícita,hipótese antes de deferimento e aplicação só após decisão abrangente.Sem exigir despacho real de produção para substituirQA nem nova aprovação genérica.",
|
||||
"next": "Cotejo delimitado do formulário/instrução e implementação do mapeamento que daí resultar;manter todo o procedimento operacional já aceite,sem o reabrir como desconhecido."
|
||||
},
|
||||
"P085": {
|
||||
"impediment": "Mesma sequência operacionalTempo já entregue,com calamidade/finalidade humanitária e aquisição próprias. Falta identificar o formulário/instrução aplicável à aquisição para atenuar calamidades e demonstrar a preparação do exemplar concreto,em vez de apenas texto/anexo genérico.Formulário aduaneiro de emergência não prova aquisição interna14(2)b.",
|
||||
"close": "Correspondência e preparação/exportação do formulário aplicável (ou dispensa de modelo demonstrada),reutilizando emergência,finalidade,bens,destinatário e despacho;pedido não concede e decisão não se estende a outros bens/operações/períodos.Sem importar condiçõesP084 ou estender automaticamente à importação.",
|
||||
"next": "Pesquisa conjunta comP084 sobre o formulário,com conclusão separada para aquisição/calâmidade;implementar os campos demonstrados,sem inventar aprovadores ou certificados."
|
||||
},
|
||||
"P001": {
|
||||
"impediment": "Falta instrução da edição recebida que distinga05/06. Nessa edição05 já identifica isenção com direito;06 tem remissão18(1)b,que noCIVA cotejado diz respeito aoIVA de importação. Os modelosAT/2019 têm outra numeração e não resolvem a edição recebida;originais e candidato544068e8 não estão em falta.",
|
||||
"close": "Identificar na instrução correspondente o conteúdo próprio de05 e06 e a remissão aplicável,registar edição/página/trecho e actualizar apenas a correspondência afectada,com prova de não duplicação e preservação dos restantes campos/histórico.",
|
||||
"next": "Localizar instruções/verso/metadados da edição recebida;conservar05/06 indeterminados onde houver conflito,não renumerar a partir de outra edição."
|
||||
}
|
||||
},
|
||||
"new_baggage_paths": [
|
||||
"civil_baggage",
|
||||
"military_baggage",
|
||||
"student_baggage",
|
||||
"early_state_baggage"
|
||||
],
|
||||
"examples": 11,
|
||||
"verification": {
|
||||
"frontend": {
|
||||
"passed": 21,
|
||||
"new_baggage_tests": 13,
|
||||
"previous_vehicle_tests": 8
|
||||
},
|
||||
"API": {
|
||||
"tests": 1,
|
||||
"assertions": 55,
|
||||
"database": "simulator_fiscal_delta_20261021_test:55441",
|
||||
"guarded": true,
|
||||
"transactions_rolled_back": true
|
||||
},
|
||||
"typecheck": "passed",
|
||||
"production_build": "passed",
|
||||
"UI": "gravação, recuperação e relatório conferidos; ui-saved-state.json, report-dom.txt, baggage-recovered-report.png"
|
||||
},
|
||||
"source_scope": "CIVA12(2)a abrange beneficiários; Decreto37/2023 artigos57/61/63. Artigo63(2) não implementado nem presumido abrangido. Não importar quotas/prazos de veículos para bagagem.",
|
||||
"limitations_preserved": [
|
||||
"P047/P058/P084/P085 pendentes",
|
||||
"P001 limitado a05/06",
|
||||
"formalidades digitais não automatizadas",
|
||||
"cálculo fiscal não implementado nos ramos entregues com âmbito limitado",
|
||||
"deduções/regularizações/restituições em lotes próprios",
|
||||
"Estatísticas e IA adiadas",
|
||||
"L1 aberto"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,23 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 25/10/2026 — impedimentos residuais e QA parcial de bagagem
|
||||
|
||||
128 fechos inventariados antes dos testes e preservados sem alterações nas respectivas linhas da matriz. Homologação e38aa749, recibo 9019f766; nenhuma alteração no activo.
|
||||
|
||||
**P047** — Residual: Ainda não há implementação específica12(1)b. As quatro remissões estão identificadas;IPP22 integral e alterações2025/2026 já obtidas. Faltam condições executáveis e sua correspondência por família (partidos7/91+14/92,mecenato4/94,veículos oficiais diplomáticos/consulares,IPP),distinguindo isenção integral de direitos,redução e pedido. Critério de fecho: Percursos por família sustentados nos textos aplicáveis,com bens/beneficiário/operação/período,condições e âmbito do benefício aduaneiro demonstrados;isençãoIVA só na hipótese12(1)b correspondente;desconhecido não zero;gravação,recuperação,relatório eQA por ramo,seguida de entrega compatível. Próxima acção: Começar pelos itensIPP22/QuadroV já cotejados;obter/cotejar os trechos específicos das outras três famílias sem condicionar o avanço independente.
|
||||
|
||||
**P058** — Residual: Veículos já entregues; quatro percursos de bagagem dos artigos 57(2)c/d, 57(3) e 61 agora demonstrados em QA, ainda não instalados. Restam a remissão incoerente de 57(2)b para o próprio n.º 2, o caso de propriedade exactamente 180 dias e a delimitação do artigo 63(2) (arma de caça e até 100 cartuchos) no âmbito dos beneficiários de CIVA 12(2)a. Não falta o Decreto 37/2023 integral. Critério de fecho: Delimitar e implementar o âmbito residual por categoria e bens; resolver por referência expressa os parâmetros ambíguos ou conservar uma limitação de âmbito expressamente aceite. Demonstrar condições, prazos, cálculo/não aplicação, gravação, recuperação e relatório, sem aplicar exigências de veículos à bagagem; entregar o conjunto compatível. O novo QA parcial não fecha P058. Próxima acção: Preparar os quatro percursos de bagagem já testados para entrega compatível; cotejar separadamente 57(2)b, propriedade de 180 dias e a correspondência de 63(2), sem presumir isenção.
|
||||
|
||||
**P084** — Residual: Procedimento operacionalTempo e acompanhamento pedido→apresentado→decisão com despacho→formulário estão definidos,implementados eentregues. O gerador actual produz pedido/preparação textual ou associa exemplar preenchido;falta correspondência concreta do formulário por aquisição interna/importação e,quando demonstrada,preenchimento/exportação dos seus campos oficiais. ModeloI.1/CBF não demonstra sozinho essa correspondência. Critério de fecho: Demonstrar formulário/versão aplicável (ou instrução que dispense modelo específico) em cada cenário,com origem identificada;reutilização de entidade,justificação,bens,operação e despacho no exemplar/saída adequados;conservar âmbito e históricos,submissão externa explícita,hipótese antes de deferimento e aplicação só após decisão abrangente.Sem exigir despacho real de produção para substituirQA nem nova aprovação genérica. Próxima acção: Cotejo delimitado do formulário/instrução e implementação do mapeamento que daí resultar;manter todo o procedimento operacional já aceite,sem o reabrir como desconhecido.
|
||||
|
||||
**P085** — Residual: Mesma sequência operacionalTempo já entregue,com calamidade/finalidade humanitária e aquisição próprias. Falta identificar o formulário/instrução aplicável à aquisição para atenuar calamidades e demonstrar a preparação do exemplar concreto,em vez de apenas texto/anexo genérico.Formulário aduaneiro de emergência não prova aquisição interna14(2)b. Critério de fecho: Correspondência e preparação/exportação do formulário aplicável (ou dispensa de modelo demonstrada),reutilizando emergência,finalidade,bens,destinatário e despacho;pedido não concede e decisão não se estende a outros bens/operações/períodos.Sem importar condiçõesP084 ou estender automaticamente à importação. Próxima acção: Pesquisa conjunta comP084 sobre o formulário,com conclusão separada para aquisição/calâmidade;implementar os campos demonstrados,sem inventar aprovadores ou certificados.
|
||||
|
||||
**P001** — Residual: Falta instrução da edição recebida que distinga05/06. Nessa edição05 já identifica isenção com direito;06 tem remissão18(1)b,que noCIVA cotejado diz respeito aoIVA de importação. Os modelosAT/2019 têm outra numeração e não resolvem a edição recebida;originais e candidato544068e8 não estão em falta. Critério de fecho: Identificar na instrução correspondente o conteúdo próprio de05 e06 e a remissão aplicável,registar edição/página/trecho e actualizar apenas a correspondência afectada,com prova de não duplicação e preservação dos restantes campos/histórico. Próxima acção: Localizar instruções/verso/metadados da edição recebida;conservar05/06 indeterminados onde houver conflito,não renumerar a partir de outra edição.
|
||||
|
||||
P058: quatro percursos adicionais de bagagem em QA, 11 exemplos; 21 testes frontend (13 novos e 8 anteriores), API 1 teste/55 asserções com guarda QA e rollback, tipos e compilação aprovados. Gravação, recuperação e relatório conferidos na interface. Os campos de veículos ficam ocultos na bagagem, preservando os valores existentes. Evidência: `docs/investmoz/simulations/iva-l1-residuals-baggage-qa-20261025.json` e `outputs/iva-l1-residuals-baggage-20261025/`.
|
||||
|
||||
Saldo: **128 entregues + 4 funcionais pendentes + P001 = 133**; nenhuma candidata de ramo completo por disponibilizar. P084/P085 já têm o procedimento operacional Tempo entregue; resta a correspondência específica de formulário/instrução e o mapeamento decorrente. L1 aberto; mantêm-se as limitações fiscais e digitais anteriormente registadas.
|
||||
|
||||
## 25/10/2026 — entrega conjunta P104 operacional e percursos parciais
|
||||
|
||||
Homologação **`e38aa749`**, sobre `a665ee9c`; candidata `7b6a95a7` aceite pelaTempo. Uma integraçãoGit,backup/restauro real semrede e conferência apenas do delta instalado;provasQA75frontend/API2/37asserções,types/build/UI reutilizadas. **127fechos anteriores preservados**.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
import type {ReturneeRow} from '@/lib/iva-returnees';
|
||||
import {baggageConditions} from '@/lib/iva-returnee-baggage';
|
||||
export function SimulationReturneeBaggageFields({row,change}:{row:ReturneeRow;change:(patch:Partial<ReturneeRow>)=>void}){
|
||||
const text=(key:'missionRef'|'missionPlannedEnd'|'earlyReturnRef'|'earlyBaggageRef',label:string,type='text')=><label className="block" key={key}>{label}<input type={type} value={row[key]??''} onChange={e=>change({[key]:e.target.value})}/></label>;
|
||||
const choice=(key:keyof ReturneeRow['conditions'],label:string)=><label className="block" key={key}>{label}<select value={row.conditions[key]??''} onChange={e=>change({conditions:{...row.conditions,[key]:e.target.value}})}><option value="">Por esclarecer</option><option value="yes">Sim,segundo factos ou pressupostos identificados</option><option value="no">Não</option></select></label>;
|
||||
return<fieldset><legend>Condições próprias da bagagem no regresso</legend>{baggageConditions.map(([key,label])=>choice(key,label))}
|
||||
{text('missionRef',row.treatment==='student_baggage'?'Referência da missão de estudo/condição de estudante ou bolseiro':'Referência da missão de serviço público e categoria do funcionário')}
|
||||
<label>Bens desta linha<select value={row.baggageKind??''} onChange={e=>change({baggageKind:e.target.value as ReturneeRow['baggageKind']})}><option value="">Por indicar</option><option value="personal">Objectos de uso pessoal com sinais de uso — artigo57(3)a</option><option value="household">Móveis,roupas e objectos de uso doméstico — artigo57(3)b</option></select></label>
|
||||
<label>Via da bagagem<select value={row.baggageRoute??''} onChange={e=>change({baggageRoute:e.target.value as ReturneeRow['baggageRoute']})}><option value="">Por indicar</option><option value="accompanied">Acompanhada pelo viajante</option><option value="unaccompanied">Não acompanhada — artigo61</option></select></label>
|
||||
{row.treatment==='early_state_baggage'&&<>{text('missionPlannedEnd','Termo inicialmente previsto para a missão superior a um ano','date')}{choice('earlyService','Regresso antecipado por motivos de serviço do Estado')}{text('earlyReturnRef','Motivo/referência do regresso antecipado ou pressuposto expresso')}</>}
|
||||
{row.baggageRoute==='unaccompanied'&&<><p>Artigo61:entrada até180dias após a chegada;desembaraço anterior à chegada apenas na excepção justificada,com autorização do chefe da estância aduaneira da jurisdição.</p>{choice('advanceClearance','Invoca desembaraço excepcional antes da chegada do viajante?')}{row.conditions.advanceClearance==='yes'&&text('earlyBaggageRef','Motivo justificado,autorização/âmbito do desembaraço antecipado ou pressuposto expresso')}</>}
|
||||
<p>Não se aplicam aqui a idade/nacionalidade,quota de veículo em cinco anos ou requerimento60+30dias do artigo63.Oartigo57(4) não dispensa formalidades nas alíneas c/d;reutilize os documentos disponíveis.Sem novo certificado criado pelo portal.Veículos,armas e munições não pertencem a esta linha de bagagem.</p>
|
||||
</fieldset>;
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import {baggageSource,isReturneeBaggage} from '@/lib/iva-returnee-baggage';
|
||||
import {SimulationReturneeBaggageFields} from './simulation-returnee-baggage-fields';
|
||||
import {useState} from 'react';
|
||||
import {returneeBook,saveReturneeBook,newReturnee,returneeTreatments,returneeConditions,recordReturnees,returneeReportRows,returneeCurrent,returneeSource,type ReturneeBook,type ReturneeRow} from '@/lib/iva-returnees';
|
||||
export function SimulationReturnees({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}){
|
||||
const[error,setError]=useState('');let book:ReturneeBook;try{book=returneeBook(draft);}catch(e){return<p role="alert">{String(e)}</p>;}
|
||||
const save=(b:ReturneeBook)=>{try{onChange('modelo_a_snapshot',saveReturneeBook(draft,b));setError('');}catch(e){setError(String(e));}};
|
||||
const change=(id:string,patch:Partial<ReturneeRow>)=>save({...book,rows:book.rows.map(r=>r.id===id?{...r,...patch}:r)});
|
||||
return<details className="rounded border p-3"><summary>Regresso definitivo — veículos (P058, cobertura parcial)</summary>
|
||||
<p>Prepare o veículo e simule o enquadramento do artigo63.º do Decreto37/2023. Isenção, redução de80% e redução excepcional de50% têm condições distintas. Estas percentagens são benefícios sobre as imposições; não são taxas deIVA. Não se aplicam aqui as condições dos mineirosP059.</p>
|
||||
<p>Bagagem e outros bens continuam por concluir emP058. A remissão do artigo57(2)b ao próprio n.º2 está incoerente no exemplar e não foi corrigida por presunção. O caso de exactamente180dias de propriedade fica indeterminado. O marco da conferência do tempo de propriedade deve vir do enquadramento/documento indicado;sem escolher a chegada ou o despacho automaticamente. Aquisição interna, direitos,ICE,dedução e alteração de destino não são calculados neste incremento.</p>
|
||||
{error&&<p role="alert">{error}</p>}<button type="button" onClick={()=>{const next=newReturnee(crypto.randomUUID(),String(draft.currency??''));const candidates=book.rows.filter(r=>r.beneficiary);if(new Set(candidates.map(r=>r.beneficiary)).size===1)next.beneficiary=candidates[0].beneficiary;save({...book,rows:[...book.rows,next]});}}>Acrescentar veículo no regresso</button>
|
||||
{book.rows.map(row=>{const text=(field:Exclude<keyof ReturneeRow,'conditions'>,label:string,type='text')=><label key={field} className="block">{label}<input type={type} value={row[field]} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>;const choice=(key:keyof ReturneeRow['conditions'],label:string)=><label key={key} className="block">{label}<select value={row.conditions[key]??''} onChange={e=>change(row.id,{conditions:{...row.conditions,[key]:e.target.value}})}><option value="">Por esclarecer</option><option value="yes">Sim,segundo factos ou pressupostos identificados</option><option value="no">Não</option></select></label>;
|
||||
return<details key={row.id}><summary>{row.description||'Veículo por preparar'}</summary><label>Percurso do veículo<select value={row.treatment} onChange={e=>change(row.id,{treatment:e.target.value,conditions:Object.fromEntries(returneeConditions.map(([k])=>[k,row.conditions[k]]))})}><option value="">Escolher percurso</option>{returneeTreatments.map(t=><option key={t.id} value={t.id}>{t.label}</option>)}</select></label>
|
||||
{text('description','Veículo identificado')}{text('beneficiary','Beneficiário')}{text('period','Período da importação','month')}{text('importDate','Data da importação ou previsão','date')}
|
||||
return<details className="rounded border p-3"><summary>Regresso definitivo — veículos e bagagem (P058, cobertura parcial)</summary>
|
||||
<p>Prepare os bens e escolha o percurso próprio:veículos no artigo63.º ou bagagem de funcionários e estudantes nos artigos57.º/61.º do Decreto37/2023. Isenção, redução de80% e redução excepcional de50% têm condições distintas. Estas percentagens são benefícios sobre as imposições; não são taxas deIVA. Não se aplicam aqui as condições dos mineirosP059.</p>
|
||||
<p>Bagagem de funcionários e estudantes tem percurso próprio57(2)c/d;fixação de domicílio57(2)b e outros bens permanecem por enquadrar. A remissão do artigo57(2)b ao próprio n.º2 está incoerente no exemplar e não foi corrigida por presunção. O caso de exactamente180dias de propriedade fica indeterminado. O marco da conferência do tempo de propriedade deve vir do enquadramento/documento indicado;sem escolher a chegada ou o despacho automaticamente. Aquisição interna, direitos,ICE,dedução e alteração de destino não são calculados neste incremento.</p>
|
||||
{error&&<p role="alert">{error}</p>}<button type="button" onClick={()=>{const next=newReturnee(crypto.randomUUID(),String(draft.currency??''));const candidates=book.rows.filter(r=>r.beneficiary);if(new Set(candidates.map(r=>r.beneficiary)).size===1)next.beneficiary=candidates[0].beneficiary;save({...book,rows:[...book.rows,next]});}}>Acrescentar bens no regresso</button>
|
||||
{book.rows.map(row=>{const baggage=isReturneeBaggage(row.treatment),vehicle=['owned','recent','separate'].includes(row.treatment);const text=(field:Exclude<keyof ReturneeRow,'conditions'>,label:string,type='text')=><label key={field} className="block">{label}<input type={type} value={row[field]} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>;const choice=(key:keyof ReturneeRow['conditions'],label:string)=><label key={key} className="block">{label}<select value={row.conditions[key]??''} onChange={e=>change(row.id,{conditions:{...row.conditions,[key]:e.target.value}})}><option value="">Por esclarecer</option><option value="yes">Sim,segundo factos ou pressupostos identificados</option><option value="no">Não</option></select></label>;
|
||||
return<details key={row.id}><summary>{row.description||'Bens por preparar'}</summary><label>Percurso dos bens<select value={row.treatment} onChange={e=>change(row.id,{treatment:e.target.value,conditions:Object.fromEntries(returneeConditions.map(([k])=>[k,row.conditions[k]]))})}><option value="">Escolher percurso</option>{returneeTreatments.map(t=><option key={t.id} value={t.id}>{t.label}</option>)}</select></label>
|
||||
{text('description',vehicle?'Veículo identificado':'Bens identificados')}{text('beneficiary','Beneficiário')}{text('period','Período da importação','month')}{text('importDate','Data da importação ou previsão','date')}
|
||||
<label>Natureza dos dados<select value={row.origin} onChange={e=>change(row.id,{origin:e.target.value as ReturneeRow['origin']})}><option value="projected">Pressupostos de projecção</option><option value="effective">Factos efectivos declarados</option></select></label>
|
||||
{text('documentRef','Documentos disponíveis ou previsão')}{text('scope','Âmbito:bens,operação,período e condições')}{text('evidence','Factos ou pressupostos expressos')}{returneeConditions.map(([k,l])=>choice(k,l))}
|
||||
{text('stayStart','Início da estadia no estrangeiro','date')}{text('stayEnd','Fim da estadia no estrangeiro','date')}{text('arrival','Chegada no regresso definitivo','date')}{text('request','Requerimento do benefício','date')}
|
||||
{row.treatment!=='separate'&&<>{text('ownedSince','Início da propriedade no país de procedência','date')}{text('ownershipAssessment','Data à qual o documento confere o tempo de propriedade no país de procedência','date')}</>}
|
||||
{choice('extension','Invoca prorrogação excepcional do prazo de60dias,até30dias adicionais?')}{row.conditions.extension==='yes'&&text('extensionRef','Prorrogação pelo Director-Geral das Alfândegas:referência,âmbito e prazo ou pressuposto expresso')}
|
||||
{text('documentRef','Documentos disponíveis ou previsão')}{text('scope','Âmbito:bens,operação,período e condições')}{text('evidence','Factos ou pressupostos expressos')}{baggage?<SimulationReturneeBaggageFields row={row} change={patch=>change(row.id,patch)}/>:vehicle?returneeConditions.map(([k,l])=>choice(k,l)):null}
|
||||
{text('stayStart','Início da estadia no estrangeiro','date')}{text('stayEnd','Fim da estadia no estrangeiro','date')}{text('arrival','Chegada no regresso definitivo','date')}{vehicle&&text('request','Requerimento do benefício','date')}
|
||||
{vehicle&&row.treatment!=='separate'&&<>{text('ownedSince','Início da propriedade no país de procedência','date')}{text('ownershipAssessment','Data à qual o documento confere o tempo de propriedade no país de procedência','date')}</>}
|
||||
{vehicle&&<>{choice('extension','Invoca prorrogação excepcional do prazo de60dias,até30dias adicionais?')}{row.conditions.extension==='yes'&&text('extensionRef','Prorrogação pelo Director-Geral das Alfândegas:referência,âmbito e prazo ou pressuposto expresso')}
|
||||
{row.treatment==='separate'&&<>{choice('substitution','Substituição excepcional do benefício por veículo separado da bagagem?')}{text('decisionRef','Referência e âmbito da substituição ou pressuposto expresso;pedido não equivale a concessão')}{choice('exception','Invoca excepção por estadia insuficiente no estrangeiro?')}{row.conditions.exception==='yes'&&text('exceptionRef','Autorização do Ministro das Finanças:motivos e âmbito ou pressuposto expresso')}</>}
|
||||
<label>Benefício anterior de veículo<select value={row.previous} onChange={e=>change(row.id,{previous:e.target.value as ReturneeRow['previous']})}><option value="">Por esclarecer</option><option value="none">Nenhum,segundo histórico identificado</option><option value="known">Sim,data conhecida</option></select></label>{row.previous==='known'&&text('previousDate','Data da numeração do despacho anterior','date')}{text('historyRef','Histórico de benefícios,incluindo fora deste dossier')}
|
||||
{['recent','separate'].includes(row.treatment)&&<>{text('normalVat','IVA normal desta importação informado emMZN')}{text('normalVatRef','Referência do IVA normal ou pressuposto expresso')}</>}
|
||||
<p>Mudar percurso conserva os campos ocultos e limpa confirmações específicas. A fonte exige documentos de titularidade e residência e declaração de uso;reutilize os documentos disponíveis. A aplicação não emite despacho nem concede o benefício.</p></details>;})}
|
||||
{!!book.rows.length&&<button type="button" onClick={()=>{try{save(recordReturnees(draft,book));}catch(e){setError(String(e));}}}>Conferir veículos no regresso e guardar versão</button>}
|
||||
</>}
|
||||
<p>Mudar percurso conserva os campos ocultos e limpa confirmações específicas. {baggage?'Reutilize os documentos da missão,estudo e bagagem;as condições de veículos não se aplicam a esta linha.':'A fonte exige documentos de titularidade e residência e declaração de uso;reutilize os documentos disponíveis.'} A aplicação não emite despacho nem concede o benefício.</p></details>;})}
|
||||
{!!book.rows.length&&<button type="button" onClick={()=>{try{save(recordReturnees(draft,book));}catch(e){setError(String(e));}}}>Conferir bens no regresso e guardar versão</button>}
|
||||
{book.result&&<><p role="status">{returneeCurrent(draft,book)?'Resultado corresponde às entradas guardadas.':'Entradas alteradas;resultado histórico conservado.'} {book.history?.length??0} versões anteriores.Cobertura parcialP058.</p><dl>{returneeReportRows(draft).map(([,label,value,source],i)=><div key={i}><dt>{label}</dt><dd>{value}<small className="block">{source}</small></dd></div>)}</dl></>}
|
||||
<details><summary>Fonte e âmbito documental</summary><p>{returneeSource}</p></details></details>;
|
||||
<details><summary>Fonte e âmbito documental</summary><p>{returneeSource}</p><p>{baggageSource}</p></details></details>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import type {ReturneeRow,Line} from './iva-returnees.ts';
|
||||
|
||||
export const baggageSource='P058 — CIVA12(2)a;Decreto37/2023,57(2)c/d e57(3),PDF10/p1474;61 e63(1),PDF11/p1475. Decreto27/2026 altera14/58/QuadrosV/VI,sem alterar57/61/63. Fonte oficial arquivada porhash;sem condições de veículos ou de mineiros transplantadas à bagagem.';
|
||||
export const baggageTreatments=[
|
||||
{id:'civil_baggage',label:'Bagagem — funcionário civil em missão pública superior a um ano',article:'57(2)c/57(3)',remaining:0},
|
||||
{id:'military_baggage',label:'Bagagem — militar em missão pública superior a um ano',article:'57(2)c/57(3)',remaining:0},
|
||||
{id:'student_baggage',label:'Bagagem — estudante/bolseiro em missão de estudo superior a um ano',article:'57(2)c/57(3)',remaining:0},
|
||||
{id:'early_state_baggage',label:'Bagagem — regresso antecipado por serviço do Estado',article:'57(2)d/57(3)',remaining:0},
|
||||
] as const;
|
||||
export const baggageConditions=[
|
||||
['definitive','Regresso definitivo no âmbito desta operação'],
|
||||
['mission','Categoria e missão pública ou de estudo identificadas,conforme o percurso escolhido'],
|
||||
['reasonable','Bagagem em quantidades e qualidades razoáveis'],
|
||||
['nonCommercial','Sem finalidade comercial'],
|
||||
['baggageGoods','Bens desta linha abrangidos por57(3);não inclui veículos,armas ou munições'],
|
||||
] as const;
|
||||
export function isReturneeBaggage(treatment:string):boolean{return baggageTreatments.some(t=>t.id===treatment);}
|
||||
function date(s:string):Date|null{const d=new Date(s+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===s?d:null;}
|
||||
function anniversary(s:string){const d=date(s)!;const month=d.getUTCMonth();d.setUTCFullYear(d.getUTCFullYear()+1);if(d.getUTCMonth()!==month)d.setUTCDate(0);return d.toISOString().slice(0,10);}
|
||||
export function evaluateBaggage(row:ReturneeRow,currency:unknown):Line{
|
||||
const rule=baggageTreatments.find(t=>t.id===row.treatment),base:Line={id:row.id,status:'indeterminate',importVat:null,deduction:null,reason:'',source:baggageSource+(rule?';'+rule.article:'')};const stop=(reason:string,status:Line['status']='indeterminate')=>({...base,status,reason});
|
||||
if(!rule)return stop('Percurso de bagagem não identificado;sem estender57(2)c/d à fixação de domicílio57(2)b.');
|
||||
if(currency!=='MZN'||row.currency!==currency||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||!date(row.importDate)||row.importDate.slice(0,7)!==row.period)return stop('Indique importação2026 emMZN e data/período coerentes;sem conversão presumida.');
|
||||
if(!['projected','effective'].includes(row.origin)||![row.description,row.beneficiary,row.scope,row.evidence,row.documentRef,row.missionRef??''].every(v=>v.trim()))return stop('Identifique beneficiário,bens,âmbito,documentos disponíveis e missão pública/estudo ou pressupostos expressos;reutilize referências,sem certificado adicional criado pelo portal.');
|
||||
if(baggageConditions.some(([key])=>row.conditions[key]==='no'))return stop('Condição deste percurso de bagagem não satisfeita;outros regimes permanecem por enquadrar.','not_applicable');
|
||||
if(baggageConditions.some(([key])=>row.conditions[key]!=='yes'))return stop('Condições da bagagem por esclarecer;desconhecido não equivale a isenção.');
|
||||
if(!['personal','household'].includes(row.baggageKind??''))return stop('Distinga objectos de uso pessoal com sinais de uso57(3)a de móveis,roupas e objectos domésticos57(3)b;separe bens fora do conceito de bagagem.');
|
||||
if(![row.stayStart,row.stayEnd,row.arrival].every(v=>date(v))||row.stayEnd<row.stayStart||row.stayEnd>row.arrival)return stop('Indique estadia no estrangeiro e chegada coerentes com a missão e o regresso.');
|
||||
if(row.treatment==='early_state_baggage'){
|
||||
if(!date(row.missionPlannedEnd??'')||row.missionPlannedEnd!<=anniversary(row.stayStart)||row.stayEnd>=row.missionPlannedEnd!)return stop('57(2)d exige missão inicialmente prevista para mais de um ano e regresso anterior ao termo previsto;sem impor uma duração efectiva mínima de um ano.');
|
||||
if(row.conditions.earlyService==='no')return stop('Regresso antecipado não decorre de serviço doEstado;este57(2)d não se aplica.','not_applicable');
|
||||
if(row.conditions.earlyService!=='yes'||!row.earlyReturnRef?.trim())return stop('Identifique motivo/referência do regresso antecipado por serviço doEstado,ou pressuposto expresso;sem inventar novo aprovador.');
|
||||
}else if(row.stayEnd<=anniversary(row.stayStart))return stop('57(2)c exige permanência superior a um ano em missão pública ou de estudo;um ano exacto não satisfaz este percurso.','not_applicable');
|
||||
if(!['accompanied','unaccompanied'].includes(row.baggageRoute??''))return stop('Indique bagagem acompanhada ou não acompanhada;os prazos da via não acompanhada são próprios doart61.');
|
||||
if(row.baggageRoute==='unaccompanied'){
|
||||
const elapsed=(date(row.importDate)!.getTime()-date(row.arrival)!.getTime())/86400000;
|
||||
if(elapsed>180)return stop('Entrada da bagagem não acompanhada além de180dias da chegada;61(1) não demonstra isenção nesta hipótese. Não aplicar a prorrogação de veículos63.');
|
||||
if(elapsed<0&&(row.conditions.advanceClearance!=='yes'||!row.earlyBaggageRef?.trim()))return stop('Desembaraço antes da chegada exige a excepção61(2),com motivo justificado e autorização do chefe da estância aduaneira da jurisdição ou pressuposto expresso;não presumida.');
|
||||
}else if(row.importDate<row.arrival)return stop('Bagagem declarada acompanhada não pode entrar antes da chegada do viajante;conferir a via e as datas.');
|
||||
const kind=row.baggageKind==='personal'?'objectos de uso pessoal com sinais de uso57(3)a':'móveis,roupas e objectos de uso doméstico57(3)b';
|
||||
return{...base,status:'exempt',importVat:'0.00',reason:`Bagagem abrangida por${rule.article}:${kind};${row.baggageRoute==='unaccompanied'?'via não acompanhada61':'via acompanhada'}. ${row.origin==='projected'?'Cenário condicionado aos pressupostos expressos':'Factos efectivos declarados no âmbito identificado'};IVA de importação isento no benefício integral indicado.Sem concessão administrativa automática,direitos,ICE,dedução ou campo11.Sem exigir nacionalidade/idade mínima,quota5anos ou requerimento60+30dias de veículos.Oart57(4) não dispensa formalidades nestas alíneas c/d.`};
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import {baggageTreatments,isReturneeBaggage,evaluateBaggage} from './iva-returnee-baggage.ts';
|
||||
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||||
export const returneeSource='P058 — CIVA12(2)a:IVA proporcional à redução de direitos;Decreto37/2023,art63(3)–(5),PDF11/p1475;art28,PDF5/p1469. Decreto27/2026,BR123-I,1.ºsuplemento01/07,PDF1–3/p1012(1–3):altera14/58 eQuadrosV/VI,não63. Veículos no regresso;sem extensão das condições dos mineirosP059.';
|
||||
export const returneeTreatments=[{id:'owned',label:'Veículo próprio há mais de 180 dias — isenção',article:'63(3)a,primeira parte',remaining:0},{id:'recent',label:'Veículo próprio há menos de 180 dias — redução de 80%',article:'63(3)a,segunda parte',remaining:20},{id:'separate',label:'Substituição por veículo separado da bagagem — redução de 50%',article:'63(3)b/e',remaining:50}] as const;
|
||||
export const returneeTreatments=[{id:'owned',label:'Veículo próprio há mais de 180 dias — isenção',article:'63(3)a,primeira parte',remaining:0},{id:'recent',label:'Veículo próprio há menos de 180 dias — redução de 80%',article:'63(3)a,segunda parte',remaining:20},{id:'separate',label:'Substituição por veículo separado da bagagem — redução de 50%',article:'63(3)b/e',remaining:50},...baggageTreatments] as const;
|
||||
export const returneeConditions=[['national','Cidadão nacional maior de 18 anos'],['definitive','Regresso definitivo ao País'],['single','Um único veículo abrangido;outros veículos pagam a totalidade'],['ownership','Documentos disponíveis comprovam titularidade e residência no país de procedência'],['use','Uso exclusivo pelo beneficiário ou cônjuge e fim declarado;art28'],['undertaking','Declaração em modelo próprio de não alteração de uso,art28(3),identificada ou prevista'],['history','Histórico identificado inclui benefícios fora deste dossier']] as const;
|
||||
type Choice=''|'yes'|'no';
|
||||
export type ReturneeRow={id:string;treatment:string;description:string;beneficiary:string;period:string;currency:string;origin:'projected'|'effective';scope:string;evidence:string;documentRef:string;arrival:string;request:string;stayStart:string;stayEnd:string;ownedSince:string;ownershipAssessment?:string;importDate:string;previous:''|'none'|'known';previousDate:string;historyRef:string;extensionRef:string;exceptionRef:string;decisionRef:string;normalVat:string;normalVatRef:string;conditions:Partial<Record<typeof returneeConditions[number][0]|'extension'|'exception'|'substitution',Choice>>};
|
||||
type Line={id:string;status:'exempt'|'reduced'|'not_applicable'|'indeterminate';importVat:string|null;deduction:null;reason:string;source:string};
|
||||
export type ReturneeRow={baggageKind?:''|'personal'|'household';baggageRoute?:''|'accompanied'|'unaccompanied';missionRef?:string;missionPlannedEnd?:string;earlyReturnRef?:string;earlyBaggageRef?:string;id:string;treatment:string;description:string;beneficiary:string;period:string;currency:string;origin:'projected'|'effective';scope:string;evidence:string;documentRef:string;arrival:string;request:string;stayStart:string;stayEnd:string;ownedSince:string;ownershipAssessment?:string;importDate:string;previous:''|'none'|'known';previousDate:string;historyRef:string;extensionRef:string;exceptionRef:string;decisionRef:string;normalVat:string;normalVatRef:string;conditions:Partial<Record<typeof returneeConditions[number][0]|'extension'|'exception'|'substitution'|'mission'|'reasonable'|'nonCommercial'|'baggageGoods'|'earlyService'|'advanceClearance',Choice>>};
|
||||
export type Line={id:string;status:'exempt'|'reduced'|'not_applicable'|'indeterminate';importVat:string|null;deduction:null;reason:string;source:string};
|
||||
type Result={revision:number;calculatedAt:string;sourceKey:string;inputs:ReturneeRow[];rows:Line[]};
|
||||
export type ReturneeBook={rows:ReturneeRow[];result?:Result;history?:Result[]};
|
||||
export function newReturnee(id:string,currency:string):ReturneeRow{return{id,currency,treatment:'',description:'',beneficiary:'',period:'',origin:'projected',scope:'',evidence:'',documentRef:'',arrival:'',request:'',stayStart:'',stayEnd:'',ownedSince:'',importDate:'',previous:'',previousDate:'',historyRef:'',extensionRef:'',exceptionRef:'',decisionRef:'',normalVat:'',normalVatRef:'',conditions:{}};}
|
||||
function store(draft:Record<string,unknown>){const s=readAcquisitionStore(draft.modelo_a_snapshot) as ReturnType<typeof readAcquisitionStore>&{returneeImports?:Record<string,ReturneeBook>};const fields=Object.keys(newReturnee('','')).filter(k=>k!=='conditions');if(s.returneeImports!==undefined&&(!s.returneeImports||typeof s.returneeImports!=='object'||Array.isArray(s.returneeImports)))throw Error('Rascunhos do regresso não recuperáveis;originais preservados.');for(const b of Object.values(s.returneeImports??{}))if(!b||!Array.isArray(b.rows)||(b.history!==undefined&&!Array.isArray(b.history))||b.rows.some(r=>!r||fields.some(k=>typeof r[k as keyof ReturneeRow]!=='string')||!r.conditions||typeof r.conditions!=='object'||Array.isArray(r.conditions)))throw Error('Linhas do regresso não recuperáveis;sem substituição por lista vazia.');return s;}
|
||||
function store(draft:Record<string,unknown>){const s=readAcquisitionStore(draft.modelo_a_snapshot) as ReturnType<typeof readAcquisitionStore>&{returneeImports?:Record<string,ReturneeBook>};const fields=Object.keys(newReturnee('','')).filter(k=>k!=='conditions');if(s.returneeImports!==undefined&&(!s.returneeImports||typeof s.returneeImports!=='object'||Array.isArray(s.returneeImports)))throw Error('Rascunhos do regresso não recuperáveis;originais preservados.');for(const b of Object.values(s.returneeImports??{}))if(!b||!Array.isArray(b.rows)||(b.history!==undefined&&!Array.isArray(b.history))||b.rows.some(r=>!r||fields.some(k=>typeof r[k as keyof ReturneeRow]!=='string')||['baggageKind','baggageRoute','missionRef','missionPlannedEnd','earlyReturnRef','earlyBaggageRef'].some(k=>r[k as keyof ReturneeRow]!==undefined&&typeof r[k as keyof ReturneeRow]!=='string')||!r.conditions||typeof r.conditions!=='object'||Array.isArray(r.conditions)))throw Error('Linhas do regresso não recuperáveis;sem substituição por lista vazia.');return s;}
|
||||
export function returneeBook(draft:Record<string,unknown>):ReturneeBook{return store(draft).returneeImports?.[acquisitionScope(draft)]??{rows:[]};}
|
||||
export function saveReturneeBook(draft:Record<string,unknown>,book:ReturneeBook):string{const s=store(draft),value=JSON.stringify({...s,returneeImports:{...s.returneeImports,[acquisitionScope(draft)]:book}});store({...draft,modelo_a_snapshot:value});return value;}
|
||||
function date(v:string):Date|null{const d=new Date(v+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(v)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===v?d:null;}
|
||||
function years(v:string,n:number){const d=date(v)!;const month=d.getUTCMonth();d.setUTCFullYear(d.getUTCFullYear()+n);if(d.getUTCMonth()!==month)d.setUTCDate(0);return d.toISOString().slice(0,10);}
|
||||
function days(a:string,b:string):number{return(date(b)!.getTime()-date(a)!.getTime())/86400000;}
|
||||
export function evaluateReturnee(r:ReturneeRow,currency:unknown):Line{
|
||||
if(isReturneeBaggage(r.treatment))return evaluateBaggage(r,currency);
|
||||
const rule=returneeTreatments.find(t=>t.id===r.treatment),base:Line={id:r.id,status:'indeterminate',importVat:null,deduction:null,reason:'',source:returneeSource+(rule?';'+rule.article:'')};const stop=(reason:string,status:Line['status']='indeterminate')=>({...base,status,reason});
|
||||
if(!rule)return stop('Escolha percurso de veículo documentado;bagagem e outros bens permanecem por enquadrar separadamente.');
|
||||
if(currency!=='MZN'||r.currency!==currency||!/^2026-(0[1-9]|1[0-2])$/.test(r.period)||!date(r.importDate)||r.importDate.slice(0,7)!==r.period)return stop('Indique importação2026 emMZN,com data/período coerentes;sem conversão presumida.');
|
||||
@@ -43,7 +45,7 @@ function key(draft:Record<string,unknown>,b:ReturneeBook){return JSON.stringify(
|
||||
export function returneeCurrent(draft:Record<string,unknown>,b:ReturneeBook){return !!b.result&&b.result.sourceKey===key(draft,b);}
|
||||
export function recordReturnees(draft:Record<string,unknown>,b:ReturneeBook):ReturneeBook{
|
||||
if(!b.rows.length||new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identifique linhas distintas;originais preservados.');
|
||||
const rows=b.rows.map(r=>evaluateReturnee(r,draft.currency));for(let i=0;i<b.rows.length;i++){const r=b.rows[i];if(!['exempt','reduced'].includes(rows[i].status))continue;if(b.rows.some((peer,j)=>j!==i&&peer.beneficiary===r.beneficiary&&peer.origin===r.origin&&date(peer.importDate)&&evaluateReturnee(peer,draft.currency).status!=='not_applicable'&&(peer.importDate<=r.importDate?r.importDate<years(peer.importDate,5):peer.importDate<years(r.importDate,5))))rows[i]={...rows[i],status:'indeterminate',importVat:null,reason:'Mais de uma linha usa o mesmo direito de veículo em cinco anos;conferir o conjunto e histórico externo,sem dupla utilização.'};}
|
||||
const rows=b.rows.map(r=>evaluateReturnee(r,draft.currency));for(let i=0;i<b.rows.length;i++){const r=b.rows[i];if(isReturneeBaggage(r.treatment))continue;if(!['exempt','reduced'].includes(rows[i].status))continue;if(b.rows.some((peer,j)=>j!==i&&!isReturneeBaggage(peer.treatment)&&peer.beneficiary===r.beneficiary&&peer.origin===r.origin&&date(peer.importDate)&&evaluateReturnee(peer,draft.currency).status!=='not_applicable'&&(peer.importDate<=r.importDate?r.importDate<years(peer.importDate,5):peer.importDate<years(r.importDate,5))))rows[i]={...rows[i],status:'indeterminate',importVat:null,reason:'Mais de uma linha usa o mesmo direito de veículo em cinco anos;conferir o conjunto e histórico externo,sem dupla utilização.'};}
|
||||
const result:Result={revision:(b.result?.revision??0)+1,calculatedAt:new Date().toISOString(),sourceKey:key(draft,b),inputs:structuredClone(b.rows),rows};const next={...b,result,history:[...(b.history??[]),...(b.result?[b.result]:[])]};saveReturneeBook(draft,next);return next;
|
||||
}
|
||||
export function returneeReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=returneeBook(draft),result=b.result;if(!result)return[];return result.inputs.map((r,i)=>['IVA mensal',`P058 — ${r.period} — ${r.id}`,`${r.description};${result.rows[i].reason};IVA:${result.rows[i].importVat??'Indeterminado'};dedução:Não apurada`,`${result.rows[i].source};${returneeCurrent(draft,b)?'Resultado actual':'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${r.documentRef};${r.scope};${r.evidence};${r.historyRef};${r.extensionRef};${r.exceptionRef};${r.decisionRef};${r.normalVatRef}`]);}catch(e){return[['IVA mensal','P058 — recuperação',String(e),'Originais preservados']];}}
|
||||
export function returneeReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=returneeBook(draft),result=b.result;if(!result)return[];return result.inputs.map((r,i)=>['IVA mensal',`P058 — ${r.period} — ${r.id}`,`${r.description};${result.rows[i].reason};IVA:${result.rows[i].importVat??'Indeterminado'};dedução:Não apurada`,`${result.rows[i].source};${returneeCurrent(draft,b)?'Resultado actual':'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${r.documentRef};${r.scope};${r.evidence};${r.historyRef};${r.extensionRef};${r.exceptionRef};${r.decisionRef};${r.normalVatRef};${isReturneeBaggage(r.treatment)?`missão:${r.missionRef};tipo:${r.baggageKind};via:${r.baggageRoute};estadia:${r.stayStart} a ${r.stayEnd};chegada:${r.arrival};entrada:${r.importDate};termo previsto:${r.missionPlannedEnd??''};regresso antecipado:${r.earlyReturnRef??''};entrada antecipada:${r.earlyBaggageRef??''}`:''}`]);}catch(e){return[['IVA mensal','P058 — recuperação',String(e),'Originais preservados']];}}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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 IvaBaggageDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_baggage_recovers_without_vehicle_constraints_or_changes_to128_accepted_books():void
|
||||
{
|
||||
$target=DB::selectOne('select current_database() as name,inet_server_port() as port');$this->assertSame('simulator_fiscal_delta_20261021_test',$target->name);$this->assertSame(55441,(int)$target->port);
|
||||
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));
|
||||
$draft=json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-baggage.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']);$this->assertSame('opportunity:366',$recovered['context_opportunity']);$s=json_decode($recovered['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$b=$s['returneeImports']['opportunity:366'];$this->assertCount(11,$b['rows']);$this->assertCount(1,$b['history']);$this->assertSame($b['rows'],$b['result']['inputs']);
|
||||
foreach($b['result']['rows'] as $row){$this->assertSame('exempt',$row['status']);$this->assertSame('0.00',$row['importVat']);$this->assertNull($row['deduction']);$this->assertStringContainsString('57(2)c/d',$row['source']);}
|
||||
$frozen=json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-baggage-preserved.json')),true,flags:JSON_THROW_ON_ERROR);$prior=json_decode($frozen['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$this->assertSame(array_diff_key($prior,['returneeImports'=>true]),array_diff_key($s,['returneeImports'=>true]));foreach($prior['returneeImports'] as $key=>$value)$this->assertSame($value,$s['returneeImports'][$key]);
|
||||
$path=base_path('outputs/iva-l1-residuals-baggage-20261025');if(!is_dir($path))mkdir($path,0775,true);file_put_contents($path.'/recovered.json',json_encode($recovered,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import {baggageTreatments,baggageConditions} from '../../resources/js/lib/iva-returnee-baggage.ts';
|
||||
import {newReturnee,evaluateReturnee,recordReturnees,saveReturneeBook,returneeBook,returneeCurrent,returneeReportRows} from '../../resources/js/lib/iva-returnees.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync('tests/frontend/fixtures/iva-baggage-preserved.json','utf8')),prior=JSON.parse(frozen.modelo_a_snapshot),draft={...frozen,context_opportunity:'opportunity:366',project_title:'SYNTHETIC QA — Bagagem no regresso / Manica'};
|
||||
function row(treatment,id=treatment,patch={}){return{...newReturnee(id,'MZN'),treatment,description:'SYNTHETIC QA bagagem '+id,beneficiary:'SYNTHETIC QA viajante '+id,period:'2026-10',origin:'projected',scope:'SYNTHETIC QA bens/operação/período',evidence:'SYNTHETIC QA factos/pressupostos expressos',documentRef:'SYNTHETIC QA documentos disponíveis',missionRef:'SYNTHETIC QA referência de missão pública/estudo',importDate:'2026-10-10',arrival:'2026-10-10',stayStart:'2024-01-01',stayEnd:'2026-10-09',missionPlannedEnd:'2027-01-01',baggageKind:'personal',baggageRoute:'accompanied',earlyReturnRef:'SYNTHETIC QA regresso por serviço doEstado',conditions:{...Object.fromEntries(baggageConditions.map(([k])=>[k,'yes'])),earlyService:'yes'},...patch};}
|
||||
const rows=[];
|
||||
for(const treatment of baggageTreatments)for(const kind of ['personal','household']){const r=row(treatment.id,treatment.id+'-'+kind,{baggageKind:kind});rows.push(r);test(treatment.id+' / '+kind+' sem condições de veículo transplantadas',()=>{const result=evaluateReturnee(r,'MZN');assert.equal(result.status,'exempt');assert.equal(result.importVat,'0.00');assert.equal(result.deduction,null);assert.equal(r.conditions.national,undefined);assert.equal(r.previous,'');assert.equal(r.request,'');assert.equal(r.historyRef,'');assert.match(result.source,/57\(2\)c\/d/);assert.match(result.reason,/57\(4\).*não dispensa/);});}
|
||||
test('P058 bagagem não acompanhada61:180dias próprios,sem60+30deveículos e sem quota5anos',()=>{const r=row('student_baggage','unaccompanied',{arrival:'2026-04-13',stayEnd:'2026-04-12',baggageRoute:'unaccompanied'});assert.equal(evaluateReturnee(r,'MZN').status,'exempt');const late={...r,arrival:'2026-04-12',stayEnd:'2026-04-11'};assert.equal(evaluateReturnee(late,'MZN').status,'indeterminate');const advance={...r,arrival:'2026-10-20',stayEnd:'2026-10-09'};assert.equal(evaluateReturnee(advance,'MZN').status,'indeterminate');const authorised={...advance,id:'advance-authorised',earlyBaggageRef:'SYNTHETIC QA estância/âmbito61(2)',conditions:{...advance.conditions,advanceClearance:'yes'}};assert.equal(evaluateReturnee(authorised,'MZN').status,'exempt');assert.equal(evaluateReturnee({...authorised,earlyBaggageRef:''},'MZN').status,'indeterminate');rows.push(r,authorised);});
|
||||
test('P058 missão superior a um ano e regresso antecipado pelo Estado são percursos distintos',()=>{const r=row('civil_baggage');assert.equal(evaluateReturnee({...r,stayStart:'2025-10-09'},'MZN').status,'not_applicable');assert.equal(evaluateReturnee({...r,stayStart:'2025-10-08'},'MZN').status,'exempt');const early=row('early_state_baggage','early-short',{stayStart:'2026-02-01',missionPlannedEnd:'2028-02-01'});assert.equal(evaluateReturnee(early,'MZN').status,'exempt');assert.equal(evaluateReturnee({...early,conditions:{...early.conditions,earlyService:'no'}},'MZN').status,'not_applicable');for(const patch of [{missionPlannedEnd:'2027-02-01'},{missionPlannedEnd:'2026-09-01'},{earlyReturnRef:''},{conditions:{...early.conditions,earlyService:''}}])assert.notEqual(evaluateReturnee({...early,...patch},'MZN').status,'exempt');rows.push(early);});
|
||||
test('P058 bagagem desconhecida/comercial/fora57(3) não vira IVAzero;57(2)b permanece por corresponder',()=>{const r=rows[0];for(const patch of [{missionRef:''},{baggageKind:''},{baggageKind:'vehicle'},{baggageRoute:''},{currency:'USD'},{period:'2027-10'},{importDate:'2026-02-30'},{documentRef:''},{conditions:{...r.conditions,mission:''}}]){const v=evaluateReturnee({...r,...patch},'MZN');assert.equal(v.status,'indeterminate');assert.equal(v.importVat,null);}for(const[k]of baggageConditions)assert.equal(evaluateReturnee({...r,conditions:{...r.conditions,[k]:'no'}},'MZN').status,'not_applicable');assert.equal(evaluateReturnee({...r,treatment:'domicile_baggage'},'MZN').status,'indeterminate');});
|
||||
test('P058 bagagem não consome quota de veículo;linhas da mesma bagagem não são duplicação do direito de veículo',()=>{const first=rows[0],second={...first,id:'same-person-household',baggageKind:'household'};assert.deepEqual(recordReturnees(draft,{rows:[first,second]}).result.rows.map(r=>r.status),['exempt','exempt']);});
|
||||
test('P058 gravação/recuperação/histórico/relatório preservam128fechos e anteriores livrosP058/P104/ofertas',()=>{let book=recordReturnees(draft,{rows});assert.equal(returneeCurrent(draft,book),true);const before=structuredClone(book.result),changed={...book,rows:book.rows.map((r,i)=>i?r:{...r,scope:'SYNTHETIC QA âmbito revisto'})};assert.equal(returneeCurrent(draft,changed),false);assert.deepEqual(book.result,before);book=recordReturnees(draft,changed);draft.modelo_a_snapshot=saveReturneeBook(draft,book);assert.deepEqual(returneeBook(draft),book);assert.deepEqual(book.history[0],before);assert.equal(returneeBook({...draft,context_opportunity:'opportunity:999'}).rows.length,0);const next=JSON.parse(draft.modelo_a_snapshot);assert.deepEqual(Object.fromEntries(Object.entries(prior).filter(([k])=>k!=='returneeImports')),Object.fromEntries(Object.entries(next).filter(([k])=>k!=='returneeImports')));for(const[k,v]of Object.entries(prior.returneeImports??{}))assert.deepEqual(next.returneeImports[k],v);assert.match(returneeReportRows(draft)[0][3],/missão:SYNTHETIC QA/);assert.match(returneeReportRows(draft)[0][3],/versão2/);assert.throws(()=>saveReturneeBook(draft,{rows:[{...rows[0],missionRef:{invalid:true}}]}),/não recuperáveis/);if(process.env.IVA_BAGGAGE_FIXTURE)fs.writeFileSync('tests/frontend/fixtures/iva-baggage.json',JSON.stringify(draft));});
|
||||
@@ -2,7 +2,7 @@ import test from 'node:test';import assert from 'node:assert/strict';import fs f
|
||||
import {newReturnee,returneeConditions,returneeTreatments,evaluateReturnee,recordReturnees,saveReturneeBook,returneeBook,returneeCurrent,returneeReportRows} from '../../resources/js/lib/iva-returnees.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync('tests/frontend/fixtures/iva-returnees-preserved.json','utf8')),prior=JSON.parse(frozen.modelo_a_snapshot),draft={...frozen,context_opportunity:'opportunity:363',project_title:'SYNTHETIC QA — Regresso definitivo / Manica'};
|
||||
function row(treatment,id=treatment){return{...newReturnee(id,'MZN'),treatment,description:'SYNTHETIC QA veículo '+id,beneficiary:'SYNTHETIC QA beneficiário '+id,period:'2026-10',origin:'projected',scope:'SYNTHETIC QA veículo/operação/período',evidence:'SYNTHETIC QA pressupostos expressos',documentRef:'SYNTHETIC QA propriedade/residência/declaração uso',arrival:'2026-09-01',request:'2026-09-20',stayStart:'2024-09-01',stayEnd:'2026-08-31',ownershipAssessment:'2026-09-01',ownedSince:treatment==='owned'?'2025-01-01':'2026-08-01',importDate:'2026-10-10',previous:'none',historyRef:'SYNTHETIC QA histórico completo incluindo outros dossiers',decisionRef:'SYNTHETIC QA pressuposto de substituição63(3)b',normalVat:'160.123456',normalVatRef:'SYNTHETIC QA IVA normal informado',conditions:{...Object.fromEntries(returneeConditions.map(([k])=>[k,'yes'])),substitution:'yes'}};}
|
||||
const rows=returneeTreatments.map(t=>row(t.id));
|
||||
const rows=returneeTreatments.filter(t=>['owned','recent','separate'].includes(t.id)).map(t=>row(t.id));
|
||||
for(const r of rows)test('P058 veículo '+r.treatment+' sem dedução nem taxa presumida',()=>{const v=evaluateReturnee(r,'MZN');assert.equal(v.status,r.treatment==='owned'?'exempt':'reduced');assert.equal(v.importVat,{owned:'0.00',recent:'32.0246912',separate:'80.061728'}[r.treatment]);assert.equal(v.deduction,null);});
|
||||
test('P058 desconhecido,âmbito e condições recusadas conservam distinção',()=>{const r=rows[0];for(const patch of [{period:'2025-10'},{currency:'USD'},{importDate:'2026-02-30'},{description:''},{documentRef:''},{historyRef:''},{scope:''},{evidence:''},{previous:''},{conditions:{...r.conditions,national:''}}])assert.equal(evaluateReturnee({...r,...patch},'MZN').status,'indeterminate');for(const[k]of returneeConditions)assert.equal(evaluateReturnee({...r,conditions:{...r.conditions,[k]:'no'}},'MZN').status,'not_applicable');});
|
||||
test('P058 prazo60/90dias e excepções referenciadas distintas do regime mineiros',()=>{const r=rows[0];assert.equal(evaluateReturnee({...r,request:'2026-10-31'},'MZN').status,'exempt');assert.equal(evaluateReturnee({...r,request:'2026-11-01'},'MZN').status,'indeterminate');const extended={...r,request:'2026-11-30',extensionRef:'SYNTHETIC QA prazo concedido',conditions:{...r.conditions,extension:'yes'}};assert.equal(evaluateReturnee(extended,'MZN').status,'exempt');assert.equal(evaluateReturnee({...extended,request:'2026-12-01'},'MZN').status,'indeterminate');assert.equal(evaluateReturnee({...extended,extensionRef:''},'MZN').status,'indeterminate');const sep=rows[2];const exceptional={...sep,stayStart:'2026-02-01',exceptionRef:'SYNTHETIC QA Ministro/âmbito',conditions:{...sep.conditions,exception:'yes'}};assert.equal(evaluateReturnee(exceptional,'MZN').status,'reduced');assert.equal(evaluateReturnee({...exceptional,exceptionRef:''},'MZN').status,'indeterminate');assert.equal(evaluateReturnee({...sep,decisionRef:''},'MZN').status,'indeterminate');assert.equal(evaluateReturnee({...r,stayStart:'2025-08-31'},'MZN').status,'indeterminate');});
|
||||
|
||||
Reference in New Issue
Block a user