Compare commits
3
Commits
main
...
2c2e9749ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c2e9749ca | ||
|
|
9a0bb4c53b | ||
|
|
04250e3e59 |
No files matched your search
@@ -125,6 +125,7 @@ export interface CatalogAuditOptions {
|
|||||||
repairNitros?: boolean;
|
repairNitros?: boolean;
|
||||||
sql?: boolean;
|
sql?: boolean;
|
||||||
applySql?: boolean;
|
applySql?: boolean;
|
||||||
|
organizeSql?: boolean;
|
||||||
repairFurniData?: boolean;
|
repairFurniData?: boolean;
|
||||||
repairStructure?: boolean;
|
repairStructure?: boolean;
|
||||||
}
|
}
|
||||||
@@ -316,6 +317,7 @@ export async function runCatalogAudit(
|
|||||||
classname: item.item_name,
|
classname: item.item_name,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
itemType: item.type,
|
itemType: item.type,
|
||||||
|
publicName: item.public_name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,7 +654,9 @@ export async function runCatalogAudit(
|
|||||||
message: `Generating catalog SQL for ${missingCatalogEntries.length} items…`,
|
message: `Generating catalog SQL for ${missingCatalogEntries.length} items…`,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const result = await generateCatalogSql(missingCatalogEntries);
|
const result = await generateCatalogSql(missingCatalogEntries, {
|
||||||
|
organizeSql: options?.organizeSql,
|
||||||
|
});
|
||||||
catalogSqlCount = result.count;
|
catalogSqlCount = result.count;
|
||||||
onEvent?.({
|
onEvent?.({
|
||||||
type: "sql_ready",
|
type: "sql_ready",
|
||||||
|
|||||||
@@ -7,13 +7,17 @@ import {
|
|||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
autoPriceFurni,
|
autoPriceFurni,
|
||||||
|
CATEGORY_PAGE,
|
||||||
|
classifyFurni,
|
||||||
getOrCreateCategoryPage,
|
getOrCreateCategoryPage,
|
||||||
|
getOrCreateImportedParentPage,
|
||||||
} from "@/lib/services/furni-import";
|
} from "@/lib/services/furni-import";
|
||||||
|
|
||||||
export interface MissingCatalogEntry {
|
export interface MissingCatalogEntry {
|
||||||
classname: string;
|
classname: string;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
itemType: string;
|
itemType: string;
|
||||||
|
publicName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogSqlResult {
|
export interface CatalogSqlResult {
|
||||||
@@ -26,14 +30,45 @@ function escSqlLiteral(value: string): string {
|
|||||||
return value.replace(/'/g, "''");
|
return value.replace(/'/g, "''");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive a human-readable Dutch page caption from a furniture classname that
|
||||||
|
* follows an event+year pattern (e.g. `xmas2025`, `hween2024`, `val_2025`).
|
||||||
|
* Falls back to `fallback` when no pattern matches.
|
||||||
|
*/
|
||||||
|
function derivePageCaption(classname: string, fallback: string): string {
|
||||||
|
const m = classname.match(
|
||||||
|
/^(xmas|hween|easter|val_|valentine|hw_|newyear|summer|carnival)(\d{4})/i,
|
||||||
|
);
|
||||||
|
if (!m) return fallback;
|
||||||
|
const event = m[1].toLowerCase();
|
||||||
|
const year = m[2];
|
||||||
|
const eventNames: Record<string, string> = {
|
||||||
|
xmas: "Kerst",
|
||||||
|
hween: "Halloween",
|
||||||
|
easter: "Pasen",
|
||||||
|
val_: "Valentijn",
|
||||||
|
valentine: "Valentijn",
|
||||||
|
hw_: "Halloween",
|
||||||
|
newyear: "Nieuwjaar",
|
||||||
|
summer: "Zomer",
|
||||||
|
carnival: "Carnaval",
|
||||||
|
};
|
||||||
|
return `${eventNames[event] ?? event} ${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build standalone `catalog_items` INSERT statements for items_base entries
|
* Build standalone `catalog_items` INSERT statements for items_base entries
|
||||||
* that exist (with assets) but have no catalog entry yet. Ids and prices are
|
* that exist (with assets) but have no catalog entry yet. Ids and prices are
|
||||||
* resolved the same way the app's own imports do it (autoPriceFurni + category
|
* resolved the same way the app's own imports do it (autoPriceFurni + category
|
||||||
* page + next catalog id), so running the SQL matches an app-managed import.
|
* page + next catalog id), so running the SQL matches an app-managed import.
|
||||||
|
*
|
||||||
|
* When `organizeSql` is true, the output also contains `catalog_pages` INSERT
|
||||||
|
* statements that organise items into sub-pages under "Imported Furniture",
|
||||||
|
* using derived Dutch captions by event+year (e.g. "Kerst 2025").
|
||||||
*/
|
*/
|
||||||
export async function generateCatalogSql(
|
export async function generateCatalogSql(
|
||||||
entries: MissingCatalogEntry[],
|
entries: MissingCatalogEntry[],
|
||||||
|
opts: { organizeSql?: boolean } = {},
|
||||||
): Promise<CatalogSqlResult> {
|
): Promise<CatalogSqlResult> {
|
||||||
const statements: string[] = [
|
const statements: string[] = [
|
||||||
"-- AtomCMS catalog repair",
|
"-- AtomCMS catalog repair",
|
||||||
@@ -42,6 +77,20 @@ export async function generateCatalogSql(
|
|||||||
"START TRANSACTION;",
|
"START TRANSACTION;",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const { organizeSql = false } = opts;
|
||||||
|
|
||||||
|
// When organizing, we also emit catalog_pages INSERTs for new sub-pages.
|
||||||
|
// Read the Imported Furniture parent page id and current max page id once.
|
||||||
|
let parentPageId: number | null = null;
|
||||||
|
let nextPageId = 0;
|
||||||
|
if (organizeSql) {
|
||||||
|
parentPageId = await getOrCreateImportedParentPage();
|
||||||
|
const [maxPageResult] = (await db.execute(sql`
|
||||||
|
SELECT MAX(id) as maxId FROM catalog_pages
|
||||||
|
`)) as unknown as [Array<{ maxId: number | bigint | null }>, unknown];
|
||||||
|
nextPageId = Number(maxPageResult[0]?.maxId ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// The generation pass does not INSERT, so MAX(id) would never advance and
|
// The generation pass does not INSERT, so MAX(id) would never advance and
|
||||||
// per-entry allocateCatalogItemId would hand out the same id every time.
|
// per-entry allocateCatalogItemId would hand out the same id every time.
|
||||||
// Read the current max once and hand out sequential ids from there.
|
// Read the current max once and hand out sequential ids from there.
|
||||||
@@ -50,17 +99,50 @@ export async function generateCatalogSql(
|
|||||||
`)) as unknown as [Array<{ maxId: number | bigint | null }>, unknown];
|
`)) as unknown as [Array<{ maxId: number | bigint | null }>, unknown];
|
||||||
let nextId = Number(maxIdResult[0]?.maxId ?? 0) + 1;
|
let nextId = Number(maxIdResult[0]?.maxId ?? 0) + 1;
|
||||||
|
|
||||||
|
// Build a map of caption_save → pageId so multiple items in the same
|
||||||
|
// category share one page (and we emit the page INSERT only once).
|
||||||
|
const pageMap = new Map<string, number>();
|
||||||
|
|
||||||
let generated = 0;
|
let generated = 0;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
let pageId: number;
|
let pageId: number;
|
||||||
|
if (organizeSql && parentPageId) {
|
||||||
|
const catKey = classifyFurni(entry.classname, entry.itemType);
|
||||||
|
const captionSave = `imp_${catKey}`;
|
||||||
|
const catInfo = CATEGORY_PAGE[catKey] || CATEGORY_PAGE.other;
|
||||||
|
const caption = derivePageCaption(
|
||||||
|
entry.classname,
|
||||||
|
entry.publicName ?? catInfo.label,
|
||||||
|
);
|
||||||
|
let existing = pageMap.get(captionSave);
|
||||||
|
if (!existing) {
|
||||||
|
const [found] = (await db.execute(sql`
|
||||||
|
SELECT id FROM catalog_pages WHERE caption_save = ${captionSave} AND parent_id = ${parentPageId}
|
||||||
|
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||||
|
if (found?.[0]?.id) {
|
||||||
|
pageMap.set(captionSave, found[0].id);
|
||||||
|
} else {
|
||||||
|
const newId = nextPageId++;
|
||||||
|
pageMap.set(captionSave, newId);
|
||||||
|
statements.push(
|
||||||
|
`INSERT INTO catalog_pages (id, caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser) VALUES (${newId}, '${escSqlLiteral(captionSave)}', '${escSqlLiteral(caption)}', 'default_3x3', ${parentPageId}, '1', ${catInfo.order}, ${catInfo.icon}, '1', '1', '', '', '', '', '', '', '');`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
existing = pageMap.get(captionSave);
|
||||||
|
}
|
||||||
|
if (!existing) continue;
|
||||||
|
pageId = existing;
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
pageId = await getOrCreateCategoryPage(entry.classname, entry.itemType);
|
pageId = await getOrCreateCategoryPage(entry.classname, entry.itemType);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const price = autoPriceFurni(entry.classname);
|
const price = autoPriceFurni(entry.classname);
|
||||||
|
const displayName = entry.publicName || entry.classname;
|
||||||
statements.push(
|
statements.push(
|
||||||
`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}, ${entry.itemId}, '${escSqlLiteral(entry.classname)}', ${price.credits}, ${price.points}, ${price.pointsType}, 1, 1, '-1', '');`,
|
`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}, ${entry.itemId}, '${escSqlLiteral(displayName)}', ${price.credits}, ${price.points}, ${price.pointsType}, 1, 1, '-1', '');`,
|
||||||
);
|
);
|
||||||
nextId++;
|
nextId++;
|
||||||
generated++;
|
generated++;
|
||||||
|
|||||||
Reference in new issue
Block a user