Seed empty client workspaces
Build and deploy Docker image / build (push) Successful in 1m57s

This commit is contained in:
Ruslan Bakiev
2026-08-24 11:35:50 +07:00
parent 615f6615e0
commit d0271bc25b
2 changed files with 247 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
import { createHash } from 'node:crypto'
import { Prisma } from '@prisma/client'
import { prisma } from './db.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' })
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] = await Promise.all([
prisma.quotation.count({ where: { teamUuid: access.teamUuid } }),
prisma.order.count({ where: { teamUuid: access.teamUuid } }),
])
const needsCalculations = quotationCount === 0
const needsOrders = orderCount === 0
if (!needsCalculations && !needsOrders) return
for (const [index, route] of demoRoutes.entries()) {
const createsOrder = index < 2
if (!needsCalculations && !(needsOrders && createsOrder)) continue
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',
operationCode: 'IMPORT',
transportTypeCode: route.transportTypeCode,
sourceCountryCode: route.sourceCountryCode,
destinationCountryCode: route.destinationCountryCode,
amountUsd: new Prisma.Decimal(route.amountUsd),
minPriceUsd: new Prisma.Decimal(route.amountUsd),
etaDays: route.etaDays,
priority: 50 + index,
currency: 'USD',
notes: demoNote,
},
update: {
teamUuid: access.teamUuid,
createdByUserId: access.userId,
amountUsd: new Prisma.Decimal(route.amountUsd),
minPriceUsd: new Prisma.Decimal(route.amountUsd),
etaDays: route.etaDays,
notes: demoNote,
},
})
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}`,
totalAmount: new Prisma.Decimal(route.amountUsd),
currency: 'USD',
etaDays: route.etaDays,
notes: demoNote,
},
update: {
teamUuid: access.teamUuid,
createdByUserId: access.userId,
selectedTariffId: tariff.id,
totalAmount: new Prisma.Decimal(route.amountUsd),
currency: 'USD',
etaDays: route.etaDays,
notes: demoNote,
},
})
if (!needsOrders || !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',
totalAmount: new Prisma.Decimal(route.amountUsd),
currency: 'USD',
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',
totalAmount: new Prisma.Decimal(route.amountUsd),
currency: 'USD',
notes: demoNote,
},
})
}
}
+15
View File
@@ -8,6 +8,7 @@ import {
} from '@prisma/client' } from '@prisma/client'
import { requireScopes, type AuthContext } from '../auth.js' import { requireScopes, type AuthContext } from '../auth.js'
import { prisma } from '../db.js' import { prisma } from '../db.js'
import { ensureDemoWorkspaceData } from '../demo-workspace.js'
const quotationInclude = { const quotationInclude = {
selectedTariff: true, selectedTariff: true,
@@ -440,6 +441,18 @@ function teamAccessWhere(access: { teamUuid?: string }): { teamUuid?: string } {
return access.teamUuid ? { teamUuid: access.teamUuid } : {} return access.teamUuid ? { teamUuid: access.teamUuid } : {}
} }
async function ensureClientWorkspaceData(access: {
teamUuid?: string
userId: string
isManager: boolean
}): Promise<void> {
if (access.isManager || !access.teamUuid) return
await ensureDemoWorkspaceData({
teamUuid: access.teamUuid,
userId: access.userId,
})
}
function mapTariffReference(item: PrismaTariffReference) { function mapTariffReference(item: PrismaTariffReference) {
return { return {
uuid: item.uuid, uuid: item.uuid,
@@ -951,6 +964,7 @@ export const teamResolvers = {
Query: { Query: {
getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => { getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => {
const access = assertTeamAccess(ctx) const access = assertTeamAccess(ctx)
await ensureClientWorkspaceData(access)
const localOrders = await prisma.order.findMany({ const localOrders = await prisma.order.findMany({
where: { where: {
@@ -981,6 +995,7 @@ export const teamResolvers = {
quotations: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => { quotations: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => {
const access = assertTeamAccess(ctx) const access = assertTeamAccess(ctx)
await ensureClientWorkspaceData(access)
const items = await prisma.quotation.findMany({ const items = await prisma.quotation.findMany({
where: { where: {