From e709c332a683a251fecf8bb5d1e6627179cd249a Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:16:42 +0700 Subject: [PATCH] Expose live display currency for transactions --- graphql-contracts | 2 +- src/pricing-context.ts | 39 ++++++++++++++++++++++ src/schemas/team.ts | 74 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 15 deletions(-) diff --git a/graphql-contracts b/graphql-contracts index e2627cb..1ea758d 160000 --- a/graphql-contracts +++ b/graphql-contracts @@ -1 +1 @@ -Subproject commit e2627cb00dd1671b6091306e008ec64c610ec472 +Subproject commit 1ea758ddd418d0f237cb3fdeba745a4f84377076 diff --git a/src/pricing-context.ts b/src/pricing-context.ts index fba9011..bda4a7a 100644 --- a/src/pricing-context.ts +++ b/src/pricing-context.ts @@ -25,6 +25,18 @@ export type LockedCurrencyPrice = { pricingCountryCode: string } +export type DisplayCurrencyConversion = { + sourceAmount: number + sourceCurrency: string + displayAmount: number + displayCurrency: string + exchangeRate: number + rateSnapshotUuid: string | null + rateEffectiveDate: string + rateQuotedAt: string + provider: string +} + const kycM2mEndpoint = process.env.KYC_M2M_GRAPHQL_ENDPOINT ?? 'https://kyc.optovia.ru/graphql/m2m' const exchangeM2mEndpoint = @@ -131,3 +143,30 @@ export async function lockCurrencyPrice(input: { ) return data.lockCurrencyPrice } + +export async function convertCurrencyForDisplay(input: { + amount: number + sourceCurrency: string + displayCurrency: string +}): Promise { + const data = await requestGraphql<{ + convertCurrencyForDisplay: DisplayCurrencyConversion + }>( + exchangeM2mEndpoint, + `query OrdersConvertCurrencyForDisplay($input: ConvertCurrencyForDisplayInput!) { + convertCurrencyForDisplay(input: $input) { + sourceAmount + sourceCurrency + displayAmount + displayCurrency + exchangeRate + rateSnapshotUuid + rateEffectiveDate + rateQuotedAt + provider + } + }`, + { input }, + ) + return data.convertCurrencyForDisplay +} diff --git a/src/schemas/team.ts b/src/schemas/team.ts index a19a615..26f23d6 100644 --- a/src/schemas/team.ts +++ b/src/schemas/team.ts @@ -11,7 +11,9 @@ import { prisma } from '../db.js' import { ensureDemoWorkspaceData } from '../demo-workspace.js' import { lockCurrencyPrice, + convertCurrencyForDisplay, resolveKycPricingContext, + type DisplayCurrencyConversion, type KycPricingContext, type LockedCurrencyPrice, } from '../pricing-context.js' @@ -112,6 +114,8 @@ export const teamTypeDefs = `#graphql sourceCurrency: String clientAmount: Float! clientCurrency: String! + displayAmount: Float! + displayCurrency: String! exchangeRate: Float exchangeRateSnapshotUuid: String exchangeRateProvider: String @@ -216,6 +220,8 @@ export const teamTypeDefs = `#graphql sourceCurrency: String clientAmount: Float! clientCurrency: String! + displayAmount: Float! + displayCurrency: String! exchangeRate: Float exchangeRateSnapshotUuid: String exchangeRateProvider: String @@ -362,10 +368,10 @@ export const teamTypeDefs = `#graphql } type Query { - getTeamOrders: [Order] - getOrder(orderUuid: String!): Order - quotations(status: String): [Quotation!]! - quotation(quotationUuid: String!): Quotation + getTeamOrders(displayCurrency: String!): [Order] + getOrder(orderUuid: String!, displayCurrency: String!): Order + quotations(status: String, displayCurrency: String!): [Quotation!]! + quotation(quotationUuid: String!, displayCurrency: String!): Quotation tariffReferences(status: String): [TariffReference!]! tariffReference(tariffReferenceUuid: String!): TariffReference quotationTariffMatches(quotationUuid: String!): [QuotationTariffMatch!]! @@ -547,7 +553,12 @@ function mapQuotationChange(item: PrismaQuotationChange) { } } -function mapQuotation(item: QuotationWithRelations) { +function mapQuotation( + item: QuotationWithRelations, + display?: DisplayCurrencyConversion, +) { + const displayAmount = display?.displayAmount ?? Number(item.clientAmount) + const displayCurrency = display?.displayCurrency ?? item.clientCurrency return { uuid: item.uuid, teamUuid: item.teamUuid, @@ -582,6 +593,8 @@ function mapQuotation(item: QuotationWithRelations) { sourceCurrency: item.sourceCurrency, clientAmount: Number(item.clientAmount), clientCurrency: item.clientCurrency, + displayAmount, + displayCurrency, exchangeRate: numberFromDecimal(item.exchangeRate), exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid, exchangeRateProvider: item.exchangeRateProvider, @@ -600,7 +613,12 @@ function mapQuotation(item: QuotationWithRelations) { } } -function mapLocalOrder(item: OrderWithRelations) { +function mapLocalOrder( + item: OrderWithRelations, + display?: DisplayCurrencyConversion, +) { + const displayAmount = display?.displayAmount ?? Number(item.clientAmount) + const displayCurrency = display?.displayCurrency ?? item.clientCurrency const progress = orderLocationProgress(item.status) const locationLatitude = interpolateCoordinate(item.sourceLatitude, item.destinationLatitude, progress) const locationLongitude = interpolateCoordinate(item.sourceLongitude, item.destinationLongitude, progress) @@ -615,6 +633,8 @@ function mapLocalOrder(item: OrderWithRelations) { sourceCurrency: item.sourceCurrency, clientAmount: Number(item.clientAmount), clientCurrency: item.clientCurrency, + displayAmount, + displayCurrency, exchangeRate: numberFromDecimal(item.exchangeRate), exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid, exchangeRateProvider: item.exchangeRateProvider, @@ -1133,7 +1153,7 @@ async function refreshTeamQuotations(teamUuid: string, actor: { userId: string; export const teamResolvers = { Query: { - getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => { + getTeamOrders: async (_: unknown, args: { displayCurrency: string }, ctx: AuthContext) => { const access = assertTeamAccess(ctx) await ensureClientWorkspaceData(access) @@ -1147,10 +1167,17 @@ export const teamResolvers = { }, }) - return localOrders.map(mapLocalOrder) + return Promise.all(localOrders.map(async item => mapLocalOrder( + item, + await convertCurrencyForDisplay({ + amount: Number(item.clientAmount), + sourceCurrency: item.clientCurrency, + displayCurrency: args.displayCurrency, + }), + ))) }, - getOrder: async (_: unknown, args: { orderUuid: string }, ctx: AuthContext) => { + getOrder: async (_: unknown, args: { orderUuid: string; displayCurrency: string }, ctx: AuthContext) => { const access = assertTeamAccess(ctx) const order = await prisma.order.findFirst({ @@ -1161,10 +1188,16 @@ export const teamResolvers = { include: orderInclude, }) - return order ? mapLocalOrder(order) : null + if (order === null) return null + const display = await convertCurrencyForDisplay({ + amount: Number(order.clientAmount), + sourceCurrency: order.clientCurrency, + displayCurrency: args.displayCurrency, + }) + return mapLocalOrder(order, display) }, - quotations: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => { + quotations: async (_: unknown, args: { status?: string | null; displayCurrency: string }, ctx: AuthContext) => { const access = assertTeamAccess(ctx) await ensureClientWorkspaceData(access) @@ -1179,10 +1212,17 @@ export const teamResolvers = { }, }) - return items.map(mapQuotation) + return Promise.all(items.map(async item => mapQuotation( + item, + await convertCurrencyForDisplay({ + amount: Number(item.clientAmount), + sourceCurrency: item.clientCurrency, + displayCurrency: args.displayCurrency, + }), + ))) }, - quotation: async (_: unknown, args: { quotationUuid: string }, ctx: AuthContext) => { + quotation: async (_: unknown, args: { quotationUuid: string; displayCurrency: string }, ctx: AuthContext) => { const access = assertTeamAccess(ctx) const item = await prisma.quotation.findFirst({ where: { @@ -1191,7 +1231,13 @@ export const teamResolvers = { }, include: quotationInclude, }) - return item ? mapQuotation(item) : null + if (item === null) return null + const display = await convertCurrencyForDisplay({ + amount: Number(item.clientAmount), + sourceCurrency: item.clientCurrency, + displayCurrency: args.displayCurrency, + }) + return mapQuotation(item, display) }, tariffReferences: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => {