feat(iva): classify authorized class K investment equipment imports
This commit is contained in:
@@ -9,7 +9,7 @@ export function SimulationImportExemptions({draft,onChange}:{draft:Record<string
|
||||
const change=(id:string,patch:Partial<ImportExemptionRow>)=>save({...book,rows:book.rows.map(row=>row.id===id?{...row,...patch}:row)});
|
||||
return <details className="space-y-3 rounded border p-3">
|
||||
<summary>IVA na importação e serviços associados — enquadramentos disponíveis</summary>
|
||||
<p>Oportunidade: {String(draft.context_opportunity||'entrada geral')}. Indique apenas importações relevantes: reimportação no mesmo estado, ouro pelo Banco de Moçambique, objectos de arte, veículo de combate a incêndios, embarcação/avião elegível ou objecto da sua exploração, bens em regime aduaneiro com isenção total de direitos, importação sob controlo alfandegário em zona/depósito/loja franca nas condições próprias, ou bens de abastecimento a bordo de embarcação/avião de navegação internacional. Escolha o ramo para apresentar as condições aplicáveis. Cobertura2026/MZN; as compras internas e os seus resultados conservam-se.</p>
|
||||
<p>Oportunidade: {String(draft.context_opportunity||'entrada geral')}. Indique apenas importações relevantes: reimportação no mesmo estado, ouro pelo Banco de Moçambique, objectos de arte, veículo de combate a incêndios, embarcação/avião elegível ou objecto da sua exploração, bens em regime aduaneiro com isenção total de direitos, importação sob controlo alfandegário em zona/depósito/loja franca nas condições próprias, equipamento classe K para empreendimento autorizado, ou bens de abastecimento a bordo de embarcação/avião de navegação internacional. Escolha o ramo para apresentar as condições aplicáveis. Cobertura2026/MZN; as compras internas e os seus resultados conservam-se.</p>
|
||||
<p>O valor declarado/previsional é conservado, sem o fixar como base aduaneira. A classificação não preenche o campo11 de dedução nem os totais do ModeloA. Uma condição não cumprida afasta só esta isenção; o imposto normal fica por enquadrar. Nas projecções, as respostas são pressupostos, sem certificação aduaneira.</p>
|
||||
<p>Nos serviços associados, identifique a importação e a inclusão do valor na sua base, conforme o artigo16. O IVA do serviço é apresentado separadamente do IVA dos bens; o simulador não soma novamente esse valor à base.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
@@ -23,6 +23,11 @@ export function SimulationImportExemptions({draft,onChange}:{draft:Record<string
|
||||
{(['description','importer','declaredValue','goodsCode','originCountry','documentRef','documentDate','period','evidence'] as const).map((field,index)=>isImportService(row.treatment)&&['goodsCode','originCountry'].includes(field)?null:<label key={field}>{isImportService(row.treatment)&&field==='description'?'Serviço associado à importação':isImportService(row.treatment)&&field==='importer'?'Importador destinatário do serviço':isImportService(row.treatment)&&field==='declaredValue'?'Valor do serviço incluído na base (moeda indicada)':isImportService(row.treatment)&&field==='documentRef'?'Documento do serviço / referência da previsão':isImportService(row.treatment)&&field==='period'?'Mês do serviço':['Bens importados','Importador','Valor declarado / previsão (moeda indicada)','Código aduaneiro, se disponível','País de origem, se disponível','Documento aduaneiro / referência da previsão','Data do documento / prevista','Mês da importação','Fundamento factual das condições / pressupostos'][index]}<input className="block rounded border p-2" type={field==='documentDate'?'date':field==='period'?'month':'text'} value={row[field]} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>)}
|
||||
{isImportService(row.treatment)&&(['serviceImportRef','serviceBaseRef','serviceProvider'] as const).map((field,index)=><label key={field}>{['Importação de bens associada / documento ou previsão','Referência da base tributável que inclui o valor do serviço','Prestador do serviço, se disponível'][index]}<input className="block rounded border p-2" value={row[field]??''} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>)}
|
||||
{row.treatment==='accessory_service_first_destination'&&<label>Primeiro destino e documento de transporte ou primeira ruptura de carga<input className="block rounded border p-2" value={row.serviceFirstDestination??''} onChange={e=>change(row.id,{serviceFirstDestination:e.target.value})}/></label>}
|
||||
{row.treatment==='authorized_investment_equipment'&&<>
|
||||
<p className="md:col-span-2">A classe K e a autorização são condições distintas. O portal conserva a referência e o âmbito aplicável; apresentar um pedido não equivale a obter autorização. Numa projecção, identifique expressamente os pressupostos.</p>
|
||||
<label>Autorização do empreendimento / pressuposto expresso<input className="block rounded border p-2" value={row.investmentAuthorizationRef??''} onChange={e=>change(row.id,{investmentAuthorizationRef:e.target.value})}/></label>
|
||||
<label>Âmbito aplicável: bens, operação, período e condições<input className="block rounded border p-2" value={row.investmentScope??''} onChange={e=>change(row.id,{investmentScope:e.target.value})}/></label>
|
||||
</>}
|
||||
<label>Moeda de origem<input className="block rounded border p-2" value={row.currency} onChange={e=>change(row.id,{currency:e.target.value})}/></label>
|
||||
</div>
|
||||
{rule?<><p>{importRuleSource(row.treatment)}</p><fieldset><legend>Condições de{rule.article}; respostas conservadas ao mudar de ramo</legend>{rule.conditions.map(([key,label])=><label className="block" key={key}>{label}<select className="block rounded border p-2" value={row.conditions[key]??''} onChange={e=>change(row.id,{conditions:{...row.conditions,[key]:e.target.value}})}><option value="">Por indicar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>)}</fieldset></>:<p>Escolha o ramo para apresentar apenas as condições aplicáveis; as respostas anteriores mantêm-se.</p>}
|
||||
|
||||
@@ -15,8 +15,11 @@ export const importSpecialRegimeSource = 'CIVA republicado pela Lei13/2016, BR15
|
||||
export const importSupplyVersion = 'import-onboard-supplies/1';
|
||||
export const importSupplySource = 'CIVA republicado pela Lei13/2016,BR156/10.º suplemento,12(1)(g),12(3)(a)(i–iv)/(b),PDF11–12/p.1426(269–270),e definição13(3). Lei22/2022PDF2/p.2252(26) e Lei10/2025PDF13/p.2180(13) mantêm g/3 por remissão. Cobertura2026/MZN; abastecimentos importados na janela própria,consumidos ou a bordo de embarcação de navegação internacional marítima/fluvial/lacustre ou avião de navegação aérea internacional;exclusões próprias de provisões e depósitos normais. Sem exigir companhia principalmente internacional,sem dedução/campo11,direitos ou base aduaneira.';
|
||||
export const importServiceVersion = 'import-included-services/1';
|
||||
export const importInvestmentVersion = 'import-investment-equipment/1';
|
||||
export const importInvestmentSource = 'CIVA republicado Lei13/2016,12(1)(k),PDF11/p.1426(269): importações de bens de equipamento classificados na classe K da Pauta Aduaneira destinados a investimentos em empreendimentos autorizados ao abrigo da Lei de Investimentos e respectivo Regulamento. Lei22/2022,PDF2/p.2252(26),e Lei10/2025,PDF13/p.2180(13),mantêm k por remissão. Cobertura2026/MZN; classificação do IVA desta importação nas condições próprias,sem conceder autorização de investimento,classificar automaticamente mercadorias,estender o âmbito da autorização,calcular direitos/base aduaneira ou dedução/campo11.';
|
||||
export const importServiceSource = 'CIVA republicado Lei13/2016,12(1)(e),PDF11/p.1426(269): prestações de serviços cujo valor esteja incluído na base tributável da importação de bens,conforme16. Art16(1)/16(1)(b)/16(2),PDF14/p.1426(272): valor aduaneiro e despesas acessórias até primeiro destino,do documento de transporte ou,na falta,primeira ruptura de carga no interior do País. Lei22/2022PDF1–2 e Lei10/2025PDF12–13 mantêm e por remissão e não alteram16. Cobertura2026/MZN; IVA do serviço,sem calcular base aduaneira,direitos,IVA dos bens,dedução/campo11 ou rectificação automática de factura.';
|
||||
export const importExemptionRules = [
|
||||
{id:'authorized_investment_equipment',branch:'P056',label:'Equipamento classe K para empreendimento autorizado',article:'12(1)(k)',conditions:[['investmentEquipment','Os bens importados são bens de equipamento; não basta uma compra de materiais, consumíveis ou serviços para um investimento'],['investmentClassK','Os bens de equipamento identificados estão classificados na classe K da Pauta Aduaneira; uma descrição genérica ou código isolado não é classificação automática pelo portal'],['investmentAuthorized','O empreendimento está autorizado ao abrigo da Lei de Investimentos e respectivo Regulamento; a referência identifica a autorização aplicável ou o pressuposto expresso da projecção, não apenas um pedido apresentado'],['investmentDestination','Estes bens destinam-se ao investimento no empreendimento autorizado identificado; a operação, período e condições correspondem ao âmbito aplicável da autorização, sem o estender automaticamente a outros bens ou operações']]},
|
||||
{id:'service_in_customs_value',branch:'P050',label:"Serviço já compreendido no valor aduaneiro",article:'12(1)(e)',conditions:[["serviceImportLinked", "A prestação está ligada à importação de bens identificada na referência própria; não basta tratar-se de serviço prestado a um importador"], ["serviceFullValueIncluded", "O valor integral do serviço desta linha está incluído na base tributável da importação identificada, nos termos do artigo16; documento ou pressuposto discrimina a inclusão, não apenas a intenção de a incluir"], ["serviceInCustomsValue", "O valor deste serviço já está compreendido no valor aduaneiro que integra a base do artigo16(1); não é uma nova parcela a somar pelo simulador"]]},
|
||||
{id:'accessory_service_first_destination',branch:'P050',label:"Serviço acessório até ao primeiro destino",article:'12(1)(e)',conditions:[["serviceImportLinked", "A prestação está ligada à importação de bens identificada na referência própria; não basta tratar-se de serviço prestado a um importador"], ["serviceFullValueIncluded", "O valor integral do serviço desta linha está incluído na base tributável da importação identificada, nos termos do artigo16; documento ou pressuposto discrimina a inclusão, não apenas a intenção de a incluir"], ["serviceAccessoryCost", "O serviço é uma despesa acessória da importação (embalagem, transporte, seguro ou outro encargo) adicionada à base por ainda não estar compreendida no valor aduaneiro, conforme16(1)(b)"], ["serviceToFirstDestination", "A despesa verifica-se até ao primeiro lugar de destino no interior do País: o constante do documento de transporte ou, na sua falta, o local da primeira ruptura de carga no interior do País, conforme16(2); destino e percurso identificados no fundamento"]]},
|
||||
|
||||
@@ -48,15 +51,15 @@ export const importExemptionRules = [
|
||||
{id:'customs_temporary_import',branch:'P048',label:'Importação temporária de bens com isenção total de direitos',article:'12(1)(c)',conditions:[['customsTemporary','Os bens identificados são importados sob regime de importação temporária; regime e operação constam do documento ou pressuposto expresso, não apenas intenção de utilização temporária'],['fullCustomsExemption','Esta importação beneficia de isenção total de direitos aduaneiros, identificada no fundamento factual; redução parcial ou mera suspensão não comprovam esta condição']]},
|
||||
{id:'customs_drawback_import',branch:'P048',label:'Importação de bens em draubaque com isenção total de direitos',article:'12(1)(c)',conditions:[['customsDrawback','Os bens identificados são importados sob regime de draubaque; regime e operação constam do documento ou pressuposto expresso, não qualquer promessa de restituição futura'],['fullCustomsExemption','Esta importação beneficia de isenção total de direitos aduaneiros, identificada no fundamento factual; redução parcial ou mera suspensão não comprovam esta condição']]},
|
||||
] as const;
|
||||
type Condition = 'serviceImportLinked'|'serviceFullValueIncluded'|'serviceInCustomsValue'|'serviceAccessoryCost'|'serviceToFirstDestination'|'supplyCargoAccessories'|'supplyFuel'|'supplyImportWindow'|'supplyInternationalAircraft'|'supplyInternationalShip'|'supplyNoCoastalFishing'|'supplyNoDismantleOtherUse'|'supplyNoFloatingHospitality'|'supplyNoRecreation'|'supplyNormalTanks'|'supplyOnboard'|'supplyProvisions'|'supplyTechnicalProducts'|'specialEconomicZone'|'freeZone'|'freeWarehouse'|'generalFreeWarehouse'|'customsWarehouse'|'dutyFreeShop'|'specialCustomsControl'|'specialApplicableProvisions'|'specialRegimePermanence'|'specialNoFinalUseConsumption'|'sameExporter'|'sameState'|'customsExempt'|'gold'|'centralBank'|'artObjects'|'eligibleArtImporter'|'fireVehicle'|'fireBrigade'|'exclusiveOwnUse'|'eligibleShip'|'importedShip'|'shipObjectLink'|'eligibleAircraft'|'importedAircraft'|'aircraftObjectLink'|'customsTransit'|'customsTemporary'|'customsDrawback'|'fullCustomsExemption';
|
||||
type Condition = 'investmentEquipment'|'investmentClassK'|'investmentAuthorized'|'investmentDestination'|'serviceImportLinked'|'serviceFullValueIncluded'|'serviceInCustomsValue'|'serviceAccessoryCost'|'serviceToFirstDestination'|'supplyCargoAccessories'|'supplyFuel'|'supplyImportWindow'|'supplyInternationalAircraft'|'supplyInternationalShip'|'supplyNoCoastalFishing'|'supplyNoDismantleOtherUse'|'supplyNoFloatingHospitality'|'supplyNoRecreation'|'supplyNormalTanks'|'supplyOnboard'|'supplyProvisions'|'supplyTechnicalProducts'|'specialEconomicZone'|'freeZone'|'freeWarehouse'|'generalFreeWarehouse'|'customsWarehouse'|'dutyFreeShop'|'specialCustomsControl'|'specialApplicableProvisions'|'specialRegimePermanence'|'specialNoFinalUseConsumption'|'sameExporter'|'sameState'|'customsExempt'|'gold'|'centralBank'|'artObjects'|'eligibleArtImporter'|'fireVehicle'|'fireBrigade'|'exclusiveOwnUse'|'eligibleShip'|'importedShip'|'shipObjectLink'|'eligibleAircraft'|'importedAircraft'|'aircraftObjectLink'|'customsTransit'|'customsTemporary'|'customsDrawback'|'fullCustomsExemption';
|
||||
function beneficiaryImport(id:string):boolean {return id==='author_art_import'||id==='fire_brigade_vehicle';}
|
||||
function assetImport(id:string):boolean {return ['eligible_ship_import','ship_operation_object_import','eligible_aircraft_import','aircraft_operation_object_import'].includes(id);}
|
||||
function customsImport(id:string):boolean {return ['customs_transit_import','customs_temporary_import','customs_drawback_import'].includes(id);}
|
||||
function specialRegimeImport(id:string):boolean {return ["special_economic_zone_import", "free_zone_import", "free_warehouse_import", "general_free_warehouse_import", "customs_warehouse_import", "duty_free_shop_import"].includes(id);}
|
||||
function supplyImport(id:string):boolean {return ["ship_provisions_import", "ship_fuel_import", "ship_technical_products_import", "ship_cargo_accessories_import", "aircraft_provisions_import", "aircraft_fuel_import", "aircraft_technical_products_import", "aircraft_cargo_accessories_import"].includes(id);}
|
||||
export function isImportService(id:string):boolean {return ['service_in_customs_value','accessory_service_first_destination'].includes(id);}
|
||||
export function importRuleSource(id:string):string {return isImportService(id)?importServiceSource:supplyImport(id)?importSupplySource:specialRegimeImport(id)?importSpecialRegimeSource:customsImport(id)?importCustomsSource:assetImport(id)?importAssetSource:beneficiaryImport(id)?importBeneficiarySource:importExemptionSource;}
|
||||
export type ImportExemptionRow = {id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';currency:string;declaredValue:string;goodsCode:string;originCountry:string;importer:string;documentRef:string;documentDate:string;evidence:string;conditions:Partial<Record<Condition,Choice>>;serviceImportRef?:string;serviceBaseRef?:string;serviceFirstDestination?:string;serviceProvider?:string};
|
||||
export function importRuleSource(id:string):string {return id==='authorized_investment_equipment'?importInvestmentSource:isImportService(id)?importServiceSource:supplyImport(id)?importSupplySource:specialRegimeImport(id)?importSpecialRegimeSource:customsImport(id)?importCustomsSource:assetImport(id)?importAssetSource:beneficiaryImport(id)?importBeneficiarySource:importExemptionSource;}
|
||||
export type ImportExemptionRow = {id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';currency:string;declaredValue:string;goodsCode:string;originCountry:string;importer:string;documentRef:string;documentDate:string;evidence:string;conditions:Partial<Record<Condition,Choice>>;serviceImportRef?:string;serviceBaseRef?:string;serviceFirstDestination?:string;serviceProvider?:string;investmentAuthorizationRef?:string;investmentScope?:string};
|
||||
type ImportResult = {id:string;status:'exempt'|'not_applicable'|'indeterminate';importVat:string|null;serviceVat?:string|null;reason:string};
|
||||
export type ImportExemptionCalculation = {version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:ImportExemptionRow[];rows:ImportResult[]};
|
||||
export type ImportExemptionBook = {rows:ImportExemptionRow[];result?:ImportExemptionCalculation;history?:ImportExemptionCalculation[]};
|
||||
@@ -66,6 +69,7 @@ function store(draft:Record<string,unknown>):Store {
|
||||
if(value?.version!==1||!value.books||typeof value.books!=='object'||Array.isArray(value.books))throw new Error('Dados fiscais não recuperáveis; originais preservados, sem substituição.');
|
||||
if(value.importExemptions!==undefined&&(!value.importExemptions||typeof value.importExemptions!=='object'||Array.isArray(value.importExemptions)))throw new Error('Importações guardadas não recuperáveis; originais preservados.');
|
||||
for(const book of Object.values(value.importExemptions??{})) {
|
||||
if(Array.isArray(book?.rows)&&book.rows.some(row=>row&&['investmentAuthorizationRef','investmentScope'].some(key=>row[key as keyof ImportExemptionRow]!==undefined&&typeof row[key as keyof ImportExemptionRow]!=='string')))throw new Error('Referências do investimento não recuperáveis; originais preservados.');
|
||||
if(Array.isArray(book?.rows)&&book.rows.some(row=>row&&['serviceImportRef','serviceBaseRef','serviceFirstDestination','serviceProvider'].some(key=>row[key as keyof ImportExemptionRow]!==undefined&&typeof row[key as keyof ImportExemptionRow]!=='string')))throw new Error('Referências do serviço associado à importação não recuperáveis; originais preservados.');
|
||||
if(!book||!Array.isArray(book.rows)||(book.history!==undefined&&!Array.isArray(book.history))||book.rows.some(row=>!row||['id','treatment','description','period','origin','currency','declaredValue','goodsCode','originCountry','importer','documentRef','documentDate','evidence'].some(k=>typeof row[k as keyof ImportExemptionRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de importação não recuperáveis; sem substituição por lista vazia.');
|
||||
}
|
||||
@@ -89,6 +93,7 @@ export function evaluateImportExemption(row:ImportExemptionRow,currency:unknown)
|
||||
if(failed.length)return stop('Não se aplica esta isenção: '+failed.map(([,label])=>label).join('; ')+'. O IVA fora deste fundamento permanece por enquadrar, sem taxa presumida.','not_applicable');
|
||||
const missing=rule.conditions.filter(([key])=>row.conditions[key]!=='yes');
|
||||
if(missing.length)return stop('Condições por indicar: '+missing.map(([,label])=>label).join('; ')+'. Desconhecido não significa não aplicável nem IVA zero.');
|
||||
if(row.treatment==='authorized_investment_equipment'&&(!row.investmentAuthorizationRef?.trim()||!row.investmentScope?.trim()))return stop('Identifique a autorização aplicável ao empreendimento e o seu âmbito nesta importação, ou os pressupostos expressos da projecção. Um pedido em preparação ou apresentado não equivale a autorização.');
|
||||
if(isImportService(row.treatment)&&(!row.serviceImportRef?.trim()||!row.serviceBaseRef?.trim()||!row.declaredValue.trim()||(row.treatment==='accessory_service_first_destination'&&!row.serviceFirstDestination?.trim())))return stop('Identifique a importação de bens, a referência da base que inclui o valor do serviço e o montante desta linha; no serviço acessório, indique o primeiro destino e o fundamento documental ou pressuposto.');
|
||||
if(!row.description.trim()||!row.importer.trim()||!row.documentRef.trim()||!row.evidence.trim())return stop(isImportService(row.treatment)?'Identifique o serviço, importador destinatário, documento/previsão e fundamento factual da inclusão; sem exigir segunda validação da fonte oficial.':'Identifique bens, importador, documento aduaneiro ou previsão e o fundamento factual das condições; não é exigida segunda validação da fonte oficial.');
|
||||
if(!validDate(row.documentDate))return stop('Data do documento/previsão em falta ou inválida; o período da importação é indicado separadamente.');
|
||||
@@ -98,7 +103,7 @@ export function evaluateImportExemption(row:ImportExemptionRow,currency:unknown)
|
||||
}
|
||||
function key(draft:Record<string,unknown>,book:ImportExemptionBook):string {
|
||||
const base={version:importExemptionVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows};
|
||||
return JSON.stringify({...base,...(book.rows.some(row=>beneficiaryImport(row.treatment))?{beneficiaryVersion:importBeneficiaryVersion}:{}),...(book.rows.some(row=>assetImport(row.treatment))?{assetVersion:importAssetVersion}:{}),...(book.rows.some(row=>customsImport(row.treatment))?{customsVersion:importCustomsVersion}:{}),...(book.rows.some(row=>specialRegimeImport(row.treatment))?{specialRegimeVersion:importSpecialRegimeVersion}:{}),...(book.rows.some(row=>supplyImport(row.treatment))?{supplyVersion:importSupplyVersion}:{}),...(book.rows.some(row=>isImportService(row.treatment))?{serviceVersion:importServiceVersion}:{})});
|
||||
return JSON.stringify({...base,...(book.rows.some(row=>row.treatment==='authorized_investment_equipment')?{investmentVersion:importInvestmentVersion}:{}),...(book.rows.some(row=>beneficiaryImport(row.treatment))?{beneficiaryVersion:importBeneficiaryVersion}:{}),...(book.rows.some(row=>assetImport(row.treatment))?{assetVersion:importAssetVersion}:{}),...(book.rows.some(row=>customsImport(row.treatment))?{customsVersion:importCustomsVersion}:{}),...(book.rows.some(row=>specialRegimeImport(row.treatment))?{specialRegimeVersion:importSpecialRegimeVersion}:{}),...(book.rows.some(row=>supplyImport(row.treatment))?{supplyVersion:importSupplyVersion}:{}),...(book.rows.some(row=>isImportService(row.treatment))?{serviceVersion:importServiceVersion}:{})});
|
||||
}
|
||||
export function importExemptionCurrent(draft:Record<string,unknown>,book:ImportExemptionBook):boolean {return !!book.result&&book.result.sourceKey===key(draft,book);}
|
||||
export function recordImportExemptions(draft:Record<string,unknown>,book:ImportExemptionBook):ImportExemptionBook {
|
||||
@@ -110,6 +115,6 @@ export function importExemptionReportRows(draft:Record<string,unknown>):Array<[s
|
||||
try {
|
||||
const book=importExemptionBook(draft),result=book.result;if(!result)return [];
|
||||
const context=`${result.opportunity}; ${result.location}; ${importExemptionCurrent(draft,book)?'Resultado actual':'Entradas alteradas; resultado histórico conservado'}; versão${result.revision}; ${result.calculatedAt}; ${result.source}`;
|
||||
return result.inputs.map((row,index)=>['IVA mensal',`${isImportService(row.treatment)?'Serviço associado à importação':'Importação'} ${importExemptionRules.find(r=>r.id===row.treatment)?.branch??row.treatment} — ${row.period} — ${row.id}`,`${row.description}; ${isImportService(row.treatment)?'IVA do serviço':'IVA na importação'}: ${(isImportService(row.treatment)?result.rows[index].serviceVat:result.rows[index].importVat)??'Indeterminado'} ${row.currency}; ${result.rows[index].reason}`,`${context}; ${row.origin==='projected'?'Projecção':'Dados efectivos declarados'}; importador ${row.importer}; valor declarado/previsional ${row.declaredValue||'Não informado'} ${row.currency}, sem fixar base aduaneira; ${isImportService(row.treatment)?`importação associada ${row.serviceImportRef||'por indicar'}; base com inclusão do serviço ${row.serviceBaseRef||'por indicar'}; prestador ${row.serviceProvider||'por indicar'}${row.treatment==='accessory_service_first_destination'?`; primeiro destino ${row.serviceFirstDestination||'por indicar'}`:''}`:`código ${row.goodsCode||'não indicado'}; origem ${row.originCountry||'não indicada'}`}; documento ${row.documentRef},${row.documentDate}; condições ${(importExemptionRules.find(rule=>rule.id===row.treatment)?.conditions??[]).map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')}; fundamento factual ${row.evidence}; campo11 e totaisModeloA não preenchidos por esta classificação.`]);
|
||||
return result.inputs.map((row,index)=>['IVA mensal',`${isImportService(row.treatment)?'Serviço associado à importação':'Importação'} ${importExemptionRules.find(r=>r.id===row.treatment)?.branch??row.treatment} — ${row.period} — ${row.id}`,`${row.description}; ${isImportService(row.treatment)?'IVA do serviço':'IVA na importação'}: ${(isImportService(row.treatment)?result.rows[index].serviceVat:result.rows[index].importVat)??'Indeterminado'} ${row.currency}; ${result.rows[index].reason}`,`${context}; ${row.origin==='projected'?'Projecção':'Dados efectivos declarados'}; importador ${row.importer}; valor declarado/previsional ${row.declaredValue||'Não informado'} ${row.currency}, sem fixar base aduaneira; ${isImportService(row.treatment)?`importação associada ${row.serviceImportRef||'por indicar'}; base com inclusão do serviço ${row.serviceBaseRef||'por indicar'}; prestador ${row.serviceProvider||'por indicar'}${row.treatment==='accessory_service_first_destination'?`; primeiro destino ${row.serviceFirstDestination||'por indicar'}`:''}`:`código ${row.goodsCode||'não indicado'}; origem ${row.originCountry||'não indicada'}`}${row.treatment==='authorized_investment_equipment'?`; autorização/pressuposto ${row.investmentAuthorizationRef||'por indicar'}; âmbito aplicável ${row.investmentScope||'por indicar'}`:''}; documento ${row.documentRef},${row.documentDate}; condições ${(importExemptionRules.find(rule=>rule.id===row.treatment)?.conditions??[]).map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')}; fundamento factual ${row.evidence}; campo11 e totaisModeloA não preenchidos por esta classificação.`]);
|
||||
}catch {return [['IVA mensal','Importações','Dados não recuperáveis','Originais preservados; sem substituir por zero.']];}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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 IvaImportInvestmentDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_investment_scope_recovers_privately_without_changing_delivered_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-g6g-imports.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:337', $recovered['context_opportunity']);
|
||||
$this->assertSame('Manica', $recovered['selection_labels']['province_id']);
|
||||
$store = json_decode($recovered['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$book = $store['importExemptions']['opportunity:337'];
|
||||
$this->assertCount(1, $book['rows']);
|
||||
$this->assertSame('authorized_investment_equipment', $book['rows'][0]['treatment']);
|
||||
$this->assertSame('0.00', $book['result']['rows'][0]['importVat']);
|
||||
$this->assertSame('exempt', $book['result']['rows'][0]['status']);
|
||||
$this->assertSame('2026-12', $book['rows'][0]['period']);
|
||||
$this->assertSame('5000', $book['rows'][0]['declaredValue']);
|
||||
$this->assertSame('SYNTHETIC QA autorização A, pressuposto expresso', $book['rows'][0]['investmentAuthorizationRef']);
|
||||
$this->assertStringContainsString('não alargar', $book['rows'][0]['investmentScope']);
|
||||
$this->assertStringContainsString('12(1)(k)', $book['result']['source']);
|
||||
$frozen = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-g6g-preserved-inputs.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$prior = json_decode($frozen['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$this->assertSame($prior['salesOperations'], $store['salesOperations']);
|
||||
$this->assertSame($prior['books'], $store['books']);
|
||||
$this->assertSame($prior['importExemptions'], array_diff_key($store['importExemptions'], ['opportunity:337' => true]));
|
||||
$this->assertSame($prior['offerPreparations'], $store['offerPreparations']);
|
||||
$this->assertCount(2, $book['history']);
|
||||
$this->assertNull($book['history'][0]['rows'][0]['importVat']);
|
||||
$this->assertSame('0.00', $store['importExemptions']['opportunity:336']['result']['rows'][0]['serviceVat']);
|
||||
$this->assertNull($store['importExemptions']['opportunity:336']['result']['rows'][0]['importVat']);
|
||||
$this->assertNull($store['books']['opportunity:329']['result']['periods'][0]['fields']['11']);
|
||||
file_put_contents(base_path('outputs/iva-l1-g6g-investment-20261024/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,42 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import {newImportExemption,evaluateImportExemption,recordImportExemptions,saveImportExemptionBook,importExemptionBook,importExemptionCurrent,importExemptionRules,importInvestmentSource} from '../../resources/js/lib/iva-import-exemptions.ts';
|
||||
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g6g-preserved-inputs.json',import.meta.url),'utf8'));
|
||||
const rule=importExemptionRules.find(r=>r.branch==='P056');
|
||||
const row=()=>({...newImportExemption('g6g-equipment','MZN'),treatment:rule.id,description:'SYNTHETIC QA equipamento industrial classe K',period:'2026-12',declaredValue:'5000',goodsCode:'SYNTHETIC QA código não determina classe',importer:'SYNTHETIC QA empreendimento',documentRef:'SYNTHETIC QA G6G importação',documentDate:'2026-11-14',investmentAuthorizationRef:'SYNTHETIC QA autorização A, pressuposto expresso',investmentScope:'SYNTHETIC QA equipamento industrial desta operação,2026-12,condições A; não alargar a outros bens',evidence:'SYNTHETIC QA classificação K e destino ao empreendimento autorizado; projecção, não documento real',conditions:Object.fromEntries(rule.conditions.map(([key])=>[key,'yes']))});
|
||||
const draft=()=>({...structuredClone(frozen),context_opportunity:'opportunity:337',project_title:'SYNTHETIC QA — G6G / Equipamento de investimento'});
|
||||
|
||||
test('P056 needs equipment,class K,authorization and own destination; sector/code/request alone cannot yield exemption',()=>{
|
||||
assert.equal(rule.article,'12(1)(k)');assert.match(importInvestmentSource,/Lei10\/2025/);
|
||||
for(const origin of ['projected','effective'])assert.equal(evaluateImportExemption({...row(),origin},'MZN').importVat,'0.00');
|
||||
for(const [key] of rule.conditions)for(const value of ['','unknown','no']){
|
||||
const r=evaluateImportExemption({...row(),conditions:{...row().conditions,[key]:value}},'MZN');assert.equal(r.status,value==='no'?'not_applicable':'indeterminate');assert.equal(r.importVat,null);assert.equal(r.serviceVat,undefined);
|
||||
}
|
||||
for(const patch of [{investmentAuthorizationRef:''},{investmentScope:''},{documentRef:''},{evidence:''},{documentDate:'2026-02-30'},{period:'2025-12'},{currency:'USD'}])assert.equal(evaluateImportExemption({...row(),...patch},'MZN').importVat,null);
|
||||
const presented={...row(),investmentAuthorizationRef:'Pedido apresentado; ainda sem autorização',conditions:{...row().conditions,investmentAuthorized:'no'}};assert.equal(evaluateImportExemption(presented,'MZN').status,'not_applicable');
|
||||
assert.equal(evaluateImportExemption({...row(),conditions:{}},'MZN').status,'indeterminate');
|
||||
});
|
||||
|
||||
test('P056 preserves all delivered books,services,partial offer states and history; own edits/context invalidate only current result',()=>{
|
||||
const d=draft(),prior=JSON.parse(d.modelo_a_snapshot);
|
||||
let book=recordImportExemptions(d,{rows:[{...row(),conditions:{}}]});book=recordImportExemptions(d,{...book,rows:[{...row(),conditions:{...row().conditions,investmentAuthorized:'no'}}]});book=recordImportExemptions(d,{...book,rows:[row()]});
|
||||
assert.equal(book.history.length,2);assert.ok(book.history.every(h=>h.rows[0].importVat===null));assert.ok(importExemptionCurrent(d,book));assert.match(book.result.sourceKey,/import-investment-equipment\/1/);
|
||||
for(const patch of [{investmentScope:'another operation'},{investmentAuthorizationRef:'different authorization'},{treatment:'fire_brigade_vehicle'}])assert.ok(!importExemptionCurrent(d,{...book,rows:[{...row(),...patch}]}));
|
||||
assert.ok(!importExemptionCurrent({...d,context_opportunity:'opportunity:338'},book));
|
||||
d.modelo_a_snapshot=saveImportExemptionBook(d,book);assert.deepEqual(importExemptionBook(JSON.parse(JSON.stringify(d))),book);
|
||||
const next=JSON.parse(d.modelo_a_snapshot);assert.deepEqual({...next,importExemptions:prior.importExemptions},prior);for(const [scope,previous] of Object.entries(prior.importExemptions)){assert.deepEqual(next.importExemptions[scope],previous);assert.ok(importExemptionCurrent({...d,context_opportunity:scope},previous));}
|
||||
assert.equal(next.importExemptions['opportunity:336'].result.rows[0].serviceVat,'0.00');assert.equal(next.importExemptions['opportunity:336'].result.rows[0].importVat,null);
|
||||
assert.equal(next.books['opportunity:329'].result.periods[0].fields['11'],null);
|
||||
const report=JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance']),version:'QA G6G'}));for(const token of ['P056','12(1)(k)','5000','opportunity:337','Manica','2026-12','autorização A','não alargar','campo11'])assert.ok(report.includes(token),token);
|
||||
if(process.env.IVA_G6G_CALCULATED)fs.writeFileSync(process.env.IVA_G6G_CALCULATED,JSON.stringify(d));
|
||||
});
|
||||
|
||||
test('investment references survive treatment switches;malformed writes preserve originals and do not require new fields on delivered rows',()=>{
|
||||
const d=draft(),original=d.modelo_a_snapshot;
|
||||
for(const field of ['investmentAuthorizationRef','investmentScope'])assert.throws(()=>saveImportExemptionBook(d,{rows:[{...row(),[field]:42}]}),/Referências do investimento/);
|
||||
assert.equal(d.modelo_a_snapshot,original);
|
||||
const changed={...row(),treatment:'central_bank_gold',conditions:{...row().conditions,gold:'yes',centralBank:'yes'}};d.modelo_a_snapshot=saveImportExemptionBook(d,{rows:[changed]});assert.equal(importExemptionBook(d).rows[0].investmentScope,row().investmentScope);assert.equal(evaluateImportExemption(changed,'MZN').importVat,'0.00');
|
||||
assert.ok(!importExemptionCurrent({...d,context_opportunity:'opportunity:338'},{rows:[row()]}));
|
||||
});
|
||||
Reference in New Issue
Block a user