From 70d1c4d2a529d5aef1cabd283ac2d147f0c4bced Mon Sep 17 00:00:00 2001 From: Tiago de Andrade Freire Date: Sat, 14 Dec 2024 20:20:51 -0300 Subject: [PATCH] fix: right splitting of seller quotes --- CHANGELOG.md | 7 +- node/clients/SellerQuotesClient.ts | 34 +++++---- node/resolvers/mutations/index.ts | 107 ++++++++++++++++------------- node/resolvers/utils/quotes.ts | 59 ++++++++++++++++ 4 files changed, 142 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78286ab..58df8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add new input quotesManagedBy on appSettings to handle splitting quotes -- Add new quote configuration to handle split quote by seller -- Verify if seller accept manage quotes -- Notify seller with quote payload if it accepts to manage quotes +- Add field quotesManagedBy on appSettings to handle splitting quotes +- Process splitting quote by seller if it accepts to manage quotes +- Notify seller with quote reference data as payload ## [2.6.4] - 2024-10-31 diff --git a/node/clients/SellerQuotesClient.ts b/node/clients/SellerQuotesClient.ts index fcd1065..0b0d9d1 100644 --- a/node/clients/SellerQuotesClient.ts +++ b/node/clients/SellerQuotesClient.ts @@ -13,13 +13,14 @@ interface VerifyQuoteSettingsResponse { receiveQuotes: boolean } -interface NotifySellerQuoteResponse { - status: string +interface SellerQuoteNotifyInput { + quoteId: string + marketplaceAccount: string } const routes = { - verifyQuoteSettings: '/verify-quote-settings', - notifyNewQuote: '/notify-new-quote', + verifyQuoteSettings: 'verify-quote-settings', + notifyNewQuote: 'notify-new-quote', } export default class SellerQuotesClient extends ExternalClient { @@ -35,24 +36,29 @@ export default class SellerQuotesClient extends ExternalClient { }) } - private getRoute(account: string, path: string) { + private getRoute(sellerAccount: string, path: string) { const subdomain = this.context.production - ? account - : `${this.context.workspace}--${account}` + ? sellerAccount + : `${this.context.workspace}--${sellerAccount}` - return `http://${subdomain}.myvtex.com/_v/b2b-seller-quotes${path}` + return `http://${subdomain}.myvtex.com/b2b-seller-quotes/_v/0/${path}` } - public async verifyQuoteSettings(account: string) { + public async verifyQuoteSettings(sellerAccount: string) { return this.http.get( - this.getRoute(account, routes.verifyQuoteSettings) + this.getRoute(sellerAccount, routes.verifyQuoteSettings) ) } - public async notifyNewQuote(account: string, quote: Quote) { - return this.http.postRaw( - this.getRoute(account, routes.notifyNewQuote), - quote + public async notifyNewQuote(sellerAccount: string, quoteId: string) { + const payload: SellerQuoteNotifyInput = { + quoteId, + marketplaceAccount: this.context.account, + } + + return this.http.postRaw( + this.getRoute(sellerAccount, routes.notifyNewQuote), + payload ) } } diff --git a/node/resolvers/mutations/index.ts b/node/resolvers/mutations/index.ts index ae61349..f7e30c2 100644 --- a/node/resolvers/mutations/index.ts +++ b/node/resolvers/mutations/index.ts @@ -24,7 +24,7 @@ import { checkQuoteStatus, checkSession, } from '../utils/checkPermissions' -import { createQuoteObject } from '../utils/quotes' +import { createQuoteObject, processSellerItems } from '../utils/quotes' export const Mutation = { clearCart: async (_: any, params: any, ctx: Context) => { @@ -81,55 +81,68 @@ export const Mutation = { throw new GraphQLError('operation-not-permitted') } - const parentQuote = createQuoteObject({ - sessionData, - storefrontPermissions, - segmentData, - settings, - items, - referenceName, - subtotal, - note, - sendToSalesRep, - }) - try { - const { DocumentId: parentQuoteId } = await masterdata.createDocument({ - dataEntity: QUOTE_DATA_ENTITY, - fields: parentQuote, - schema: SCHEMA_VERSION, - }) + const quoteBySeller: Record = {} if (settings?.adminSetup.quotesManagedBy === 'SELLER') { - const quoteBySeller: Record = {} - - items.forEach(({ seller, sellingPrice, ...itemData }) => { - if (!quoteBySeller[seller]) { - quoteBySeller[seller] = { - items: [], - referenceName, - note, - sendToSalesRep, - subtotal: 0, - } - } + const sellerItems = items.filter( + ({ seller }) => seller && seller !== '1' + ) - quoteBySeller[seller].items.push({ - seller, - sellingPrice, - ...itemData, - }) - quoteBySeller[seller].subtotal += sellingPrice * itemData.quantity + await processSellerItems({ + ctx, + quoteBySeller, + referenceName, + note, + sendToSalesRep, + items: sellerItems, }) + } - const documentIds = await Promise.all( - Object.entries(quoteBySeller).map(async ([seller, sellerQuote]) => { - const verifyResponse = await ctx.clients.sellerQuotes.verifyQuoteSettings( - seller - ) + const hasSellerQuotes = Object.keys(quoteBySeller).length + + const parentQuoteItems = hasSellerQuotes + ? items.filter( + (item) => + !Object.values(quoteBySeller).some((quote) => + quote.items.some((quoteItem) => quoteItem.id === item.id) + ) + ) + : items + + // We believe that parent quote should contain the overall subtotal. + // If for some reason it is necessary to subtract the subtotal from + // sellers quotes, we can use the adjustedSubtotal below, assigning + // it to subtotal in createQuoteObject (subtotal: adjustedSubtotal). + // + // const adjustedSubtotal = hasSellerQuotes + // ? Object.values(quoteBySeller).reduce( + // (acc, quote) => acc - quote.subtotal, + // subtotal + // ) + // : subtotal + + const parentQuote = createQuoteObject({ + sessionData, + storefrontPermissions, + segmentData, + settings, + items: parentQuoteItems, + referenceName, + subtotal, + note, + sendToSalesRep, + }) - if (!verifyResponse.receiveQuotes) return null + const { DocumentId: parentQuoteId } = await masterdata.createDocument({ + dataEntity: QUOTE_DATA_ENTITY, + fields: parentQuote, + schema: SCHEMA_VERSION, + }) + if (hasSellerQuotes) { + const sellerQuoteIds = await Promise.all( + Object.entries(quoteBySeller).map(async ([seller, sellerQuote]) => { const sellerQuoteObject = createQuoteObject({ sessionData, storefrontPermissions, @@ -151,16 +164,16 @@ export const Mutation = { schema: SCHEMA_VERSION, }) - await ctx.clients.sellerQuotes.notifyNewQuote(seller, { - id: data.DocumentId, - ...sellerQuoteObject, - }) + await ctx.clients.sellerQuotes.notifyNewQuote( + seller, + data.DocumentId + ) return data.DocumentId }) ) - if (documentIds.filter(Boolean).length) { + if (sellerQuoteIds.length) { await masterdata.updatePartialDocument({ dataEntity: QUOTE_DATA_ENTITY, fields: { hasChildren: true }, diff --git a/node/resolvers/utils/quotes.ts b/node/resolvers/utils/quotes.ts index 8a8cc44..9e614c7 100644 --- a/node/resolvers/utils/quotes.ts +++ b/node/resolvers/utils/quotes.ts @@ -1,3 +1,62 @@ +export async function processSellerItems({ + ctx, + quoteBySeller, + referenceName, + note, + sendToSalesRep, + items, + index = 0, +}: { + ctx: Context + quoteBySeller: Record + referenceName: string + note: string + sendToSalesRep: boolean + items: QuoteItem[] + index?: number +}): Promise { + if (index >= items.length) return + + const item = items[index] + const { seller } = item + + const next = async () => + processSellerItems({ + ctx, + quoteBySeller, + referenceName, + note, + sendToSalesRep, + items, + index: index + 1, + }) + + const verifyResponse = await ctx.clients.sellerQuotes + .verifyQuoteSettings(seller) + .catch(() => null) + + if (!verifyResponse?.receiveQuotes) { + await next() + + return + } + + if (!quoteBySeller[seller]) { + quoteBySeller[seller] = { + items: [], + referenceName, + note, + sendToSalesRep, + subtotal: 0, + } + } + + quoteBySeller[seller].items.push(item) + quoteBySeller[seller].subtotal += item.sellingPrice * item.quantity + + await next() +} + export const createQuoteObject = ({ sessionData, storefrontPermissions,