Files
orders/src/demo-workspace.ts
T
Ruslan Bakiev d34db71b51
Build and deploy Docker image / build (push) Successful in 51s
Fix transaction currency on quotations
2026-08-24 21:10:09 +07:00

308 lines
11 KiB
TypeScript

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<void> {
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,
},
})
}
}