133 lines
3.3 KiB
TypeScript
133 lines
3.3 KiB
TypeScript
|
|
/**
|
||
|
|
* Maps scripts/nss-hex-bolts-din931-pilot.csv into PocketBase
|
||
|
|
* `stock_items` records for review.
|
||
|
|
*
|
||
|
|
* Output: scripts/nss-hex-bolts-din931-pb-import.json
|
||
|
|
*
|
||
|
|
* Run: npx tsx scripts/nss-hex-bolts-din931-pb-transform.ts
|
||
|
|
*/
|
||
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
||
|
|
import { join } from "node:path";
|
||
|
|
|
||
|
|
const SOURCE_CSV = join(
|
||
|
|
import.meta.dirname,
|
||
|
|
"nss-hex-bolts-din931-pilot.csv",
|
||
|
|
);
|
||
|
|
const OUTPUT_JSON = join(
|
||
|
|
import.meta.dirname,
|
||
|
|
"nss-hex-bolts-din931-pb-import.json",
|
||
|
|
);
|
||
|
|
const SOURCE_URL =
|
||
|
|
"https://nssfasteners.co.za/hexagon-head-fasteners/hex-head-bolts/hex-bolt-din931.html?gradematerial=772,773";
|
||
|
|
const SOURCE_LABEL =
|
||
|
|
"NSS Fasteners price list - hex bolt DIN931 8.8 + A2";
|
||
|
|
const EXTRACTED_AT = "2026-09-16";
|
||
|
|
|
||
|
|
const SUPPLIER_NSS_ID = "6xuu7a0pmmubtqd";
|
||
|
|
const CATEGORY_FASTENERS_ID = "4emvaanzogj0586";
|
||
|
|
const MARKUP_PERCENT = 80;
|
||
|
|
|
||
|
|
type CsvRow = {
|
||
|
|
sku: string;
|
||
|
|
posDescription: string;
|
||
|
|
description: string;
|
||
|
|
material: string;
|
||
|
|
grade: string;
|
||
|
|
diameter: string;
|
||
|
|
length: string;
|
||
|
|
finish: string;
|
||
|
|
unitPrice: string;
|
||
|
|
url: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
const parsePrice = (s: string): number | null => {
|
||
|
|
const m = s.match(/([\d.]+)/);
|
||
|
|
if (!m) return null;
|
||
|
|
const n = parseFloat(m[1]);
|
||
|
|
return Number.isFinite(n) ? n : null;
|
||
|
|
};
|
||
|
|
|
||
|
|
const round2 = (n: number) => Math.round(n * 100) / 100;
|
||
|
|
|
||
|
|
const toPbRecord = (r: CsvRow) => {
|
||
|
|
const costPrice = parsePrice(r.unitPrice);
|
||
|
|
const notes = [
|
||
|
|
`Material: ${r.material}`,
|
||
|
|
`Grade: ${r.grade}`,
|
||
|
|
`Diameter: ${r.diameter}`,
|
||
|
|
`Length: ${r.length}`,
|
||
|
|
`Finish: ${r.finish}`,
|
||
|
|
`Source: ${SOURCE_LABEL}`,
|
||
|
|
].join(" | ");
|
||
|
|
|
||
|
|
const costHistory = [
|
||
|
|
{
|
||
|
|
cost_price: costPrice,
|
||
|
|
currency: "ZAR",
|
||
|
|
source: "nssfasteners.co.za",
|
||
|
|
extracted_at: EXTRACTED_AT,
|
||
|
|
source_url: SOURCE_URL,
|
||
|
|
qty_basis: 1,
|
||
|
|
extraction_method: "browser_automation",
|
||
|
|
notes: SOURCE_LABEL,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
return {
|
||
|
|
name: r.posDescription,
|
||
|
|
description: r.posDescription,
|
||
|
|
sku: r.sku,
|
||
|
|
location: SOURCE_LABEL,
|
||
|
|
notes,
|
||
|
|
cost_history: costHistory,
|
||
|
|
cost_price: costPrice,
|
||
|
|
selling_price:
|
||
|
|
costPrice != null ? round2(costPrice * (1 + MARKUP_PERCENT / 100)) : 0,
|
||
|
|
markup: MARKUP_PERCENT,
|
||
|
|
unit: "Each",
|
||
|
|
current_qty: 0,
|
||
|
|
min_qty: 0,
|
||
|
|
target_qty: 0,
|
||
|
|
is_active: true,
|
||
|
|
supplier: SUPPLIER_NSS_ID,
|
||
|
|
category: CATEGORY_FASTENERS_ID,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const csv = readFileSync(SOURCE_CSV, "utf8").trim();
|
||
|
|
const [headerLine, ...dataLines] = csv.split("\n");
|
||
|
|
const headers = headerLine.split(",") as (keyof CsvRow)[];
|
||
|
|
|
||
|
|
const rows: CsvRow[] = dataLines.map((line) => {
|
||
|
|
const cols = line.split(",");
|
||
|
|
const obj = {} as CsvRow;
|
||
|
|
headers.forEach((h, i) => {
|
||
|
|
(obj as Record<string, string>)[h] = cols[i] ?? "";
|
||
|
|
});
|
||
|
|
return obj;
|
||
|
|
});
|
||
|
|
|
||
|
|
const records = rows.map(toPbRecord);
|
||
|
|
|
||
|
|
writeFileSync(
|
||
|
|
OUTPUT_JSON,
|
||
|
|
JSON.stringify(
|
||
|
|
{
|
||
|
|
meta: {
|
||
|
|
source_csv: "scripts/nss-hex-bolts-din931-pilot.csv",
|
||
|
|
source_url: SOURCE_URL,
|
||
|
|
extracted_at: EXTRACTED_AT,
|
||
|
|
row_count: records.length,
|
||
|
|
pb_collection: "stock_items",
|
||
|
|
notes:
|
||
|
|
"selling_price = cost_price * (1 + 80%). name=description both carry the PosCode (e.g., 'M16x100 BOLT 8.8 PLN'). unit=Each (bolts are sold by piece).",
|
||
|
|
},
|
||
|
|
records,
|
||
|
|
},
|
||
|
|
null,
|
||
|
|
2,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log(`Wrote ${records.length} records to ${OUTPUT_JSON}`);
|