28 lines
20 KiB
TypeScript
28 lines
20 KiB
TypeScript
import {newSpecialCombinations,specialCombinationsSource} from '@/lib/irpc-special-combinations';
|
|
import type {NormalBook} from '@/lib/irpc-normal';
|
|
import {newSpecialInput,newSpecialExpense,newAcceleratedAsset,specialAccelerationSource,specialCessationPriorSource,specialStartPeriodSource,specialMonthlySource,specialSources,type SpecialInput,type Qualification} from '@/lib/irpc-special';
|
|
|
|
type Props={draft:Record<string,unknown>;book:NormalBook;save:(b:NormalBook)=>void};
|
|
const states=[['','Informação em falta'],['known','Tratamento conhecido — qualificar condições'],['absent','Ausência declarada com suporte'],['not_applicable','Tratamento não aplicável — justificar']]as const;
|
|
function QualificationFields({label,value,onChange}:{label:string;value:Qualification;onChange:(v:Qualification)=>void}){
|
|
const field=(key:'from'|'to'|'reference'|'periodReference',text:string)=><label className="block">{text}<input aria-label={label+' — '+text} value={value[key]} onChange={e=>onChange({...value,[key]:e.target.value})}/></label>;
|
|
return<div className="space-y-2"><label className="block">Estado — {label}<select aria-label={'Estado — '+label} value={value.state} onChange={e=>onChange({...value,state:e.target.value as Qualification['state'],eligible:''})}>{states.map(([v,t])=><option key={v} value={v}>{t}</option>)}</select></label>{value.state==='known'&&<label className="block">Condições — {label}<select aria-label={'Condições — '+label} value={value.eligible} onChange={e=>onChange({...value,eligible:e.target.value as Qualification['eligible']})}><option value="">Por esclarecer</option><option value="yes">Sim — condições documentadas</option><option value="no">Não — condição negativa documentada</option></select></label>}{field('reference','Prova das condições, ausência ou não aplicação')}{field('from','Período — início (AAAA-MM-DD)')}{field('to','Período — termo (AAAA-MM-DD)')}{field('periodReference','Suporte do período e da vigência aplicável')}</div>;
|
|
}
|
|
export function SimulationIrpcSpecial({draft,book:b,save}:Props){
|
|
const i=b.special;
|
|
const start=()=>{const next=newSpecialInput(),p=b.periodsFx??b.schedules?.period;for(const part of[next.expenses,next.cooperative,next.acceleration])Object.assign(part.qualification,{from:p?.from??b.year+'-01-01',to:p?.to??b.year+'-12-31'});save({...b,special:next});};
|
|
if(!i)return<section className="rounded border p-3"><h5>L7 — benefícios e casos especiais</h5><p>Preparação opcional e separada do percursoL6 já entregue. Localização e sector não activam benefícios. A liquidação final permanece emL8.</p><button type="button" onClick={start}>Preparar qualificação L7</button></section>;
|
|
const change=(patch:Partial<SpecialInput>)=>save({...b,special:{...i,...patch}});
|
|
const yesno=(label:string,value:string,onChange:(v:''|'yes'|'no')=>void)=><label className="block">{label}<select aria-label={label} value={value} onChange={e=>onChange(e.target.value as ''|'yes'|'no')}><option value="">Informação em falta</option><option value="yes">Sim — documentado</option><option value="no">Não — documentado</option></select></label>;
|
|
return<section aria-label="L7 — qualificação e tratamentos" className="space-y-3 rounded border p-3"><h5>L7 — qualificação comum e tratamentos documentados</h5><p>Reutiliza sujeito, NUIT, período, despesas e activos L6. Contexto herdado:sector {String(draft.sector_id??'por identificar')};província {String(draft.province_id??'por identificar')}. A localização, por si só, não concede benefícios. Os valores L6 continuam como referência;os efeitosL7 são apresentados separadamente. Saldo final a pagar/recuperar:âmbitoL8.</p>
|
|
{!i.combinations?<button type="button" onClick={()=>change({combinations:newSpecialCombinations()})}>Preparar cumulações dos tratamentos L7 registados</button>:<fieldset aria-label="Cumulações L7" className="space-y-2 rounded border p-3"><legend>Cumulações — tratamentos, incompatibilidades e limites</legend><p>{specialCombinationsSource} Reutiliza a cooperativa,os activos,as despesas e os períodos já declarados abaixo. Esta preparação discrimina os benefícios em vez da confirmação global de ausência de outros benefícios;conserva os valores anteriores e não altera automaticamente qualificações ou activos.</p><label className="block">Suporte da combinação no sujeito/projecto/período<input aria-label="Suporte da combinação L7" value={i.combinations.reference} onChange={e=>change({combinations:{...i.combinations!,reference:e.target.value}})}/></label><label className="block">Benefícios específicosCBF<select aria-label="Benefícios específicos CBF na combinação" value={i.combinations.specificState} onChange={e=>change({combinations:{...i.combinations!,specificState:e.target.value as ''|'absent'|'known',specificException:''}})}><option value="">Informação em falta</option><option value="absent">Ausência declarada com suporte</option><option value="known">Existem — identificar artigo e âmbito</option></select></label><label className="block">Suporte da presença ou ausência de benefícios específicos<input aria-label="Suporte dos benefícios específicos CBF" value={i.combinations.specificReference} onChange={e=>change({combinations:{...i.combinations!,specificReference:e.target.value}})}/></label>{i.combinations.specificState==='known'&&<label className="block">Excepção expressa à não cumulação<select aria-label="Excepção de cumulação CBF" value={i.combinations.specificException} onChange={e=>change({combinations:{...i.combinations!,specificException:e.target.value as ''|'absent'|'claimed'}})}><option value="">Informação em falta</option><option value="absent">Ausência documentada — combinação incompatível</option><option value="claimed">Excepção invocada — correspondência própria em falta</option></select></label>}<label className="block">Outros benefícios além dos tratamentos registados<select aria-label="Outros benefícios na combinação L7" value={i.combinations.otherState} onChange={e=>change({combinations:{...i.combinations!,otherState:e.target.value as ''|'absent'|'known'}})}><option value="">Informação em falta</option><option value="absent">Ausência declarada com suporte</option><option value="known">Existem — tratamento próprio por corresponder</option></select></label><label className="block">Suporte dos outros benefícios ou ausência<input aria-label="Suporte dos outros benefícios L7" value={i.combinations.otherReference} onChange={e=>change({combinations:{...i.combinations!,otherReference:e.target.value}})}/></label><p>Não somar de novo a amortização contabilística ou a parcela anterior recuperada. A redução cooperativa afecta só a colecta sobre a base elegível;o imposto autónomo continua separado. Uma incompatibilidade não escolhe o benefício a conservar nem apaga o declarado. Outros benefícios/excepções sem regra correspondida mantêm o totalL7 indeterminado.</p><button type="button" onClick={()=>{const next={...i};delete next.combinations;save({...b,special:next});}}>Manter tratamentos sem preparar cumulação</button></fieldset>}
|
|
<fieldset className="space-y-2 rounded border p-3"><legend>Despesas — tributação autónoma61(4)</legend><p>{specialSources.expenses}</p><QualificationFields label="Despesas autónomas" value={i.expenses.qualification} onChange={qualification=>change({expenses:{...i.expenses,qualification}})}/>
|
|
<button type="button" onClick={()=>change({expenses:{...i.expenses,rows:[...i.expenses.rows,...(b.schedules?.rows??[]).filter(r=>r.rule==='undocumented'&&!i.expenses.rows.some(x=>x.scheduleId===r.id)).map(r=>({...newSpecialExpense('expense-'+r.id),scheduleId:r.id}))]}})}>Reutilizar despesas36g de L6 para qualificar</button>
|
|
{i.expenses.rows.map((r,index)=>{const origin=b.schedules?.rows.find(x=>x.id===r.scheduleId),patch=(p:Partial<typeof r>)=>change({expenses:{...i.expenses,rows:i.expenses.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>{origin?origin.document+'/'+origin.line:'Parcela por corresponder'} — {origin?.amount??'Indeterminado'} MZN</legend><p>O acréscimo36g já pertence aL6;esta linha só apura o eventual imposto autónomo.</p><label className="block">Natureza legal da despesa<select aria-label={'Natureza da despesa '+(index+1)} value={r.nature} onChange={e=>patch({nature:e.target.value as typeof r.nature})}><option value="">Informação em falta</option><option value="undocumented">Encargo não devidamente documentado</option><option value="confidential">Despesa confidencial</option><option value="illicit">Despesa ilícita</option><option value="other">Outra natureza — correspondência própria</option></select></label>{yesno('Incidência61(4) — parcela '+(index+1),r.eligible,eligible=>patch({eligible}))}<label className="block">Suporte da classificação e incidência<input aria-label={'Suporte da despesa '+(index+1)} value={r.reference} onChange={e=>patch({reference:e.target.value})}/></label><button type="button" onClick={()=>change({expenses:{...i.expenses,rows:i.expenses.rows.filter(x=>x.id!==r.id)}})}>Retirar qualificação da parcela {index+1}</button></fieldset>;})}</fieldset>
|
|
<fieldset className="space-y-2 rounded border p-3"><legend>Cooperativas abrangidas — artigo12</legend><p>{specialSources.cooperative} Este incremento exige base integralmente elegível;parcelas mistas continuam explícitas.</p><QualificationFields label="Cooperativa" value={i.cooperative.qualification} onChange={qualification=>change({cooperative:{...i.cooperative,qualification}})}/>{(i.cooperative.qualification.state==='known'||i.cooperative.kind!==''||i.cooperative.allIncomeEligible!==''||i.cooperative.withheldIncome!=='')&&<><label className="block">Tipo de cooperativa documentado<select value={i.cooperative.kind} onChange={e=>change({cooperative:{...i.cooperative,kind:e.target.value as typeof i.cooperative.kind}})}><option value="">Por identificar</option><option value="agrarian">Agrária</option><option value="craft">Artesanato</option><option value="cultural">Cultural</option><option value="other">Outra — não correspondida</option></select></label>{yesno('Base ordinária integralmente elegível',i.cooperative.allIncomeEligible,allIncomeEligible=>change({cooperative:{...i.cooperative,allIncomeEligible}}))}<label className="block">Rendimentos sujeitos a retenção<select value={i.cooperative.withheldIncome} onChange={e=>change({cooperative:{...i.cooperative,withheldIncome:e.target.value as typeof i.cooperative.withheldIncome}})}><option value="">Informação em falta</option><option value="absent">Ausência documentada</option><option value="known">Existem — segregação própria em falta</option></select></label></>}</fieldset>
|
|
<fieldset className="space-y-2 rounded border p-3"><legend>Amortizações aceleradas — CBF16</legend><p>{i.combinations?specialAccelerationSource(i):i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialSources.acceleration} Apenas activos já suportados e método constante;prepare explicitamente o recorte anual ou de início de actividade.</p><QualificationFields label="Amortização acelerada" value={i.acceleration.qualification} onChange={qualification=>change({acceleration:{...i.acceleration,qualification}})}/>{i.acceleration.qualification.state==='known'&&<>{yesno('EnquadramentoCBF3 e pressupostos9 documentados',i.acceleration.cbfRequirements,cbfRequirements=>change({acceleration:{...i.acceleration,cbfRequirements}}))}<label className="block">ReconhecimentoCBF10<select value={i.acceleration.recognition} onChange={e=>change({acceleration:{...i.acceleration,recognition:e.target.value as typeof i.acceleration.recognition}})}><option value="">Informação em falta</option><option value="documented">Instrumento/reconhecimento documentado</option><option value="not_required">Excepção prevista — identificar norma e suporte</option></select></label><label className="block">Instrumento ou norma da excepção<input value={i.acceleration.recognitionReference} onChange={e=>change({acceleration:{...i.acceleration,recognitionReference:e.target.value}})}/></label>{!i.combinations&&yesno('Ausência documentada de outros benefícios a cumular',i.acceleration.noOtherBenefits,noOtherBenefits=>change({acceleration:{...i.acceleration,noOtherBenefits}}))}</>}
|
|
{!i.acceleration.monthly?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{version:1,reference:''}}})}>Preparar CBF16 com duodécimos</button>:<fieldset aria-label="CBF16 por duodécimos" className="space-y-2 rounded border p-2"><legend>CBF16 — reutilizar duodécimos L6</legend><p>{i.combinations?specialAccelerationSource(i):i.acceleration.monthly.continuation?specialCessationPriorSource:i.acceleration.monthly.startPeriod?specialStartPeriodSource:specialMonthlySource} Conserva datas,opção mensal e valores do activo existente;preencha no mapaL6 apenas o que falta ou precisa de correcção.</p><label className="block">Suporte da reutilização dos duodécimos L6<input aria-label="Suporte dos duodécimos CBF16" value={i.acceleration.monthly.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,reference:e.target.value}}})}/></label>{!i.acceleration.monthly.continuation&&(!i.acceleration.monthly.startPeriod?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:''}}}})}>Preparar CBF16 para início de actividade</button>:<fieldset aria-label="CBF16 no início de actividade" className="rounded border p-2"><legend>CBF16 — período curto de início de actividade</legend><p>Período L6:{b.periodsFx?.from||'Por identificar'} a {b.periodsFx?.to||'Por identificar'};tipo:{b.periodsFx?.kind||'Por identificar'}. Reutiliza o período declarado;esta opção não altera datas,activos ou enquadramento.</p><label className="block">Suporte do início de actividade<input aria-label="Suporte do início de actividade CBF16" value={i.acceleration.monthly.startPeriod.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:e.target.value}}}})}/></label><button type="button" disabled={b.periodsFx?.kind!=='start'||!b.periodsFx.reference.trim()} onClick={()=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:b.periodsFx!.reference}}}})}>Reutilizar suporte do início de actividade L6</button><button type="button" onClick={()=>{const monthly={...i.acceleration.monthly!};delete monthly.startPeriod;change({acceleration:{...i.acceleration,monthly}});}}>Manter duodécimos no exercício anual</button></fieldset>)}{!i.acceleration.monthly.startPeriod&&(!i.acceleration.monthly.continuation?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,continuation:{version:1,period:'',reference:'',historyReference:'',monthlyFromStart:''}}}})}>Preparar CBF16 para cessação e quotas anteriores</button>:<fieldset aria-label="CBF16 cessação e quotas anteriores" className="rounded border p-2"><legend>CBF16 — cessação e quotas anteriores</legend><p>{specialCessationPriorSource} Reutiliza o mapaL6;nenhum saldo declarado ou histórico é alterado. PeríodoL6:{b.periodsFx?.from||b.year+'-01-01'} a {b.periodsFx?.to||b.year+'-12-31'};tipo:{b.periodsFx?.kind||'anual'}. Corrija dados de origem nos mapasL6 quando necessário.</p><label className="block">Período a qualificar<select aria-label="Período CBF16 cessação e quotas anteriores" value={i.acceleration.monthly.continuation.period} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,continuation:{...i.acceleration.monthly!.continuation!,period:e.target.value as 'annual'|'cessation'|''}}}})}><option value="">Informação em falta</option><option value="annual">Ano civil2026 — activo e quotas anteriores</option><option value="cessation">Cessação da actividade em2026 — período curtoL6</option></select></label><label className="block">Suporte do período e condiçõesCBF16<input aria-label="Suporte CBF16 cessação" value={i.acceleration.monthly.continuation.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,continuation:{...i.acceleration.monthly!.continuation!,reference:e.target.value}}}})}/></label><label className="block">Suporte das quotas anteriores ou ausência e da aplicaçãoCBF16<input aria-label="Suporte das quotas anteriores CBF16" value={i.acceleration.monthly.continuation.historyReference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,continuation:{...i.acceleration.monthly!.continuation!,historyReference:e.target.value}}}})}/></label>{yesno('Duodécimos desde a entrada emuso documentados',i.acceleration.monthly.continuation.monthlyFromStart,monthlyFromStart=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,continuation:{...i.acceleration.monthly!.continuation!,monthlyFromStart}}}}))}<p>Cessação da actividade e saída do activo são eventos distintos. Só a saída documentada emL6 exclui o mês do evento. A recuperação de quotas anteriores é aritmética para esta simulação;saldo oficial não é consumido.</p><button type="button" onClick={()=>{const monthly={...i.acceleration.monthly!};delete monthly.continuation;change({acceleration:{...i.acceleration,monthly}});}}>Manter recorte anual sem quotas anteriores</button></fieldset>)}<p>Não altera automaticamente o activo nem transforma ausência de documentos em elegibilidade. A versão anterior permanece no histórico depois de calcular e guardar.</p><button type="button" onClick={()=>{const acceleration={...i.acceleration};delete acceleration.monthly;change({acceleration});}}>Manter preparação anual CBF16</button></fieldset>}
|
|
<button type="button" onClick={()=>change({acceleration:{...i.acceleration,rows:[...i.acceleration.rows,newAcceleratedAsset(crypto.randomUUID())]}})}>Qualificar activo existente paraCBF16</button>{i.acceleration.rows.map((r,index)=>{const patch=(p:Partial<typeof r>)=>change({acceleration:{...i.acceleration,rows:i.acceleration.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>Activo qualificado {index+1}</legend><label className="block">Activo do mapaL6<select value={r.assetId} onChange={e=>patch({assetId:e.target.value})}><option value="">Seleccione activo existente</option>{b.depreciation?.assets.map(a=><option key={a.id} value={a.id}>{a.description} — {a.assetKey}</option>)}</select></label><label className="block">Afectação documentada<select value={r.kind} onChange={e=>patch({kind:e.target.value as typeof r.kind})}><option value="">Por identificar</option><option value="new_project_building">Imóvel novo utilizado no projecto</option><option value="rehabilitated_project_building">Imóvel reabilitado — regra ainda por corresponder</option><option value="industrial_equipment">Máquina/equipamento industrial</option><option value="agroindustrial_equipment">Máquina/equipamento agro-industrial</option><option value="other">Outro — correspondência própria</option></select></label>{yesno('Condições do activoCBF16 — '+(index+1),r.eligible,eligible=>patch({eligible}))}<label className="block">Prova da afectação e condições do activo<input value={r.reference} onChange={e=>patch({reference:e.target.value})}/></label><button type="button" onClick={()=>change({acceleration:{...i.acceleration,rows:i.acceleration.rows.filter(x=>x.id!==r.id)}})}>Retirar tratamento do activo {index+1}</button></fieldset>;})}</fieldset>
|
|
<p>Use «Calcular e guardar» abaixo para gravar uma nova versão e apresentar os efeitos. Os históricos anteriores são conservados. M20/M22 mantém apenas correspondências demonstradas;camposL7 não são preenchidos por aproximação.</p></section>;
|
|
}
|