Explorar o código

fix(core): Correctly resolve prices of deleted ProductVariants in orders

Fixes #1508
Michael Bromley %!s(int64=3) %!d(string=hai) anos
pai
achega
5061dd923f

+ 52 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -3604,6 +3604,16 @@ export type GetActiveCustomerWithOrdersProductSlugQuery = {
     }>;
 };
 
+export type GetActiveCustomerWithOrdersProductPriceQueryVariables = Exact<{
+    options?: Maybe<OrderListOptions>;
+}>;
+
+export type GetActiveCustomerWithOrdersProductPriceQuery = {
+    activeCustomer?: Maybe<{
+        orders: { items: Array<{ lines: Array<{ productVariant: Pick<ProductVariant, 'price'> }> }> };
+    }>;
+};
+
 type DiscriminateUnion<T, U> = T extends U ? T : never;
 
 export namespace TestOrderFragment {
@@ -4269,3 +4279,45 @@ export namespace GetActiveCustomerWithOrdersProductSlug {
         >['product']
     >;
 }
+
+export namespace GetActiveCustomerWithOrdersProductPrice {
+    export type Variables = GetActiveCustomerWithOrdersProductPriceQueryVariables;
+    export type Query = GetActiveCustomerWithOrdersProductPriceQuery;
+    export type ActiveCustomer = NonNullable<GetActiveCustomerWithOrdersProductPriceQuery['activeCustomer']>;
+    export type Orders = NonNullable<
+        NonNullable<GetActiveCustomerWithOrdersProductPriceQuery['activeCustomer']>['orders']
+    >;
+    export type Items = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<GetActiveCustomerWithOrdersProductPriceQuery['activeCustomer']>['orders']
+            >['items']
+        >[number]
+    >;
+    export type Lines = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<
+                    NonNullable<
+                        NonNullable<GetActiveCustomerWithOrdersProductPriceQuery['activeCustomer']>['orders']
+                    >['items']
+                >[number]
+            >['lines']
+        >[number]
+    >;
+    export type ProductVariant = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<
+                    NonNullable<
+                        NonNullable<
+                            NonNullable<
+                                GetActiveCustomerWithOrdersProductPriceQuery['activeCustomer']
+                            >['orders']
+                        >['items']
+                    >[number]
+                >['lines']
+            >[number]
+        >['productVariant']
+    >;
+}

+ 18 - 0
packages/core/e2e/graphql/shop-definitions.ts

@@ -759,3 +759,21 @@ export const GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_SLUG = gql`
         }
     }
 `;
+export const GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_PRICE = gql`
+    query GetActiveCustomerWithOrdersProductPrice($options: OrderListOptions) {
+        activeCustomer {
+            orders(options: $options) {
+                items {
+                    lines {
+                        linePrice
+                        productVariant {
+                            id
+                            name
+                            price
+                        }
+                    }
+                }
+            }
+        }
+    }
+`;

+ 20 - 0
packages/core/e2e/order.e2e-spec.ts

@@ -71,6 +71,7 @@ import {
     ApplyCouponCode,
     DeletionResult,
     GetActiveCustomerOrderWithItemFulfillments,
+    GetActiveCustomerWithOrdersProductPrice,
     GetActiveCustomerWithOrdersProductSlug,
     GetActiveOrder,
     GetOrderByCodeWithPayments,
@@ -101,6 +102,7 @@ import {
     ADD_ITEM_TO_ORDER,
     ADD_PAYMENT,
     APPLY_COUPON_CODE,
+    GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_PRICE,
     GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_SLUG,
     GET_ACTIVE_ORDER,
     GET_ACTIVE_ORDER_CUSTOMER_WITH_ITEM_FULFILLMENTS,
@@ -2290,6 +2292,24 @@ describe('Orders resolver', () => {
                     .product.slug,
             ).toBe('gaming-pc');
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/1508
+        it('resolves price of deleted ProductVariant of OrderLine', async () => {
+            const { activeCustomer } = await shopClient.query<
+                GetActiveCustomerWithOrdersProductPrice.Query,
+                GetActiveCustomerWithOrdersProductPrice.Variables
+            >(GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_PRICE, {
+                options: {
+                    sort: {
+                        createdAt: SortOrder.ASC,
+                    },
+                },
+            });
+            expect(
+                activeCustomer!.orders.items[activeCustomer!.orders.items.length - 1].lines[0].productVariant
+                    .price,
+            ).toBe(108720);
+        });
     });
 });
 

+ 4 - 2
packages/core/src/service/helpers/utils/translate-entity.ts

@@ -60,8 +60,10 @@ export function translateEntity<T extends Translatable & VendureEntity>(
         });
     }
 
-    const translated = { ...(translatable as any) };
-    Object.setPrototypeOf(translated, Object.getPrototypeOf(translatable));
+    const translated = Object.create(
+        Object.getPrototypeOf(translatable),
+        Object.getOwnPropertyDescriptors(translatable),
+    );
 
     for (const [key, value] of Object.entries(translation)) {
         if (key === 'customFields') {

+ 1 - 16
packages/core/src/service/services/order.service.ts

@@ -266,28 +266,13 @@ export class OrderService {
     ): Promise<PaginatedList<Order>> {
         return this.listQueryBuilder
             .build(Order, options, {
-                relations: [
-                    'lines',
-                    'lines.items',
-                    'lines.productVariant',
-                    'lines.productVariant.options',
-                    'customer',
-                    'channels',
-                    'shippingLines',
-                ],
+                relations: ['lines', 'lines.items', 'customer', 'channels', 'shippingLines'],
                 channelId: ctx.channelId,
                 ctx,
             })
             .andWhere('order.customer.id = :customerId', { customerId })
             .getManyAndCount()
             .then(([items, totalItems]) => {
-                items.forEach(item => {
-                    item.lines.forEach(line => {
-                        line.productVariant = translateDeep(line.productVariant, ctx.languageCode, [
-                            'options',
-                        ]);
-                    });
-                });
                 return {
                     items,
                     totalItems,

+ 5 - 4
packages/core/src/service/services/product-variant.service.ts

@@ -251,9 +251,10 @@ export class ProductVariantService {
      */
     async getVariantByOrderLineId(ctx: RequestContext, orderLineId: ID): Promise<Translated<ProductVariant>> {
         const { productVariant } = await this.connection.getEntityOrThrow(ctx, OrderLine, orderLineId, {
-            relations: ['productVariant'],
+            relations: ['productVariant', 'productVariant.taxCategory'],
+            includeSoftDeleted: true,
         });
-        return translateDeep(productVariant, ctx.languageCode);
+        return translateDeep(await this.applyChannelPriceAndTax(productVariant, ctx), ctx.languageCode);
     }
 
     /**
@@ -589,7 +590,7 @@ export class ProductVariantService {
                             ctx,
                             ProductVariant,
                             variant.id,
-                            { relations: ['productVariantPrices'] },
+                            { relations: ['productVariantPrices'], includeSoftDeleted: true },
                         );
                         variant.productVariantPrices = variantWithPrices.productVariantPrices;
                     }
@@ -598,7 +599,7 @@ export class ProductVariantService {
                             ctx,
                             ProductVariant,
                             variant.id,
-                            { relations: ['taxCategory'] },
+                            { relations: ['taxCategory'], includeSoftDeleted: true },
                         );
                         variant.taxCategory = variantWithTaxCategory.taxCategory;
                     }