Fix transaction currency on quotations
Build and deploy Docker image / build (push) Successful in 51s
Build and deploy Docker image / build (push) Successful in 51s
This commit is contained in:
+1
-1
Submodule graphql-contracts updated: 48cd334d24...e2627cb00d
@@ -99,6 +99,7 @@ const demoRoutes: DemoRoute[] = [
|
||||
|
||||
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')
|
||||
@@ -128,6 +129,7 @@ export async function ensureDemoWorkspaceData(access: DemoWorkspaceAccess): Prom
|
||||
const needsPricingBackfill = demoQuotationPricingCount > 0 || demoOrderPricingCount > 0
|
||||
const kyc = needsCalculations || needsOrders || needsPricingBackfill
|
||||
? await ensureDemoKycPricingContext({
|
||||
teamUuid: access.teamUuid,
|
||||
userId: access.userId,
|
||||
countryCode: demoPricingCountryCode,
|
||||
})
|
||||
@@ -179,6 +181,7 @@ export async function ensureDemoWorkspaceData(access: DemoWorkspaceAccess): Prom
|
||||
const price = await lockCurrencyPrice({
|
||||
sourceAmount: route.amountUsd,
|
||||
sourceCurrency: 'USD',
|
||||
clientCurrency: demoClientCurrency,
|
||||
pricingCountryCode: kyc.countryCode,
|
||||
})
|
||||
const pricingEffectiveDate = new Date(`${price.rateEffectiveDate}T00:00:00.000Z`)
|
||||
|
||||
+17
-15
@@ -4,7 +4,7 @@ type GraphqlEnvelope<T> = {
|
||||
}
|
||||
|
||||
export type KycPricingContext = {
|
||||
userId: string
|
||||
teamUuid: string
|
||||
profileUuid: string
|
||||
countryCode: string
|
||||
resolvedAt: string
|
||||
@@ -56,39 +56,40 @@ async function requestGraphql<T>(
|
||||
}
|
||||
|
||||
export async function resolveKycPricingContext(
|
||||
userId: string,
|
||||
teamUuid: string,
|
||||
): Promise<KycPricingContext> {
|
||||
const data = await requestGraphql<{
|
||||
pricingContextForUser: KycPricingContext | null
|
||||
pricingContextForTeam: KycPricingContext | null
|
||||
}>(
|
||||
kycM2mEndpoint,
|
||||
`query OrdersKycPricingContext($userId: String!) {
|
||||
pricingContextForUser(userId: $userId) {
|
||||
userId
|
||||
`query OrdersKycPricingContext($teamUuid: String!) {
|
||||
pricingContextForTeam(teamUuid: $teamUuid) {
|
||||
teamUuid
|
||||
profileUuid
|
||||
countryCode
|
||||
resolvedAt
|
||||
}
|
||||
}`,
|
||||
{ userId },
|
||||
{ teamUuid },
|
||||
)
|
||||
if (data.pricingContextForUser === null) {
|
||||
throw new Error(`Active KYC pricing context is required for user ${userId}`)
|
||||
if (data.pricingContextForTeam === null) {
|
||||
throw new Error(`Active KYC pricing context is required for team ${teamUuid}`)
|
||||
}
|
||||
return data.pricingContextForUser
|
||||
return data.pricingContextForTeam
|
||||
}
|
||||
|
||||
export async function ensureDemoKycPricingContext(input: {
|
||||
teamUuid: string
|
||||
userId: string
|
||||
countryCode: string
|
||||
}): Promise<KycPricingContext> {
|
||||
const data = await requestGraphql<{
|
||||
ensureDemoPricingContextForUser: KycPricingContext
|
||||
ensureDemoPricingContextForTeam: KycPricingContext
|
||||
}>(
|
||||
kycM2mEndpoint,
|
||||
`mutation OrdersEnsureDemoKycPricingContext($userId: String!, $countryCode: String!) {
|
||||
ensureDemoPricingContextForUser(userId: $userId, countryCode: $countryCode) {
|
||||
userId
|
||||
`mutation OrdersEnsureDemoKycPricingContext($teamUuid: String!, $userId: String!, $countryCode: String!) {
|
||||
ensureDemoPricingContextForTeam(teamUuid: $teamUuid, userId: $userId, countryCode: $countryCode) {
|
||||
teamUuid
|
||||
profileUuid
|
||||
countryCode
|
||||
resolvedAt
|
||||
@@ -96,12 +97,13 @@ export async function ensureDemoKycPricingContext(input: {
|
||||
}`,
|
||||
input,
|
||||
)
|
||||
return data.ensureDemoPricingContextForUser
|
||||
return data.ensureDemoPricingContextForTeam
|
||||
}
|
||||
|
||||
export async function lockCurrencyPrice(input: {
|
||||
sourceAmount: number
|
||||
sourceCurrency: string
|
||||
clientCurrency: string
|
||||
pricingCountryCode: string
|
||||
rateDate?: string
|
||||
}): Promise<LockedCurrencyPrice> {
|
||||
|
||||
+19
-12
@@ -306,6 +306,7 @@ export const teamTypeDefs = `#graphql
|
||||
}
|
||||
|
||||
input CreateQuotationInput {
|
||||
clientCurrency: String
|
||||
title: String
|
||||
status: String
|
||||
operationCode: String
|
||||
@@ -333,6 +334,7 @@ export const teamTypeDefs = `#graphql
|
||||
}
|
||||
|
||||
input UpdateQuotationInput {
|
||||
clientCurrency: String
|
||||
title: String
|
||||
status: String
|
||||
operationCode: String
|
||||
@@ -779,6 +781,7 @@ function buildCreateQuotationData(input: CreateQuotationInput, ctx: { teamUuid:
|
||||
return {
|
||||
teamUuid: ctx.teamUuid,
|
||||
createdByUserId: ctx.userId,
|
||||
clientCurrency: normalizeUpper(input.clientCurrency) ?? 'USD',
|
||||
title: normalizeString(input.title) ?? 'Quotation',
|
||||
status: normalizeString(input.status) ?? 'draft',
|
||||
operationCode: normalizeUpper(input.operationCode),
|
||||
@@ -809,6 +812,9 @@ function buildCreateQuotationData(input: CreateQuotationInput, ctx: { teamUuid:
|
||||
function buildUpdateQuotationData(input: UpdateQuotationInput): Prisma.QuotationUpdateInput {
|
||||
const data: Prisma.QuotationUpdateInput = {}
|
||||
|
||||
if (hasOwn(input, 'clientCurrency')) {
|
||||
data.clientCurrency = normalizeRequiredUpdateUpper(input.clientCurrency)
|
||||
}
|
||||
if (hasOwn(input, 'title')) data.title = normalizeRequiredUpdateString(input.title)
|
||||
if (hasOwn(input, 'status')) data.status = normalizeRequiredUpdateString(input.status)
|
||||
if (hasOwn(input, 'operationCode')) data.operationCode = normalizeUpper(input.operationCode)
|
||||
@@ -975,24 +981,19 @@ function actorLabel(ctx: { userId: string; teamUuid: string }): string {
|
||||
async function lockTariffPrice(
|
||||
tariff: PrismaTariffReference,
|
||||
sourceAmount: number,
|
||||
userId: string,
|
||||
teamUuid: string,
|
||||
clientCurrency: string,
|
||||
): Promise<{ kyc: KycPricingContext; price: LockedCurrencyPrice }> {
|
||||
const kyc = await resolveKycPricingContext(userId)
|
||||
const kyc = await resolveKycPricingContext(teamUuid)
|
||||
const price = await lockCurrencyPrice({
|
||||
sourceAmount,
|
||||
sourceCurrency: tariff.sourceCurrency,
|
||||
clientCurrency,
|
||||
pricingCountryCode: kyc.countryCode,
|
||||
})
|
||||
return { kyc, price }
|
||||
}
|
||||
|
||||
function quotationPricingUserId(quotation: PrismaQuotation): string {
|
||||
if (!quotation.createdByUserId) {
|
||||
throw new GraphQLError('Quotation owner is required to resolve KYC pricing currency')
|
||||
}
|
||||
return quotation.createdByUserId
|
||||
}
|
||||
|
||||
function lockedQuotationPriceData(
|
||||
locked: { kyc: KycPricingContext; price: LockedCurrencyPrice } | null,
|
||||
currentClientCurrency: string,
|
||||
@@ -1053,7 +1054,8 @@ async function refreshQuotationSelection(quotationUuid: string, teamUuid: string
|
||||
? await lockTariffPrice(
|
||||
bestMatch.tariff,
|
||||
bestMatch.projectedSourceAmount,
|
||||
quotationPricingUserId(quotation),
|
||||
quotation.teamUuid,
|
||||
quotation.clientCurrency,
|
||||
)
|
||||
: null
|
||||
|
||||
@@ -1231,12 +1233,13 @@ export const teamResolvers = {
|
||||
})
|
||||
|
||||
const matches = computeTariffMatches(quotation, tariffs)
|
||||
const kyc = await resolveKycPricingContext(quotationPricingUserId(quotation))
|
||||
const kyc = await resolveKycPricingContext(quotation.teamUuid)
|
||||
return Promise.all(
|
||||
matches.map(async item => {
|
||||
const price = await lockCurrencyPrice({
|
||||
sourceAmount: item.projectedSourceAmount,
|
||||
sourceCurrency: item.tariff.sourceCurrency,
|
||||
clientCurrency: quotation.clientCurrency,
|
||||
pricingCountryCode: kyc.countryCode,
|
||||
})
|
||||
return {
|
||||
@@ -1316,6 +1319,9 @@ export const teamResolvers = {
|
||||
updateQuotation: async (_: unknown, args: { quotationUuid: string; input: UpdateQuotationInput }, ctx: AuthContext) => {
|
||||
const access = assertTeamAccess(ctx)
|
||||
const current = await loadQuotationOrThrow(args.quotationUuid, access.teamUuid)
|
||||
if (hasOwn(args.input, 'clientCurrency') && current.status === 'converted') {
|
||||
throw new GraphQLError('Converted quotation currency is fixed')
|
||||
}
|
||||
const teamAccess = { teamUuid: current.teamUuid, userId: access.userId }
|
||||
const actor = { userId: access.userId, label: actorLabel(teamAccess) }
|
||||
await prisma.quotation.update({
|
||||
@@ -1357,7 +1363,8 @@ export const teamResolvers = {
|
||||
const locked = await lockTariffPrice(
|
||||
tariff,
|
||||
projectedSourceAmount,
|
||||
quotationPricingUserId(quotation),
|
||||
quotation.teamUuid,
|
||||
quotation.clientCurrency,
|
||||
)
|
||||
await prisma.quotation.update({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user