667 lines
35 KiB
TypeScript
667 lines
35 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
digitalBook,
|
|
digitalConditions,
|
|
digitalCurrent,
|
|
digitalReportRows,
|
|
digitalSource,
|
|
digitalTreatments,
|
|
newDigitalRow,
|
|
recordDigital,
|
|
saveDigitalBook,
|
|
switchDigitalKind,
|
|
switchDigitalTreatment,
|
|
type DigitalBook,
|
|
type DigitalRow,
|
|
} from '@/lib/iva-digital';
|
|
|
|
export function SimulationDigital({
|
|
draft,
|
|
onChange,
|
|
}: {
|
|
draft: Record<string, unknown>;
|
|
onChange: (field: string, value: string) => void;
|
|
}) {
|
|
const [error, setError] = useState('');
|
|
let book: DigitalBook;
|
|
try {
|
|
book = digitalBook(draft);
|
|
} catch (e) {
|
|
return <p role="alert">{String(e)}</p>;
|
|
}
|
|
const save = (next: DigitalBook) => {
|
|
try {
|
|
onChange('modelo_a_snapshot', saveDigitalBook(draft, next));
|
|
setError('');
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
};
|
|
const change = (id: string, patch: Partial<DigitalRow>) =>
|
|
save({
|
|
...book,
|
|
rows: book.rows.map((r) => (r.id === id ? { ...r, ...patch } : r)),
|
|
});
|
|
return (
|
|
<details className="space-y-3 rounded border p-3">
|
|
<summary>
|
|
Bens e serviços digitais — natureza, localização e obrigações
|
|
</summary>
|
|
<p>
|
|
Distinga o activo transmitido da funcionalidade prestada. O nome
|
|
comercial, a moeda, a nacionalidade ou um servidor não
|
|
determinam sozinhos o tratamento. Reutilize as referências da
|
|
operação e os documentos disponíveis.
|
|
</p>
|
|
<p>
|
|
Localização não concede isenção nem calcula IVA ou dedução. A
|
|
identificação do responsável não apresenta uma declaração nem
|
|
inicia pagamento. As formalidades remetidas para regulamentação
|
|
ficam assinaladas.
|
|
</p>
|
|
<p>
|
|
<strong>Cobertura digital deste incremento:</strong>{' '}
|
|
identificação da natureza, localização, responsável, rascunho,
|
|
histórico e relatório. As formalidades regulamentares ainda não
|
|
cotejadas — modelos da declaração e do mapa de retenções,
|
|
procedimentos de entrega e pagamento — não estão automatizadas.
|
|
Não representa cobertura digital integral nem calcula imposto ou
|
|
dedução.
|
|
</p>
|
|
<p>{digitalSource}</p>
|
|
{error && <p role="alert">{error}</p>}
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
onClick={() =>
|
|
save({
|
|
...book,
|
|
rows: [
|
|
...book.rows,
|
|
newDigitalRow(crypto.randomUUID()),
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Acrescentar operação digital
|
|
</button>
|
|
{book.rows.map((row) => (
|
|
<details key={row.id} className="rounded border p-3">
|
|
<summary>
|
|
{row.description || 'Operação digital por identificar'}{' '}
|
|
— {row.period || 'mês por indicar'}
|
|
</summary>
|
|
<div className="grid gap-3 md:grid-cols-2">
|
|
<label>
|
|
Natureza da operação digital
|
|
<select
|
|
value={row.kind}
|
|
onChange={(e) =>
|
|
change(
|
|
row.id,
|
|
switchDigitalKind(row, e.target.value),
|
|
)
|
|
}
|
|
>
|
|
<option value="">Por esclarecer</option>
|
|
<option value="goods">
|
|
P130 — Transmissão de bem digital
|
|
</option>
|
|
<option value="service">
|
|
P121 — Prestação de serviço digital
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{row.kind && (
|
|
<label>
|
|
Tratamento digital
|
|
<select
|
|
value={row.treatment}
|
|
onChange={(e) =>
|
|
change(
|
|
row.id,
|
|
switchDigitalTreatment(
|
|
row,
|
|
e.target.value,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
<option value="">Por indicar</option>
|
|
{digitalTreatments
|
|
.filter((t) => t.kind === row.kind)
|
|
.map((t) => (
|
|
<option key={t.id} value={t.id}>
|
|
{t.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
)}
|
|
<label>
|
|
Natureza dos dados
|
|
<select
|
|
value={row.origin}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
origin: e.target
|
|
.value as DigitalRow['origin'],
|
|
})
|
|
}
|
|
>
|
|
<option value="projected">
|
|
Projectados / pressupostos
|
|
</option>
|
|
<option value="effective">
|
|
Efectivos declarados
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{(
|
|
[
|
|
[
|
|
'description',
|
|
'Operação digital identificada',
|
|
],
|
|
[
|
|
'operationRef',
|
|
'Referência da operação / pressuposto',
|
|
],
|
|
[
|
|
'operationDate',
|
|
'Data da operação / prevista',
|
|
],
|
|
['period', 'Mês da operação'],
|
|
[
|
|
'subjectRef',
|
|
'Objecto, direitos transmitidos ou funcionalidade prestada',
|
|
],
|
|
[
|
|
'natureRef',
|
|
'Fundamento da distinção bem/serviço e natureza digital',
|
|
],
|
|
[
|
|
'useRef',
|
|
'Destinatário e utilização / pressuposto',
|
|
],
|
|
[
|
|
'documentRef',
|
|
'Documento existente / referência da previsão',
|
|
],
|
|
[
|
|
'documentDate',
|
|
'Data do documento / prevista',
|
|
],
|
|
[
|
|
'scope',
|
|
'Âmbito: operação, bens/serviços, período e condições',
|
|
],
|
|
[
|
|
'evidence',
|
|
'Factos disponíveis / pressupostos expressos',
|
|
],
|
|
[
|
|
'buyerPlace',
|
|
'Estabelecimento ou domicílio do adquirente',
|
|
],
|
|
[
|
|
'buyerRef',
|
|
'Referência do estabelecimento ou domicílio adquirente',
|
|
],
|
|
] as const
|
|
).map(([field, label]) => (
|
|
<label key={field}>
|
|
{label}
|
|
<input
|
|
type={
|
|
[
|
|
'operationDate',
|
|
'documentDate',
|
|
].includes(field)
|
|
? 'date'
|
|
: field === 'period'
|
|
? 'month'
|
|
: 'text'
|
|
}
|
|
value={row[field]}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
[field]: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
<label>
|
|
País do estabelecimento ou domicílio adquirente
|
|
<select
|
|
value={row.buyerCountry}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
buyerCountry: e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por esclarecer</option>
|
|
<option value="MZ">Moçambique</option>
|
|
<option value="foreign">Estrangeiro</option>
|
|
</select>
|
|
</label>
|
|
{row.buyerCountry !== 'foreign' && (
|
|
<>
|
|
<label>
|
|
Sede, EP ou domicílio a partir do qual o
|
|
fornecedor opera
|
|
<input
|
|
value={row.providerPlace}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerPlace: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Referência desse estabelecimento fornecedor
|
|
<input
|
|
value={row.providerRef}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerRef: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
País do estabelecimento fornecedor
|
|
<select
|
|
value={row.providerCountry}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerCountry: e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por esclarecer</option>
|
|
<option value="MZ">Moçambique</option>
|
|
<option value="foreign">
|
|
Estrangeiro
|
|
</option>
|
|
</select>
|
|
</label>
|
|
</>
|
|
)}
|
|
{row.buyerCountry === 'MZ' &&
|
|
row.providerCountry === 'foreign' && (
|
|
<>
|
|
<label>
|
|
Adquirente sujeito passivo nacional
|
|
—26(4)
|
|
<select
|
|
value={row.buyerTaxable}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
buyerTaxable:
|
|
e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">Sim</option>
|
|
<option value="no">Não</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Fundamento da qualidade fiscal do
|
|
adquirente
|
|
<input
|
|
value={row.buyerTaxRef}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
buyerTaxRef: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<p>
|
|
A qualidade fiscal serve para
|
|
identificar o responsável; a sua falta
|
|
não transforma a localização nacional em
|
|
operação estrangeira.
|
|
</p>
|
|
{row.buyerTaxable === 'no' && (
|
|
<>
|
|
<label>
|
|
Fornecedor não residente sujeito
|
|
passivo
|
|
<select
|
|
value={row.providerTaxable}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerTaxable:
|
|
e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">
|
|
Sim
|
|
</option>
|
|
<option value="no">
|
|
Não
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Fundamento da qualidade fiscal
|
|
do fornecedor não residente
|
|
<input
|
|
value={row.providerTaxRef}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerTaxRef:
|
|
e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Representante residente nomeado
|
|
—26(1)
|
|
<select
|
|
value={row.representative}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
representative:
|
|
e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">
|
|
Sim
|
|
</option>
|
|
<option value="no">
|
|
Não
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{row.representative === 'yes' && (
|
|
<>
|
|
<label>
|
|
Representante e
|
|
referência da
|
|
procuração/poderes
|
|
<input
|
|
value={
|
|
row.representativeRef
|
|
}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
representativeRef:
|
|
e.target
|
|
.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Nomeação comunicada à
|
|
contraparte antes da
|
|
operação —26(2)
|
|
<select
|
|
value={
|
|
row.communicationPrior
|
|
}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
communicationPrior:
|
|
e.target
|
|
.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">
|
|
Sim
|
|
</option>
|
|
<option value="no">
|
|
Não
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Referência da
|
|
comunicação
|
|
<input
|
|
value={
|
|
row.communicationRef
|
|
}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
communicationRef:
|
|
e.target
|
|
.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
</>
|
|
)}
|
|
{row.representative === 'no' && (
|
|
<>
|
|
<label>
|
|
Aquisição no exercício
|
|
de actividade comercial,
|
|
industrial ou
|
|
profissional —26(3)
|
|
<select
|
|
value={
|
|
row.businessUse
|
|
}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
businessUse:
|
|
e.target
|
|
.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">
|
|
Sim
|
|
</option>
|
|
<option value="no">
|
|
Não
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Referência dessa
|
|
actividade e aquisição
|
|
<input
|
|
value={
|
|
row.businessRef
|
|
}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
businessRef:
|
|
e.target
|
|
.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
{row.buyerCountry === 'MZ' &&
|
|
row.providerCountry === 'MZ' && (
|
|
<>
|
|
<label>
|
|
Fornecedor sujeito passivo nacional
|
|
<select
|
|
value={row.providerTaxable}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerTaxable:
|
|
e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por esclarecer
|
|
</option>
|
|
<option value="yes">Sim</option>
|
|
<option value="no">Não</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Fundamento da qualidade fiscal do
|
|
fornecedor
|
|
<input
|
|
value={row.providerTaxRef}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
providerTaxRef:
|
|
e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
{row.kind === 'goods' && (
|
|
<>
|
|
<label>
|
|
Local do bem na colocação à
|
|
disposição —6(1)
|
|
<select
|
|
value={row.goodsPlace}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
goodsPlace:
|
|
e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por demonstrar
|
|
</option>
|
|
<option value="MZ">
|
|
Moçambique, segundo
|
|
fundamento identificado
|
|
</option>
|
|
<option value="foreign">
|
|
Estrangeiro, segundo
|
|
fundamento identificado
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Fundamento do local do bem
|
|
digital — sem inferência pelo
|
|
servidor
|
|
<input
|
|
value={row.goodsPlaceRef}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
goodsPlaceRef:
|
|
e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
<fieldset>
|
|
<legend>Factos próprios da natureza escolhida</legend>
|
|
{digitalConditions(row.kind).map(([key, label]) => (
|
|
<label className="block" key={key}>
|
|
{label}
|
|
<select
|
|
value={row.conditions[key] ?? ''}
|
|
onChange={(e) =>
|
|
change(row.id, {
|
|
conditions: {
|
|
...row.conditions,
|
|
[key]: e.target.value as
|
|
| ''
|
|
| 'yes'
|
|
| 'no',
|
|
},
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por esclarecer</option>
|
|
<option value="yes">Sim</option>
|
|
<option value="no">Não</option>
|
|
</select>
|
|
</label>
|
|
))}
|
|
</fieldset>
|
|
<p>
|
|
Mudar natureza ou tratamento conserva referências e
|
|
campos ocultos; reveja as confirmações próprias. Os
|
|
dados não são copiados de outra oportunidade.
|
|
</p>
|
|
</details>
|
|
))}
|
|
{!!book.rows.length && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
try {
|
|
save(recordDigital(draft, book));
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
}}
|
|
>
|
|
Classificar operações digitais e guardar versão
|
|
</button>
|
|
)}
|
|
{book.result && (
|
|
<>
|
|
<p role="status">
|
|
{digitalCurrent(draft, book)
|
|
? 'Resultados correspondem às entradas guardadas.'
|
|
: 'Entradas alteradas; resultados históricos conservados. Reveja o delta afectado.'}{' '}
|
|
{book.history?.length ?? 0} versões anteriores. Sem
|
|
cálculo de IVA ou dedução.
|
|
</p>
|
|
<dl>
|
|
{digitalReportRows(draft).map(
|
|
([, label, value, origin], i) => (
|
|
<div key={i}>
|
|
<dt>{label}</dt>
|
|
<dd>
|
|
{value}
|
|
<small className="block">
|
|
{origin}
|
|
</small>
|
|
</dd>
|
|
</div>
|
|
),
|
|
)}
|
|
</dl>
|
|
</>
|
|
)}
|
|
</details>
|
|
);
|
|
}
|