WFDASH-WEBAPP/scripts/nss-spring-pins-iso8752-pb-transform.ts
Dave d8e8714720 feat(stock): update pricing logic and transform script for fasteners
- Enhanced `nss-fasteners-pb-import.json` to compute `selling_price` as `cost_price * (1 + 80%)` and included `markup` in the records.
- Updated `nss-fasteners-pb-transform.ts` to allow configurable CSV and output file names via environment variables, improving flexibility for different product runs.
- Added support for uploading photos in the repair detail page, including validation for file types and sizes, and implemented a dialog for creating customer quotes linked to repairs.
- Introduced a new `customer_quote` field in the `Repair` type to link generated quotes back to their respective repairs.

These changes streamline the pricing process and enhance the user experience in managing repairs and quotes.
2026-09-21 12:32:44 +02:00

136 lines
No EOL
3.5 KiB
TypeScript

/**
* Maps scripts/nss-spring-pins-iso8752-pilot.csv into PocketBase
* `stock_items` records for review.
*
* Output: scripts/nss-spring-pins-iso8752-pb-import.json
*
* Run: npx tsx scripts/nss-spring-pins-iso8752-pb-transform.ts
*/
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const SOURCE_CSV = join(
import.meta.dirname,
"nss-spring-pins-iso8752-pilot.csv",
);
const OUTPUT_JSON = join(
import.meta.dirname,
"nss-spring-pins-iso8752-pb-import.json",
);
const SOURCE_URL =
"https://nssfasteners.co.za/pins/spring-pins/spring-pin-iso8752.html";
const SOURCE_LABEL =
"NSS Fasteners price list - spring pin ISO8752 (Spring Steel CS70)";
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;
pack: 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}`,
`Diameter: ${r.diameter}`,
`Length: ${r.length}`,
`Finish: ${r.finish}`,
r.pack ? `Pack: ${r.pack}` : null,
`Source: ${SOURCE_LABEL}`,
]
.filter(Boolean)
.join(" | ");
const costHistory = [
{
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-spring-pins-iso8752-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., '5x20 SPRING PIN PLN'). unit=Each (spring pins are sold by piece). Material = Spring Steel CS70 (slotted spring pin, ISO8752). No Grade column. Finishes currently only Plain on NSS for this range.",
},
records,
},
null,
2,
),
);
console.log(`Wrote ${records.length} records to ${OUTPUT_JSON}`);