fix(cf): merge ePay + intern extracts into a single Extrase CF list

Yesterday's pin to /api/ancpi exposed a real architecture split: there
are two CfExtract stores with no overlap and the previous pilot routing
only ever showed one at a time:

  architools_postgres.CfExtract  → ePay paid orders   (type=epay)
  gis_core.CfExtract via gis-api  → CF intern         (type=intern)

The pin made today's 50198 ePay visible but hid the 51 historic intern
rows; the pre-pin state was the opposite. Neither was right — users
think of "my CF extracts" as one timeline regardless of source.

Revert the pin and add client-side merge for pilot users (`useGisAc=true`):
fetchCfOrdersList now fans out to both /api/ancpi/orders and /api/cf/orders
in parallel, normalizes each row through a dedicated adapter (legacy or
gisApi), dedupes by id, and sorts by createdAt descending. fetchCfHas-
CompletedForCadastral checks both backends too (either a fresh intern
or a recent ePay row means "you already have one").

CfExtractRecord grows a required `type: 'epay' | 'intern'` field; the
existing rendering adds a small colored badge (sky=intern, emerald=ePay)
next to the status pill so users can tell where each row came from at
a glance. cfDownloadUrl is now type-aware — intern rows download via
/api/cf/:id/pdf, ePay rows via /api/ancpi/download regardless of pilot
flag, matching how each store keeps its files. Legacy (useGisAc, id)
signature still works for the few call sites that don't have the full
row in scope.

No data was deleted yesterday; the 51 intern rows in gis_core stayed
intact (verified via gis_superuser). The single edit was cancelling
the stuck 354686 pending row from 2026-05-19.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-24 00:37:15 +03:00
parent a2581de599
commit 588e4344e7
2 changed files with 218 additions and 81 deletions
+28 -11
View File
@@ -670,15 +670,32 @@ export function EpayTab() {
{/* Status */}
<td className="px-3 py-2">
<span
className={cn(
"inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium",
badge.className,
badge.pulse && "animate-pulse",
)}
>
{badge.label}
</span>
<div className="flex flex-wrap items-center gap-1">
<span
className={cn(
"inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium",
badge.className,
badge.pulse && "animate-pulse",
)}
>
{badge.label}
</span>
<span
className={cn(
"inline-flex items-center rounded-full border px-1.5 py-0.5 text-[9px] font-medium",
order.type === "intern"
? "border-sky-500/30 bg-sky-500/10 text-sky-700 dark:text-sky-300"
: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
)}
title={
order.type === "intern"
? "CF intern (gratuit, copycf)"
: "Extras CF ePay (1 credit ANCPI)"
}
>
{order.type === "intern" ? "intern" : "ePay"}
</span>
</div>
{order.errorMessage && (
<p
className="text-[10px] text-destructive mt-0.5 max-w-48 truncate"
@@ -730,7 +747,7 @@ export function EpayTab() {
asChild
>
<a
href={cfDownloadUrl(useGisAc, order.id)}
href={cfDownloadUrl(order)}
target="_blank"
rel="noopener noreferrer"
>
@@ -756,7 +773,7 @@ export function EpayTab() {
asChild
>
<a
href={cfDownloadUrl(useGisAc, order.id)}
href={cfDownloadUrl(order)}
target="_blank"
rel="noopener noreferrer"
>