New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customer_id not set in the context of a pricing strategy when doing medusa.products.list, called with a cart that has a customer assigned. #7055
Comments
Here is my pricing strategy import {
AbstractPriceSelectionStrategy,
Customer,
CustomerGroup,
MoneyAmount,
PriceSelectionContext,
PriceSelectionResult,
PriceType,
} from '@medusajs/medusa';
import MoneyAmountRepository from '@medusajs/medusa/dist/repositories/money-amount';
import { TaxServiceRate } from '@medusajs/medusa/dist/types/tax-service';
import { FlagRouter, TaxInclusivePricingFeatureFlag } from '@medusajs/utils';
import { isDefined } from 'medusa-core-utils';
import { EntityManager } from 'typeorm';
class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
protected manager_: EntityManager;
protected readonly featureFlagRouter_: FlagRouter;
protected moneyAmountRepository_: typeof MoneyAmountRepository;
constructor({
manager,
featureFlagRouter,
moneyAmountRepository,
}: {
manager: EntityManager;
featureFlagRouter: FlagRouter;
moneyAmountRepository: typeof MoneyAmountRepository;
}) {
// @ts-expect-error Not sure
// eslint-disable-next-line prefer-rest-params
super(...arguments);
this.manager_ = manager;
this.moneyAmountRepository_ = moneyAmountRepository;
this.featureFlagRouter_ = featureFlagRouter;
console.info('CONSTRUCTOR PriceSelectionStrategy');
}
async calculateVariantPrice(
data: {
variantId: string;
quantity?: number;
}[],
context: PriceSelectionContext,
): Promise<Map<string, PriceSelectionResult>> {
const dataMap = new Map(data.map((d) => [d.variantId, d]));
console.info(context);
const nonCachedData: {
variantId: string;
quantity?: number;
}[] = [];
const variantPricesMap = new Map<string, PriceSelectionResult>();
nonCachedData.push(...dataMap.values());
let results: Map<string, PriceSelectionResult> = new Map();
if (
this.featureFlagRouter_.isFeatureEnabled(
TaxInclusivePricingFeatureFlag.key,
)
) {
results = await this.calculateVariantPrice_new(nonCachedData, context);
} else {
results = await this.calculateVariantPrice_old(nonCachedData, context);
}
[...results].map(([variantId, prices]) => {
variantPricesMap.set(variantId, prices);
});
return variantPricesMap;
}
private async modifyVariantPrices(
variantPrices: Record<string, MoneyAmount[]>,
customerId?: Customer['id'],
): Promise<Record<string, MoneyAmount[]>> {
if (!customerId) return variantPrices;
const customerGroup = await this.manager_
.createQueryBuilder(CustomerGroup, 'customer_group')
.leftJoinAndSelect('customer_group.customers', 'customers')
.where('customers_customer_group.customer_id = :customerId', {
customerId,
})
.getOne();
const companyId = customerGroup?.metadata.martsCompanyId;
//Based on the region and companyId we set the margins
console.info({ companyId });
const margin = 50000;
console.info('Adding margin', margin);
for (const prices of Object.values(variantPrices)) {
for (const price of prices) {
price.amount = Math.round(price.amount * (1 + margin / 100));
}
}
return variantPrices;
}
private async calculateVariantPrice_new(
data: {
variantId: string;
quantity?: number;
}[],
context: PriceSelectionContext,
): Promise<Map<string, PriceSelectionResult>> {
const moneyRepo = this.activeManager_.withRepository(
this.moneyAmountRepository_,
);
const [variantsPricesUnmodified] =
await moneyRepo.findManyForVariantsInRegion(
data.map((d) => d.variantId),
context.region_id,
context.currency_code,
context.customer_id,
context.include_discount_prices,
);
const variantsPrices = await this.modifyVariantPrices(
variantsPricesUnmodified,
context.customer_id,
);
const variantPricesMap = new Map<string, PriceSelectionResult>();
for (const [variantId, prices] of Object.entries(variantsPrices)) {
const dataItem = data.find((d) => d.variantId === variantId)!;
const result: PriceSelectionResult = {
originalPrice: null,
calculatedPrice: null,
prices,
originalPriceIncludesTax: null,
calculatedPriceIncludesTax: null,
};
if (!prices.length || !context) {
variantPricesMap.set(variantId, result);
}
const taxRate = context.tax_rates?.reduce(
(accRate: number, nextTaxRate: TaxServiceRate) => {
return accRate + (nextTaxRate.rate || 0) / 100;
},
0,
);
for (const ma of prices) {
let isTaxInclusive = ma.currency?.includes_tax || false;
if (ma.price_list?.includes_tax) {
// PriceList specific price so use the PriceList tax setting
isTaxInclusive = ma.price_list.includes_tax;
} else if (ma.region?.includes_tax) {
// Region specific price so use the Region tax setting
isTaxInclusive = ma.region.includes_tax;
}
delete ma.currency;
delete ma.region;
if (
context.region_id &&
ma.region_id === context.region_id &&
ma.price_list_id === null &&
ma.min_quantity === null &&
ma.max_quantity === null
) {
result.originalPriceIncludesTax = isTaxInclusive;
result.originalPrice = ma.amount;
}
if (
context.currency_code &&
ma.currency_code === context.currency_code &&
ma.price_list_id === null &&
ma.min_quantity === null &&
ma.max_quantity === null &&
result.originalPrice === null // region prices take precedence
) {
result.originalPriceIncludesTax = isTaxInclusive;
result.originalPrice = ma.amount;
}
if (
isValidQuantity(ma, dataItem.quantity) &&
isValidAmount(ma.amount, result, isTaxInclusive, taxRate) &&
((context.currency_code &&
ma.currency_code === context.currency_code) ||
(context.region_id && ma.region_id === context.region_id))
) {
result.calculatedPrice = ma.amount;
result.calculatedPriceType = ma.price_list?.type || PriceType.DEFAULT;
result.calculatedPriceIncludesTax = isTaxInclusive;
}
}
variantPricesMap.set(variantId, result);
}
return variantPricesMap;
}
private async calculateVariantPrice_old(
data: {
variantId: string;
quantity?: number;
}[],
context: PriceSelectionContext,
): Promise<Map<string, PriceSelectionResult>> {
const moneyRepo = this.activeManager_.withRepository(
this.moneyAmountRepository_,
);
const [variantsPricesUnmodified] =
await moneyRepo.findManyForVariantsInRegion(
data.map((d) => d.variantId),
context.region_id,
context.currency_code,
context.customer_id,
context.include_discount_prices,
);
const variantsPrices = await this.modifyVariantPrices(
variantsPricesUnmodified,
context.customer_id,
);
const variantPricesMap = new Map<string, PriceSelectionResult>();
for (const [variantId, prices] of Object.entries(variantsPrices)) {
const dataItem = data.find((d) => d.variantId === variantId)!;
const result: PriceSelectionResult = {
originalPrice: null,
calculatedPrice: null,
prices,
};
if (!prices.length || !context) {
variantPricesMap.set(variantId, result);
}
for (const ma of prices) {
delete ma.currency;
delete ma.region;
if (
context.region_id &&
ma.region_id === context.region_id &&
ma.price_list_id === null &&
ma.min_quantity === null &&
ma.max_quantity === null
) {
result.originalPrice = ma.amount;
}
if (
context.currency_code &&
ma.currency_code === context.currency_code &&
ma.price_list_id === null &&
ma.min_quantity === null &&
ma.max_quantity === null &&
result.originalPrice === null // region prices take precedence
) {
result.originalPrice = ma.amount;
}
if (
isValidQuantity(ma, dataItem.quantity) &&
(result.calculatedPrice === null ||
ma.amount < result.calculatedPrice) &&
((context.currency_code &&
ma.currency_code === context.currency_code) ||
(context.region_id && ma.region_id === context.region_id))
) {
result.calculatedPrice = ma.amount;
result.calculatedPriceType = ma.price_list?.type || PriceType.DEFAULT;
}
}
variantPricesMap.set(variantId, result);
}
return variantPricesMap;
}
}
const isValidAmount = (
amount: number,
result: PriceSelectionResult,
isTaxInclusive: boolean,
taxRate?: number,
): boolean => {
if (result.calculatedPrice === null) {
return true;
}
if (isTaxInclusive === result.calculatedPriceIncludesTax) {
// if both or neither are tax inclusive compare equally
return amount < result.calculatedPrice;
}
if (typeof taxRate !== 'undefined') {
return isTaxInclusive
? amount < (1 + taxRate) * result.calculatedPrice
: (1 + taxRate) * amount < result.calculatedPrice;
}
// if we dont have a taxrate we can't compare mixed prices
return false;
};
const isValidQuantity = (price: MoneyAmount, quantity?: number): boolean =>
(isDefined(quantity) && isValidPriceWithQuantity(price, quantity)) ||
(typeof quantity === 'undefined' && isValidPriceWithoutQuantity(price));
const isValidPriceWithoutQuantity = (price: MoneyAmount): boolean =>
!!(
(!price.max_quantity && !price.min_quantity) ||
((!price.min_quantity || price.min_quantity === 0) && price.max_quantity)
);
const isValidPriceWithQuantity = (
price: MoneyAmount,
quantity: number,
): boolean =>
(!price.min_quantity || price.min_quantity <= quantity) &&
(!price.max_quantity || price.max_quantity >= quantity);
export default PriceSelectionStrategy; |
@ebdrup right now, only signed-in customers will get customer-specific pricing. The medusa/packages/medusa/src/api/routes/store/products/list-products.ts Lines 325 to 331 in 122b3ea
This does not immediately solve your need (unless your customers will be signed in), but thought I'd highlight, so you know why |
Bug report (I think)
Describe the bug
I have created a customer in medusa, and I have created a cart in medusa and updated the cart with the customer id of the customer I created.
Now I list products with medusa.products.list and pass the cart_id as the cart I have created.
I have then implemented a Pricing Strategy, that gets run when I call medusa.products.list, but for some reason the customer_id is not set in the context passed to the pricing strategy.
The context in the pricing strategy has the right cart_id. When I look up the cart in the medusa DB for that id, the cart does have a customer_id set, but the pricing strategy does not have the customer_id of the cart in it's context.
When adding items to the cart, the pricing strategy gets called with a context that has the right cart_id and customer_id.
How do I make the customer_id be set in the context of the pricing strategy, when doing medusa.products.list?
System information
Medusa version (including plugins): 1.20.1
Node.js version: v20.9.0
Database: Postgesql
Operating system: OSX
Browser (if relevant):
Steps to reproduce the behavior
I have created a customer in medusa, and I have created a cart in medusa and updated the cart with the customer id of the customer I created.
Now I list products with medusa.products.list and pass the cart_id as the cart I have created.
I have then implemented a Pricing Strategy, that gets run when I call medusa.products.list, but for some reason the customer_id is not set in the context passed to the pricing strategy.
Expected behavior
The customer_id is set in the context of the Pricing strategy, when calling medusa.products.list with a cart_id of a cart that has a customer assigned.
The text was updated successfully, but these errors were encountered: