101 lines
18 KiB
TypeScript
101 lines
18 KiB
TypeScript
import {FiscalCalculationGaps} from './fiscal-calculation-gaps';
|
||
import {FiscalL3Preparation} from './fiscal-l3-preparation';
|
||
import { useState } from 'react';
|
||
import {AcquisitionClosure} from './acquisition-closure';
|
||
import {AcquisitionSubstitution} from './acquisition-substitution';
|
||
import {AcquisitionRefund} from './acquisition-refund';
|
||
import {AcquisitionBeverageRestitution} from './acquisition-beverage-restitution';
|
||
import {AcquisitionRegularizations} from './acquisition-regularizations';
|
||
import { AcquisitionOperationLink } from './acquisition-operation-link';
|
||
import { AcquisitionImportsExclusions } from './acquisition-imports-exclusions';
|
||
import { AcquisitionAllocationEditor } from './acquisition-allocation';
|
||
import { acquisitionRightVersion } from '@/lib/iva-acquisition-right';
|
||
import { acquisitionBook, acquisitionCurrent, acquisitionExcludedFields, acquisitionPlanReferences, acquisitionReportRows, recordAcquisitions, newAcquisition, reuseAcquisitions, saveAcquisitionBook, type Acquisition, type AcquisitionBook } from '@/lib/iva-acquisitions';
|
||
import { acquisitionTreatmentIssue, continueWithoutFormerExploration, formerExplorationSource, type AcquisitionTreatment } from '@/lib/iva-acquisition-continuity';
|
||
|
||
export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}) {
|
||
const [error,setError]=useState('');
|
||
const [manualPeriod,setManualPeriod]=useState('');
|
||
let book:AcquisitionBook;
|
||
try {book=acquisitionBook(draft);} catch(cause) {return <p role="alert">{String(cause)}</p>;}
|
||
const save=(next:AcquisitionBook)=>{try {onChange('modelo_a_snapshot',saveAcquisitionBook(draft,next));setError('');}catch(cause){setError(String(cause));}};
|
||
const change=(id:string,patch:Partial<Acquisition>)=>save({...book,...('deductionLink' in patch?{rightVersion:acquisitionRightVersion}:{}),rows:book.rows.map(row=>row.id===id?{...row,...patch}:row)});
|
||
const run=(action:()=>void)=>{try{action();setError('');}catch(cause){setError(cause instanceof Error?cause.message:String(cause));}};
|
||
let references:ReturnType<typeof acquisitionPlanReferences>=[];
|
||
try {references=acquisitionPlanReferences(draft);}catch { /* The action reports the concrete currency/source error without replacing saved values. */ }
|
||
const labels:Record<string,string>={'03':'Base à taxa reduzida','04':'IVA à taxa reduzida','05':'Isentas com direito à dedução','06':'Operações art.18(1)(b)','07':'Isentas sem direito','11':'Importações','12':'Regularizações a favor do sujeito passivo','13':'Regularizações a favor do Estado'};
|
||
const select=(row:Acquisition,key:keyof Acquisition,label:string,options:Array<[string,string]>)=><label className="block" key={key}>{label}<select className="block rounded border p-2" value={String(row[key]??'')} onChange={e=>change(row.id,{[key]:e.target.value})}><option value="">Por indicar</option>{options.map(([value,text])=><option key={value} value={value}>{text}</option>)}</select></label>;
|
||
const yesNo:Array<[string,string]>=[['yes','Sim'],['no','Não']];
|
||
return <section aria-label="Aquisições e apuramento parcial Modelo A" className="space-y-4 rounded border p-3">
|
||
<h4 className="font-semibold">Compras e apuramento parcial do Modelo A</h4>
|
||
<p>Oportunidade em tratamento: {String(draft.context_opportunity||'entrada geral')}. Moeda de origem: {book.currency||'por indicar'}. A compra registada não gera automaticamente IVA dedutível.</p>
|
||
{book.currency && draft.currency!==book.currency && <p role="alert">As compras conservam {book.currency}; o projecto está em {String(draft.currency||'moeda por indicar')}. Reveja a moeda no projecto. Os montantes e o resultado anterior não foram convertidos ou reinterpretados.</p>}
|
||
{!book.currency && !!draft.currency && <button type="button" className="underline" onClick={()=>save({...book,currency:String(draft.currency),rows:book.rows.map(row=>row.sourceCurrency?row:{...row,sourceCurrency:String(draft.currency)})})}>Confirmar explicitamente {String(draft.currency)} como moeda de origem dos valores sem moeda, conservando os montantes</button>}
|
||
<p>Aquisições em MZN/2026,ligadas à operação de destino:com direito,sem direito ou indeterminadas. A/B/C/D distinguem compras internas,importações de bens,exclusões/excepções e afectação/proporções. Documento e dedução no mesmo mês;afectação mista,proporções18(6),dedução posterior e regularizações conservam percursos próprios. Nas projecções,cada confirmação é um pressuposto. Compras internas:indique dois valores entre líquido,IVA e bruto. Importações:IVA expressamente identificado no bilhete;custos líquido/bruto são opcionais,sem calcular direitos ou presumir taxa.</p>
|
||
<label className="block">Adquirente sujeito passivo de IVA no regime normal (efectivo ou pressuposto da projecção)<select className="block rounded border p-2" value={book.taxpayerRegistered??''} onChange={e=>save({...book,taxpayerRegistered:e.target.value as AcquisitionBook['taxpayerRegistered']})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não / outro enquadramento — fora deste lote</option></select></label>
|
||
{error&&<p role="alert">{error}</p>}
|
||
<details className="rounded border p-2"><summary>Indicar mês da aquisição pelo documento ou pressuposto da projecção</summary>
|
||
<p>Para compras introduzidas directamente, indique o mês aplicável sem depender de ter vendas nesse mês. A reutilização de valores do orçamento mantém a conferência da origem.</p>
|
||
<label>Mês da aquisição<input type="month" min="2026-01" max="2026-12" value={manualPeriod} onChange={e=>setManualPeriod(e.target.value)}/></label>
|
||
<button type="button" onClick={()=>run(()=>{if(!/^2026-(0[1-9]|1[0-2])$/.test(manualPeriod))throw new Error('Indique um mês válido de2026.');if(book.periods.some(p=>p.period===manualPeriod))throw new Error('Esse mês já consta da cobertura; valores conservados.');save({...book,periods:[...book.periods,{period:manualPeriod,complete:false,absent:[]}]});})}>Acrescentar mês indicado</button>
|
||
</details>
|
||
<button type="button" className="underline" onClick={()=>run(()=>save(reuseAcquisitions(draft,book)))}>Reutilizar compras e períodos existentes</button>
|
||
<button type="button" className="ml-3 underline" disabled={!book.periods.length} onClick={()=>save({...book,rightVersion:acquisitionRightVersion,rows:[...book.rows,newAcquisition(crypto.randomUUID(),book.periods[0]?.period??'',book.currency)]})}>Acrescentar aquisição em falta</button>
|
||
{book.rows.map(row=><details key={row.id} className="rounded border p-3"><summary>{row.description||'Aquisição adicional'} — {row.period} — campo{row.field}</summary>
|
||
<AcquisitionAllocationEditor draft={draft} row={row} onChange={patch=>change(row.id,patch)}/>
|
||
{!row.d?.method&&<AcquisitionOperationLink draft={draft} row={row} onChange={patch=>change(row.id,patch)}/>}
|
||
<AcquisitionImportsExclusions draft={draft} row={row} onChange={patch=>change(row.id,patch)}/>
|
||
<details className="rounded border p-2"><summary>Enquadramento especial da aquisição / referência anterior</summary>
|
||
<label>Tratamento da aquisição<select value={row.acquisitionTreatment??''} onChange={e=>row.acquisitionTreatment==='former_exploration_exemption'&&e.target.value!=='former_exploration_exemption'?run(()=>save({...book,rows:book.rows.map(item=>item.id===row.id?continueWithoutFormerExploration(item):item)})):change(row.id,{acquisitionTreatment:e.target.value as AcquisitionTreatment})}><option value="">Enquadramento normal; sem benefício especial indicado</option><option value="former_exploration_exemption">Antiga isenção de prospecção/pesquisa —14(2)(c),eliminada</option><option value="normal">Continuidade pelo enquadramento normal</option></select></label>
|
||
{acquisitionTreatmentIssue(row.acquisitionTreatment)&&<p role="status">{acquisitionTreatmentIssue(row.acquisitionTreatment)}</p>}
|
||
{row.acquisitionTreatment==='former_exploration_exemption'&&<><p>{formerExplorationSource}</p><p>A continuidade conserva montantes,moeda,mês,documento e respostas existentes. Não introduz taxa nem altera o IVA declarado; reveja as condições antes de calcular.</p><button type="button" onClick={()=>run(()=>save({...book,rows:book.rows.map(item=>item.id===row.id?continueWithoutFormerExploration(item):item)}))}>Continuar aquisição sem esta isenção e rever condições</button></>}
|
||
{row.acquisitionTreatmentOrigin&&<p>Origem conservada: antiga referência14(2)(c),eliminada. {row.acquisitionTreatment==='former_exploration_exemption'?'A continuidade normal foi escolhida numa versão anterior; a referência antiga está agora seleccionada para análise.':'Continuidade normal escolhida expressamente; não é nova concessão de isenção.'}</p>}
|
||
</details>
|
||
<p>Origem: {row.sourceMonth===null?'Introdução directa':`Plano, mês ${row.sourceMonth} (${row.sourcePeriod??'por indicar'}): ${row.sourceAmount??'sem informação'} ${row.sourceCurrency}`}. O mês da encomenda/orçamento não determina a data do documento ou a dedução. Alterações não substituem o resultado anterior.</p>
|
||
<div className="grid gap-3 md:grid-cols-2">
|
||
<label>Descrição<input className="block rounded border p-2" value={row.description} onChange={e=>change(row.id,{description:e.target.value})}/></label>
|
||
{row.sourceMonth===null && <label>Reutilizar valor existente, sem presumir que o orçamento é uma factura<select className="block rounded border p-2" value="" onChange={e=>run(()=>{const ref=references.find(r=>r.key===e.target.value);if(ref)change(row.id,{sourceMonth:ref.month,sourcePeriod:ref.period,sourceField:ref.kind,sourceAmount:ref.amount,sourceCurrency:ref.currency,...(ref.kind==='asset'?{}:{period:ref.period})});})}><option value="">Escolher apenas se corresponde a esta aquisição</option>{references.filter(ref=>ref.kind===(row.field==='08'?'asset':row.field==='10'?'operating':'inventory')).map(ref=><option key={ref.key} value={ref.key}>{ref.label}: {ref.amount??'sem informação'} {ref.currency}</option>)}</select></label>}
|
||
{select(row,'field','Categoria',[['08','08 — Imobilizado'],['09','09 — Existências'],['10','10 — Outros bens e serviços'],...(row.bc?[['11','11 — Importações de bens'] as [string,string]]:[])])}
|
||
{row.field==='11'&&<label><input type="checkbox" checked={row.investmentAsset??false} onChange={e=>change(row.id,{investmentAsset:e.target.checked})}/>Bem de investimento importado — incluir no controlo de activos;conserva campo11 e condições próprias da importação.</label>}
|
||
{select(row,'origin','Natureza dos dados',[['projected','Projectados / pressupostos'],['effective','Efectivos declarados']])}
|
||
<label>Período de dedução<input className="block rounded border p-2" type="month" value={row.period} onChange={e=>change(row.id,{period:e.target.value})}/></label>
|
||
{row.sourceMonth!==null && <label>O valor de origem corresponde a<select className="block rounded border p-2" value={row.sourceBasis} onChange={e=>{const basis=e.target.value as Acquisition['sourceBasis'];change(row.id,{sourceBasis:basis,...(basis==='net'&&!row.net?{net:row.sourceAmount??''}:basis==='gross'&&!row.gross?{gross:row.sourceAmount??''}:{})});}}><option value="">Por classificar</option><option value="net">Valor sem IVA</option><option value="gross">Valor com IVA</option></select></label>}
|
||
{(['net','vat','gross'] as const).map((field,index)=><label key={field}>{['Valor líquido','IVA indicado','Valor bruto'][index]} ({row.sourceCurrency})<input className="block rounded border p-2" inputMode="decimal" value={row[field]} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>)}
|
||
{select(row,'documentType','Tipo de documento',row.domestic==='no'&&row.bc?[['customs','Bilhete de despacho de importação']]:[['invoice','Factura'],['equivalent','Documento equivalente']])}
|
||
<label>Referência do documento / previsão<input className="block rounded border p-2" value={row.documentRef} onChange={e=>change(row.id,{documentRef:e.target.value})}/></label>
|
||
<label>Linha do documento, se discriminado em várias aquisições<input className="block rounded border p-2" value={row.documentLine} onChange={e=>change(row.id,{documentLine:e.target.value})}/></label>
|
||
<label>Data do documento / prevista<input className="block rounded border p-2" type="date" value={row.documentDate} onChange={e=>change(row.id,{documentDate:e.target.value})}/></label>
|
||
{!(row.bc&&row.domestic==='no')&&<label>Fornecedor / previsto<input className="block rounded border p-2" value={row.supplier} onChange={e=>change(row.id,{supplier:e.target.value})}/></label>}
|
||
{select(row,'domestic','Aquisição interna',yesNo)}
|
||
{row.acquisitionTreatment!=='former_exploration_exemption'&&<>
|
||
{!(row.bc&&row.domestic==='no')&&select(row,'supplierRegistered','Fornecedor inscrito no IVA',yesNo)}
|
||
{select(row,'legalDocument','Documento emitido em forma legal',yesNo)}
|
||
{select(row,'held','Documento na posse do adquirente / previsto na projecção',yesNo)}
|
||
{select(row,'allocation','Afectação',[['taxable_only',row.d?.method==='reduced'?'Exclusiva à origem18(6)':row.deductionLink?'Exclusiva à operação ligada':'Exclusiva a operações tributadas'],['mixed',row.d?'Mista / parcial — métodoD':'Mista / parcial — preparar métodoD'],['no_right','Operações sem direito à dedução']])}
|
||
{select(row,'exclusion','Exclusões de dedução',[['none','Sem exclusões ou casos especiais dos arts.18–20'],['excluded','Despesa excluída'],['special','Veículos, combustível, viagens, alojamento, comunicações, luxo ou outra situação especial — por enquadrar']])}
|
||
{select(row,'integrity','Operação real / pressuposto regular, sem simulação, margem de segunda mão ou impedimentos do art.18(3–5)',yesNo)}
|
||
</>}
|
||
</div>
|
||
<p>Fundamento:CIVA18–21,fontes e versão no resultado. {row.d?'MétodoD aplicado apenas à parcela elegível pelas condiçõesB/C;fracções exactas,sem regularização anual automática ou restituiçãoL3.':row.bc?'Aplicam-se apenas as exclusões/excepções documentadas escolhidas;gasóleo50% conserva fracção exacta. Sem pro rata,proporções18(6) ou regularização neste incremento.':'Afectação parcial,despesa especial e dedução posterior exigem percurso próprio;use a análiseC quando aplicável.'}</p>
|
||
{row.sourceMonth===null && <button type="button" className="mr-3 underline" onClick={()=>save({...book,rows:book.rows.filter(r=>r.id!==row.id)})}>Remover esta aquisição introduzida directamente</button>}
|
||
{row.sourceMonth!==null && <button type="button" className="underline" onClick={()=>run(()=>{const ref=acquisitionPlanReferences(draft).find(r=>r.month===row.sourceMonth && r.kind===(row.sourceField??'inventory'));if(!ref)throw new Error('Origem não disponível; conserve o registo e reveja o plano.');if(draft.currency!==row.sourceCurrency)throw new Error('Moeda divergente; não adoptar montantes de outra moeda.');change(row.id,{sourceAmount:ref.amount,sourcePeriod:ref.period,...(row.sourceBasis==='net'?{net:ref.amount??'',gross:''}:row.sourceBasis==='gross'?{gross:ref.amount??'',net:''}:{})});})}>Adoptar explicitamente o valor actual do plano (conserva histórico)</button>}
|
||
</details>)}
|
||
<AcquisitionRegularizations draft={draft} onChange={onChange}/>
|
||
{book.periods.map(period=><details key={period.period} className="rounded border p-3"><summary>{period.period} — cobertura das parcelas — {period.complete && period.absent.length===acquisitionExcludedFields.length?'confirmada':'por completar'}</summary>
|
||
{book.rows.some(r=>r.bc)&&<label className="block"><input type="checkbox" checked={period.importsComplete??false} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,importsComplete:e.target.checked}:p)})}/> Incluí todas as importações de bens/campo11 deste período,mesmo isentas ou sem direito;sem somar novamente IVA de serviços incluídos na base.</label>}
|
||
<label className="block"><input type="checkbox" checked={period.complete} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,complete:e.target.checked}:p)})}/> Incluí todas as aquisições08–10 deste período, incluindo as sem direito à dedução; uma lista vazia significa que não houve essas aquisições.</label>
|
||
<p>Campos fora deste lote: assinale apenas quando não existiram as respectivas operações. Uma parcela não assinalada permanece indeterminada.</p>
|
||
{acquisitionExcludedFields.map(field=><label className="block" key={field}><input type="checkbox" checked={period.absent.includes(field)} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,absent:e.target.checked?[...p.absent,field]:p.absent.filter(f=>f!==field)}:p)})}/> Sem operações no campo{field} — {labels[field]}</label>)}
|
||
</details>)}
|
||
<button type="button" disabled={!book.periods.length} className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50" onClick={()=>run(()=>save(recordAcquisitions(draft,book)))}>Calcular e guardar versão do apuramento parcial</button>
|
||
<p>O apuramento01–18 conserva o seu resultado antes dos créditos. O fechoA/B abaixo reutiliza a versão congelada,sem ordem de pagamento,submissãoAT ou promessa de reembolso.</p>
|
||
<AcquisitionClosure draft={draft} onChange={onChange}/>
|
||
<AcquisitionSubstitution draft={draft} onChange={onChange}/>
|
||
<AcquisitionRefund draft={draft} onChange={onChange}/>
|
||
<AcquisitionBeverageRestitution draft={draft} onChange={onChange}/>
|
||
<FiscalCalculationGaps draft={draft}/>
|
||
<FiscalL3Preparation draft={draft}/>
|
||
{book.result&&<><p role="status">{acquisitionCurrent(draft,book)?'Resultado correspondente às entradas guardadas.':'Entradas ou origem alteradas: resultado histórico preservado; actualizar o cálculo.'}</p><dl>{acquisitionReportRows(draft).map(([,label,value,origin],i)=><div key={i}><dt className="font-semibold">{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
|
||
</section>;
|
||
}
|