feat: make catalog-pages dedup a choosable step in Fix alles

The maintenance "Fix alles" now has a checkbox to include or skip the
duplicate catalog_pages merge, so admins can choose whether to run it.
removeDuplicates takes an includePages flag and fixEverything threads it
through to the action.
This commit is contained in:
openhands committed 2026-08-24 18:35:00 +02:00
1 parent 6dced0fb54
commit 3a292a44f1
3 files changed
+45 -11

No files matched your search

+4 -4
View File
@@ -99,12 +99,12 @@ export async function applyAlignIdsAction(): Promise<
}
}
export async function fixEverythingAction(): Promise<
ActionResult<maintenance.FixAllResult>
> {
export async function fixEverythingAction(input?: {
dedupePages?: boolean;
}): Promise<ActionResult<maintenance.FixAllResult>> {
try {
await guard();
return actionOk(await maintenance.fixEverything());
return actionOk(await maintenance.fixEverything(input ?? {}));
} catch (e) {
return handleActionError(e);
}
@@ -22,6 +22,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
interface Health {
itemsBaseTotal: number;
@@ -38,6 +39,7 @@ export function CatalogMaintenancePanel() {
const [health, setHealth] = useState<Health | null>(null);
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState<string | null>(null);
const [dedupePages, setDedupePages] = useState(true);
const [alignPreview, setAlignPreview] = useState<{
moves: number;
inserted: number;
@@ -286,12 +288,38 @@ export function CatalogMaintenancePanel() {
</CardDescription>
</CardHeader>
<CardContent>
<div className="mb-3 flex items-center gap-2 rounded-md border p-2">
<Checkbox
id="dedupe-pages"
checked={dedupePages}
onCheckedChange={(v) => setDedupePages(v === true)}
/>
<label
htmlFor="dedupe-pages"
className="text-sm text-muted-foreground"
>
Also merge duplicate catalog pages (double links)
</label>
</div>
<Button
variant="destructive"
disabled={busy !== null}
onClick={() =>
void run("all", fixEverythingAction, "All repairs applied")
}
onClick={async () => {
setBusy("all");
try {
const res = await fixEverythingAction({ dedupePages });
if (res.ok) {
toast.success("All repairs applied");
await refresh();
} else {
toast.error(res.error ?? "Failed");
}
} catch (e) {
toast.error(String(e));
} finally {
setBusy(null);
}
}}
>
Fix everything now
</Button>
+10 -4
View File
@@ -388,9 +388,13 @@ async function dedupCatalogTable(table: string): Promise<number> {
* product on the same page). The page merge runs before the row dedup so items
* moved onto the survivor are still de-duplicated within the page afterwards.
*/
export async function removeDuplicates(): Promise<DedupResult> {
export async function removeDuplicates(
includePages = true,
): Promise<DedupResult> {
const ib = await removeDuplicateItemsBase();
const pageDedup = await dedupeCatalogPages();
const pageDedup = includePages
? await dedupeCatalogPages()
: { groups: 0, removed: 0, remapped: 0 };
const catalogRemoved = await dedupCatalogTable("catalog_items");
const catalogBcRemoved = await dedupCatalogTable("catalog_items_bc");
return {
@@ -630,12 +634,14 @@ export interface FixAllResult {
}
/** Run every repair in a sensible order, then return before/after health. */
export async function fixEverything(): Promise<FixAllResult> {
export async function fixEverything(
opts: { dedupePages?: boolean } = {},
): Promise<FixAllResult> {
const healthBefore = await getFurniHealth();
const sprite = await fixSpriteIds();
const offers = await fixCatalogOffers();
const reconcile = await reconcileIds();
const dedup = await removeDuplicates();
const dedup = await removeDuplicates(opts.dedupePages ?? true);
const align = await forceItemsBaseIdsToFurnidata(true);
const healthAfter = await getFurniHealth();
return { healthBefore, sprite, offers, reconcile, dedup, align, healthAfter };