x
This commit is contained in:
1 parent
4fcdd8a816
commit
f49776f9cb
14 files changed
+4012
-21
No files matched your search
@@ -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);
|
||||
Reference in new issue
Block a user