fix: keep opportunity preparation focused on the pending selection
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## Continuidade entre escolha e preparação — 23/10/2026
|
||||
|
||||
Falha reportada pela Tempo: depois de criar o dossier para Florestas/Beira, o editor começava por uma avaliação do estado ainda não confirmado (`contexto v0`). A escolha solicitada vinha separadamente em `source`/`requestedChoices`; não era outro investimento, mas a avaliação do estado guardado vazio. A ligação do quadro voltava ao editor sem os parâmetros da escolha pendente, permitindo perder essa referência antes de confirmar. Não foi identificada nem corrigida por escrita uma troca de dados entre dossiers.
|
||||
|
||||
O editor passa a começar por **Preparar investimento**, pela oportunidade e pela localização por ficha. Retirado o quadro de avaliação do estado guardado deste ecrã de edição, incluindo a ligação que descartava a escolha pendente. Requisitos guardados só são apresentados quando correspondem às escolhas que estão no formulário. Botões coerentes: **Criar dossier e preparar investimento**, **Rever dados antes de guardar**, **Guardar dados do investimento**. A confirmação continua necessária para guardar alterações; abrir uma referência não altera o dossier.
|
||||
|
||||
Nos consumidores de avaliação, retirados números internos de contexto/versão, explicações de implementação, quadro genérico de ausência de zona e elementos sem título na lista de etapas. Mantidas condições úteis, fontes oficiais e limites efectivos do simulador. O exemplo real foi recuperado por GET com `source=opportunity:53` no dossier indicado pela Tempo, sem guardar ou criar dados. Teste QA isolado: 20 asserções sobre recuperação da escolha e igualdade do contexto antes/depois da abertura. Tipos e build conferidos; sem migrations ou alterações de cálculo, autorização ou dados activos.
|
||||
|
||||
## Dossier orientado ao investidor — 23/10/2026
|
||||
|
||||
Correcção da apresentação solicitada pela Tempo: o componente partilhado de documentos do contexto mostrava, por defeito, identidade técnica, versões, hashes, notas de permissões e mensagens de ausência de suporte. Era reutilizado na entrada, edição e consulta do dossier; a entrada repetia ainda a mesma oportunidade num bloco de origem técnica.
|
||||
|
||||
@@ -61,6 +61,8 @@ export function InvestmentContextAssessment({
|
||||
if (!snapshot) return null;
|
||||
const fields = snapshot.fields ?? {};
|
||||
const zone = snapshot.zone;
|
||||
const steps = (snapshot.path?.steps ?? []).filter(step => !!step.title?.trim());
|
||||
if (!zone && !fields.investor_origin && !fields.tax_residency_country && !snapshot.preparation?.enabled) return null;
|
||||
const regime = zone?.fields?.investment_regime ?? fields.investment_regime;
|
||||
const names: Record<string, string> = {
|
||||
zee: 'ZEE',
|
||||
@@ -73,10 +75,9 @@ export function InvestmentContextAssessment({
|
||||
aria-label="Aplicabilidade ao investimento"
|
||||
>
|
||||
<h3 className="font-semibold">
|
||||
Aplicabilidade ao investimento · contexto v
|
||||
{snapshot.context_revision}
|
||||
Condições do investimento
|
||||
</h3>
|
||||
<p>
|
||||
{(fields.investor_origin || fields.tax_residency_country) && <p>
|
||||
Origem do investidor:{' '}
|
||||
{{
|
||||
national: 'nacional',
|
||||
@@ -84,9 +85,8 @@ export function InvestmentContextAssessment({
|
||||
mixed: 'misto',
|
||||
}[fields.investor_origin] ?? 'informação em falta'}
|
||||
. Residência fiscal declarada:{' '}
|
||||
{fields.tax_residency_country || 'informação em falta'}. Estas
|
||||
condições são distintas; uma não determina a outra.
|
||||
</p>
|
||||
{fields.tax_residency_country || 'Por preencher'}.
|
||||
</p>}
|
||||
{zone ? (
|
||||
<>
|
||||
<p>
|
||||
@@ -95,8 +95,7 @@ export function InvestmentContextAssessment({
|
||||
{names[regime ?? ''] ?? 'zona seleccionada'}.
|
||||
</strong>{' '}
|
||||
<Link className="underline" href={zone.href}>
|
||||
{zone.title} · versão {zone.version} · consultar
|
||||
ficha e fundamentos
|
||||
{zone.title} · consultar condições
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
@@ -162,39 +161,19 @@ export function InvestmentContextAssessment({
|
||||
</p>
|
||||
)}
|
||||
<p role="status">
|
||||
Benefício fiscal deste enquadramento: indeterminado. Não
|
||||
existe neste consumidor uma regra executável ligada ao
|
||||
reconhecimento e condições da zona. No simulador, o IVA
|
||||
disponível corresponde apenas ao cenário geral sem
|
||||
benefício, sobre vendas declaradas compatíveis; não
|
||||
simula o regime especial. O cenário com benefício e o
|
||||
apuramento final estão indisponíveis.
|
||||
Os benefícios desta zona ainda não estão disponíveis no simulador. O cenário geral disponível não inclui benefícios especiais nem apuramento fiscal final.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
Enquadramento geral de preparação: sem ZEE, ZFI ou parque
|
||||
seleccionado. Isso não confirma a elegibilidade para uma
|
||||
operação fiscal. O cálculo mantém o âmbito e as exclusões da
|
||||
versão publicada.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
Confirmado neste quadro significa apenas dado recuperado do
|
||||
dossier; declarações e referências não são decisões jurídicas.
|
||||
Requisitos específicos não documentados permanecem
|
||||
indeterminados.
|
||||
</p>
|
||||
{snapshot.path && (
|
||||
) : null}
|
||||
{!!steps.length && (
|
||||
<details>
|
||||
<summary>
|
||||
Passos adaptados pelo percurso existente · versão{' '}
|
||||
{snapshot.path.definition_version}
|
||||
Etapas a preparar
|
||||
</summary>
|
||||
<ul className="list-disc pl-5">
|
||||
{snapshot.path.steps.map((step) => (
|
||||
{steps.map((step) => (
|
||||
<li key={step.code}>
|
||||
{step.code} — {step.title}:{' '}
|
||||
{step.title}:{' '}
|
||||
{step.context_review_required
|
||||
? 'informação alterada; rever fundamento'
|
||||
: step.applicability ===
|
||||
@@ -224,7 +203,7 @@ export function InvestmentContextAssessment({
|
||||
className="underline"
|
||||
href={`/investimentos/${contextId}/contexto/editar`}
|
||||
>
|
||||
Alterar contexto e comparar impacto antes de guardar
|
||||
Rever dados do investimento
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function ContextStart({
|
||||
>
|
||||
{step && !source
|
||||
? 'Abrir ' + step + ' no mesmo dossier'
|
||||
: 'Comparar no mesmo dossier'}
|
||||
: 'Preparar neste dossier'}
|
||||
</Link>
|
||||
)}
|
||||
<p>
|
||||
@@ -120,7 +120,7 @@ export default function ContextStart({
|
||||
Autorizo reutilizar o meu perfil neste dossier privado.
|
||||
</label>
|
||||
<button className={button} disabled={!consent || busy}>
|
||||
Criar dossier e comparar a referência
|
||||
Criar dossier e preparar investimento
|
||||
</button>
|
||||
{error && <p role="alert">{error}</p>}
|
||||
</form>
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
PreparationEditor,
|
||||
PreparationImpact,
|
||||
} from '@/components/private-investment-preparation';
|
||||
import { InvestmentContextAssessment } from '@/components/investment-context-assessment';
|
||||
import ContextDocuments from '@/components/context-documents';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { useState } from 'react';
|
||||
@@ -167,7 +166,7 @@ export default function ContextEditor({
|
||||
setPlan(null);
|
||||
setOperation(createOperationKey());
|
||||
setMessage(
|
||||
'Contexto guardado no mesmo dossier. Resultados afetados identificados para revisão; conteúdo submetido e histórico preservados.',
|
||||
'Dados guardados. Pode continuar a preparação do investimento.',
|
||||
);
|
||||
} catch (e) {
|
||||
setError(
|
||||
@@ -180,28 +179,18 @@ export default function ContextEditor({
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Frame title="Contexto do investimento">
|
||||
<InvestmentContextAssessment
|
||||
contextId={view.case.id}
|
||||
raw={view.prefill.simulator?.context_applicability_snapshot}
|
||||
/>
|
||||
<ContextDocuments source={view.context.selection.opportunity} />
|
||||
<InvestmentRequirements assessment={view.context.requirements_assessment} contextId={view.case.id}/>
|
||||
<Frame title="Preparar investimento">
|
||||
<div className="space-y-5 [overflow-wrap:anywhere]">
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/investimentos/${view.case.id}`}
|
||||
>
|
||||
Voltar ao mesmo dossier
|
||||
Voltar ao dossier
|
||||
</Link>
|
||||
<p>
|
||||
{view.case.title}
|
||||
</p>
|
||||
<p className="rounded border border-emerald-300 bg-emerald-50 p-3">
|
||||
As referências apoiam a preparação. Associar uma
|
||||
oportunidade, sector ou localização não confirma incentivos,
|
||||
licença, terra disponível, reserva ou autorização.
|
||||
</p>
|
||||
<p>Confirme as oportunidades e a localização, complete os dados disponíveis e reveja antes de guardar.</p>
|
||||
{error && (
|
||||
<div
|
||||
role="alert"
|
||||
@@ -220,9 +209,11 @@ export default function ContextEditor({
|
||||
{message && <p role="status">{message}</p>}
|
||||
<section className={card + ' space-y-4'}>
|
||||
<OpportunityChoices choices={choices} disabled={busy} onChange={next=>{setChoices(next);setManageChoices(true);setPlan(null);}}/>
|
||||
<ContextDocuments source={source ?? view.context.selection.opportunity} />
|
||||
{view.context.revision > 0 && !Object.keys(changes).length && JSON.stringify(choices) === JSON.stringify(view.context.opportunity_choices ?? []) && <InvestmentRequirements assessment={view.context.requirements_assessment} contextId={view.case.id}/>}
|
||||
<label className="block">Acrescentar uma oportunidade à escolha<select className={input} value="" onChange={e=>{const o=options.opportunities.find(o=>o.key===e.target.value);if(o && !choices.some(c=>c.opportunity.key===o.key)){setChoices([...choices,{opportunity:{...o,id:o.key.split(':')[1],href:'/oportunidades',version:null,sha256:'',fields:{}},territory:null,location_reason:'',location_state:'undetermined'}]);setManageChoices(true);setPlan(null);}}}><option value="">Seleccionar ficha</option>{options.opportunities.filter(o=>o.kind==='opportunity'&&!choices.some(c=>c.opportunity.key===o.key)).map(o=><option key={o.key} value={o.key}>{o.title}</option>)}</select></label>
|
||||
<h2 className="text-xl font-bold">
|
||||
Escolher referências existentes
|
||||
Dados do investimento
|
||||
</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<label className="min-w-0 flex-1">
|
||||
@@ -574,7 +565,7 @@ export default function ContextEditor({
|
||||
disabled={busy}
|
||||
onClick={() => void preview()}
|
||||
>
|
||||
Comparar impacto antes de guardar
|
||||
Rever dados antes de guardar
|
||||
</button>
|
||||
</section>
|
||||
{plan && (
|
||||
@@ -706,7 +697,7 @@ export default function ContextEditor({
|
||||
disabled={busy}
|
||||
onClick={() => void confirm()}
|
||||
>
|
||||
Confirmar e guardar o contexto
|
||||
Guardar dados do investimento
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -33,6 +33,20 @@ final class InvestmentOpportunityChoicesTest extends TestCase
|
||||
return $this->postJson('/investimentos', ['title' => 'QA: investimento conjunto', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
||||
}
|
||||
|
||||
public function test_edit_recovers_requested_opportunity_without_presenting_it_as_already_saved(): void
|
||||
{
|
||||
$record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail();
|
||||
$id = $this->dossier();
|
||||
$before = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context');
|
||||
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$record->id)->assertOk()
|
||||
->assertInertia(fn (\Inertia\Testing\AssertableInertia $page) => $page->component('investor-path/context')
|
||||
->where('source.key', 'opportunity:'.$record->id)
|
||||
->where('requestedChoices.0.opportunity.key', 'opportunity:'.$record->id)
|
||||
->where('requestedChoices.0.opportunity.title', $record->title));
|
||||
$after = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context');
|
||||
$this->assertSame($before, $after);
|
||||
}
|
||||
|
||||
public function test_multiple_opportunities_keep_sites_sources_requirements_and_results_in_one_dossier(): void
|
||||
{
|
||||
$entries = array_slice(app(CorridorAgribusinessOpportunities::class)->entries(), 0, 2);
|
||||
|
||||
Reference in New Issue
Block a user