WFDASH-WEBAPP/scripts/nss-fasteners-pb-transform.ts
Dave da90706ce0 feat(stock): implement auto-markup for selling price and markup on stock item creation and updates
This commit enhances the stock item management by introducing automatic calculation of selling price and markup based on the cost price. Key changes include:

- Integration of `applyDefaultMarkupToPatch` to automatically set `selling_price` and `markup` at 80% of the cost price when they are not explicitly provided during stock item creation and updates.
- Updates to the `create` and `update` functions in the stock items service to ensure that existing values are respected and not overwritten if they are already set by the operator.
- Tests added to verify the correct behavior of auto-markup during both creation and updates of stock items, ensuring that the functionality works as intended across various scenarios.

This feature aims to streamline the pricing process for stock items, reducing manual input and potential errors.
2026-09-16 09:49:52 +02:00

144 lines
3.9 KiB
TypeScript

/**
* Maps scripts/nss-fasteners-single-pieces.csv into PocketBase
* `stock_items` records for review.
*
* Output: scripts/nss-fasteners-pb-import.json
*
* No data is written to PocketBase yet — this is for the user to
* inspect the field mapping before any ingest.
*
* Run: bun scripts/nss-fasteners-pb-transform.ts
* or: npx tsx scripts/nss-fasteners-pb-transform.ts
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const SOURCE_CSV = join(import.meta.dirname, 'nss-fasteners-single-pieces.csv');
const OUTPUT_JSON = join(import.meta.dirname, 'nss-fasteners-pb-import.json');
// === supplied by operator on 2026-09-16 ===
const SUPPLIER_NSS_ID = '6xuu7a0pmmubtqd';
const CATEGORY_FASTENERS_ID = '4emvaanzogj0586';
const MARKUP_PERCENT = 80; // selling_price = cost_price * (1 + MARKUP_PERCENT / 100)
const SOURCE_URL =
'https://nssfasteners.co.za/nuts/hex-nuts/hex-nut-din934.html?gradematerial=771,773';
const SOURCE_LABEL = 'NSS Fasteners price list - hex nut DIN934 (Grade 8 + A2)';
const EXTRACTED_AT = '2026-09-16'; // YYYY-MM-DD
type CsvRow = {
SKU: string;
PosDescription: string;
Description: string;
Material: string;
Grade: string;
Diameter: string;
Thread: string;
Finish: string;
Pack: string;
UnitPrice: string;
};
const parsePrice = (s: string): number | null => {
// "R 59.66" -> 59.66
const m = s.match(/([\d.]+)/);
if (!m) return null;
const n = parseFloat(m[1]);
return Number.isFinite(n) ? n : null;
};
const toPbRecord = (r: CsvRow) => {
const costPrice = parsePrice(r.UnitPrice);
const notes = [
`Material: ${r.Material}`,
`Grade: ${r.Grade}`,
`Diameter: ${r.Diameter}`,
`Thread: ${r.Thread}`,
`Finish: ${r.Finish}`,
`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',
},
];
return {
// === required ===
name: r.PosDescription,
// === identifying ===
sku: r.SKU,
description: r.Description,
// === sourcing / tracking (user's suggestion) ===
location: SOURCE_LABEL,
notes,
cost_history: costHistory,
// === pricing ===
cost_price: costPrice,
// selling_price and markup intentionally left for the user to set
// (depends on customer-facing markup policy, not supplier data).
// === inventory defaults (no source data) ===
unit: r.Pack || 'Each',
current_qty: 0,
min_qty: 0,
target_qty: 0,
is_active: true,
// === relations intentionally NULL ===
// supplier + category are relations that need IDs the user must look up.
// Do not invent them here.
supplier: null,
category: null,
};
};
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 => {
// naive CSV split - no embedded commas in current dataset, so safe.
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-fasteners-single-pieces.csv',
source_url: SOURCE_URL,
extracted_at: EXTRACTED_AT,
row_count: records.length,
pb_collection: 'stock_items',
notes:
'selling_price and markup are unset (null/0). supplier and category are null — look up IDs separately before ingest.',
},
records,
},
null,
2,
),
);
console.log(`Wrote ${records.length} records to ${OUTPUT_JSON}`);