Files
Investidor-V2/resources/js/components/simulation-travel-events.tsx
T

45 lines
8.6 KiB
TypeScript

import {useState} from 'react';
import {travelEventBook,travelEventCurrent,travelEventReportRows,travelEventSource,travelEventSources,newTravelEvent,recordTravelEvents,saveTravelEventBook,switchTravelEvent,type TravelEventBook,type TravelEventRow} from '@/lib/iva-travel-events';
export function SimulationTravelEvents({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}){
const[error,setError]=useState('');let book:TravelEventBook;try{book=travelEventBook(draft);}catch(e){return<p role="alert">{String(e)}</p>;}
const save=(next:TravelEventBook)=>{try{onChange('modelo_a_snapshot',saveTravelEventBook(draft,next));setError('');}catch(e){setError(String(e));}};
const change=(id:string,patch:Partial<TravelEventRow>)=>save({...book,rows:book.rows.map(r=>r.id===id?{...r,...patch}:r)});
const fields=(row:TravelEventRow,list:ReadonlyArray<readonly[Exclude<keyof TravelEventRow,'fxSelection'>,string]>)=>list.map(([key,label])=><label className="block" key={key}>{label}<input type={key==='period'?'month':String(key).endsWith('Date')||key==='legalDeadline'?'date':'text'} value={row[key]} onChange={e=>change(row.id,{[key]:e.target.value})}/></label>);
const select=(row:TravelEventRow,key:Exclude<keyof TravelEventRow,'fxSelection'>,label:string,options:ReadonlyArray<readonly[string,string]>)=><label>{label}<select value={row[key]} onChange={e=>change(row.id,{[key]:e.target.value})}><option value="">Por esclarecer</option>{options.map(([value,text])=><option key={value} value={value}>{text}</option>)}</select></label>;
const yn=[['yes','Sim'],['no','Não']] as const;
let sources:ReturnType<typeof travelEventSources>=[];try{sources=travelEventSources(draft);}catch{/* Origin reader preserves originals. */}
return<details className="rounded border p-3"><summary>Eventos de viagens e cessação de regimes suspensivos</summary><p>Conserve separados facto gerador e exigibilidade. Não inferir pagamento integral por um sinal, início pela emissão do bilhete ou cessação do regime por mudança de armazém. Estes eventos não calculam imposto nem concedem benefício.</p><p>{travelEventSource}</p>{error&&<p role="alert">{error}</p>}
<button type="button" onClick={()=>save({...book,rows:[...book.rows,newTravelEvent(crypto.randomUUID(),String(draft.currency??''))]})}>Acrescentar evento especial</button>
{book.rows.map(row=>{const source=sources.find(s=>s.id===row.travelRowId);return<details className="rounded border p-3" key={row.id}><summary>{row.description||'Evento por identificar'} — {row.period||'período por indicar'}</summary>
<label>Percurso do evento especial<select value={row.treatment} onChange={e=>change(row.id,switchTravelEvent(row,e.target.value))}><option value="">Por esclarecer</option><option value="travel_special">Viagens — pagamento integral/início7(9)</option><option value="suspensive_exit">Cessação de regime/procedimento7(8)</option></select></label>
{fields(row,[['description','Evento ou operação identificada'],['operationRef','Referência da operação existente'],['period','Mês de contexto do evento'],['documentRef','Documento disponível / referência da previsão'],['documentDate','Data do documento / prevista'],['reviewDate','Data até à qual os eventos são conferidos'],['scope','Âmbito e parcela efectivamente abrangidos'],['evidence','Factos ou pressupostos expressos']])}
{select(row,'origin','Natureza dos dados',[['projected','Projectados / pressupostos'],['effective','Efectivos declarados']])}
{select(row,'factsConfirmed','Factos correspondem ao percurso e âmbito identificados',yn)}
{row.treatment==='suspensive_exit'&&<>
{select(row,'regimeClause','Regime/procedimento identificado no artigo14(1)',[['a','a — Importação/colocação em regimes ou depósitos indicados'],['b','b — Expedição/transporte para as zonas ou depósitos e conexos'],['c','c — Transmissão e serviços conexos sob o regime'],['d','d — Trânsito, transbordo, draubaque ou importação temporária e conexos']])}
{fields(row,[['entryDate','Data de colocação sob o regime/procedimento'],['entryRef','Referência do regime, bens e operação de entrada']])}
{select(row,'regimeStatus','Situação do regime na conferência',[['still','Continua abrangido pelo regime'],['ceased','Cessou efectivamente o regime']])}
{row.regimeStatus==='ceased'&&<>{fields(row,[['exitDate','Data da cessação efectiva'],['exitRef','Referência e âmbito da cessação']])}{select(row,'cessationConfirmed','Confirmada cessação fiscal, distinguida de transferência entre regimes ou mudança física',yn)}</>}
<p>A referência demonstra o facto e o âmbito. Não se exige certificado ou autorização inventados; não há desembaraço, valor aduaneiro, direitos ou ICE automáticos.</p>
</>}
{row.treatment==='travel_special'&&<>
<label>Operação de viagens já classificada nesta oportunidade<select value={row.travelRowId} onChange={e=>change(row.id,{travelRowId:e.target.value})}><option value="">Escolher operação</option>{sources.map(s=><option key={s.id} value={s.id}>{s.description} — {s.period} — {s.classification.regime}{s.current?'':' — histórico'}</option>)}</select></label>
{source&&<><p>Origem: {source.operationRef}; {source.scope}. Período do registo: {source.period}. {source.classification.reason}</p><button type="button" onClick={()=>change(row.id,{operationRef:row.operationRef||source.operationRef,description:row.description||source.description,documentRef:row.documentRef||source.input.documentRef,documentDate:row.documentDate||source.input.documentDate,currency:row.currency||source.input.currency,scope:row.scope||source.scope})}>Preencher apenas referências em falta a partir da origem</button><p>Referências já preenchidas são conservadas; divergências precisam de conferência. O período do evento pode diferir do período da margem.</p></>}
{select(row,'fullPaymentState','Estado do pagamento da contraprestação',[['full','Pagamento integral confirmado'],['partial','Apenas pagamento parcial'],['none','Sem pagamentos']])}
{row.fullPaymentState==='full'&&fields(row,[['fullPaymentDate','Data do pagamento integral'],['fullPaymentRef','Referência da liquidação integral / conjunto de recebimentos']])}
{select(row,'startState','Início da viagem/alojamento e primeiro serviço',[['started','Primeiro serviço/início ocorrido no cenário conferido'],['not_started','Ainda não iniciado']])}
{row.startState==='started'&&fields(row,[['startDate','Data do primeiro serviço ao cliente'],['startRef','Referência do primeiro serviço e âmbito']])}
<p>«Imediatamente antes» identifica o momento; não se subtrai um dia à data. Pagamento e início na mesma data não demonstram ordem intradiária.</p>
{fields(row,[['amount','Contraprestação total da parcela analisada'],['currency','Moeda da contraprestação']])}
{select(row,'invoiceRequired','Operação com obrigação de emitir factura25',yn)}
{row.invoiceRequired==='yes'&&<>{fields(row,[['legalDeadline','Termo do prazo legal de emissão aplicável'],['deadlineRef','Disposição, calendário e âmbito que fundamentam o prazo']])}{select(row,'invoiceState','Factura até à conferência',[['issued','Emitida'],['not_issued','Não emitida']])}{row.invoiceState==='issued'&&fields(row,[['invoiceDate','Data de emissão da factura'],['invoiceRef','Referência da factura']])}</>}
{select(row,'paymentState','Recebimento ou parcela anterior à liquidação integral',[['none','Sem recebimento'],['received','Recebimento identificado']])}
{row.paymentState==='received'&&fields(row,[['paymentDate','Data do recebimento identificado'],['paymentAmount','Montante recebido nessa parcela'],['paymentRef','Referência e âmbito da parcela recebida']])}
<p>Se existe sinal anterior e posterior liquidação integral, identifique ambos: a parcela e o saldo não são somados duas vezes. A menção «IVA incluído» não é factura emitida por este simulador.</p>
</>}
<p>Mudar percurso conserva campos ocultos e referências; reveja as confirmações próprias.</p></details>;})}
{!!book.rows.length&&<button type="button" onClick={()=>{try{save(recordTravelEvents(draft,book));}catch(e){setError(String(e));}}}>Conferir eventos especiais e guardar versão</button>}
{book.result&&<><p role="status">{travelEventCurrent(draft,book)?'Resultados correspondem às entradas e origens guardadas.':'Entradas/origens alteradas; resultado histórico conservado. Reveja o delta.'} {book.history?.length??0} versões anteriores. Sem base/IVA/dedução calculados.</p><dl>{travelEventReportRows(draft).map(([,label,value,origin],i)=><div key={i}><dt>{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
</details>;
}