diff --git a/packages/core/src/api/resolvers/shop/shop-order.resolver.ts b/packages/core/src/api/resolvers/shop/shop-order.resolver.ts index d418034587..98758ce23f 100644 --- a/packages/core/src/api/resolvers/shop/shop-order.resolver.ts +++ b/packages/core/src/api/resolvers/shop/shop-order.resolver.ts @@ -76,7 +76,8 @@ export class ShopOrderResolver { async order( @Ctx() ctx: RequestContext, @Args() args: QueryOrderArgs, - @Relations(Order) relations: RelationPaths, + @Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] }) + relations: RelationPaths, ): Promise { const requiredRelations: RelationPaths = ['customer', 'customer.user']; const order = await this.orderService.findOne( @@ -98,7 +99,8 @@ export class ShopOrderResolver { @Allow(Permission.Owner) async activeOrder( @Ctx() ctx: RequestContext, - @Relations(Order) relations: RelationPaths, + @Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] }) + relations: RelationPaths, @Args() args: ActiveOrderArgs, ): Promise { if (ctx.authorizedAsOwnerOnly) { @@ -107,7 +109,7 @@ export class ShopOrderResolver { args[ACTIVE_ORDER_INPUT_FIELD_NAME], ); if (sessionOrder) { - return this.orderService.findOne(ctx, sessionOrder.id); + return this.orderService.findOne(ctx, sessionOrder.id, relations); } else { return; } @@ -119,7 +121,8 @@ export class ShopOrderResolver { async orderByCode( @Ctx() ctx: RequestContext, @Args() args: QueryOrderByCodeArgs, - @Relations(Order) relations: RelationPaths, + @Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] }) + relations: RelationPaths, ): Promise { if (ctx.authorizedAsOwnerOnly) { const requiredRelations: RelationPaths = ['customer', 'customer.user']; @@ -294,6 +297,8 @@ export class ShopOrderResolver { async addItemToOrder( @Ctx() ctx: RequestContext, @Args() args: MutationAddItemToOrderArgs & ActiveOrderArgs, + @Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] }) + relations: RelationPaths, ): Promise> { const order = await this.activeOrderService.getActiveOrder( ctx, @@ -306,6 +311,7 @@ export class ShopOrderResolver { args.productVariantId, args.quantity, (args as any).customFields, + relations, ); } @@ -315,6 +321,8 @@ export class ShopOrderResolver { async adjustOrderLine( @Ctx() ctx: RequestContext, @Args() args: MutationAdjustOrderLineArgs & ActiveOrderArgs, + @Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] }) + relations: RelationPaths, ): Promise> { if (args.quantity === 0) { return this.removeOrderLine(ctx, { orderLineId: args.orderLineId }); @@ -330,6 +338,7 @@ export class ShopOrderResolver { args.orderLineId, args.quantity, (args as any).customFields, + relations, ); } diff --git a/packages/core/src/entity/order-line/order-line.entity.ts b/packages/core/src/entity/order-line/order-line.entity.ts index a9c44fc229..4f747ca67d 100644 --- a/packages/core/src/entity/order-line/order-line.entity.ts +++ b/packages/core/src/entity/order-line/order-line.entity.ts @@ -76,6 +76,9 @@ export class OrderLine extends VendureEntity implements HasCustomFields { @ManyToOne(type => TaxCategory) taxCategory: TaxCategory; + @EntityId({ nullable: true }) + taxCategoryId: ID; + @Index() @ManyToOne(type => Asset, asset => asset.featuredInVariants, { onDelete: 'SET NULL' }) featuredAsset: Asset; diff --git a/packages/core/src/entity/tax-rate/tax-rate.entity.ts b/packages/core/src/entity/tax-rate/tax-rate.entity.ts index eeb2ea433a..630ef74de3 100644 --- a/packages/core/src/entity/tax-rate/tax-rate.entity.ts +++ b/packages/core/src/entity/tax-rate/tax-rate.entity.ts @@ -1,5 +1,5 @@ import { TaxLine } from '@vendure/common/lib/generated-types'; -import { DeepPartial } from '@vendure/common/lib/shared-types'; +import { DeepPartial, ID } from '@vendure/common/lib/shared-types'; import { Column, Entity, Index, ManyToOne } from 'typeorm'; import { grossPriceOf, netPriceOf, taxComponentOf, taxPayableOn } from '../../common/tax-utils'; @@ -8,6 +8,7 @@ import { HasCustomFields } from '../../config/custom-field/custom-field-types'; import { VendureEntity } from '../base/base.entity'; import { CustomTaxRateFields } from '../custom-entity-fields'; import { CustomerGroup } from '../customer-group/customer-group.entity'; +import { EntityId } from '../entity-id.decorator'; import { TaxCategory } from '../tax-category/tax-category.entity'; import { DecimalTransformer } from '../value-transformers'; import { Zone } from '../zone/zone.entity'; @@ -38,10 +39,16 @@ export class TaxRate extends VendureEntity implements HasCustomFields { @ManyToOne(type => TaxCategory, taxCategory => taxCategory.taxRates) category: TaxCategory; + @EntityId({ nullable: true }) + categoryId: ID; + @Index() @ManyToOne(type => Zone, zone => zone.taxRates) zone: Zone; + @EntityId({ nullable: true }) + zoneId: ID; + @Index() @ManyToOne(type => CustomerGroup, customerGroup => customerGroup.taxRates, { nullable: true }) customerGroup?: CustomerGroup; @@ -84,7 +91,13 @@ export class TaxRate extends VendureEntity implements HasCustomFields { }; } - test(zone: Zone, taxCategory: TaxCategory): boolean { - return idsAreEqual(taxCategory.id, this.category.id) && idsAreEqual(zone.id, this.zone.id); + test(zone: Zone | ID, taxCategory: TaxCategory | ID): boolean { + const taxCategoryId = this.isId(taxCategory) ? taxCategory : taxCategory.id; + const zoneId = this.isId(zone) ? zone : zone.id; + return idsAreEqual(taxCategoryId, this.categoryId) && idsAreEqual(zoneId, this.zoneId); + } + + private isId(entityOrId: T | ID): entityOrId is ID { + return typeof entityOrId === 'string' || typeof entityOrId === 'number'; } } diff --git a/packages/core/src/service/helpers/order-calculator/order-calculator.ts b/packages/core/src/service/helpers/order-calculator/order-calculator.ts index c1cb98f705..b0e153cc46 100644 --- a/packages/core/src/service/helpers/order-calculator/order-calculator.ts +++ b/packages/core/src/service/helpers/order-calculator/order-calculator.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { filterAsync } from '@vendure/common/lib/filter-async'; import { AdjustmentType } from '@vendure/common/lib/generated-types'; +import { ID } from '@vendure/common/lib/shared-types'; import { RequestContext } from '../../../api/common/request-context'; import { RequestContextCacheService } from '../../../cache/request-context-cache.service'; @@ -76,7 +77,6 @@ export class OrderCalculator { ctx, order, updatedOrderLine, - activeTaxZone, this.createTaxRateGetter(ctx, activeTaxZone), ); } @@ -113,7 +113,7 @@ export class OrderCalculator { private async applyTaxes(ctx: RequestContext, order: Order, activeZone: Zone) { const getTaxRate = this.createTaxRateGetter(ctx, activeZone); for (const line of order.lines) { - await this.applyTaxesToOrderLine(ctx, order, line, activeZone, getTaxRate); + await this.applyTaxesToOrderLine(ctx, order, line, getTaxRate); } this.calculateOrderTotals(order); } @@ -126,10 +126,9 @@ export class OrderCalculator { ctx: RequestContext, order: Order, line: OrderLine, - activeZone: Zone, - getTaxRate: (taxCategory: TaxCategory) => Promise, + getTaxRate: (taxCategoryId: ID) => Promise, ) { - const applicableTaxRate = await getTaxRate(line.taxCategory); + const applicableTaxRate = await getTaxRate(line.taxCategoryId); const { taxLineCalculationStrategy } = this.configService.taxOptions; line.taxLines = await taxLineCalculationStrategy.calculate({ ctx, @@ -147,16 +146,16 @@ export class OrderCalculator { private createTaxRateGetter( ctx: RequestContext, activeZone: Zone, - ): (taxCategory: TaxCategory) => Promise { - const taxRateCache = new Map(); + ): (taxCategoryId: ID) => Promise { + const taxRateCache = new Map(); - return async (taxCategory: TaxCategory): Promise => { - const cached = taxRateCache.get(taxCategory); + return async (taxCategoryId: ID): Promise => { + const cached = taxRateCache.get(taxCategoryId); if (cached) { return cached; } - const rate = await this.taxRateService.getApplicableTaxRate(ctx, activeZone, taxCategory); - taxRateCache.set(taxCategory, rate); + const rate = await this.taxRateService.getApplicableTaxRate(ctx, activeZone, taxCategoryId); + taxRateCache.set(taxCategoryId, rate); return rate; }; } diff --git a/packages/core/src/service/helpers/order-modifier/order-modifier.ts b/packages/core/src/service/helpers/order-modifier/order-modifier.ts index 839738202f..a5153e1319 100644 --- a/packages/core/src/service/helpers/order-modifier/order-modifier.ts +++ b/packages/core/src/service/helpers/order-modifier/order-modifier.ts @@ -141,7 +141,7 @@ export class OrderModifier { ): Promise { for (const line of order.lines) { const match = - idsAreEqual(line.productVariant.id, productVariantId) && + idsAreEqual(line.productVariantId, productVariantId) && (await this.customFieldsAreEqual(ctx, line, customFields, line.customFields)); if (match) { return line; diff --git a/packages/core/src/service/helpers/order-splitter/order-splitter.ts b/packages/core/src/service/helpers/order-splitter/order-splitter.ts index 9dc510bb46..d550e63723 100644 --- a/packages/core/src/service/helpers/order-splitter/order-splitter.ts +++ b/packages/core/src/service/helpers/order-splitter/order-splitter.ts @@ -90,7 +90,9 @@ export class OrderSplitter { ...pick(line, [ 'quantity', 'productVariant', + 'productVariantId', 'taxCategory', + 'taxCategoryId', 'featuredAsset', 'shippingLine', 'shippingLineId', diff --git a/packages/core/src/service/services/order-testing.service.ts b/packages/core/src/service/services/order-testing.service.ts index 56603b46c2..b40973da94 100644 --- a/packages/core/src/service/services/order-testing.service.ts +++ b/packages/core/src/service/services/order-testing.service.ts @@ -130,6 +130,7 @@ export class OrderTestingService { taxLines: [], quantity: line.quantity, taxCategory: productVariant.taxCategory, + taxCategoryId: productVariant.taxCategoryId, }); mockOrder.lines.push(orderLine); diff --git a/packages/core/src/service/services/order.service.ts b/packages/core/src/service/services/order.service.ts index 851f38eaf0..180411c361 100644 --- a/packages/core/src/service/services/order.service.ts +++ b/packages/core/src/service/services/order.service.ts @@ -489,7 +489,7 @@ export class OrderService { * @since 2.2.0 */ async updateOrderCustomer(ctx: RequestContext, { customerId, orderId, note }: SetOrderCustomerInput) { - const order = await this.getOrderOrThrow(ctx, orderId); + const order = await this.getOrderOrThrow(ctx, orderId, ['channels', 'customer']); const currentCustomer = order.customer; if (currentCustomer?.id === customerId) { // No change in customer, so just return the order as-is @@ -539,6 +539,7 @@ export class OrderService { productVariantId: ID, quantity: number, customFields?: { [key: string]: any }, + relations?: RelationPaths, ): Promise> { const order = await this.getOrderOrThrow(ctx, orderId); const existingOrderLine = await this.orderModifier.getExistingOrderLine( @@ -597,7 +598,7 @@ export class OrderService { await this.orderModifier.updateOrderLineQuantity(ctx, orderLine, correctedQuantity, order); } const quantityWasAdjustedDown = correctedQuantity < quantity; - const updatedOrder = await this.applyPriceAdjustments(ctx, order, [orderLine]); + const updatedOrder = await this.applyPriceAdjustments(ctx, order, [orderLine], relations); if (quantityWasAdjustedDown) { return new InsufficientStockError({ quantityAvailable: correctedQuantity, order: updatedOrder }); } else { @@ -615,6 +616,7 @@ export class OrderService { orderLineId: ID, quantity: number, customFields?: { [key: string]: any }, + relations?: RelationPaths, ): Promise> { const order = await this.getOrderOrThrow(ctx, orderId); const orderLine = this.getOrderLineOrThrow(order, orderLineId); @@ -661,7 +663,7 @@ export class OrderService { await this.orderModifier.updateOrderLineQuantity(ctx, orderLine, correctedQuantity, order); } const quantityWasAdjustedDown = correctedQuantity < quantity; - const updatedOrder = await this.applyPriceAdjustments(ctx, order, updatedOrderLines); + const updatedOrder = await this.applyPriceAdjustments(ctx, order, updatedOrderLines, relations); if (quantityWasAdjustedDown) { return new InsufficientStockError({ quantityAvailable: correctedQuantity, order: updatedOrder }); } else { @@ -1664,8 +1666,23 @@ export class OrderService { return order; } - private async getOrderOrThrow(ctx: RequestContext, orderId: ID): Promise { - const order = await this.findOne(ctx, orderId); + private async getOrderOrThrow( + ctx: RequestContext, + orderId: ID, + relations?: RelationPaths, + ): Promise { + const order = await this.findOne( + ctx, + orderId, + relations ?? [ + 'lines', + 'lines.productVariant', + 'lines.productVariant.productVariantPrices', + 'shippingLines', + 'surcharges', + 'customer', + ], + ); if (!order) { throw new EntityNotFoundError('Order', orderId); } @@ -1731,6 +1748,7 @@ export class OrderService { ctx: RequestContext, order: Order, updatedOrderLines?: OrderLine[], + relations?: RelationPaths, ): Promise { const promotions = await this.promotionService.getActivePromotionsInChannel(ctx); const activePromotionsPre = await this.promotionService.getActivePromotionsOnOrder(ctx, order.id); @@ -1816,7 +1834,7 @@ export class OrderService { await this.connection.getRepository(ctx, ShippingLine).save(order.shippingLines, { reload: false }); await this.promotionService.runPromotionSideEffects(ctx, order, activePromotionsPre); - return assertFound(this.findOne(ctx, order.id)); + return assertFound(this.findOne(ctx, order.id, relations)); } /** diff --git a/packages/core/src/service/services/tax-rate.service.ts b/packages/core/src/service/services/tax-rate.service.ts index c0c2979d1a..cad588b3aa 100644 --- a/packages/core/src/service/services/tax-rate.service.ts +++ b/packages/core/src/service/services/tax-rate.service.ts @@ -164,7 +164,11 @@ export class TaxRateService { * Returns the applicable TaxRate based on the specified Zone and TaxCategory. Used when calculating Order * prices. */ - async getApplicableTaxRate(ctx: RequestContext, zone: Zone, taxCategory: TaxCategory): Promise { + async getApplicableTaxRate( + ctx: RequestContext, + zone: Zone | ID, + taxCategory: TaxCategory | ID, + ): Promise { const rate = (await this.getActiveTaxRates(ctx)).find(r => r.test(zone, taxCategory)); return rate || this.defaultTaxRate; }