Browse Source

feat(admin-ui): Allow order shipping method to be modified

Relates to #978
Michael Bromley 2 years ago
parent
commit
7f34329b0b

File diff suppressed because it is too large
+ 3 - 1
packages/admin-ui/src/lib/core/src/common/generated-types.ts


+ 1 - 0
packages/admin-ui/src/lib/core/src/common/introspection-result.ts

@@ -130,6 +130,7 @@
       "CouponCodeExpiredError",
       "CouponCodeInvalidError",
       "CouponCodeLimitError",
+      "IneligibleShippingMethodError",
       "InsufficientStockError",
       "NegativeQuantityError",
       "NoChangesSpecifiedError",

+ 1 - 0
packages/admin-ui/src/lib/order/src/common/modify-order-types.ts

@@ -12,6 +12,7 @@ export interface OrderSnapshot {
     currencyCode: CurrencyCode;
     couponCodes: string[];
     lines: OrderDetailFragment['lines'];
+    shippingLines: OrderDetailFragment['shippingLines'];
 }
 
 export type ProductSelectorItem = ProductSelectorSearchQuery['search']['items'][number];

+ 16 - 2
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.html

@@ -22,6 +22,7 @@
                 [shippingAddressForm]="shippingAddressForm"
                 [billingAddressForm]="billingAddressForm"
                 [couponCodesControl]="couponCodesControl"
+                [updatedShippingMethods]="updatedShippingMethods"
             ></vdr-order-modification-summary>
 
             <div *ngIf="!hasModifications()" class="no-modifications">
@@ -194,12 +195,12 @@
                 <vdr-dt2-column
                     *ngFor="let customField of orderLineCustomFields"
                     [id]="customField.name"
-                    [heading]="customField | customFieldLabel:(uiLanguage$ | async)"
+                    [heading]="customField | customFieldLabel : (uiLanguage$ | async)"
                     [hiddenByDefault]="true"
                 >
                     <ng-template let-line="item" let-index="index">
                         <vdr-custom-field-control
-                                [compact]="true"
+                            [compact]="true"
                             [entityName]="'OrderLine'"
                             [customField]="customField"
                             [customFieldsFormGroup]="orderLineCustomFieldsFormArray.at(index)"
@@ -255,6 +256,19 @@
                 </div>
             </div>
         </vdr-card>
+        <vdr-card [title]="'order.shipping' | translate">
+            <div *ngFor="let shippingLine of order.shippingLines" class="flex items-center">
+                <ng-container *ngIf="getShippingLineDetails(shippingLine) as details">
+                    <div>{{ details.name }}:</div>
+                    <div class="mx-1">
+                        {{ details.price | localeCurrency : order.currencyCode }}
+                    </div>
+                    <button class="button-small" (click)="setShippingMethod(shippingLine.id)">
+                        {{ 'order.set-shipping-method' | translate }}
+                    </button>
+                </ng-container>
+            </div>
+        </vdr-card>
         <vdr-card [title]="'order.add-surcharge' | translate">
             <form [formGroup]="surchargeForm" (submit)="addSurcharge(surchargeForm.value)">
                 <div class="form-grid">

+ 76 - 2
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.ts

