Expose live display currency for transactions
Build and deploy Docker image / build (push) Successful in 1m25s

This commit is contained in:
Ruslan Bakiev
2026-08-24 21:16:42 +07:00
parent 952253dceb
commit e709c332a6
3 changed files with 100 additions and 15 deletions
+39
View File
@@ -25,6 +25,18 @@ export type LockedCurrencyPrice = {
pricingCountryCode: string 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 = const kycM2mEndpoint =
process.env.KYC_M2M_GRAPHQL_ENDPOINT ?? 'https://kyc.optovia.ru/graphql/m2m' process.env.KYC_M2M_GRAPHQL_ENDPOINT ?? 'https://kyc.optovia.ru/graphql/m2m'
const exchangeM2mEndpoint = const exchangeM2mEndpoint =
@@ -131,3 +143,30 @@ export async function lockCurrencyPrice(input: {
) )
return data.lockCurrencyPrice return data.lockCurrencyPrice
} }
export async function convertCurrencyForDisplay(input: {
amount: number
sourceCurrency: string
displayCurrency: string
}): Promise<DisplayCurrencyConversion> {
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
}
+60 -14
View File
@@ -11,7 +11,9 @@ import { prisma } from '../db.js'
import { ensureDemoWorkspaceData } from '../demo-workspace.js' import { ensureDemoWorkspaceData } from '../demo-workspace.js'
import { import {
lockCurrencyPrice, lockCurrencyPrice,
convertCurrencyForDisplay,
resolveKycPricingContext, resolveKycPricingContext,
type DisplayCurrencyConversion,
type KycPricingContext, type KycPricingContext,
type LockedCurrencyPrice, type LockedCurrencyPrice,
} from '../pricing-context.js' } from '../pricing-context.js'
@@ -112,6 +114,8 @@ export const teamTypeDefs = `#graphql
sourceCurrency: String sourceCurrency: String
clientAmount: Float! clientAmount: Float!
clientCurrency: String! clientCurrency: String!
displayAmount: Float!
displayCurrency: String!
exchangeRate: Float exchangeRate: Float
exchangeRateSnapshotUuid: String exchangeRateSnapshotUuid: String
exchangeRateProvider: String exchangeRateProvider: String
@@ -216,6 +220,8 @@ export const teamTypeDefs = `#graphql
sourceCurrency: String sourceCurrency: String
clientAmount: Float! clientAmount: Float!
clientCurrency: String! clientCurrency: String!
displayAmount: Float!
displayCurrency: String!
exchangeRate: Float exchangeRate: Float
exchangeRateSnapshotUuid: String exchangeRateSnapshotUuid: String
exchangeRateProvider: String exchangeRateProvider: String
@@ -362,10 +368,10 @@ export const teamTypeDefs = `#graphql
} }
type Query { type Query {
getTeamOrders: [Order] getTeamOrders(displayCurrency: String!): [Order]
getOrder(orderUuid: String!): Order getOrder(orderUuid: String!, displayCurrency: String!): Order
quotations(status: String): [Quotation!]! quotations(status: String, displayCurrency: String!): [Quotation!]!
quotation(quotationUuid: String!): Quotation quotation(quotationUuid: String!, displayCurrency: String!): Quotation
tariffReferences(status: String): [TariffReference!]! tariffReferences(status: String): [TariffReference!]!
tariffReference(tariffReferenceUuid: String!): TariffReference tariffReference(tariffReferenceUuid: String!): TariffReference
quotationTariffMatches(quotationUuid: String!): [QuotationTariffMatch!]! 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 { return {
uuid: item.uuid, uuid: item.uuid,
teamUuid: item.teamUuid, teamUuid: item.teamUuid,
@@ -582,6 +593,8 @@ function mapQuotation(item: QuotationWithRelations) {
sourceCurrency: item.sourceCurrency, sourceCurrency: item.sourceCurrency,
clientAmount: Number(item.clientAmount), clientAmount: Number(item.clientAmount),
clientCurrency: item.clientCurrency, clientCurrency: item.clientCurrency,
displayAmount,
displayCurrency,
exchangeRate: numberFromDecimal(item.exchangeRate), exchangeRate: numberFromDecimal(item.exchangeRate),
exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid, exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid,
exchangeRateProvider: item.exchangeRateProvider, 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 progress = orderLocationProgress(item.status)
const locationLatitude = interpolateCoordinate(item.sourceLatitude, item.destinationLatitude, progress) const locationLatitude = interpolateCoordinate(item.sourceLatitude, item.destinationLatitude, progress)
const locationLongitude = interpolateCoordinate(item.sourceLongitude, item.destinationLongitude, progress) const locationLongitude = interpolateCoordinate(item.sourceLongitude, item.destinationLongitude, progress)
@@ -615,6 +633,8 @@ function mapLocalOrder(item: OrderWithRelations) {
sourceCurrency: item.sourceCurrency, sourceCurrency: item.sourceCurrency,
clientAmount: Number(item.clientAmount), clientAmount: Number(item.clientAmount),
clientCurrency: item.clientCurrency, clientCurrency: item.clientCurrency,
displayAmount,
displayCurrency,
exchangeRate: numberFromDecimal(item.exchangeRate), exchangeRate: numberFromDecimal(item.exchangeRate),
exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid, exchangeRateSnapshotUuid: item.exchangeRateSnapshotUuid,
exchangeRateProvider: item.exchangeRateProvider, exchangeRateProvider: item.exchangeRateProvider,
@@ -1133,7 +1153,7 @@ async function refreshTeamQuotations(teamUuid: string, actor: { userId: string;
export const teamResolvers = { export const teamResolvers = {
Query: { Query: {
getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => { getTeamOrders: async (_: unknown, args: { displayCurrency: string }, ctx: AuthContext) => {
const access = assertTeamAccess(ctx) const access = assertTeamAccess(ctx)
await ensureClientWorkspaceData(access) 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 access = assertTeamAccess(ctx)
const order = await prisma.order.findFirst({ const order = await prisma.order.findFirst({
@@ -1161,10 +1188,16 @@ export const teamResolvers = {
include: orderInclude, 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) const access = assertTeamAccess(ctx)
await ensureClientWorkspaceData(access) 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 access = assertTeamAccess(ctx)
const item = await prisma.quotation.findFirst({ const item = await prisma.quotation.findFirst({
where: { where: {
@@ -1191,7 +1231,13 @@ export const teamResolvers = {
}, },
include: quotationInclude, 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) => { tariffReferences: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => {