import { createHash } from 'node:crypto' import { Prisma } from '@prisma/client' import { prisma } from './db.js' import { ensureDemoKycPricingContext, lockCurrencyPrice } from './pricing-context.js' interface DemoWorkspaceAccess { teamUuid: string userId: string } interface DemoRoute { title: string sourceCountryCode: string sourceLocationName: string sourceLatitude: number sourceLongitude: number destinationCountryCode: string destinationLocationName: string destinationLatitude: number destinationLongitude: number transportTypeCode: string amountUsd: number etaDays: number grossWeightKg: number volumeCbm: number routeDistanceKm: number } const demoRoutes: DemoRoute[] = [ { title: 'Mombasa → Almaty', sourceCountryCode: 'KE', sourceLocationName: 'Mombasa', sourceLatitude: -4.0435, sourceLongitude: 39.6682, destinationCountryCode: 'KZ', destinationLocationName: 'Almaty', destinationLatitude: 43.2389, destinationLongitude: 76.8897, transportTypeCode: 'MULTIMODAL', amountUsd: 2450, etaDays: 16, grossWeightKg: 18400, volumeCbm: 68, routeDistanceKm: 8240, }, { title: 'Casablanca → Moscow', sourceCountryCode: 'MA', sourceLocationName: 'Casablanca', sourceLatitude: 33.5731, sourceLongitude: -7.5898, destinationCountryCode: 'RU', destinationLocationName: 'Moscow', destinationLatitude: 55.7558, destinationLongitude: 37.6173, transportTypeCode: 'MULTIMODAL', amountUsd: 3100, etaDays: 12, grossWeightKg: 12600, volumeCbm: 52, routeDistanceKm: 5060, }, { title: 'Nairobi → Dubai', sourceCountryCode: 'KE', sourceLocationName: 'Nairobi', sourceLatitude: -1.2921, sourceLongitude: 36.8219, destinationCountryCode: 'AE', destinationLocationName: 'Dubai', destinationLatitude: 25.2048, destinationLongitude: 55.2708, transportTypeCode: 'AIR', amountUsd: 1850, etaDays: 4, grossWeightKg: 4200, volumeCbm: 18, routeDistanceKm: 3560, }, { title: 'Johannesburg → Warsaw', sourceCountryCode: 'ZA', sourceLocationName: 'Johannesburg', sourceLatitude: -26.2041, sourceLongitude: 28.0473, destinationCountryCode: 'PL', destinationLocationName: 'Warsaw', destinationLatitude: 52.2297, destinationLongitude: 21.0122, transportTypeCode: 'AIR', amountUsd: 4200, etaDays: 7, grossWeightKg: 7600, volumeCbm: 31, routeDistanceKm: 8690, }, ] const demoNote = JSON.stringify({ seed: 'empty-team-workspace' }) const demoPricingCountryCode = process.env.DEMO_PRICING_COUNTRY_CODE ?? 'RU' const demoClientCurrency = process.env.DEMO_CLIENT_CURRENCY ?? 'RUB' function stableUuid(teamUuid: string, entity: string, index: number): string { const hex = createHash('sha256') .update(`${teamUuid}:${entity}:${index}`) .digest('hex') .slice(0, 32) .split('') hex[12] = '4' hex[16] = '8' const value = hex.join('') return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}` } export async function ensureDemoWorkspaceData(access: DemoWorkspaceAccess): Promise { const [quotationCount, orderCount, demoQuotationPricingCount, demoOrderPricingCount] = await Promise.all([ prisma.quotation.count({ where: { teamUuid: access.teamUuid } }), prisma.order.count({ where: { teamUuid: access.teamUuid } }), prisma.quotation.count({ where: { teamUuid: access.teamUuid, notes: demoNote, sourceAmount: null }, }), prisma.order.count({ where: { teamUuid: access.teamUuid, notes: demoNote, sourceAmount: null }, }), ]) const needsCalculations = quotationCount === 0 const needsOrders = orderCount === 0 const needsPricingBackfill = demoQuotationPricingCount > 0 || demoOrderPricingCount > 0 const kyc = needsCalculations || needsOrders || needsPricingBackfill ? await ensureDemoKycPricingContext({ teamUuid: access.teamUuid, userId: access.userId, countryCode: demoPricingCountryCode, }) : null for (const [index, route] of demoRoutes.entries()) { const createsOrder = index < 2 const tariffUuid = stableUuid(access.teamUuid, 'demo-tariff', index) const tariff = await prisma.tariffReference.upsert({ where: { uuid: tariffUuid }, create: { uuid: tariffUuid, teamUuid: access.teamUuid, createdByUserId: access.userId, name: route.title, status: 'active', sourceCountryCode: route.sourceCountryCode, destinationCountryCode: route.destinationCountryCode, sourceAmount: new Prisma.Decimal(route.amountUsd), minSourcePrice: new Prisma.Decimal(route.amountUsd), etaDays: route.etaDays, priority: 50 + index, sourceCurrency: 'USD', notes: demoNote, }, update: { teamUuid: access.teamUuid, createdByUserId: access.userId, name: route.title, status: 'active', operationCode: null, incotermsCode: null, transportTypeCode: null, tareTypeCode: null, sourceCountryCode: route.sourceCountryCode, destinationCountryCode: route.destinationCountryCode, sourceAmount: new Prisma.Decimal(route.amountUsd), minSourcePrice: new Prisma.Decimal(route.amountUsd), etaDays: route.etaDays, priority: 50 + index, sourceCurrency: 'USD', notes: demoNote, }, }) if (!needsCalculations && !(needsOrders && createsOrder) && !needsPricingBackfill) continue if (kyc === null) throw new Error('KYC pricing context was not resolved for demo data') const price = await lockCurrencyPrice({ sourceAmount: route.amountUsd, sourceCurrency: 'USD', clientCurrency: demoClientCurrency, pricingCountryCode: kyc.countryCode, }) const pricingEffectiveDate = new Date(`${price.rateEffectiveDate}T00:00:00.000Z`) const pricingQuotedAt = new Date(price.rateQuotedAt) const pricingLockedAt = new Date(price.rateLockedAt) const quotationUuid = stableUuid(access.teamUuid, 'demo-quotation', index) const quotation = await prisma.quotation.upsert({ where: { uuid: quotationUuid }, create: { uuid: quotationUuid, teamUuid: access.teamUuid, createdByUserId: access.userId, title: route.title, status: createsOrder && needsOrders ? 'converted' : 'priced', operationCode: 'IMPORT', incotermsCode: 'DAP', transportTypeCode: route.transportTypeCode, sourceCountryCode: route.sourceCountryCode, sourceLocationName: route.sourceLocationName, sourceLatitude: route.sourceLatitude, sourceLongitude: route.sourceLongitude, destinationCountryCode: route.destinationCountryCode, destinationLocationName: route.destinationLocationName, destinationLatitude: route.destinationLatitude, destinationLongitude: route.destinationLongitude, grossWeightKg: new Prisma.Decimal(route.grossWeightKg), chargeableWeightKg: new Prisma.Decimal(route.grossWeightKg), volumeCbm: new Prisma.Decimal(route.volumeCbm), unitsCount: 1 + index, routeDistanceKm: route.routeDistanceKm, selectedTariffId: tariff.id, tariffMatchSummary: `Demo tariff: ${route.title}`, sourceAmount: new Prisma.Decimal(price.sourceAmount), sourceCurrency: price.sourceCurrency, clientAmount: new Prisma.Decimal(price.clientAmount), clientCurrency: price.clientCurrency, exchangeRate: new Prisma.Decimal(price.exchangeRate), exchangeRateSnapshotUuid: price.rateSnapshotUuid, exchangeRateProvider: price.provider, exchangeRateEffectiveDate: pricingEffectiveDate, exchangeRateQuotedAt: pricingQuotedAt, exchangeRateLockedAt: pricingLockedAt, pricingCountryCode: price.pricingCountryCode, pricingKycProfileUuid: kyc.profileUuid, etaDays: route.etaDays, notes: demoNote, }, update: { teamUuid: access.teamUuid, createdByUserId: access.userId, selectedTariffId: tariff.id, sourceAmount: new Prisma.Decimal(price.sourceAmount), sourceCurrency: price.sourceCurrency, clientAmount: new Prisma.Decimal(price.clientAmount), clientCurrency: price.clientCurrency, exchangeRate: new Prisma.Decimal(price.exchangeRate), exchangeRateSnapshotUuid: price.rateSnapshotUuid, exchangeRateProvider: price.provider, exchangeRateEffectiveDate: pricingEffectiveDate, exchangeRateQuotedAt: pricingQuotedAt, exchangeRateLockedAt: pricingLockedAt, pricingCountryCode: price.pricingCountryCode, pricingKycProfileUuid: kyc.profileUuid, etaDays: route.etaDays, notes: demoNote, }, }) if ((!needsOrders && !needsPricingBackfill) || !createsOrder) continue await prisma.order.upsert({ where: { uuid: stableUuid(access.teamUuid, 'demo-order', index) }, create: { uuid: stableUuid(access.teamUuid, 'demo-order', index), teamUuid: access.teamUuid, quotationId: quotation.id, createdByUserId: access.userId, name: route.title, status: index === 0 ? 'in_transit' : 'processing', sourceAmount: new Prisma.Decimal(price.sourceAmount), sourceCurrency: price.sourceCurrency, clientAmount: new Prisma.Decimal(price.clientAmount), clientCurrency: price.clientCurrency, exchangeRate: new Prisma.Decimal(price.exchangeRate), exchangeRateSnapshotUuid: price.rateSnapshotUuid, exchangeRateProvider: price.provider, exchangeRateEffectiveDate: pricingEffectiveDate, exchangeRateQuotedAt: pricingQuotedAt, exchangeRateLockedAt: pricingLockedAt, pricingCountryCode: price.pricingCountryCode, pricingKycProfileUuid: kyc.profileUuid, sourceLocationName: route.sourceLocationName, sourceCountryCode: route.sourceCountryCode, sourceLatitude: route.sourceLatitude, sourceLongitude: route.sourceLongitude, destinationLocationName: route.destinationLocationName, destinationCountryCode: route.destinationCountryCode, destinationLatitude: route.destinationLatitude, destinationLongitude: route.destinationLongitude, etaDays: route.etaDays, notes: demoNote, }, update: { teamUuid: access.teamUuid, createdByUserId: access.userId, status: index === 0 ? 'in_transit' : 'processing', sourceAmount: new Prisma.Decimal(price.sourceAmount), sourceCurrency: price.sourceCurrency, clientAmount: new Prisma.Decimal(price.clientAmount), clientCurrency: price.clientCurrency, exchangeRate: new Prisma.Decimal(price.exchangeRate), exchangeRateSnapshotUuid: price.rateSnapshotUuid, exchangeRateProvider: price.provider, exchangeRateEffectiveDate: pricingEffectiveDate, exchangeRateQuotedAt: pricingQuotedAt, exchangeRateLockedAt: pricingLockedAt, pricingCountryCode: price.pricingCountryCode, pricingKycProfileUuid: kyc.profileUuid, notes: demoNote, }, }) } }