Conservar progresso editorial entre renovações MFA e tornar a retoma visível
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -97,7 +97,7 @@ final class FundingProgrammeReferences
|
|||||||
'id' => $row?->id, 'published' => $row?->published_version_id !== null];
|
'id' => $row?->id, 'published' => $row?->published_version_id !== null];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['dataset_hash' => $package['sha256'], 'items' => $items];
|
return ['dataset_hash' => $package['sha256'], 'editor_id' => $actor->id, 'items' => $items];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array{id:string,created:bool} */
|
/** @return array{id:string,created:bool} */
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||||
|
|
||||||
|
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||||
|
|
||||||
|
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||||
|
|
||||||
|
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||||
|
|
||||||
|
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||||
|
|
||||||
|
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||||
|
|
||||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||||
|
|
||||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||||
|
|
||||||
|
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||||
|
|
||||||
|
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||||
|
|
||||||
|
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||||
|
|
||||||
|
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||||
|
|
||||||
|
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||||
|
|
||||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||||
|
|
||||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,15 @@
|
|||||||
|
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||||
|
|
||||||
|
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||||
|
|
||||||
|
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||||
|
|
||||||
|
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||||
|
|
||||||
|
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||||
|
|
||||||
|
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||||
|
|
||||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||||
|
|
||||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from "@inertiajs/react";
|
import { Link, router } from "@inertiajs/react";
|
||||||
import { useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
financialCategories,
|
financialCategories,
|
||||||
financialTypes,
|
financialTypes,
|
||||||
@@ -27,21 +27,82 @@ type Receipt = {
|
|||||||
published?: boolean;
|
published?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Assisted({
|
type Progress = {
|
||||||
|
selected: string[];
|
||||||
|
receipts: Record<string, Receipt>;
|
||||||
|
reason: string;
|
||||||
|
pending: Record<string, object>;
|
||||||
|
};
|
||||||
|
const emptyProgress = (): Progress => ({
|
||||||
|
selected: [],
|
||||||
|
receipts: {},
|
||||||
|
reason: "",
|
||||||
|
pending: {},
|
||||||
|
});
|
||||||
|
export function readProgress(key: string): Progress {
|
||||||
|
try {
|
||||||
|
const value = JSON.parse(sessionStorage.getItem(key) ?? "null");
|
||||||
|
if (
|
||||||
|
!value ||
|
||||||
|
!Array.isArray(value.selected) ||
|
||||||
|
!value.selected.every((c: unknown) => typeof c === "string") ||
|
||||||
|
!value.receipts ||
|
||||||
|
typeof value.receipts !== "object" ||
|
||||||
|
typeof value.reason !== "string" ||
|
||||||
|
!value.pending ||
|
||||||
|
typeof value.pending !== "object"
|
||||||
|
)
|
||||||
|
return emptyProgress();
|
||||||
|
return value;
|
||||||
|
} catch {
|
||||||
|
return emptyProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveProgress(key: string, value: Progress): void {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(value));
|
||||||
|
} catch {
|
||||||
|
/* Native receipts remain authoritative if tab storage is unavailable. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AssistedEditor({
|
||||||
batch,
|
batch,
|
||||||
}: {
|
}: {
|
||||||
batch: { dataset_hash: string; items: Item[] };
|
batch: { dataset_hash: string; editor_id: number; items: Item[] };
|
||||||
}) {
|
}) {
|
||||||
const [selected, setSelected] = useState<string[]>([]);
|
const storageKey = `investmoz-funding-progress:${batch.editor_id}:${batch.dataset_hash}`;
|
||||||
const [receipts, setReceipts] = useState<Record<string, Receipt>>({});
|
const [restored] = useState(() => readProgress(storageKey));
|
||||||
|
const [selected, setSelected] = useState<string[]>(
|
||||||
|
restored.selected.filter((c) => batch.items.some((i) => i.code === c)),
|
||||||
|
);
|
||||||
|
const [receipts, setReceipts] = useState<Record<string, Receipt>>(
|
||||||
|
restored.receipts,
|
||||||
|
);
|
||||||
const [reviewed, setReviewed] = useState(false);
|
const [reviewed, setReviewed] = useState(false);
|
||||||
const [confirmed, setConfirmed] = useState(false);
|
const [confirmed, setConfirmed] = useState(false);
|
||||||
const [reason, setReason] = useState("");
|
const [reason, setReason] = useState(restored.reason);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const running = useRef(false);
|
const running = useRef(false);
|
||||||
const stop = useRef(false);
|
const stop = useRef(false);
|
||||||
const pending = useRef<Record<string, object>>({});
|
const pending = useRef<Record<string, object>>(restored.pending);
|
||||||
|
const [needsMfa, setNeedsMfa] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
saveProgress(storageKey, {
|
||||||
|
selected,
|
||||||
|
receipts,
|
||||||
|
reason,
|
||||||
|
pending: pending.current,
|
||||||
|
});
|
||||||
|
}, [storageKey, selected, receipts, reason]);
|
||||||
|
const ready = selected.filter(
|
||||||
|
(c) =>
|
||||||
|
receipts[c]?.matches_reference &&
|
||||||
|
receipts[c]?.publication &&
|
||||||
|
!receipts[c]?.published,
|
||||||
|
).length;
|
||||||
|
const published = selected.filter((c) => receipts[c]?.published).length;
|
||||||
async function run(publish: boolean) {
|
async function run(publish: boolean) {
|
||||||
if (
|
if (
|
||||||
running.current ||
|
running.current ||
|
||||||
@@ -57,6 +118,7 @@ export default function Assisted({
|
|||||||
try {
|
try {
|
||||||
for (const code of selected) {
|
for (const code of selected) {
|
||||||
if (stop.current) break;
|
if (stop.current) break;
|
||||||
|
if (!publish && receipts[code]) continue;
|
||||||
setMessage(
|
setMessage(
|
||||||
`${publish ? "Publicação" : "Preparação"}: ${batch.items.find((i) => i.code === code)?.title}`,
|
`${publish ? "Publicação" : "Preparação"}: ${batch.items.find((i) => i.code === code)?.title}`,
|
||||||
);
|
);
|
||||||
@@ -74,6 +136,12 @@ export default function Assisted({
|
|||||||
confirm: true,
|
confirm: true,
|
||||||
};
|
};
|
||||||
pending.current[code] = body;
|
pending.current[code] = body;
|
||||||
|
saveProgress(storageKey, {
|
||||||
|
selected,
|
||||||
|
receipts,
|
||||||
|
reason,
|
||||||
|
pending: pending.current,
|
||||||
|
});
|
||||||
await profileJson(
|
await profileJson(
|
||||||
`/management/fundos/${receipt.id}/publicar`,
|
`/management/fundos/${receipt.id}/publicar`,
|
||||||
body,
|
body,
|
||||||
@@ -108,6 +176,14 @@ export default function Assisted({
|
|||||||
: "Preparação concluída. Confira os resultados antes de confirmar a publicação.",
|
: "Preparação concluída. Confira os resultados antes de confirmar a publicação.",
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setNeedsMfa(
|
||||||
|
!!error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"status" in error &&
|
||||||
|
(error.status === 401 ||
|
||||||
|
error.status === 403 ||
|
||||||
|
error.status === 419),
|
||||||
|
);
|
||||||
setMessage(
|
setMessage(
|
||||||
`${error instanceof Error ? error.message : "Operação interrompida."} Os resultados anteriores foram conservados. Se a sessão ou MFA expirou, autentique-se no portal e retome. A publicação incerta pode ser repetida com a mesma confirmação.`,
|
`${error instanceof Error ? error.message : "Operação interrompida."} Os resultados anteriores foram conservados. Se a sessão ou MFA expirou, autentique-se no portal e retome. A publicação incerta pode ser repetida com a mesma confirmação.`,
|
||||||
);
|
);
|
||||||
@@ -145,71 +221,94 @@ export default function Assisted({
|
|||||||
setConfirmed(false);
|
setConfirmed(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Seleccionar instituições do documento
|
Seleccionar todas as instituições (270)
|
||||||
</button>
|
</button>
|
||||||
<p>{selected.length} seleccionadas</p>
|
<p role="status">
|
||||||
{batch.items.map((item) => (
|
{selected.length} seleccionadas ·{" "}
|
||||||
<details key={item.code} className="border-b py-2">
|
{Object.keys(receipts).length} preparadas nesta retoma ·{" "}
|
||||||
<summary>
|
{ready} prontas para publicar · {published} publicadas
|
||||||
<label>
|
</p>
|
||||||
<input
|
<p>
|
||||||
type="checkbox"
|
O progresso fica guardado nesta aba, separado por conta e
|
||||||
checked={selected.includes(item.code)}
|
fontes. Depois de renovar o MFA, retome os passos sem voltar
|
||||||
onChange={(e) => {
|
a preparar as fichas conferidas. As confirmações devem ser
|
||||||
setSelected((old) =>
|
marcadas novamente; nenhuma publicação é automática.
|
||||||
e.target.checked
|
</p>
|
||||||
? [...old, item.code]
|
<details>
|
||||||
: old.filter(
|
<summary>
|
||||||
(c) => c !== item.code,
|
Conferir a lista e alterar a selecção (
|
||||||
),
|
{batch.items.length} referências)
|
||||||
);
|
</summary>
|
||||||
setConfirmed(false);
|
{batch.items.map((item) => (
|
||||||
}}
|
<details key={item.code} className="border-b py-2">
|
||||||
/>{" "}
|
<summary>
|
||||||
{item.title}
|
<label>
|
||||||
</label>{" "}
|
<input
|
||||||
—{" "}
|
type="checkbox"
|
||||||
{receipts[item.code]?.published
|
checked={selected.includes(item.code)}
|
||||||
? "Publicação confirmada"
|
onChange={(e) => {
|
||||||
: receipts[item.code]
|
setSelected((old) =>
|
||||||
? receipts[item.code].matches_reference
|
e.target.checked
|
||||||
? "Conferida, aguarda publicação"
|
? [...old, item.code]
|
||||||
: "Edições existentes: rever individualmente"
|
: old.filter(
|
||||||
: item.published
|
(c) =>
|
||||||
? "Já tem publicação"
|
c !== item.code,
|
||||||
: "Por preparar"}
|
),
|
||||||
</summary>
|
);
|
||||||
<p>{item.summary}</p>
|
setConfirmed(false);
|
||||||
<p>
|
}}
|
||||||
{item.categories.map(c => financialCategories[c] ?? c).join(", ")} —{" "}
|
/>{" "}
|
||||||
{item.types.map(t => financialTypes[t] ?? t).join(", ")} — {item.status}
|
{item.title}
|
||||||
</p>
|
</label>{" "}
|
||||||
<p>
|
—{" "}
|
||||||
{item.source_document}{" "}
|
{receipts[item.code]?.published
|
||||||
<code className="break-all">
|
? "Publicação confirmada"
|
||||||
{item.source_sha256}
|
: receipts[item.code]
|
||||||
</code>
|
? receipts[item.code].matches_reference
|
||||||
</p>
|
? "Conferida, aguarda publicação"
|
||||||
<p>
|
: "Edições existentes: rever individualmente"
|
||||||
{item.locations.length} localizações;{" "}
|
: item.published
|
||||||
{item.contacts.length} contactos.
|
? "Já tem publicação"
|
||||||
</p>
|
: "Por preparar"}
|
||||||
<Link
|
</summary>
|
||||||
className="underline"
|
<p>{item.summary}</p>
|
||||||
href={`/management/fundos/referencias/${item.code}`}
|
<p>
|
||||||
>
|
{item.categories
|
||||||
Conferir fonte e dados completos
|
.map((c) => financialCategories[c] ?? c)
|
||||||
</Link>
|
.join(", ")}{" "}
|
||||||
{receipts[item.code] && (
|
—{" "}
|
||||||
|
{item.types
|
||||||
|
.map((t) => financialTypes[t] ?? t)
|
||||||
|
.join(", ")}{" "}
|
||||||
|
— {item.status}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{item.source_document}{" "}
|
||||||
|
<code className="break-all">
|
||||||
|
{item.source_sha256}
|
||||||
|
</code>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{item.locations.length} localizações;{" "}
|
||||||
|
{item.contacts.length} contactos.
|
||||||
|
</p>
|
||||||
<Link
|
<Link
|
||||||
className="ml-4 underline"
|
className="underline"
|
||||||
href={`/management/fundos/${receipts[item.code].id}`}
|
href={`/management/fundos/referencias/${item.code}`}
|
||||||
>
|
>
|
||||||
Alterar ficha existente
|
Conferir fonte e dados completos
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
{receipts[item.code] && (
|
||||||
</details>
|
<Link
|
||||||
))}
|
className="ml-4 underline"
|
||||||
|
href={`/management/fundos/${receipts[item.code].id}`}
|
||||||
|
>
|
||||||
|
Alterar ficha existente
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</details>
|
||||||
|
))}
|
||||||
|
</details>
|
||||||
<label className="block py-3">
|
<label className="block py-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -245,6 +344,11 @@ export default function Assisted({
|
|||||||
Confirmo a publicação pública das versões conferidas,
|
Confirmo a publicação pública das versões conferidas,
|
||||||
preservando os dados e as incertezas apresentados.
|
preservando os dados e as incertezas apresentados.
|
||||||
</label>
|
</label>
|
||||||
|
<p>
|
||||||
|
Para publicar: termine a preparação, marque a conferência,
|
||||||
|
preencha o fundamento e confirme a publicação. Fichas com
|
||||||
|
edições existentes exigem revisão individual.
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={button}
|
className={button}
|
||||||
@@ -263,6 +367,33 @@ export default function Assisted({
|
|||||||
2. Publicar conferidas
|
2. Publicar conferidas
|
||||||
</button>
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{needsMfa && (
|
||||||
|
<div role="alert" className={card}>
|
||||||
|
<p>
|
||||||
|
A sessão ou confirmação MFA precisa de renovação. O
|
||||||
|
progresso desta aba foi conservado.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className={button}
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() =>
|
||||||
|
router.post(
|
||||||
|
"/logout",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
onSuccess: () =>
|
||||||
|
router.visit(
|
||||||
|
"/management/fundos/referencias-assistidas",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Renovar entrada com MFA e retomar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{busy && (
|
{busy && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -283,3 +414,16 @@ export default function Assisted({
|
|||||||
</Frame>
|
</Frame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function Assisted({
|
||||||
|
batch,
|
||||||
|
}: {
|
||||||
|
batch: { dataset_hash: string; editor_id: number; items: Item[] };
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<AssistedEditor
|
||||||
|
key={`${batch.editor_id}:${batch.dataset_hash}`}
|
||||||
|
batch={batch}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -124,7 +124,13 @@ const text = (node) =>
|
|||||||
? text(node.props?.children)
|
? text(node.props?.children)
|
||||||
: String(node ?? "");
|
: String(node ?? "");
|
||||||
|
|
||||||
test("assisted editorial preparation never publishes until a separate confirmation and retries the same publication", async () => {
|
test("assisted editor resumes after renewed authentication without preparing completed fiches or automatically publishing", async () => {
|
||||||
|
const previousStorage = globalThis.sessionStorage;
|
||||||
|
const tab = new Map();
|
||||||
|
globalThis.sessionStorage = {
|
||||||
|
getItem: (key) => tab.get(key) ?? null,
|
||||||
|
setItem: (key, value) => tab.set(key, value),
|
||||||
|
};
|
||||||
const states = [],
|
const states = [],
|
||||||
refs = [],
|
refs = [],
|
||||||
calls = [];
|
calls = [];
|
||||||
@@ -133,9 +139,13 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
|||||||
lost = true;
|
lost = true;
|
||||||
const hooks = {
|
const hooks = {
|
||||||
...React,
|
...React,
|
||||||
|
useEffect(fn) {
|
||||||
|
fn();
|
||||||
|
},
|
||||||
useState(initial) {
|
useState(initial) {
|
||||||
const i = si++;
|
const i = si++;
|
||||||
if (!(i in states)) states[i] = initial;
|
if (!(i in states))
|
||||||
|
states[i] = typeof initial === "function" ? initial() : initial;
|
||||||
return [
|
return [
|
||||||
states[i],
|
states[i],
|
||||||
(v) => {
|
(v) => {
|
||||||
@@ -175,10 +185,11 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
|||||||
return { package_sha256: "qa-dataset", hash: "qa-source" };
|
return { package_sha256: "qa-dataset", hash: "qa-source" };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).default;
|
}).AssistedEditor;
|
||||||
const props = {
|
const props = {
|
||||||
batch: {
|
batch: {
|
||||||
dataset_hash: "qa-dataset",
|
dataset_hash: "qa-dataset",
|
||||||
|
editor_id: 42,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
code: "qa",
|
code: "qa",
|
||||||
@@ -214,6 +225,23 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
|||||||
assert.equal(calls.length, 2);
|
assert.equal(calls.length, 2);
|
||||||
assert.ok(calls.every((c) => !c.url.endsWith("/publicar")));
|
assert.ok(calls.every((c) => !c.url.endsWith("/publicar")));
|
||||||
tree = render();
|
tree = render();
|
||||||
|
states.length = 0;
|
||||||
|
refs.length = 0; // Full page remount after login/MFA in the same tab.
|
||||||
|
tree = render();
|
||||||
|
assert.match(text(tree), /1 seleccionadas/);
|
||||||
|
assert.match(text(tree), /1 preparadas nesta retoma/);
|
||||||
|
boxes = flatten(tree).filter((n) => n.type === "input");
|
||||||
|
assert.equal(boxes[1].props.checked, false);
|
||||||
|
boxes[1].props.onChange({ target: { checked: true } });
|
||||||
|
tree = render();
|
||||||
|
click(tree, "1.");
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
assert.equal(
|
||||||
|
calls.length,
|
||||||
|
2,
|
||||||
|
"A completed fiche must not be prepared again",
|
||||||
|
);
|
||||||
|
tree = render();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
flatten(tree).find(
|
flatten(tree).find(
|
||||||
(n) => n.type === "button" && text(n).startsWith("2."),
|
(n) => n.type === "button" && text(n).startsWith("2."),
|
||||||
@@ -246,6 +274,25 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
|||||||
assert.deepEqual(calls[3], calls[2]);
|
assert.deepEqual(calls[3], calls[2]);
|
||||||
assert.equal(calls[2].body.confirm, true);
|
assert.equal(calls[2].body.confirm, true);
|
||||||
assert.match(text(render()), /Publicação confirmada/);
|
assert.match(text(render()), /Publicação confirmada/);
|
||||||
|
props.batch.editor_id = 43;
|
||||||
|
states.length = 0;
|
||||||
|
refs.length = 0;
|
||||||
|
assert.match(
|
||||||
|
text(render()),
|
||||||
|
/0 seleccionadas/,
|
||||||
|
"Another account must not inherit progress",
|
||||||
|
);
|
||||||
|
props.batch.editor_id = 42;
|
||||||
|
props.batch.dataset_hash = "changed-source";
|
||||||
|
states.length = 0;
|
||||||
|
refs.length = 0;
|
||||||
|
assert.match(
|
||||||
|
text(render()),
|
||||||
|
/0 seleccionadas/,
|
||||||
|
"Changed sources must not inherit receipts",
|
||||||
|
);
|
||||||
|
if (previousStorage === undefined) delete globalThis.sessionStorage;
|
||||||
|
else globalThis.sessionStorage = previousStorage;
|
||||||
});
|
});
|
||||||
test("actual dossier continuation freezes a lost response retry and retains dossier/opportunity and selected product", async () => {
|
test("actual dossier continuation freezes a lost response retry and retains dossier/opportunity and selected product", async () => {
|
||||||
const states = [],
|
const states = [],
|
||||||
@@ -255,9 +302,11 @@ test("actual dossier continuation freezes a lost response retry and retains doss
|
|||||||
const calls = [];
|
const calls = [];
|
||||||
const hooks = {
|
const hooks = {
|
||||||
...React,
|
...React,
|
||||||
|
useEffect() {},
|
||||||
useState(initial) {
|
useState(initial) {
|
||||||
const i = stateIndex++;
|
const i = stateIndex++;
|
||||||
if (!(i in states)) states[i] = initial;
|
if (!(i in states))
|
||||||
|
states[i] = typeof initial === "function" ? initial() : initial;
|
||||||
return [states[i], (value) => (states[i] = value)];
|
return [states[i], (value) => (states[i] = value)];
|
||||||
},
|
},
|
||||||
useRef(initial) {
|
useRef(initial) {
|
||||||
|
|||||||
Reference in New Issue
Block a user