fix: only reorganize items already in the Imported Furniture section

organizeCatalogAll no longer pulls every items_base row into the catalog.
It now touches only items whose catalog entry lives on a page under the
Imported Furniture parent (re-homing/re-pricing onto line/category
sub-pages). Items are never newly added. Button renamed to 'Organiseer
import'.
This commit is contained in:
openhands committed 2026-08-22 11:48:47 +02:00
1 parent eb885c044f
commit 488f40919d
2 files changed
+41 -49

No files matched your search

@@ -881,14 +881,14 @@ export function StudioClient({
className="gap-1.5"
disabled={organizing || !!batchProgress}
onClick={organizeAll}
title="Alle meubels automatisch in catalogus-pagina's ordenen (pagina's worden aangemaakt indien nodig)"
title="Alle meubels uit de Import-sectie automatisch ordenen in catalogus-pagina's (pagina's worden aangemaakt indien nodig)"
>
{organizing ? (
<Loader2 size={14} className="animate-spin" />
) : (
<WandSparkles size={14} />
)}
Organiseer alles
Organiseer import
</Button>
{selectedNonImported > 0 && (
+39 -47
View File
@@ -1,14 +1,11 @@
import { readdir } from "node:fs/promises";
import { sql } from "drizzle-orm";
import { db } from "@/lib/db";
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
import {
readFurniData,
withFurniDataLock,
writeFurniData,
} from "@/lib/services/furni-data";
import {
allocateCatalogItemId,
autoPriceFurni,
CATEGORY_PAGE,
classifyFurni,
@@ -17,7 +14,6 @@ import {
resetCatalogPageCache,
} from "@/lib/services/furni-import";
import {
assetNameCandidates,
classifyCatalogItem,
NO_CATALOG_KINDS,
} from "@/lib/services/item-kind";
@@ -466,18 +462,17 @@ export interface OrganizeCatalogResult {
}
/**
* Fully automatic one-shot organization of the entire `items_base` table into
* the catalog. For every furniture item it:
* 1. ensures the "Imported Furniture" parent page and the correct target
* sub-page exist (creating it if missing). The target is the item's
* furniture line (e.g. "weebz", "habbox") when known, otherwise its
* auto-detected category (e.g. "Room & Building", "Rares"),
* 2. creates a catalog_items entry if the item isn't in the catalog yet
* (placed on its line/category sub-page with the auto price),
* 3. re-homes and re-prices any existing catalog entry so it sits on its
* Fully automatic one-shot organization of everything that already lives under
* the "Imported Furniture" section of the catalog. For each imported item it:
* 1. ensures the correct target sub-page exists (creating it if missing).
* The target is the item's furniture line (e.g. "weebz", "habbox") when
* known and large enough, otherwise its auto-detected category
* (e.g. "Room & Building", "Rares"),
* 2. re-homes and re-prices any existing catalog entry so it sits on its
* correct sub-page with the canonical auto-detected price.
*
* No manual selection required — runs over the entire database.
* Only items already present in the Imported section are touched — no new
* items are pulled in from `items_base`. No manual selection required.
*/
export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
// 1) Parent page (sub-pages are created on demand below).
@@ -577,7 +572,6 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
unknown,
];
const idsInCatalog = new Set<number>();
const catalogPageByItem = new Map<number, number>();
const catalogCreditsByItem = new Map<number, number>();
for (const row of catalogRows) {
@@ -586,7 +580,6 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
.map((s) => Number(s.trim()))
.filter(Boolean);
for (const id of ids) {
idsInCatalog.add(id);
if (!catalogPageByItem.has(id)) {
catalogPageByItem.set(id, Number(row.page_id));
catalogCreditsByItem.set(id, Number(row.cost_credits));
@@ -598,11 +591,33 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
`)) as unknown as [Array<{ id: number }>, unknown];
const validPageIds = new Set(validPages.map((r) => Number(r.id)));
// Count items per furniture line so we only spin up a dedicated line page
// for lines that actually have enough items. Everything else falls back to
// the category page.
// Only items already living under the Imported Furniture section are
// reorganized. An item counts as "in import" when its catalog entry sits on
// a sub-page (or the parent) of the imported parent page.
const [importedPages] = (await db.execute(sql`
SELECT id FROM catalog_pages
WHERE parent_id = ${parentPageId} OR id = ${parentPageId}
`)) as unknown as [Array<{ id: number }>, unknown];
const importedPageIds = new Set(importedPages.map((r) => Number(r.id)));
const importedItemIds = new Set<number>();
for (const row of catalogRows) {
if (!importedPageIds.has(Number(row.page_id))) continue;
for (const id of (row.item_ids ?? "")
.split(";")
.map((s) => Number(s.trim()))
.filter(Boolean)) {
importedItemIds.add(id);
}
}
const itemById = new Map(items.map((it) => [Number(it.id), it]));
// Count imported items per furniture line so we only spin up a dedicated
// line page for lines that actually have enough items. Everything else
// falls back to the category page.
const lineCounts = new Map<string, number>();
for (const item of items) {
for (const id of importedItemIds) {
const item = itemById.get(id);
if (!item) continue;
if (NO_CATALOG_KINDS.has(classifyCatalogItem(item))) continue;
const fl = resolveFurniline(item.item_name);
if (fl) lineCounts.set(fl, (lineCounts.get(fl) ?? 0) + 1);
@@ -612,12 +627,7 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
if (count >= LINE_PAGE_MIN_ITEMS) qualifyingLines.add(line);
}
// Nitro presence (pets are purchasable without a furni .nitro bundle).
const { nitroDir } = await getFurniAssetDirs();
const nitroFiles = await readdir(nitroDir).catch(() => [] as string[]);
const nitroSet = new Set(nitroFiles);
let catalogAdded = 0;
const catalogAdded = 0;
let catalogRehomed = 0;
for (const item of items) {
@@ -625,17 +635,10 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
if (NO_CATALOG_KINDS.has(kind)) continue;
const itemId = Number(item.id);
const inCatalog = idsInCatalog.has(itemId);
// Items not yet in the catalog are only added when they are actually
// purchasable furniture (have a .nitro or are a pet).
if (!inCatalog) {
const hasNitro = assetNameCandidates(item.item_name).nitro.some((n) =>
nitroSet.has(n),
);
const purchasable = kind === "pet" || hasNitro;
if (!purchasable) continue;
}
// Only reorganize items already in the Imported Furniture section.
// Never pull new items in from items_base.
if (!importedItemIds.has(itemId)) continue;
// Target page: the item's furniture line (e.g. "weebz", "habbox") when
// it qualifies for its own page, otherwise its auto-detected category.
@@ -662,17 +665,6 @@ export async function organizeCatalogAll(): Promise<OrganizeCatalogResult> {
const pageId = await ensureSubPage(captionSave, caption);
const price = autoPriceFurni(item.item_name);
if (!inCatalog) {
await allocateCatalogItemId(async (nextId) => {
await db.execute(sql`
INSERT INTO catalog_items (id, page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, order_number, offer_id, extradata)
VALUES (${nextId}, ${pageId}, ${itemId}, ${item.item_name}, ${price.credits}, ${price.points}, ${price.pointsType}, 1, 1, ${itemId}, '')
`);
});
catalogAdded++;
continue;
}
// Re-classify onto the correct sub-page and re-apply the auto price if
// either drifted away from the canonical values.
const currentPage = catalogPageByItem.get(itemId);