import { createHash } from 'node:crypto' import { readFileSync } from 'node:fs' import { Prisma } from '@prisma/client' import { prisma } from '../dist/db.js' type SupplierSeed = { lei: string name: string countryCode: string city: string } type HubSeed = { code: string name: string countryCode: string } type ProductSeed = { name: string hsCodes: string[] label: string } const countryNames = new Intl.DisplayNames(['en'], { type: 'region' }) const productImages = [ 'https://images.unsplash.com/photo-1625246333195-78d9c38ad449?auto=format&fit=crop&w=900&q=80', 'https://images.unsplash.com/photo-1574323347407-f5e1ad6d020b?auto=format&fit=crop&w=900&q=80', 'https://images.unsplash.com/photo-1592924357228-91a4daadcfea?auto=format&fit=crop&w=900&q=80', ] function parseCsvLine(line: string): string[] { const fields: string[] = [] let field = '' let quoted = false for (let index = 0; index < line.length; index += 1) { const character = line[index] if (character === '"') { if (quoted && line[index + 1] === '"') { field += '"' index += 1 } else { quoted = !quoted } } else if (character === ',' && !quoted) { fields.push(field) field = '' } else { field += character } } if (quoted) throw new Error(`Unclosed CSV quote: ${line}`) fields.push(field) return fields } function stableUuid(namespace: string, value: string): string { const hex = createHash('sha256').update(`${namespace}:${value}`).digest('hex') return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}` } function normalize(value: string): string { return value.normalize('NFKD').replace(/[^a-zA-Z0-9]/g, '').toLowerCase() } function readSuppliers(): SupplierSeed[] { const value = JSON.parse( readFileSync(new URL('../data/africa_suppliers.json', import.meta.url), 'utf8'), ) as SupplierSeed[] for (const supplier of value) { if (!supplier.lei || !supplier.name || supplier.countryCode.length !== 2) { throw new Error(`Invalid Africa supplier reference: ${JSON.stringify(supplier)}`) } } return value } function readHubs(): HubSeed[] { const csv = readFileSync( new URL('../data/unlocode_africa_road_terminals.csv', import.meta.url), 'utf8', ) return csv.trim().split(/\r?\n/).slice(1).map((line) => { const [code, name, countryCode] = parseCsvLine(line) if (!code || !name || countryCode.length !== 2) throw new Error(`Invalid hub reference: ${line}`) return { code, name, countryCode } }) } function readProducts(): ProductSeed[] { const csv = readFileSync(new URL('../data/africa_products.csv', import.meta.url), 'utf8') const grouped = new Map() for (const line of csv.trim().split(/\r?\n/).slice(1)) { const [name, hsCode, label] = parseCsvLine(line) if (!name || !/^\d{6}$/.test(hsCode) || !label) throw new Error(`Invalid product reference: ${line}`) const current = grouped.get(name) if (current === undefined) grouped.set(name, { name, hsCodes: [hsCode], label }) else current.hsCodes.push(hsCode) } return [...grouped.values()] } function supplierHub(supplier: SupplierSeed, hubs: HubSeed[]): HubSeed { const countryHubs = hubs.filter((hub) => hub.countryCode === supplier.countryCode) if (countryHubs.length === 0) throw new Error(`No Africa hub for ${supplier.countryCode}`) const city = normalize(supplier.city) const cityHub = city.length > 0 ? countryHubs.find((hub) => normalize(hub.name).includes(city) || city.includes(normalize(hub.name))) : undefined return cityHub ?? countryHubs[0] } const suppliers = readSuppliers() const hubs = readHubs() const products = readProducts() for (const [index, product] of products.entries()) { const uuid = stableUuid('africa-product', product.name) const data = { sku: `HS-${product.hsCodes[0]}`, name: product.name, categoryName: 'African commodities', unit: 'ton', imageUrl: productImages[index % productImages.length], description: `${product.label}. HS codes: ${product.hsCodes.join(', ')}.`, isActive: true, } await prisma.product.upsert({ where: { uuid }, create: { uuid, ...data }, update: data }) } for (const supplier of suppliers) { const uuid = stableUuid('africa-supplier', supplier.lei) const country = countryNames.of(supplier.countryCode) if (country === undefined) throw new Error(`Unknown country code: ${supplier.countryCode}`) const data = { teamUuid: null, name: supplier.name, description: `African supplier reference from GLEIF (${supplier.lei}), ${supplier.city}.`, country, countryCode: supplier.countryCode, logoUrl: `https://ui-avatars.com/api/?name=${encodeURIComponent(supplier.name)}&background=0B6BFF&color=fff&bold=true`, isVerified: false, isActive: true, } await prisma.supplier.upsert({ where: { uuid }, create: { uuid, ...data }, update: data }) } const storedProducts = await prisma.product.findMany({ where: { uuid: { in: products.map((product) => stableUuid('africa-product', product.name)) } }, orderBy: { name: 'asc' }, }) const storedSuppliers = await prisma.supplier.findMany({ where: { uuid: { in: suppliers.map((supplier) => stableUuid('africa-supplier', supplier.lei)) } }, orderBy: { name: 'asc' }, }) let quoteCount = 0 for (const [supplierIndex, supplier] of suppliers.entries()) { const storedSupplier = storedSuppliers.find((item) => item.uuid === stableUuid('africa-supplier', supplier.lei)) if (storedSupplier === undefined) throw new Error(`Seeded supplier is missing: ${supplier.lei}`) const hub = supplierHub(supplier, hubs) for (let productOffset = 0; productOffset < 2; productOffset += 1) { const productSeed = products[(supplierIndex * 2 + productOffset) % products.length] const storedProduct = storedProducts.find((item) => item.uuid === stableUuid('africa-product', productSeed.name)) if (storedProduct === undefined) throw new Error(`Seeded product is missing: ${productSeed.name}`) const uuid = stableUuid('africa-quote', `${supplier.lei}:${productSeed.name}`) const data = { supplierId: storedSupplier.id, productId: storedProduct.id, status: 'active', quantity: new Prisma.Decimal(250 + (supplierIndex % 12) * 50 + productOffset * 25), unit: storedProduct.unit, pricePerUnit: new Prisma.Decimal(700 + (supplierIndex % 10) * 65 + productOffset * 40), currency: 'USD', incotermsCode: productOffset === 0 ? 'FOB' : 'DAP', originPointUuid: `africa-${hub.code.toLowerCase()}`, originName: hub.name, validUntil: new Date('2028-12-31'), notes: 'Africa reference catalog', } await prisma.quote.upsert({ where: { uuid }, create: { uuid, ...data }, update: data }) quoteCount += 1 } } console.log(`Seeded Africa exchange reference data: suppliers ${suppliers.length}, products ${products.length}, quotes ${quoteCount}`) await prisma.$disconnect()