32 lines
11 KiB
TypeScript
32 lines
11 KiB
TypeScript
import {newIrpcInterestRule,type IrpcInterestRule} from '@/lib/irpc-interest';
|
|
import {mrftaCases,mrftaCaseHtml} from '@/lib/irpc-mrfta-cases';
|
|
import {useState} from 'react';
|
|
import {irpcCurrent,type IrpcBook} from '@/lib/irpc-withholdings';
|
|
import {newMrftaInput,recordMrfta,mrftaHtml,mrftaReportRows,mrftaCurrent,type MrftaInput} from '@/lib/irpc-mrfta';
|
|
|
|
export function SimulationIrpcMrfta({draft,book,save}:{draft:Record<string,unknown>;book:IrpcBook;save:(book:IrpcBook)=>void}){
|
|
const [error,setError]=useState(''),i=book.mrfta??newMrftaInput();
|
|
const change=(patch:Partial<MrftaInput>)=>save({...book,mrfta:{...i,...patch}});
|
|
const interestMode=i.interest.mode||(i.interest.calculation==='automatic'?'automatic':'');
|
|
const interest=(patch:Partial<MrftaInput['interest']>)=>change({interest:{...i.interest,...patch}});
|
|
const field=(key:'period'|'nuit'|'name'|'commercialName'|'taxArea'|'address'|'contact'|'completenessRef'|'otherRubricsRef',label:string,type='text')=><label className="block">{label}<input className="block rounded border p-2" type={type} value={i[key]} onChange={e=>change({[key]:e.target.value})}/></label>;
|
|
const select=(key:'declaration'|'timing'|'completeness'|'otherRubrics',label:string,options:Array<[string,string]>)=><label className="block">{label}<select value={i[key]} onChange={e=>change({[key]:e.target.value})}>{options.map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>;
|
|
const periods=[...new Set(book.result?.rows.map(r=>r.period).filter(Boolean)??[])];
|
|
const identities=Object.entries((JSON.parse(String(draft.modelo_a_snapshot||'{}')).irpcWithholdings??{}) as Record<string,IrpcBook>).filter(([,b])=>b.mrfta?.nuit&&b.mrfta?.name&&(!i.nuit.trim()||i.nuit===b.mrfta.nuit)&&(!i.name.trim()||i.name===b.mrfta.name)&&(['nuit','name','commercialName','taxArea','address','contact'] as const).some(key=>!i[key].trim()&&b.mrfta![key].trim()));
|
|
return <fieldset aria-label="Preparação MRFTA e juros" className="space-y-3 rounded border p-4"><legend>MRFTA — preenchimento e juros compensatórios</legend><p>Reutiliza o cálculo guardado, documentos e entregas IRPC. Um mês/ano por declaração. A retenção sofrida e as dispensas ficam separadas. Quadros 8/9 reservados à assinatura e aos serviços; não há submissão ou aceitação AT.</p>
|
|
{identities.map(([scope,b])=><button key={scope} type="button" onClick={()=>{const p:Partial<MrftaInput>={};for(const key of ['nuit','name','commercialName','taxArea','address','contact']as const)if(!i[key].trim())p[key]=b.mrfta![key];change(p);}}>Reutilizar identidade fiscal {b.mrfta!.name} ({scope}) nos campos vazios</button>)}
|
|
<div className="grid gap-3 md:grid-cols-2">{field('period','Quadro 2 — mês/ano','month')}<label>Período existente<select value="" onChange={e=>{if(e.target.value)change({period:e.target.value});}}><option value="">Seleccione</option>{periods.map(p=><option key={p} value={p}>{p}</option>)}</select></label>{select('declaration','Quadro 1 — retenção na fonte',[['','Por identificar'],['initial','Inicial'],['replacement','Substituição']])}{select('timing','Quadro 2 — situação do prazo',[['','Por esclarecer'],['within','Dentro do prazo'],['late','Fora do prazo']])}{field('nuit','Quadro 3 — NUIT')}{field('taxArea','Quadro 3 — área fiscal')}{field('name','Quadro 4 — nome do sujeito passivo')}{field('commercialName','Quadro 5 — nome comercial')}{field('address','Quadro 6 — domicílio fiscal')}{field('contact','Quadro 6 — contacto')}{select('completeness','Retenções do sujeito passivo neste período conferidas, incluindo demais livros',[['','Por esclarecer'],['yes','Sim'],['no','Ainda não']])}{field('completenessRef','Referência da reconciliação do período')}{select('otherRubrics','IRPS, tributação autónoma e outras rubricas fora deste recorte',[['','Por esclarecer'],['none','Ausência de outros valores devidos documentada'],['pending','Existem ou estão por apurar']])}{field('otherRubricsRef','Documento da situação das outras rubricas')}</div>
|
|
<h5>Quadro 7 — CER das rubricas correspondidas</h5><p>Base, taxa e imposto vêm do cálculo guardado. O CER é transcrito da referência aplicável; os números de campos da guia antiga não são automaticamente códigos de receita. Códigos introduzidos não são validados por catálogo oficial; códigos provisórios ou de simulação não são correspondências oficiais.</p>
|
|
{book.result?.inputs.filter(r=>r.direction==='paid'&&book.result?.rows.some(v=>v.id===r.id&&v.period===i.period&&v.status==='calculated'&&v.treatment!=='dispensed')).map(r=>{const code=i.codes.find(c=>c.rowId===r.id)??{rowId:r.id,cer:'',reference:''};const update=(p:Partial<typeof code>)=>change({codes:[...i.codes.filter(c=>c.rowId!==r.id),{...code,...p}]});return <div key={r.id} className="grid gap-2 md:grid-cols-3"><span>{r.documentRef} — {r.counterparty}</span><label>CER<input value={code.cer} onChange={e=>update({cer:e.target.value})}/></label><label>Documento / referência do CER<input value={code.reference} onChange={e=>update({reference:e.target.value})}/></label></div>;})}
|
|
<h5>Juros compensatórios — separados do principal</h5><p>Regulamento IRPC, artigos 25/35: a taxa refere MAIBOR de 12 meses + 2 pontos. O Banco de Moçambique documenta a descontinuidade da MAIBOR. O portal não adopta MIMO ou Prime como substituto nem calcula juros de mora.</p>
|
|
<label>Tratamento dos juros<select value={interestMode} onChange={e=>interest(e.target.value==='automatic'?{mode:'',calculation:'automatic'}:{mode:e.target.value as MrftaInput['interest']['mode'],calculation:undefined})}><option value="">Informação insuficiente / cálculo automático por resolver</option><option value="assessment">Transcrever liquidação documentada de juros compensatórios</option><option value="automatic">Cálculo com taxa e regra documentadas — sem parâmetro pré-definido</option><option value="timely">Conferir entrega integral dentro do prazo documentado</option></select></label>
|
|
{interestMode&&<><label>Período documentado dos juros / entregas<input type="month" value={i.interest.period} onChange={e=>interest({period:e.target.value})}/></label><label>Documento da liquidação / reconciliação<input value={i.interest.reference} onChange={e=>interest({reference:e.target.value})}/></label></>}
|
|
{['assessment','automatic'].includes(interestMode)&&<>{i.interest.mode==='assessment'&&<label>Montante dos juros em MZN<input value={i.interest.amount} onChange={e=>interest({amount:e.target.value})}/></label>}<label>Data da liquidação<input type="date" value={i.interest.date} onChange={e=>interest({date:e.target.value})}/></label></>}
|
|
{['timely','automatic'].includes(interestMode)&&<><label>Prazo aplicável documentado<input type="date" value={i.interest.dueDate} onChange={e=>interest({dueDate:e.target.value})}/></label><label>Fonte do prazo, incluindo eventual ajuste de calendário<input value={i.interest.dueReference} onChange={e=>interest({dueReference:e.target.value})}/></label></>}
|
|
{interestMode==='automatic'&&<fieldset className="space-y-2 rounded border p-2"><legend>Parâmetros documentados do cálculo — sem taxa fiscal de2026 pré-definida</legend><p>Simulação pelos pressupostos introduzidos, sem confirmação fiscal dos juros. Preencha apenas a regra aplicável ao caso. Sem parâmetro completo, os juros continuam indeterminados. A taxa bancária actual não é adoptada por substituição. Imposto por conta não retido e juros de mora mantêm regra própria.</p>{(['annualRate','rateSource','fiscalBasis','validFrom','validTo','dayBasisRef','roundingRef','occurrenceRef']as const).map(k=><label key={k} className="block">{({annualRate:'Taxa anual final (%) documentada, incluindo acréscimos aplicáveis',rateSource:'Documento e citação da taxa',fiscalBasis:'Fundamento da aplicação fiscal ao caso',validFrom:'Parâmetro aplicável desde',validTo:'Parâmetro aplicável até',dayBasisRef:'Documento da base anual e contagem diária',roundingRef:'Documento do arredondamento do total',occurrenceRef:'Documento da retenção e ocorrência'})[k]}<input type={k==='validFrom'||k==='validTo'?'date':'text'} value={i.interest.automatic?.[k]??''} onChange={e=>interest({automatic:{...(i.interest.automatic??newIrpcInterestRule()),[k]:e.target.value}})}/></label>)}{(['dayBasis','rounding','retention']as const).map(k=><label key={k} className="block">{({dayBasis:'Base anual conforme documento',rounding:'Arredondamento do total conforme documento',retention:'Retenção efectuada'})[k]}<select value={i.interest.automatic?.[k]??''} onChange={e=>interest({automatic:{...(i.interest.automatic??newIrpcInterestRule()),[k]:e.target.value} as IrpcInterestRule})}>{(k==='dayBasis'?[['','Por identificar'],['365','365 dias'],['366','366 dias'],['actual_year','Dias efectivos de cada ano']]:k==='rounding'?[['','Por identificar'],['total_cent_half_up','Total aos cêntimos — metade por excesso'],['total_cent_down','Total aos cêntimos — por defeito']]:[['','Por identificar'],['withheld','Sim, documentada'],['not_withheld','Não efectuada, documentado']]).map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>)}</fieldset>}
|
|
<button type="button" className="rounded border p-2" onClick={()=>{try{save(recordMrfta(book,irpcCurrent(draft,book)));setError('');}catch(e){setError(String(e));}}}>Preparar e guardar MRFTA</button><button type="button" className="rounded border p-2" onClick={()=>{try{const html=mrftaHtml(book,irpcCurrent(draft,book)),u=URL.createObjectURL(new Blob([html],{type:'text/html;charset=utf-8'})),a=document.createElement('a');a.href=u;a.download='MRFTA-'+(i.period||'periodo')+'-preparacao.html';a.click();URL.revokeObjectURL(u);setError('');}catch(e){setError(String(e));}}}>Exportar preenchimento MRFTA</button>
|
|
{book.mrftaResult&&<fieldset className="space-y-2 rounded border p-2"><legend>Casos de trabalho separados por período, rubrica, taxa e CER</legend><p>Estas fichas conservam as pendências do período. Não são declarações autónomas, anexos oficiais ou uma autorização para fundir taxas.</p>{mrftaCases(book.mrftaResult.model).map(c=><div key={c.key}><span>{c.period}; {c.rubric}; {c.rate}%; CER:{c.cer||'por corresponder'}; documentos:{c.documents.join('; ')}.</span><button type="button" onClick={()=>{try{if(!irpcCurrent(draft,book)||!mrftaCurrent(book,true))throw Error('Reprepare as entradas actuais antes de exportar o caso.');const html=mrftaCaseHtml(book.mrftaResult!.model,c.key),u=URL.createObjectURL(new Blob([html],{type:'text/html;charset=utf-8'})),a=document.createElement('a');a.href=u;a.download='MRFTA-caso-'+c.key+'.html';a.click();URL.revokeObjectURL(u);setError('');}catch(e){setError(String(e));}}}>Exportar ficha de trabalho {c.rate}% / {c.cer||'CER pendente'}</button></div>)}</fieldset>}
|
|
{error&&<p role="alert">{error}</p>}{mrftaReportRows(book,irpcCurrent(draft,book)).map((r,n)=><p key={n}><strong>{r[1]}</strong>: {r[2]}<small>{r[3]}</small></p>)}
|
|
</fieldset>;
|
|
}
|