@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import {
     FormControl,
     FormGroup,
@@ -10,6 +10,7 @@ import {
 import {
     CustomFieldConfig,
     DataService,
+    DraftOrderEligibleShippingMethodsQuery,
     ErrorResult,
     GetAvailableCountriesQuery,
     HistoryEntryType,
@@ -41,6 +42,7 @@ import {
     OrderEditResultType,
     OrderEditsPreviewDialogComponent,
 } from '../order-edits-preview-dialog/order-edits-preview-dialog.component';
+import { SelectShippingMethodDialogComponent } from '../select-shipping-method-dialog/select-shipping-method-dialog.component';
 
 @Component({
     selector: 'vdr-order-editor',
@@ -109,6 +111,11 @@ export class OrderEditorComponent
     previousState: string;
     editingShippingAddress = false;
     editingBillingAddress = false;
+    updatedShippingMethods: {
+        [
+            shippingLineId: string
+        ]: DraftOrderEligibleShippingMethodsQuery['eligibleShippingMethodsForDraftOrder'][number];
+    } = {};
     private addedVariants = new Map<string, ProductSelectorItem>();
 
     constructor(
@@ -116,6 +123,7 @@ export class OrderEditorComponent
         private notificationService: NotificationService,
         private modalService: ModalService,
         private orderTransitionService: OrderTransitionService,
+        private changeDetectorRef: ChangeDetectorRef,
     ) {
         super();
     }
@@ -237,7 +245,8 @@ export class OrderEditorComponent
             !!adjustOrderLines?.length ||
             (this.shippingAddressForm.dirty && this.shippingAddressForm.valid) ||
             (this.billingAddressForm.dirty && this.billingAddressForm.valid) ||
-            this.couponCodesControl.dirty
+            this.couponCodesControl.dirty ||
+            Object.entries(this.updatedShippingMethods).length > 0
         );
     }
 
@@ -341,6 +350,61 @@ export class OrderEditorComponent
         this.addedVariants.set(result.productVariantId, result);
     }
 
+    getShippingLineDetails(shippingLine: OrderDetailFragment['shippingLines'][number]): {
+        name: string;
+        price: number;
+    } {
+        const updatedMethod = this.updatedShippingMethods[shippingLine.id];
+        if (updatedMethod) {
+            return {
+                name: updatedMethod.name || updatedMethod.code,
+                price: updatedMethod.priceWithTax,
+            };
+        } else {
+            return {
+                name: shippingLine.shippingMethod.name || shippingLine.shippingMethod.code,
+                price: shippingLine.discountedPriceWithTax,
+            };
+        }
+    }
+
+    setShippingMethod(shippingLineId: string) {
+        const currentShippingMethod =
+            this.updatedShippingMethods[shippingLineId] ??
+            this.entity?.shippingLines.find(l => l.id === shippingLineId)?.shippingMethod;
+        if (!currentShippingMethod) {
+            return;
+        }
+        this.dataService.order
+            .getDraftOrderEligibleShippingMethods(this.id)
+            .mapSingle(({ eligibleShippingMethodsForDraftOrder }) => eligibleShippingMethodsForDraftOrder)
+            .pipe(
+                switchMap(methods =>
+                    this.modalService
+                        .fromComponent(SelectShippingMethodDialogComponent, {
+                            locals: {
+                                eligibleShippingMethods: methods,
+                                currencyCode: this.entity?.currencyCode,
+                                currentSelectionId: currentShippingMethod.id,
+                            },
+                        })
+                        .pipe(
+                            map(result => {
+                                if (result) {
+                                    return methods.find(method => method.id === result);
+                                }
+                            }),
+                        ),
+                ),
+            )
+            .subscribe(result => {
+                if (result) {
+                    this.updatedShippingMethods[shippingLineId] = result;
+                    this.changeDetectorRef.markForCheck();
+                }
+            });
+    }
+
     private isMatchingAddItemRow(
         row: ModifyOrderData['addItems'][number],
         result: ProductSelectorItem,
@@ -405,6 +469,13 @@ export class OrderEditorComponent
                 recalculateShipping: this.recalculateShipping,
             },
         };
+        if (Object.entries(this.updatedShippingMethods).length) {
+            input.shippingMethodIds = order.shippingLines.map(l =>
+                this.updatedShippingMethods[l.id]
+                    ? this.updatedShippingMethods[l.id].id
+                    : l.shippingMethod.id,
+            );
+        }
         this.dataService.order
             .modifyOrder(input)
             .pipe(
@@ -423,6 +494,7 @@ export class OrderEditorComponent
                                     shippingAddressForm: this.shippingAddressForm,
                                     billingAddressForm: this.billingAddressForm,
                                     couponCodesControl: this.couponCodesControl,
+                                    updatedShippingMethods: this.updatedShippingMethods,
                                 },
                             });
                         case 'InsufficientStockError':
@@ -434,6 +506,7 @@ export class OrderEditorComponent
                         case 'RefundPaymentIdMissingError':
                         case 'CouponCodeLimitError':
                         case 'CouponCodeExpiredError':
