This commit is contained in:
Your Name committed 2026-10-18 13:55:18 +07:00
1 parent 4fcdd8a816
commit f49776f9cb
14 files changed
+4012 -21

No files matched your search

+157
View File
@@ -0,0 +1,157 @@
import * as fs from "fs";
import * as path from "path";
const PROD_API = "https://api.homaroma.shop/api";
interface RawMaterialRef {
id: number;
name: string;
pricePerUnit: number;
unit: string;
}
interface VariantPlan {
id: number;
sku: string;
option: string;
price: number;
hasBom: boolean;
totalBomCost: number;
}
interface ProductPlan {
index: number;
productId: number;
productName: string;
scentName: string;
category: string | null;
bomStatus: string;
configuredVariants: number;
totalVariants: number;
variants: VariantPlan[];
matchedFragrance: RawMaterialRef | null;
packagingReady: boolean;
}
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const BOTTLE_MAP: Record<number, number> = {
30: 10, // ขวดพร้อมฝา Oilozone 30ml
60: 11, // ขวดพร้อมฝา Oilozone 60ml
85: 12, // ขวดพร้อมฝา Oilozone 85ml
120: 9, // ขวดพร้อมฝา Oilozone 120ml
};
const FORMULA_MAP: Record<number, { fragQty: number; dpgQty: number }> = {
30: { fragQty: 2.4, dpgQty: 27.6 },
60: { fragQty: 4.8, dpgQty: 55.2 },
85: { fragQty: 6.8, dpgQty: 78.2 },
120: { fragQty: 9.6, dpgQty: 110.4 },
};
function detectSize(variant: VariantPlan): number {
const opt = (variant.option || "").toLowerCase();
const sku = (variant.sku || "").toLowerCase();
if (opt.includes("120") || sku.includes("120ml")) return 120;
if (opt.includes("85") || sku.includes("85ml")) return 85;
if (opt.includes("60") || sku.includes("60ml")) return 60;
if (opt.includes("30") || sku.includes("30ml")) return 30;
throw new Error(`Cannot detect size for variant ${variant.id} (${variant.sku} / ${variant.option})`);
}
async function main() {
const jsonPath = path.resolve(process.cwd(), "oil_ozone_products.json");
if (!fs.existsSync(jsonPath)) {
throw new Error(`File not found: ${jsonPath}`);
}
const products: ProductPlan[] = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
console.log(`Loaded ${products.length} products from ${jsonPath}`);
let successCount = 0;
let skippedCount = 0;
let failedCount = 0;
for (const product of products) {
console.log(`\n--------------------------------------------------`);
console.log(`[${product.index}/${products.length}] Product #${product.productId}: ${product.productName}`);
if (!product.matchedFragrance) {
console.error(`❌ Skipped: No matching fragrance found for scent '${product.scentName}'`);
failedCount++;
continue;
}
if (product.bomStatus === "CONFIGURED" && product.configuredVariants === product.totalVariants) {
console.log(`⚡ Already configured (${product.configuredVariants}/${product.totalVariants} variants). Skipping.`);
skippedCount++;
continue;
}
// Build variants payload
const variantsPayload = product.variants.map((v) => {
const size = detectSize(v);
const formula = FORMULA_MAP[size];
const bottleId = BOTTLE_MAP[size];
return {
variantId: v.id,
sku: v.sku,
bomItems: [
{
rawMaterialId: product.matchedFragrance!.id,
name: product.matchedFragrance!.name,
quantity: formula.fragQty,
},
{
rawMaterialId: 2, // DPG
name: "DPG",
quantity: formula.dpgQty,
},
{
rawMaterialId: bottleId,
quantity: 1,
},
],
};
});
console.log(`Applying BOM for ${variantsPayload.length} variants with fragrance [ID ${product.matchedFragrance.id}] ${product.matchedFragrance.name}...`);
try {
const res = await fetch(`${PROD_API}/products/${product.productId}/bom`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ variants: variantsPayload }),
});
if (!res.ok) {
const errText = await res.text();
throw new Error(`HTTP ${res.status}: ${errText}`);
}
const resData = (await res.json()) as any;
console.log(`✓ BOM saved successfully for #${product.productId}! Updated variants: ${resData.updatedVariants ?? variantsPayload.length}`);
successCount++;
} catch (err: any) {
console.error(`❌ Failed to update BOM for #${product.productId}: ${err.message}`);
failedCount++;
}
// Rate-limit pause
await sleep(200);
}
console.log(`\n==================================================`);
console.log(`BOM Application Summary:`);
console.log(` - Newly configured: ${successCount}`);
console.log(` - Already configured (skipped): ${skippedCount}`);
console.log(` - Failed: ${failedCount}`);
console.log(`==================================================\n`);
}
main().catch(console.error);