+                        case 'IneligibleShippingMethodError':
                         case 'CouponCodeInvalidError': {
                             this.notificationService.error(modifyOrder.message);
                             return of(false as const);
@@ -506,6 +579,7 @@ export class OrderEditorComponent
             currencyCode: order.currencyCode,
             couponCodes: order.couponCodes,
             lines: [...order.lines].map(line => ({ ...line })),
+            shippingLines: [...order.shippingLines].map(line => ({ ...line })),
         };
     }
 

+ 1 - 0
packages/admin-ui/src/lib/order/src/components/order-edits-preview-dialog/order-edits-preview-dialog.component.html

@@ -23,6 +23,7 @@
                 [shippingAddressForm]="shippingAddressForm"
                 [billingAddressForm]="billingAddressForm"
                 [couponCodesControl]="couponCodesControl"
+                [updatedShippingMethods]="updatedShippingMethods"
             ></vdr-order-modification-summary>
             <vdr-labeled-data [label]="'order.note' | translate">
                 {{ modifyOrderInput.note || '-' }}

+ 1 - 0
packages/admin-ui/src/lib/order/src/components/order-edits-preview-dialog/order-edits-preview-dialog.component.ts

@@ -52,6 +52,7 @@ export class OrderEditsPreviewDialogComponent implements OnInit, Dialog<OrderEdi
     addedLines: AddedLine[];
     shippingAddressForm: OrderEditorComponent['shippingAddressForm'];
     billingAddressForm: OrderEditorComponent['billingAddressForm'];
+    updatedShippingMethods: OrderEditorComponent['updatedShippingMethods'];
     couponCodesControl: FormControl<string[] | null>;
 
     refundablePayments: RefundablePayment[];

+ 6 - 1
packages/admin-ui/src/lib/order/src/components/order-modification-summary/order-modification-summary.component.html

@@ -45,4 +45,9 @@
 </vdr-labeled-data>
 <vdr-labeled-data *ngIf="couponCodeChanges.length" [label]="'order.set-coupon-codes' | translate">
     <div *ngFor="let change of couponCodeChanges" class="mb-1">{{ change }}</div>
-</vdr-labeled-data>
+</vdr-labeled-data>
+<ng-container *ngIf="getUpdatedShippingMethodLines() as updatedShippingMethods">
+    <vdr-labeled-data *ngIf="updatedShippingMethods.length" [label]="'order.shipping-method' | translate">
+        <div *ngFor="let change of updatedShippingMethods" class="mb-1">{{ change }}</div>
+    </vdr-labeled-data>
+</ng-container>

+ 15 - 0
packages/admin-ui/src/lib/order/src/components/order-modification-summary/order-modification-summary.component.ts

@@ -16,6 +16,7 @@ export class OrderModificationSummaryComponent {
     @Input() addedLines: AddedLine[];
     @Input() shippingAddressForm: OrderEditorComponent['shippingAddressForm'];
     @Input() billingAddressForm: OrderEditorComponent['billingAddressForm'];
+    @Input() updatedShippingMethods: OrderEditorComponent['updatedShippingMethods'];
     @Input() couponCodesControl: FormControl<string[] | null>;
 
     get adjustedLines(): string[] {
@@ -47,6 +48,20 @@ export class OrderModificationSummaryComponent {
             .join(', ');
     }
 
+    getUpdatedShippingMethodLines() {
+        return Object.entries(this.updatedShippingMethods || {})
+            .map(([lineId, shippingMethod]) => {
+                const previousMethod = this.orderSnapshot.shippingLines.find(l => l.id === lineId);
+                if (!previousMethod) {
+                    return;
+                }
+                const previousName = previousMethod.shippingMethod.name || previousMethod.shippingMethod.code;
+                const newName = shippingMethod.name || shippingMethod.code;
+                return `${previousName} -> ${newName}`;
+            })
+            .filter(notNullOrUndefined);
+    }
+
     get couponCodeChanges(): string[] {
         const originalCodes = this.orderSnapshot.couponCodes || [];
         const newCodes = this.couponCodesControl.value || [];

Some files were not shown because too many files changed in this diff