ソースを参照

feat(admin-ui): Implement order modification flow

Relates to #314
Michael Bromley 5 年 前
コミット
d3e3a88423
52 ファイル変更1991 行追加388 行削除
  1. 28 23
      packages/admin-ui/i18n-coverage.json
  2. 1 1
      packages/admin-ui/src/lib/core/src/common/detail-breadcrumb.ts
  3. 81 3
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  4. 45 0
      packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts
  5. 19 0
      packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts
  6. 67 0
      packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.html
  7. 0 0
      packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.scss
  8. 15 0
      packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.ts
  9. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts
  10. 2 0
      packages/admin-ui/src/lib/core/src/shared/pipes/state-i18n-token.pipe.ts
  11. 2 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  12. 5 67
      packages/admin-ui/src/lib/customer/src/components/address-detail-dialog/address-detail-dialog.component.html
  13. 22 0
      packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.html
  14. 3 0
      packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.scss
  15. 48 0
      packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.ts
  16. 1 1
      packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.html
  17. 1 1
      packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.scss
  18. 14 0
      packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.html
  19. 0 0
      packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.scss
  20. 65 0
      packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.ts
  21. 19 187
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.html
  22. 1 55
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.scss
  23. 85 35
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts
  24. 29 0
      packages/admin-ui/src/lib/order/src/components/order-detail/transition-to-pre-modifying-state.ts
  25. 318 0
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.html
  26. 10 0
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.scss
  27. 347 0
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.ts
  28. 29 0
      packages/admin-ui/src/lib/order/src/components/order-edits-preview-dialog/order-edits-preview-dialog.component.html
  29. 0 0
      packages/admin-ui/src/lib/order/src/components/order-edits-preview-dialog/order-edits-preview-dialog.component.scss
  30. 80 0
      packages/admin-ui/src/lib/order/src/components/order-edits-preview-dialog/order-edits-preview-dialog.component.ts
  31. 26 1
      packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.html
  32. 8 0
      packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.ts
  33. 1 1
      packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html
  34. 47 0
      packages/admin-ui/src/lib/order/src/components/order-table/order-table-mixin.scss
  35. 165 0
      packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.html
  36. 15 0
      packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.scss
  37. 50 0
      packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.ts
  38. 1 1
      packages/admin-ui/src/lib/order/src/components/refund-order-dialog/refund-order-dialog.component.html
  39. 1 1
      packages/admin-ui/src/lib/order/src/components/refund-order-dialog/refund-order-dialog.component.scss
  40. 10 0
      packages/admin-ui/src/lib/order/src/order.module.ts
  41. 29 1
      packages/admin-ui/src/lib/order/src/order.routes.ts
  42. 1 1
      packages/admin-ui/src/lib/settings/src/components/test-order-builder/test-order-builder.component.html
  43. 31 1
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  44. 31 1
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  45. 31 1
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  46. 30 0
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  47. 53 3
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  48. 31 1
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  49. 31 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  50. 30 0
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  51. 30 0
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json
  52. 1 0
      packages/admin-ui/src/lib/static/styles/theme/_forms.scss

+ 28 - 23
packages/admin-ui/i18n-coverage.json

@@ -1,46 +1,51 @@
 {
 {
-  "generatedOn": "2020-12-04T16:28:55.124Z",
-  "lastCommit": "8e5b1dd3b6d362fd9431d242e5e829419e6f3179",
+  "generatedOn": "2020-12-21T15:26:51.662Z",
+  "lastCommit": "3fda1021d033a38b878a6c6d8b5543bf0d554155",
   "translationStatus": {
   "translationStatus": {
     "cs": {
     "cs": {
-      "tokenCount": 713,
-      "translatedCount": 685,
-      "percentage": 96
+      "tokenCount": 743,
+      "translatedCount": 687,
+      "percentage": 92
     },
     },
     "de": {
     "de": {
-      "tokenCount": 713,
-      "translatedCount": 594,
-      "percentage": 83
+      "tokenCount": 743,
+      "translatedCount": 596,
+      "percentage": 80
     },
     },
     "en": {
     "en": {
-      "tokenCount": 713,
-      "translatedCount": 713,
-      "percentage": 100
+      "tokenCount": 743,
+      "translatedCount": 739,
+      "percentage": 99
     },
     },
     "es": {
     "es": {
-      "tokenCount": 713,
+      "tokenCount": 743,
       "translatedCount": 455,
       "translatedCount": 455,
-      "percentage": 64
+      "percentage": 61
+    },
+    "fr": {
+      "tokenCount": 743,
+      "translatedCount": 692,
+      "percentage": 93
     },
     },
     "pl": {
     "pl": {
-      "tokenCount": 713,
-      "translatedCount": 549,
-      "percentage": 77
+      "tokenCount": 743,
+      "translatedCount": 551,
+      "percentage": 74
     },
     },
     "pt_BR": {
     "pt_BR": {
-      "tokenCount": 713,
-      "translatedCount": 640,
-      "percentage": 90
+      "tokenCount": 743,
+      "translatedCount": 642,
+      "percentage": 86
     },
     },
     "zh_Hans": {
     "zh_Hans": {
-      "tokenCount": 713,
+      "tokenCount": 743,
       "translatedCount": 533,
       "translatedCount": 533,
-      "percentage": 75
+      "percentage": 72
     },
     },
     "zh_Hant": {
     "zh_Hant": {
-      "tokenCount": 713,
+      "tokenCount": 743,
       "translatedCount": 533,
       "translatedCount": 533,
-      "percentage": 75
+      "percentage": 72
     }
     }
   }
   }
 }
 }

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/detail-breadcrumb.ts

@@ -29,7 +29,7 @@ export function detailBreadcrumb<T>(options: {
                 },
                 },
                 {
                 {
                     label,
                     label,
-                    link: [options.id],
+                    link: [options.route, options.id],
                 },
                 },
             ];
             ];
         }),
         }),

+ 81 - 3
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -3345,7 +3345,8 @@ export enum HistoryEntryType {
   ORDER_FULFILLMENT_TRANSITION = 'ORDER_FULFILLMENT_TRANSITION',
   ORDER_FULFILLMENT_TRANSITION = 'ORDER_FULFILLMENT_TRANSITION',
   ORDER_NOTE = 'ORDER_NOTE',
   ORDER_NOTE = 'ORDER_NOTE',
   ORDER_COUPON_APPLIED = 'ORDER_COUPON_APPLIED',
   ORDER_COUPON_APPLIED = 'ORDER_COUPON_APPLIED',
-  ORDER_COUPON_REMOVED = 'ORDER_COUPON_REMOVED'
+  ORDER_COUPON_REMOVED = 'ORDER_COUPON_REMOVED',
+  ORDER_MODIFIED = 'ORDER_MODIFIED'
 }
 }
 
 
 export type HistoryEntryList = PaginatedList & {
 export type HistoryEntryList = PaginatedList & {
@@ -5451,7 +5452,7 @@ export type RefundFragment = (
 
 
 export type OrderAddressFragment = (
 export type OrderAddressFragment = (
   { __typename?: 'OrderAddress' }
   { __typename?: 'OrderAddress' }
-  & Pick<OrderAddress, 'fullName' | 'company' | 'streetLine1' | 'streetLine2' | 'city' | 'province' | 'postalCode' | 'country' | 'phoneNumber'>
+  & Pick<OrderAddress, 'fullName' | 'company' | 'streetLine1' | 'streetLine2' | 'city' | 'province' | 'postalCode' | 'country' | 'countryCode' | 'phoneNumber'>
 );
 );
 
 
 export type OrderFragment = (
 export type OrderFragment = (
@@ -5541,7 +5542,23 @@ export type OrderDetailFragment = (
   )>>, fulfillments?: Maybe<Array<(
   )>>, fulfillments?: Maybe<Array<(
     { __typename?: 'Fulfillment' }
     { __typename?: 'Fulfillment' }
     & FulfillmentFragment
     & FulfillmentFragment
-  )>> }
+  )>>, modifications: Array<(
+    { __typename?: 'OrderModification' }
+    & Pick<OrderModification, 'id' | 'createdAt' | 'isSettled' | 'priceChange' | 'note'>
+    & { payment?: Maybe<(
+      { __typename?: 'Payment' }
+      & Pick<Payment, 'id' | 'amount'>
+    )>, orderItems?: Maybe<Array<(
+      { __typename?: 'OrderItem' }
+      & Pick<OrderItem, 'id'>
+    )>>, refund?: Maybe<(
+      { __typename?: 'Refund' }
+      & Pick<Refund, 'id' | 'paymentId' | 'total'>
+    )>, surcharges?: Maybe<Array<(
+      { __typename?: 'Surcharge' }
+      & Pick<Surcharge, 'id'>
+    )>> }
+  )> }
 );
 );
 
 
 export type GetOrderListQueryVariables = Exact<{
 export type GetOrderListQueryVariables = Exact<{
@@ -5798,6 +5815,50 @@ export type GetOrderSummaryQuery = { orders: (
     )> }
     )> }
   ) };
   ) };
 
 
+export type ModifyOrderMutationVariables = Exact<{
+  input: ModifyOrderInput;
+}>;
+
+
+export type ModifyOrderMutation = { modifyOrder: (
+    { __typename?: 'Order' }
+    & OrderDetailFragment
+  ) | (
+    { __typename?: 'NoChangesSpecifiedError' }
+    & ErrorResult_NoChangesSpecifiedError_Fragment
+  ) | (
+    { __typename?: 'OrderModificationStateError' }
+    & ErrorResult_OrderModificationStateError_Fragment
+  ) | (
+    { __typename?: 'PaymentMethodMissingError' }
+    & ErrorResult_PaymentMethodMissingError_Fragment
+  ) | (
+    { __typename?: 'RefundPaymentIdMissingError' }
+    & ErrorResult_RefundPaymentIdMissingError_Fragment
+  ) | (
+    { __typename?: 'OrderLimitError' }
+    & ErrorResult_OrderLimitError_Fragment
+  ) | (
+    { __typename?: 'NegativeQuantityError' }
+    & ErrorResult_NegativeQuantityError_Fragment
+  ) | (
+    { __typename?: 'InsufficientStockError' }
+    & ErrorResult_InsufficientStockError_Fragment
+  ) };
+
+export type AddManualPaymentMutationVariables = Exact<{
+  input: ManualPaymentInput;
+}>;
+
+
+export type AddManualPaymentMutation = { addManualPaymentToOrder: (
+    { __typename?: 'Order' }
+    & OrderDetailFragment
+  ) | (
+    { __typename?: 'ManualPaymentStateError' }
+    & ErrorResult_ManualPaymentStateError_Fragment
+  ) };
+
 export type AssetFragment = (
 export type AssetFragment = (
   { __typename?: 'Asset' }
   { __typename?: 'Asset' }
   & Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source' | 'width' | 'height'>
   & Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source' | 'width' | 'height'>
@@ -8108,6 +8169,11 @@ export namespace OrderDetail {
   export type Refunds = NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['payments']>)[number]>['refunds']>)[number]>;
   export type Refunds = NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['payments']>)[number]>['refunds']>)[number]>;
   export type OrderItems = NonNullable<(NonNullable<NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['payments']>)[number]>['refunds']>)[number]>['orderItems']>)[number]>;
   export type OrderItems = NonNullable<(NonNullable<NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['payments']>)[number]>['refunds']>)[number]>['orderItems']>)[number]>;
   export type Fulfillments = NonNullable<(NonNullable<OrderDetailFragment['fulfillments']>)[number]>;
   export type Fulfillments = NonNullable<(NonNullable<OrderDetailFragment['fulfillments']>)[number]>;
+  export type Modifications = NonNullable<(NonNullable<OrderDetailFragment['modifications']>)[number]>;
+  export type Payment = (NonNullable<NonNullable<(NonNullable<OrderDetailFragment['modifications']>)[number]>['payment']>);
+  export type _OrderItems = NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['modifications']>)[number]>['orderItems']>)[number]>;
+  export type Refund = (NonNullable<NonNullable<(NonNullable<OrderDetailFragment['modifications']>)[number]>['refund']>);
+  export type _Surcharges = NonNullable<(NonNullable<NonNullable<(NonNullable<OrderDetailFragment['modifications']>)[number]>['surcharges']>)[number]>;
 }
 }
 
 
 export namespace GetOrderList {
 export namespace GetOrderList {
@@ -8211,6 +8277,18 @@ export namespace GetOrderSummary {
   export type Items = NonNullable<(NonNullable<(NonNullable<GetOrderSummaryQuery['orders']>)['items']>)[number]>;
   export type Items = NonNullable<(NonNullable<(NonNullable<GetOrderSummaryQuery['orders']>)['items']>)[number]>;
 }
 }
 
 
+export namespace ModifyOrder {
+  export type Variables = ModifyOrderMutationVariables;
+  export type Mutation = ModifyOrderMutation;
+  export type ModifyOrder = (NonNullable<ModifyOrderMutation['modifyOrder']>);
+}
+
+export namespace AddManualPayment {
+  export type Variables = AddManualPaymentMutationVariables;
+  export type Mutation = AddManualPaymentMutation;
+  export type AddManualPaymentToOrder = (NonNullable<AddManualPaymentMutation['addManualPaymentToOrder']>);
+}
+
 export namespace Asset {
 export namespace Asset {
   export type Fragment = AssetFragment;
   export type Fragment = AssetFragment;
   export type FocalPoint = (NonNullable<AssetFragment['focalPoint']>);
   export type FocalPoint = (NonNullable<AssetFragment['focalPoint']>);

+ 45 - 0
packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts

@@ -33,6 +33,7 @@ export const ORDER_ADDRESS_FRAGMENT = gql`
         province
         province
         postalCode
         postalCode
         country
         country
+        countryCode
         phoneNumber
         phoneNumber
     }
     }
 `;
 `;
@@ -199,6 +200,28 @@ export const ORDER_DETAIL_FRAGMENT = gql`
         fulfillments {
         fulfillments {
             ...Fulfillment
             ...Fulfillment
         }
         }
+        modifications {
+            id
+            createdAt
+            isSettled
+            priceChange
+            note
+            payment {
+                id
+                amount
+            }
+            orderItems {
+                id
+            }
+            refund {
+                id
+                paymentId
+                total
+            }
+            surcharges {
+                id
+            }
+        }
     }
     }
     ${ADJUSTMENT_FRAGMENT}
     ${ADJUSTMENT_FRAGMENT}
     ${ORDER_ADDRESS_FRAGMENT}
     ${ORDER_ADDRESS_FRAGMENT}
@@ -396,3 +419,25 @@ export const GET_ORDER_SUMMARY = gql`
         }
         }
     }
     }
 `;
 `;
+
+export const MODIFY_ORDER = gql`
+    mutation ModifyOrder($input: ModifyOrderInput!) {
+        modifyOrder(input: $input) {
+            ...OrderDetail
+            ...ErrorResult
+        }
+    }
+    ${ORDER_DETAIL_FRAGMENT}
+    ${ERROR_RESULT_FRAGMENT}
+`;
+
+export const ADD_MANUAL_PAYMENT_TO_ORDER = gql`
+    mutation AddManualPayment($input: ManualPaymentInput!) {
+        addManualPaymentToOrder(input: $input) {
+            ...OrderDetail
+            ...ErrorResult
+        }
+    }
+    ${ORDER_DETAIL_FRAGMENT}
+    ${ERROR_RESULT_FRAGMENT}
+`;

+ 19 - 0
packages/admin-ui/src/lib/core/src/data/providers/order-data.service.ts

@@ -1,4 +1,5 @@
 import {
 import {
+    AddManualPayment,
     AddNoteToOrder,
     AddNoteToOrder,
     AddNoteToOrderInput,
     AddNoteToOrderInput,
     CancelOrder,
     CancelOrder,
@@ -11,6 +12,9 @@ import {
     GetOrderList,
     GetOrderList,
     GetOrderSummary,
     GetOrderSummary,
     HistoryEntryListOptions,
     HistoryEntryListOptions,
+    ManualPaymentInput,
+    ModifyOrder,
+    ModifyOrderInput,
     OrderListOptions,
     OrderListOptions,
     RefundOrder,
     RefundOrder,
     RefundOrderInput,
     RefundOrderInput,
@@ -25,6 +29,7 @@ import {
     UpdateOrderNoteInput,
     UpdateOrderNoteInput,
 } from '../../common/generated-types';
 } from '../../common/generated-types';
 import {
 import {
+    ADD_MANUAL_PAYMENT_TO_ORDER,
     ADD_NOTE_TO_ORDER,
     ADD_NOTE_TO_ORDER,
     CANCEL_ORDER,
     CANCEL_ORDER,
     CREATE_FULFILLMENT,
     CREATE_FULFILLMENT,
@@ -33,6 +38,7 @@ import {
     GET_ORDERS_LIST,
     GET_ORDERS_LIST,
     GET_ORDER_HISTORY,
     GET_ORDER_HISTORY,
     GET_ORDER_SUMMARY,
     GET_ORDER_SUMMARY,
+    MODIFY_ORDER,
     REFUND_ORDER,
     REFUND_ORDER,
     SETTLE_PAYMENT,
     SETTLE_PAYMENT,
     SETTLE_REFUND,
     SETTLE_REFUND,
@@ -165,4 +171,17 @@ export class OrderDataService {
             },
             },
         );
         );
     }
     }
+
+    modifyOrder(input: ModifyOrderInput) {
+        return this.baseDataService.mutate<ModifyOrder.Mutation, ModifyOrder.Variables>(MODIFY_ORDER, {
+            input,
+        });
+    }
+
+    addManualPaymentToOrder(input: ManualPaymentInput) {
+        return this.baseDataService.mutate<AddManualPayment.Mutation, AddManualPayment.Variables>(
+            ADD_MANUAL_PAYMENT_TO_ORDER,
+            { input },
+        );
+    }
 }
 }

+ 67 - 0
packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.html

@@ -0,0 +1,67 @@
+<form [formGroup]="formGroup">
+    <clr-input-container>
+        <label>{{ 'customer.full-name' | translate }}</label>
+        <input formControlName="fullName" type="text" clrInput />
+    </clr-input-container>
+
+    <div class="clr-row">
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.street-line-1' | translate }}</label>
+                <input formControlName="streetLine1" type="text" clrInput />
+            </clr-input-container>
+        </div>
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.street-line-2' | translate }}</label>
+                <input formControlName="streetLine2" type="text" clrInput />
+            </clr-input-container>
+        </div>
+    </div>
+    <div class="clr-row">
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.city' | translate }}</label>
+                <input formControlName="city" type="text" clrInput />
+            </clr-input-container>
+        </div>
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.province' | translate }}</label>
+                <input formControlName="province" type="text" clrInput />
+            </clr-input-container>
+        </div>
+    </div>
+    <div class="clr-row">
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.postal-code' | translate }}</label>
+                <input formControlName="postalCode" type="text" clrInput />
+            </clr-input-container>
+        </div>
+        <div class="clr-col-md-4">
+            <clr-input-container>
+                <label>{{ 'customer.country' | translate }}</label>
+                <select name="countryCode" formControlName="countryCode" clrInput clrSelect>
+                    <option *ngFor="let country of availableCountries" [value]="country.code">
+                        {{ country.name }}
+                    </option>
+                </select>
+            </clr-input-container>
+        </div>
+    </div>
+    <clr-input-container>
+        <label>{{ 'customer.phone-number' | translate }}</label>
+        <input formControlName="phoneNumber" type="text" clrInput />
+    </clr-input-container>
+    <section formGroupName="customFields" *ngIf="formGroup.get('customFields') as customFieldsGroup">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                entityName="Facet"
+                [customFieldsFormGroup]="customFieldsGroup"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
+</form>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.scss


+ 15 - 0
packages/admin-ui/src/lib/core/src/shared/components/address-form/address-form.component.ts

@@ -0,0 +1,15 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { CustomFieldConfig, GetAvailableCountries } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'vdr-address-form',
+    templateUrl: './address-form.component.html',
+    styleUrls: ['./address-form.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AddressFormComponent {
+    @Input() customFields: CustomFieldConfig;
+    @Input() formGroup: FormGroup;
+    @Input() availableCountries: GetAvailableCountries.Items[];
+}

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts

@@ -3,7 +3,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 
 
 /**
 /**
  * A form input control which displays currency in decimal format, whilst working
  * A form input control which displays currency in decimal format, whilst working
- * with the intege cent value in the background.
+ * with the integer cent value in the background.
  */
  */
 @Component({
 @Component({
     selector: 'vdr-currency-input',
     selector: 'vdr-currency-input',

+ 2 - 0
packages/admin-ui/src/lib/core/src/shared/pipes/state-i18n-token.pipe.ts

@@ -22,6 +22,8 @@ export class StateI18nTokenPipe implements PipeTransform {
         Failed: _('state.failed'),
         Failed: _('state.failed'),
         Error: _('state.error'),
         Error: _('state.error'),
         Declined: _('state.declined'),
         Declined: _('state.declined'),
+        Modifying: _('state.modifying'),
+        ArrangingAdditionalPayment: _('state.arranging-additional-payment'),
     };
     };
     transform<T extends unknown>(value: T): T {
     transform<T extends unknown>(value: T): T {
         if (typeof value === 'string') {
         if (typeof value === 'string') {

+ 2 - 0
packages/admin-ui/src/lib/core/src/shared/shared.module.ts

@@ -20,6 +20,7 @@ import {
     ActionBarLeftComponent,
     ActionBarLeftComponent,
     ActionBarRightComponent,
     ActionBarRightComponent,
 } from './components/action-bar/action-bar.component';
 } from './components/action-bar/action-bar.component';
+import { AddressFormComponent } from './components/address-form/address-form.component';
 import { AffixedInputComponent } from './components/affixed-input/affixed-input.component';
 import { AffixedInputComponent } from './components/affixed-input/affixed-input.component';
 import { PercentageSuffixInputComponent } from './components/affixed-input/percentage-suffix-input.component';
 import { PercentageSuffixInputComponent } from './components/affixed-input/percentage-suffix-input.component';
 import { AssetFileInputComponent } from './components/asset-file-input/asset-file-input.component';
 import { AssetFileInputComponent } from './components/asset-file-input/asset-file-input.component';
@@ -191,6 +192,7 @@ const DECLARATIONS = [
     ProductSelectorComponent,
     ProductSelectorComponent,
     HelpTooltipComponent,
     HelpTooltipComponent,
     CustomerGroupFormInputComponent,
     CustomerGroupFormInputComponent,
+    AddressFormComponent,
 ];
 ];
 
 
 const DYNAMIC_FORM_INPUTS = [
 const DYNAMIC_FORM_INPUTS = [

+ 5 - 67
packages/admin-ui/src/lib/customer/src/components/address-detail-dialog/address-detail-dialog.component.html

@@ -3,73 +3,11 @@
     <span *ngIf="addressForm.get('countryCode')?.value as countryCode"> {{ countryCode }}</span>
     <span *ngIf="addressForm.get('countryCode')?.value as countryCode"> {{ countryCode }}</span>
 </ng-template>
 </ng-template>
 
 
-<form [formGroup]="addressForm">
-    <clr-input-container>
-        <label>{{ 'customer.full-name' | translate }}</label>
-        <input formControlName="fullName" type="text" clrInput />
-    </clr-input-container>
-
-    <div class="clr-row">
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.street-line-1' | translate }}</label>
-                <input formControlName="streetLine1" type="text" clrInput />
-            </clr-input-container>
-        </div>
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.street-line-2' | translate }}</label>
-                <input formControlName="streetLine2" type="text" clrInput />
-            </clr-input-container>
-        </div>
-    </div>
-    <div class="clr-row">
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.city' | translate }}</label>
-                <input formControlName="city" type="text" clrInput />
-            </clr-input-container>
-        </div>
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.province' | translate }}</label>
-                <input formControlName="province" type="text" clrInput />
-            </clr-input-container>
-        </div>
-    </div>
-    <div class="clr-row">
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.postal-code' | translate }}</label>
-                <input formControlName="postalCode" type="text" clrInput />
-            </clr-input-container>
-        </div>
-        <div class="clr-col-md-4">
-            <clr-input-container>
-                <label>{{ 'customer.country' | translate }}</label>
-                <select name="countryCode" formControlName="countryCode" clrInput clrSelect>
-                    <option *ngFor="let country of availableCountries" [value]="country.code">
-                        {{ country.name }}
-                    </option>
-                </select>
-            </clr-input-container>
-        </div>
-    </div>
-    <clr-input-container>
-        <label>{{ 'customer.phone-number' | translate }}</label>
-        <input formControlName="phoneNumber" type="text" clrInput />
-    </clr-input-container>
-    <section formGroupName="customFields" *ngIf="addressForm.get('customFields') as customFieldsGroup">
-        <label>{{ 'common.custom-fields' | translate }}</label>
-        <ng-container *ngFor="let customField of customFields">
-            <vdr-custom-field-control
-                entityName="Facet"
-                [customFieldsFormGroup]="customFieldsGroup"
-                [customField]="customField"
-            ></vdr-custom-field-control>
-        </ng-container>
-    </section>
-</form>
+<vdr-address-form
+    [formGroup]="addressForm"
+    [availableCountries]="availableCountries"
+    [customFields]="customFields"
+></vdr-address-form>
 
 
 <ng-template vdrDialogButtons>
 <ng-template vdrDialogButtons>
     <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
     <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>

+ 22 - 0
packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.html

@@ -0,0 +1,22 @@
+<ng-template vdrDialogTitle>{{ 'order.add-payment-to-order' | translate }}</ng-template>
+<form [formGroup]="form">
+    <vdr-form-field [label]="'order.payment-method' | translate" for="method">
+        <ng-select
+            [items]="paymentMethods$ | async"
+            bindLabel="code"
+            autofocus
+            bindValue="code"
+            [addTag]="true"
+            formControlName="method"
+        ></ng-select>
+    </vdr-form-field>
+    <vdr-form-field [label]="'order.transaction-id' | translate" for="transactionId">
+        <input id="transactionId" type="text" formControlName="transactionId" />
+    </vdr-form-field>
+</form>
+<ng-template vdrDialogButtons>
+    <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+    <button type="submit" (click)="submit()" class="btn btn-primary" [disabled]="form.invalid || form.pristine">
+        {{ 'order.add-payment' | translate }}  ({{ outstandingAmount / 100 | currency: currencyCode }})
+    </button>
+</ng-template>

+ 3 - 0
packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.scss

@@ -0,0 +1,3 @@
+.ng-select {
+    min-width: 100%;
+}

+ 48 - 0
packages/admin-ui/src/lib/order/src/components/add-manual-payment-dialog/add-manual-payment-dialog.component.ts

@@ -0,0 +1,48 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import {
+    CurrencyCode,
+    DataService,
+    Dialog,
+    GetPaymentMethodList,
+    ManualPaymentInput,
+} from '@vendure/admin-ui/core';
+import { Observable } from 'rxjs';
+
+@Component({
+    selector: 'vdr-add-manual-payment-dialog',
+    templateUrl: './add-manual-payment-dialog.component.html',
+    styleUrls: ['./add-manual-payment-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AddManualPaymentDialogComponent implements OnInit, Dialog<Omit<ManualPaymentInput, 'orderId'>> {
+    // populated by ModalService call
+    outstandingAmount: number;
+    currencyCode: CurrencyCode;
+
+    resolveWith: (result?: Omit<ManualPaymentInput, 'orderId'>) => void;
+    form = new FormGroup({
+        method: new FormControl('', Validators.required),
+        transactionId: new FormControl('', Validators.required),
+    });
+    paymentMethods$: Observable<GetPaymentMethodList.Items[]>;
+    constructor(private dataService: DataService) {}
+
+    ngOnInit(): void {
+        this.paymentMethods$ = this.dataService.settings
+            .getPaymentMethods(999)
+            .mapSingle(data => data.paymentMethods.items);
+    }
+
+    submit() {
+        const formValue = this.form.value;
+        this.resolveWith({
+            method: formValue.method,
+            transactionId: formValue.transactionId,
+        });
+    }
+
+    cancel() {
+        this.resolveWith();
+    }
+}

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.html

@@ -1,7 +1,7 @@
 <ng-template vdrDialogTitle>{{ 'order.fulfill-order' | translate }}</ng-template>
 <ng-template vdrDialogTitle>{{ 'order.fulfill-order' | translate }}</ng-template>
 
 
 <div class="fulfillment-wrapper">
 <div class="fulfillment-wrapper">
-    <div class="order-lines">
+    <div class="order-table">
         <table class="table">
         <table class="table">
             <thead>
             <thead>
                 <tr>
                 <tr>

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.scss

@@ -23,7 +23,7 @@
             margin-top: 24px;
             margin-top: 24px;
         }
         }
     }
     }
-    .order-lines {
+    .order-table {
         flex: 1;
         flex: 1;
         overflow-y: auto;
         overflow-y: auto;
         table {
         table {

+ 14 - 0
packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.html

@@ -0,0 +1,14 @@
+<vdr-labeled-data [label]="'common.ID' | translate">{{ modification.id }}</vdr-labeled-data>
+<vdr-labeled-data *ngIf="modification.note" [label]="'order.note' | translate">{{
+    modification.note
+}}</vdr-labeled-data>
+<vdr-labeled-data *ngFor="let surcharge of modification.surcharges" [label]="'order.surcharges' | translate">
+    {{ getSurcharge(surcharge.id)?.description }}
+    {{ getSurcharge(surcharge.id)?.priceWithTax / 100 | currency: order.currencyCode }}</vdr-labeled-data
+>
+<vdr-labeled-data *ngIf="getAddedItems().length" [label]="'order.added-items' | translate">
+    <vdr-simple-item-list [items]="getAddedItems()"></vdr-simple-item-list>
+</vdr-labeled-data>
+<vdr-labeled-data *ngIf="getRemovedItems().length" [label]="'order.removed-items' | translate">
+    <vdr-simple-item-list [items]="getRemovedItems()"></vdr-simple-item-list>
+</vdr-labeled-data>

+ 0 - 0
packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.scss


+ 65 - 0
packages/admin-ui/src/lib/order/src/components/modification-detail/modification-detail.component.ts

@@ -0,0 +1,65 @@
+import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
+import { OrderDetail, OrderDetailFragment } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'vdr-modification-detail',
+    templateUrl: './modification-detail.component.html',
+    styleUrls: ['./modification-detail.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ModificationDetailComponent implements OnChanges {
+    @Input() order: OrderDetailFragment;
+    @Input() modification: OrderDetail.Modifications;
+    private addedItems = new Map<OrderDetail.Lines, number>();
+    private removedItems = new Map<OrderDetail.Lines, number>();
+
+    ngOnChanges(): void {
+        const { added, removed } = this.getModifiedLines();
+        this.addedItems = added;
+        this.removedItems = removed;
+    }
+
+    getSurcharge(id: string) {
+        return this.order.surcharges.find(m => m.id === id);
+    }
+
+    getAddedItems() {
+        return [...this.addedItems.entries()].map(([line, count]) => {
+            return { name: line.productVariant.name, quantity: count };
+        });
+    }
+
+    getRemovedItems() {
+        return [...this.removedItems.entries()].map(([line, count]) => {
+            return { name: line.productVariant.name, quantity: count };
+        });
+    }
+
+    private getModifiedLines() {
+        const added = new Map<OrderDetail.Lines, number>();
+        const removed = new Map<OrderDetail.Lines, number>();
+        for (const _item of this.modification.orderItems || []) {
+            const result = this.getOrderLineAndItem(_item.id);
+            if (result) {
+                const { line, item } = result;
+                if (item.cancelled) {
+                    const count = removed.get(line) ?? 0;
+                    removed.set(line, count + 1);
+                } else {
+                    const count = added.get(line) ?? 0;
+                    added.set(line, count + 1);
+                }
+            }
+        }
+        return { added, removed };
+    }
+
+    private getOrderLineAndItem(itemId: string) {
+        for (const line of this.order.lines) {
+            const item = line.items.find(i => i.id === itemId);
+            if (item) {
+                return { line, item };
+            }
+        }
+    }
+}

+ 19 - 187
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.html

@@ -16,6 +16,14 @@
 
 
     <vdr-ab-right>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="order-detail"></vdr-action-bar-items>
         <vdr-action-bar-items locationId="order-detail"></vdr-action-bar-items>
+        <button
+            class="btn btn-primary"
+            *ngIf="hasUnsettledModifications(order)"
+            (click)="addManualPayment(order)"
+        >
+            {{ 'order.add-payment-to-order' | translate }}
+            ({{ getOutstandingModificationAmount(order) / 100 | currency: order.currencyCode }})
+        </button>
         <button class="btn btn-primary" (click)="fulfillOrder()" [disabled]="!canAddFulfillment(order)">
         <button class="btn btn-primary" (click)="fulfillOrder()" [disabled]="!canAddFulfillment(order)">
             {{ 'order.fulfill-order' | translate }}
             {{ 'order.fulfill-order' | translate }}
         </button>
         </button>
@@ -24,6 +32,13 @@
                 <clr-icon shape="ellipsis-vertical"></clr-icon>
                 <clr-icon shape="ellipsis-vertical"></clr-icon>
             </button>
             </button>
             <vdr-dropdown-menu vdrPosition="bottom-right">
             <vdr-dropdown-menu vdrPosition="bottom-right">
+                <ng-container *ngIf="order.nextStates.includes('Modifying')">
+                    <button type="button" class="btn" vdrDropdownItem (click)="transitionToModifying()">
+                        <clr-icon shape="pencil"></clr-icon>
+                        {{ 'order.modify-order' | translate }}
+                    </button>
+                    <div class="dropdown-divider"></div>
+                </ng-container>
                 <button
                 <button
                     type="button"
                     type="button"
                     class="btn"
                     class="btn"
@@ -57,193 +72,10 @@
 <div *ngIf="entity$ | async as order">
 <div *ngIf="entity$ | async as order">
     <div class="clr-row">
     <div class="clr-row">
         <div class="clr-col-lg-8">
         <div class="clr-col-lg-8">
-            <table class="order-lines table">
-                <thead>
-                    <tr>
-                        <th></th>
-                        <th>{{ 'order.product-name' | translate }}</th>
-                        <th>{{ 'order.product-sku' | translate }}</th>
-                        <th>{{ 'order.unit-price' | translate }}</th>
-                        <th>{{ 'order.quantity' | translate }}</th>
-                        <ng-container *ngFor="let customField of visibleOrderLineCustomFields">
-                            <th class="order-line-custom-field">
-                                <button
-                                    class="custom-field-header-button"
-                                    (click)="toggleOrderLineCustomFields()"
-                                    [title]="'common.hide-custom-fields' | translate"
-                                >
-                                    {{ customField | customFieldLabel }}
-                                </button>
-                            </th>
-                        </ng-container>
-                        <ng-container *ngIf="showElided">
-                            <th>
-                                <button
-                                    class="custom-field-header-button"
-                                    (click)="toggleOrderLineCustomFields()"
-                                    [title]="'common.display-custom-fields' | translate"
-                                >
-                                    <clr-icon
-                                        shape="ellipsis-horizontal"
-                                        class="custom-field-ellipsis"
-                                    ></clr-icon>
-                                </button>
-                            </th>
-                        </ng-container>
-                        <th>{{ 'order.total' | translate }}</th>
-                    </tr>
-                </thead>
-                <tbody>
-                    <tr
-                        *ngFor="let line of order.lines"
-                        class="order-line"
-                        [class.is-cancelled]="line.quantity === 0"
-                    >
-                        <td class="align-middle thumb">
-                            <img
-                                *ngIf="line.featuredAsset"
-                                [src]="line.featuredAsset | assetPreview: 'tiny'"
-                            />
-                        </td>
-                        <td class="align-middle name">{{ line.productVariant.name }}</td>
-                        <td class="align-middle sku">{{ line.productVariant.sku }}</td>
-                        <td class="align-middle unit-price">
-                            {{ line.unitPriceWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ line.unitPrice / 100 | currency: order.currencyCode }}
-                            </div>
-                        </td>
-                        <td class="align-middle quantity">
-                            {{ line.quantity }}
-                            <vdr-line-refunds [line]="line"></vdr-line-refunds>
-                            <vdr-line-fulfillment
-                                [line]="line"
-                                [orderState]="order.state"
-                            ></vdr-line-fulfillment>
-                        </td>
-                        <ng-container *ngFor="let customField of visibleOrderLineCustomFields">
-                            <td class="order-line-custom-field align-middle">
-                                <ng-container [ngSwitch]="customField.type">
-                                    <ng-template [ngSwitchCase]="'datetime'">
-                                        <span [title]="line.customFields[customField.name]">{{
-                                            line.customFields[customField.name] | date: 'short'
-                                        }}</span>
-                                    </ng-template>
-                                    <ng-template [ngSwitchCase]="'boolean'">
-                                        <ng-template [ngIf]="line.customFields[customField.name] === true">
-                                            <clr-icon shape="check"></clr-icon>
-                                        </ng-template>
-                                        <ng-template [ngIf]="line.customFields[customField.name] === false">
-                                            <clr-icon shape="times"></clr-icon>
-                                        </ng-template>
-                                    </ng-template>
-                                    <ng-template ngSwitchDefault>
-                                        {{ line.customFields[customField.name] }}
-                                    </ng-template>
-                                </ng-container>
-                            </td>
-                        </ng-container>
-                        <ng-container *ngIf="showElided"
-                            ><td class="order-line-custom-field align-middle">
-                                <clr-icon
-                                    shape="ellipsis-horizontal"
-                                    class="custom-field-ellipsis"
-                                ></clr-icon></td
-                        ></ng-container>
-                        <td class="align-middle total">
-                            {{ line.linePriceWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ line.linePrice / 100 | currency: order.currencyCode }}
-                            </div>
-
-                            <ng-container *ngIf="getLineDiscounts(line) as discounts">
-                                <vdr-dropdown *ngIf="discounts.length">
-                                    <div class="promotions-label" vdrDropdownTrigger>
-                                        {{ 'order.promotions-applied' | translate }}
-                                    </div>
-                                    <vdr-dropdown-menu>
-                                        <div class="line-promotion" *ngFor="let discount of discounts">
-                                            <a
-                                                class="promotion-name"
-                                                [routerLink]="getPromotionLink(discount)"
-                                                >{{ discount.description }}</a
-                                            >
-                                            <div class="promotion-amount">
-                                                {{ discount.amount / 100 | currency: order.currencyCode }}
-                                            </div>
-                                        </div>
-                                    </vdr-dropdown-menu>
-                                </vdr-dropdown>
-                            </ng-container>
-                        </td>
-                    </tr>
-                    <tr class="surcharge" *ngFor="let surcharge of order.surcharges">
-                        <td class="align-middle name left" colspan="2">{{ surcharge.description }}</td>
-                        <td class="align-middle sku">{{ surcharge.sku }}</td>
-                        <td class="align-middle"></td>
-                        <td [attr.colspan]="1 + visibleOrderLineCustomFields.length"></td>
-                        <ng-container *ngIf="showElided"><td></td></ng-container>
-                        <td class="align-middle total">
-                            {{ surcharge.priceWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ surcharge.price / 100 | currency: order.currencyCode }}
-                            </div>
-                        </td>
-                    </tr>
-                    <tr class="order-adjustment" *ngFor="let discount of order.discounts">
-                        <td
-                            [attr.colspan]="5 + visibleOrderLineCustomFields.length"
-                            class="left clr-align-middle"
-                        >
-                            <a [routerLink]="getPromotionLink(discount)">{{ discount.description }}</a>
-                            <vdr-chip *ngIf="getCouponCodeForAdjustment(order, discount) as couponCode">{{
-                                couponCode
-                            }}</vdr-chip>
-                        </td>
-                        <ng-container *ngIf="showElided"><td></td></ng-container>
-                        <td class="clr-align-middle">
-                            {{ discount.amount / 100 | currency: order.currencyCode }}
-                        </td>
-                    </tr>
-                    <tr class="sub-total">
-                        <td class="left clr-align-middle">{{ 'order.sub-total' | translate }}</td>
-                        <td></td>
-                        <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
-                        <ng-container *ngIf="showElided"><td></td></ng-container>
-                        <td class="clr-align-middle">
-                            {{ order.subTotalWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ order.subTotal / 100 | currency: order.currencyCode }}
-                            </div>
-                        </td>
-                    </tr>
-                    <tr class="shipping">
-                        <td class="left clr-align-middle">{{ 'order.shipping' | translate }}</td>
-                        <td class="clr-align-middle">{{ order.shippingLines[0]?.shippingMethod?.name }}</td>
-                        <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
-                        <ng-container *ngIf="showElided"><td></td></ng-container>
-                        <td class="clr-align-middle">
-                            {{ order.shippingWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ order.shipping / 100 | currency: order.currencyCode }}
-                            </div>
-                        </td>
-                    </tr>
-                    <tr class="total">
-                        <td class="left clr-align-middle">{{ 'order.total' | translate }}</td>
-                        <td></td>
-                        <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
-                        <ng-container *ngIf="showElided"><td></td></ng-container>
-                        <td class="clr-align-middle">
-                            {{ order.totalWithTax / 100 | currency: order.currencyCode }}
-                            <div class="net-price" [title]="'order.net-price' | translate">
-                                {{ order.total / 100 | currency: order.currencyCode }}
-                            </div>
-                        </td>
-                    </tr>
-                </tbody>
-            </table>
-
+            <vdr-order-table
+                [order]="order"
+                [orderLineCustomFields]="orderLineCustomFields"
+            ></vdr-order-table>
             <h4>{{ 'order.tax-summary' | translate }}</h4>
             <h4>{{ 'order.tax-summary' | translate }}</h4>
             <table class="table">
             <table class="table">
                 <thead>
                 <thead>

+ 1 - 55
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.scss

@@ -1,66 +1,12 @@
 @import "variables";
 @import "variables";
 @import "mixins";
 @import "mixins";
+@import "../order-table/order-table-mixin";
 
 
 .shipping-address {
 .shipping-address {
     list-style-type: none;
     list-style-type: none;
     line-height: 1.3em;
     line-height: 1.3em;
 }
 }
 
 
-.order-lines {
-
-    .is-cancelled td {
-        text-decoration: line-through;
-        background-color: $color-grey-200;
-    }
-
-    .sub-total td {
-        border-top: 1px dashed $color-grey-300;
-    }
-
-    .total td {
-        font-weight: bold;
-        border-top: 1px dashed $color-grey-300;
-    }
-}
-
-.custom-field-header-button {
-    background: none;
-    margin: 0;
-    padding: 0;
-    border: none;
-    cursor: pointer;
-    color: $color-primary-600;
-}
-
-.order-line-custom-field {
-    background-color: $color-grey-100;
-
-    .custom-field-ellipsis {
-        color: $color-grey-300;
-    }
-}
-
-.net-price {
-    font-size: 11px;
-    color: $color-grey-400;
-}
-.promotions-label {
-    text-decoration: underline dotted $color-grey-300;
-    font-size: 11px;
-    margin-top: 6px;
-    cursor: pointer;
-    text-transform: lowercase;
-}
-
-.line-promotion {
-    display: flex;
-    justify-content: space-between;
-    padding: 6px 12px;
-    .promotion-amount {
-        margin-left: 12px;
-    }
-}
-
 .order-cards {
 .order-cards {
     h6 {
     h6 {
         margin-top: 6px;
         margin-top: 6px;

+ 85 - 35
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts

@@ -3,7 +3,6 @@ import { FormGroup } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import {
 import {
-    AdjustmentType,
     BaseDetailComponent,
     BaseDetailComponent,
     CancelOrder,
     CancelOrder,
     CustomFieldConfig,
     CustomFieldConfig,
@@ -12,24 +11,30 @@ import {
     GetOrderHistory,
     GetOrderHistory,
     GetOrderQuery,
     GetOrderQuery,
     HistoryEntry,
     HistoryEntry,
+    HistoryEntryType,
     ModalService,
     ModalService,
     NotificationService,
     NotificationService,
     Order,
     Order,
     OrderDetail,
     OrderDetail,
+    OrderDetailFragment,
     OrderLineFragment,
     OrderLineFragment,
     RefundOrder,
     RefundOrder,
     ServerConfigService,
     ServerConfigService,
     SortOrder,
     SortOrder,
 } from '@vendure/admin-ui/core';
 } from '@vendure/admin-ui/core';
+import { summate } from '@vendure/common/lib/shared-utils';
 import { EMPTY, merge, Observable, of, Subject } from 'rxjs';
 import { EMPTY, merge, Observable, of, Subject } from 'rxjs';
 import { map, mapTo, startWith, switchMap, take } from 'rxjs/operators';
 import { map, mapTo, startWith, switchMap, take } from 'rxjs/operators';
 
 
+import { AddManualPaymentDialogComponent } from '../add-manual-payment-dialog/add-manual-payment-dialog.component';
 import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-dialog.component';
 import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-dialog.component';
 import { FulfillOrderDialogComponent } from '../fulfill-order-dialog/fulfill-order-dialog.component';
 import { FulfillOrderDialogComponent } from '../fulfill-order-dialog/fulfill-order-dialog.component';
 import { OrderProcessGraphDialogComponent } from '../order-process-graph-dialog/order-process-graph-dialog.component';
 import { OrderProcessGraphDialogComponent } from '../order-process-graph-dialog/order-process-graph-dialog.component';
 import { RefundOrderDialogComponent } from '../refund-order-dialog/refund-order-dialog.component';
 import { RefundOrderDialogComponent } from '../refund-order-dialog/refund-order-dialog.component';
 import { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refund-dialog.component';
 import { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refund-dialog.component';
 
 
+import { transitionToPreModifyingState } from './transition-to-pre-modifying-state';
+
 @Component({
 @Component({
     selector: 'vdr-order-detail',
     selector: 'vdr-order-detail',
     templateUrl: './order-detail.component.html',
     templateUrl: './order-detail.component.html',
@@ -45,7 +50,6 @@ export class OrderDetailComponent
     fetchHistory = new Subject<void>();
     fetchHistory = new Subject<void>();
     customFields: CustomFieldConfig[];
     customFields: CustomFieldConfig[];
     orderLineCustomFields: CustomFieldConfig[];
     orderLineCustomFields: CustomFieldConfig[];
-    orderLineCustomFieldsVisible = false;
     private readonly defaultStates = [
     private readonly defaultStates = [
         'AddingItems',
         'AddingItems',
         'ArrangingPayment',
         'ArrangingPayment',
@@ -54,6 +58,8 @@ export class OrderDetailComponent
         'PartiallyDelivered',
         'PartiallyDelivered',
         'Delivered',
         'Delivered',
         'Cancelled',
         'Cancelled',
+        'Modifying',
+        'ArrangingAdditionalPayment',
     ];
     ];
     constructor(
     constructor(
         router: Router,
         router: Router,
@@ -67,19 +73,15 @@ export class OrderDetailComponent
         super(route, router, serverConfigService, dataService);
         super(route, router, serverConfigService, dataService);
     }
     }
 
 
-    get visibleOrderLineCustomFields(): CustomFieldConfig[] {
-        return this.orderLineCustomFieldsVisible ? this.orderLineCustomFields : [];
-    }
-
-    get showElided(): boolean {
-        return !this.orderLineCustomFieldsVisible && 0 < this.orderLineCustomFields.length;
-    }
-
     ngOnInit() {
     ngOnInit() {
         this.init();
         this.init();
+        this.entity$.pipe(take(1)).subscribe(order => {
+            if (order.state === 'Modifying') {
+                this.router.navigate(['./', 'modify'], { relativeTo: this.route });
+            }
+        });
         this.customFields = this.getCustomFieldConfig('Order');
         this.customFields = this.getCustomFieldConfig('Order');
         this.orderLineCustomFields = this.getCustomFieldConfig('OrderLine');
         this.orderLineCustomFields = this.getCustomFieldConfig('OrderLine');
-        this.orderLineCustomFieldsVisible = this.orderLineCustomFields.length < 2;
         this.history$ = this.fetchHistory.pipe(
         this.history$ = this.fetchHistory.pipe(
             startWith(null),
             startWith(null),
             switchMap(() => {
             switchMap(() => {
@@ -106,19 +108,6 @@ export class OrderDetailComponent
         this.destroy();
         this.destroy();
     }
     }
 
 
-    toggleOrderLineCustomFields() {
-        this.orderLineCustomFieldsVisible = !this.orderLineCustomFieldsVisible;
-    }
-
-    getLineDiscounts(line: OrderDetail.Lines) {
-        return line.discounts.filter(a => a.type === AdjustmentType.PROMOTION);
-    }
-
-    getPromotionLink(promotion: OrderDetail.Discounts): any[] {
-        const id = promotion.adjustmentSource.split(':')[1];
-        return ['/marketing', 'promotions', id];
-    }
-
     openStateDiagram() {
     openStateDiagram() {
         this.entity$
         this.entity$
             .pipe(
             .pipe(
@@ -148,6 +137,20 @@ export class OrderDetailComponent
         });
         });
     }
     }
 
 
+    transitionToModifying() {
+        this.dataService.order
+            .transitionToState(this.id, 'Modifying')
+            .subscribe(({ transitionOrderToState }) => {
+                switch (transitionOrderToState?.__typename) {
+                    case 'Order':
+                        this.router.navigate(['./modify'], { relativeTo: this.route });
+                        break;
+                    case 'OrderStateTransitionError':
+                        this.notificationService.error(transitionOrderToState.transitionError);
+                }
+            });
+    }
+
     updateCustomFields(customFieldsValue: any) {
     updateCustomFields(customFieldsValue: any) {
         this.dataService.order
         this.dataService.order
             .updateOrderCustomFields({
             .updateOrderCustomFields({
@@ -159,17 +162,6 @@ export class OrderDetailComponent
             });
             });
     }
     }
 
 
-    getCouponCodeForAdjustment(
-        order: OrderDetail.Fragment,
-        promotionAdjustment: OrderDetail.Discounts,
-    ): string | undefined {
-        const id = promotionAdjustment.adjustmentSource.split(':')[1];
-        const promotion = order.promotions.find(p => p.id === id);
-        if (promotion) {
-            return promotion.couponCode || undefined;
-        }
-    }
-
     getOrderAddressLines(orderAddress?: { [key: string]: string }): string[] {
     getOrderAddressLines(orderAddress?: { [key: string]: string }): string[] {
         if (!orderAddress) {
         if (!orderAddress) {
             return [];
             return [];
@@ -205,12 +197,70 @@ export class OrderDetailComponent
             .every(item => !!item.fulfillment);
             .every(item => !!item.fulfillment);
         return (
         return (
             !allItemsFulfilled &&
             !allItemsFulfilled &&
+            !this.hasUnsettledModifications(order) &&
             (order.nextStates.includes('Shipped') ||
             (order.nextStates.includes('Shipped') ||
                 order.nextStates.includes('PartiallyShipped') ||
                 order.nextStates.includes('PartiallyShipped') ||
                 order.nextStates.includes('Delivered'))
                 order.nextStates.includes('Delivered'))
         );
         );
     }
     }
 
 
+    hasUnsettledModifications(order: OrderDetailFragment): boolean {
+        return 0 < order.modifications.filter(m => !m.isSettled).length;
+    }
+
+    getOutstandingModificationAmount(order: OrderDetailFragment): number {
+        return summate(
+            order.modifications.filter(m => !m.isSettled),
+            'priceChange',
+        );
+    }
+
+    addManualPayment(order: OrderDetailFragment) {
+        this.modalService
+            .fromComponent(AddManualPaymentDialogComponent, {
+                closable: true,
+                locals: {
+                    outstandingAmount: this.getOutstandingModificationAmount(order),
+                    currencyCode: order.currencyCode,
+                },
+            })
+            .pipe(
+                switchMap(result => {
+                    if (result) {
+                        return this.dataService.order.addManualPaymentToOrder({
+                            orderId: this.id,
+                            transactionId: result.transactionId,
+                            method: result.method,
+                            metadata: result.metadata || {},
+                        });
+                    } else {
+                        return EMPTY;
+                    }
+                }),
+                switchMap(({ addManualPaymentToOrder }) => {
+                    switch (addManualPaymentToOrder.__typename) {
+                        case 'Order':
+                            this.notificationService.success('order.add-payment-to-order-success');
+                            return transitionToPreModifyingState(this.dataService, order.id);
+                        case 'ManualPaymentStateError':
+                            this.notificationService.error(addManualPaymentToOrder.message);
+                            return EMPTY;
+                        default:
+                            return EMPTY;
+                    }
+                }),
+            )
+            .subscribe(({ transitionOrderToState }) => {
+                switch (transitionOrderToState?.__typename) {
+                    case 'Order':
+                        this.refetchOrder(transitionOrderToState);
+                        break;
+                    case 'OrderStateTransitionError':
+                        this.notificationService.error(transitionOrderToState.message);
+                }
+            });
+    }
+
     fulfillOrder() {
     fulfillOrder() {
         this.entity$
         this.entity$
             .pipe(
             .pipe(

+ 29 - 0
packages/admin-ui/src/lib/order/src/components/order-detail/transition-to-pre-modifying-state.ts

@@ -0,0 +1,29 @@
+import { DataService, HistoryEntryType, SortOrder } from '@vendure/admin-ui/core';
+import { EMPTY } from 'rxjs';
+import { switchMap } from 'rxjs/operators';
+
+export function transitionToPreModifyingState(dataService: DataService, orderId: string) {
+    return dataService.order
+        .getOrderHistory(orderId, {
+            filter: {
+                type: {
+                    eq: HistoryEntryType.ORDER_STATE_TRANSITION,
+                },
+            },
+            sort: {
+                createdAt: SortOrder.DESC,
+            },
+        })
+        .mapSingle(result => result.order)
+        .pipe(
+            switchMap(result => {
+                const item = result?.history.items.find(i => i.data.to === 'Modifying');
+                if (item) {
+                    const originalState = item.data.from;
+                    return dataService.order.transitionToState(orderId, originalState);
+                } else {
+                    return EMPTY;
+                }
+            }),
+        );
+}

+ 318 - 0
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.html

@@ -0,0 +1,318 @@
+<vdr-action-bar *ngIf="entity$ | async as order">
+    <vdr-ab-left>
+        <div class="flex clr-align-items-center">
+            <vdr-entity-info [entity]="entity$ | async"></vdr-entity-info>
+            <vdr-order-state-label [state]="order.state"></vdr-order-state-label>
+        </div>
+    </vdr-ab-left>
+
+    <vdr-ab-right>
+        <button class="btn btn-secondary" (click)="transitionToPriorState(order)">
+            {{ 'order.cancel-modification' | translate }}
+        </button>
+    </vdr-ab-right>
+</vdr-action-bar>
+
+<div *ngIf="entity$ | async as order">
+    <div class="clr-row">
+        <div class="clr-col-lg-8">
+            <table class="order-table table">
+                <thead>
+                    <tr>
+                        <th></th>
+                        <th>{{ 'order.product-name' | translate }}</th>
+                        <th>{{ 'order.product-sku' | translate }}</th>
+                        <th>{{ 'order.unit-price' | translate }}</th>
+                        <th>{{ 'order.quantity' | translate }}</th>
+                        <th *ngIf="orderLineCustomFields.length">{{ 'common.custom-fields' | translate }}</th>
+                        <th>{{ 'order.total' | translate }}</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr
+                        *ngFor="let line of order.lines"
+                        class="order-line"
+                        [class.is-cancelled]="line.quantity === 0"
+                        [class.modified]="isLineModified(line)"
+                    >
+                        <td class="align-middle thumb">
+                            <img
+                                *ngIf="line.featuredAsset"
+                                [src]="line.featuredAsset | assetPreview: 'tiny'"
+                            />
+                        </td>
+                        <td class="align-middle name">{{ line.productVariant.name }}</td>
+                        <td class="align-middle sku">{{ line.productVariant.sku }}</td>
+                        <td class="align-middle unit-price">
+                            {{ line.unitPriceWithTax / 100 | currency: order.currencyCode }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{ line.unitPrice / 100 | currency: order.currencyCode }}
+                            </div>
+                        </td>
+                        <td class="align-middle quantity">
+                            <input
+                                class="clr-input"
+                                type="number"
+                                min="0"
+                                [value]="line.quantity"
+                                (input)="updateLineQuantity(line, $event.target.value)"
+                            />
+                            <vdr-line-refunds [line]="line"></vdr-line-refunds>
+                            <vdr-line-fulfillment
+                                [line]="line"
+                                [orderState]="order.state"
+                            ></vdr-line-fulfillment>
+                        </td>
+                        <td *ngIf="orderLineCustomFields.length" class="order-line-custom-field align-middle">
+                            <ng-container *ngFor="let customField of orderLineCustomFields">
+                                <vdr-labeled-data [label]="customField | customFieldLabel">{{
+                                    line.customFields[customField.name] || '-'
+                                }}</vdr-labeled-data>
+                            </ng-container>
+                        </td>
+                        <td class="align-middle total">
+                            {{ line.linePriceWithTax / 100 | currency: order.currencyCode }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{ line.linePrice / 100 | currency: order.currencyCode }}
+                            </div>
+                        </td>
+                    </tr>
+                    <tr
+                        *ngFor="let addedLine of addedLines; trackBy: trackByProductVariantId"
+                        class="modified"
+                    >
+                        <td class="align-middle thumb">
+                            <img
+                                *ngIf="addedLine.productAsset"
+                                [src]="addedLine.productAsset | assetPreview: 'tiny'"
+                            />
+                        </td>
+                        <td class="align-middle name">{{ addedLine.productVariantName }}</td>
+                        <td class="align-middle sku">{{ addedLine.sku }}</td>
+                        <td class="align-middle unit-price">
+                            {{ addedLine.priceWithTax / 100 | currency: order.currencyCode }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{ addedLine.price / 100 | currency: order.currencyCode }}
+                            </div>
+                        </td>
+                        <td class="align-middle quantity">
+                            <input
+                                class="clr-input"
+                                type="number"
+                                min="0"
+                                [value]="addedLine.quantity"
+                                (input)="updateAddedItemQuantity(addedLine, $event.target.value)"
+                            />
+                            <button class="icon-button" (click)="removeAddedItem(addedLine.productVariantId)">
+                                <clr-icon shape="trash"></clr-icon>
+                            </button>
+                        </td>
+                        <td *ngIf="orderLineCustomFields.length" class="order-line-custom-field align-middle">
+                            <!--  <ng-container *ngFor="let customField of orderLineCustomFields">
+                        <vdr-labeled-data [label]="customField | customFieldLabel">{{
+                            line.customFields[customField.name] || '-'
+                        }}</vdr-labeled-data>
+                    </ng-container>-->
+                        </td>
+                        <td class="align-middle total">
+                            {{
+                                (addedLine.priceWithTax * addedLine.quantity) / 100
+                                    | currency: order.currencyCode
+                            }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{
+                                    (addedLine.price * addedLine.quantity) / 100
+                                        | currency: order.currencyCode
+                                }}
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="surcharge" *ngFor="let surcharge of order.surcharges">
+                        <td class="align-middle name left" colspan="2">{{ surcharge.description }}</td>
+                        <td class="align-middle sku">{{ surcharge.sku }}</td>
+                        <td class="align-middle"></td>
+                        <td></td>
+                        <td *ngIf="orderLineCustomFields.length"></td>
+                        <td class="align-middle total">
+                            {{ surcharge.priceWithTax / 100 | currency: order.currencyCode }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{ surcharge.price / 100 | currency: order.currencyCode }}
+                            </div>
+                        </td>
+                    </tr>
+                    <tr
+                        class="surcharge modified"
+                        *ngFor="let surcharge of modifyOrderInput.surcharges; let i = index"
+                    >
+                        <td class="align-middle name left" colspan="2">
+                            {{ surcharge.description }}
+                            <button class="icon-button" (click)="removeSurcharge(i)">
+                                <clr-icon shape="trash"></clr-icon>
+                            </button>
+                        </td>
+                        <td class="align-middle sku">{{ surcharge.sku }}</td>
+                        <td class="align-middle"></td>
+                        <td></td>
+                        <td *ngIf="orderLineCustomFields.length"></td>
+                        <td class="align-middle total">
+                            <ng-container *ngIf="getSurchargePrices(surcharge) as surchargePrice">
+                                {{ surchargePrice.priceWithTax / 100 | currency: order.currencyCode }}
+                                <div class="net-price" [title]="'order.net-price' | translate">
+                                    {{ surchargePrice.price / 100 | currency: order.currencyCode }}
+                                </div>
+                            </ng-container>
+                        </td>
+                    </tr>
+                    <tr class="shipping">
+                        <td class="left clr-align-middle">{{ 'order.shipping' | translate }}</td>
+                        <td class="clr-align-middle">{{ order.shippingLines[0]?.shippingMethod?.name }}</td>
+                        <td colspan="3"></td>
+                        <td *ngIf="orderLineCustomFields.length"></td>
+                        <td class="clr-align-middle">
+                            {{ order.shippingWithTax / 100 | currency: order.currencyCode }}
+                            <div class="net-price" [title]="'order.net-price' | translate">
+                                {{ order.shipping / 100 | currency: order.currencyCode }}
+                            </div>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+
+            <h4 class="mb2">{{ 'order.modifications' | translate }}</h4>
+            <clr-accordion>
+                <clr-accordion-panel>
+                    <clr-accordion-title>{{ 'order.add-item-to-order' | translate }}</clr-accordion-title>
+                    <clr-accordion-content *clrIfExpanded>
+                        <vdr-product-selector (productSelected)="addItemToOrder($event)">
+                        </vdr-product-selector>
+                    </clr-accordion-content>
+                </clr-accordion-panel>
+
+                <clr-accordion-panel>
+                    <clr-accordion-title>{{ 'order.add-surcharge' | translate }}</clr-accordion-title>
+                    <clr-accordion-content *clrIfExpanded>
+                        <form [formGroup]="surchargeForm" (submit)="addSurcharge(surchargeForm.value)">
+                            <vdr-form-field [label]="'common.description' | translate" for="description"
+                                ><input
+                                    class="clr-input"
+                                    id="description"
+                                    type="text"
+                                    formControlName="description"
+                            /></vdr-form-field>
+                            <vdr-form-field [label]="'order.product-sku' | translate" for="sku"
+                                ><input class="clr-input" id="sku" type="text" formControlName="sku"
+                            /></vdr-form-field>
+                            <vdr-form-field [label]="'common.price' | translate" for="price"
+                                ><vdr-currency-input
+                                    [currencyCode]="order.currencyCode"
+                                    id="price"
+                                    formControlName="price"
+                                ></vdr-currency-input
+                            ></vdr-form-field>
+                            <vdr-form-field
+                                [label]="'catalog.price-includes-tax-at' | translate: { rate: surchargeForm.get('taxRate')?.value }"
+                                for="priceIncludesTax"
+                                ><input
+                                    id="priceIncludesTax"
+                                    type="checkbox"
+                                    clrCheckbox
+                                    formControlName="priceIncludesTax"
+                            /></vdr-form-field>
+                            <vdr-form-field [label]="'order.tax-rate' | translate" for="taxRate"
+                                ><vdr-affixed-input suffix="%"
+                                    ><input
+                                        class="clr-input"
+                                        id="taxRate"
+                                        type="number"
+                                        min="0"
+                                        max="100"
+                                        formControlName="taxRate" /></vdr-affixed-input
+                            ></vdr-form-field>
+                            <vdr-form-field [label]="'order.tax-description' | translate" for="taxDescription"
+                                ><input
+                                    class="clr-input"
+                                    id="taxDescription"
+                                    type="text"
+                                    formControlName="taxDescription"
+                            /></vdr-form-field>
+                            <button
+                                class="btn btn-secondary"
+                                [disabled]="surchargeForm.invalid || surchargeForm.pristine"
+                            >
+                                {{ 'order.add-surcharge' | translate }}
+                            </button>
+                        </form>
+                    </clr-accordion-content>
+                </clr-accordion-panel>
+                <clr-accordion-panel>
+                    <clr-accordion-title>{{ 'order.edit-shipping-address' | translate }}</clr-accordion-title>
+                    <clr-accordion-content *clrIfExpanded>
+                        <vdr-address-form
+                            [formGroup]="shippingAddressForm"
+                            [availableCountries]="availableCountries$ | async"
+                            [customFields]="addressCustomFields"
+                        ></vdr-address-form>
+                    </clr-accordion-content>
+                </clr-accordion-panel>
+                <clr-accordion-panel>
+                    <clr-accordion-title>{{ 'order.edit-billing-address' | translate }}</clr-accordion-title>
+                    <clr-accordion-content *clrIfExpanded>
+                        <vdr-address-form
+                            [formGroup]="billingAddressForm"
+                            [availableCountries]="availableCountries$ | async"
+                            [customFields]="addressCustomFields"
+                        ></vdr-address-form>
+                    </clr-accordion-content>
+                </clr-accordion-panel>
+            </clr-accordion>
+        </div>
+        <div class="clr-col-lg-4 order-cards">
+            <div class="card">
+                <div class="card-header">
+                    {{ 'order.modification-summary' | translate }}
+                </div>
+                <div class="card-block">
+                    <ul>
+                        <li *ngIf="modifyOrderInput.addItems?.length">
+                            {{
+                                'order.modification-adding-items'
+                                    | translate: { count: modifyOrderInput.addItems?.length }
+                            }}
+                        </li>
+                        <li *ngIf="modifyOrderInput.adjustOrderLines?.length">
+                            {{
+                                'order.modification-adjusting-lines'
+                                    | translate: { count: modifyOrderInput.adjustOrderLines?.length }
+                            }}
+                        </li>
+                        <li *ngIf="modifyOrderInput.surcharges?.length">
+                            {{
+                                'order.modification-adding-surcharges'
+                                    | translate: { count: modifyOrderInput.surcharges?.length }
+                            }}
+                        </li>
+                        <li *ngIf="shippingAddressForm.dirty">
+                            {{ 'order.modification-updating-shipping-address' | translate }}
+                        </li>
+                        <li *ngIf="billingAddressForm.dirty">
+                            {{ 'order.modification-updating-billing-address' | translate }}
+                        </li>
+                    </ul>
+                </div>
+                <div class="card-block">
+                    <label class="clr-control-label">{{ 'order.note' | translate }}</label>
+                    <textarea [(ngModel)]="note" name="note" clrTextarea required></textarea>
+                    <clr-checkbox-wrapper class="">
+                        <input type="checkbox" clrCheckbox [(ngModel)]="recalculateShipping" />
+                        <label>{{ 'order.modification-recalculate-shipping' | translate }}</label>
+                    </clr-checkbox-wrapper>
+                </div>
+                <div class="card-footer">
+                    <button class="btn btn-primary" [disabled]="!canPreviewChanges()" (click)="previewAndModify(order)">
+                        {{ 'order.preview-changes' | translate }}
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 10 - 0
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.scss

@@ -0,0 +1,10 @@
+@import "variables";
+@import "../order-table/order-table-mixin";
+
+.order-table {
+    @include order-table;
+
+    tr.modified td {
+        background-color: $color-warning-100;
+    }
+}

+ 347 - 0
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.ts

@@ -0,0 +1,347 @@
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+import {
+    BaseDetailComponent,
+    CustomFieldConfig,
+    DataService,
+    ErrorResult,
+    GetAvailableCountries,
+    HistoryEntryType,
+    LanguageCode,
+    ModalService,
+    ModifyOrderInput,
+    NotificationService,
+    OrderDetail,
+    ProductSelectorSearch,
+    ServerConfigService,
+    SortOrder,
+    SurchargeInput,
+} from '@vendure/admin-ui/core';
+import { assertNever, notNullOrUndefined } from '@vendure/common/lib/shared-utils';
+import { EMPTY, Observable, of } from 'rxjs';
+import { mapTo, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
+
+import { transitionToPreModifyingState } from '../order-detail/transition-to-pre-modifying-state';
+import {
+    OrderEditResultType,
+    OrderEditsPreviewDialogComponent,
+} from '../order-edits-preview-dialog/order-edits-preview-dialog.component';
+
+interface AddedLine {
+    productVariantId: string;
+    productAsset?: ProductSelectorSearch.ProductAsset | null;
+    productVariantName: string;
+    sku: string;
+    priceWithTax: number;
+    price: number;
+    quantity: number;
+}
+
+@Component({
+    selector: 'vdr-order-editor',
+    templateUrl: './order-editor.component.html',
+    styleUrls: ['./order-editor.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class OrderEditorComponent
+    extends BaseDetailComponent<OrderDetail.Fragment>
+    implements OnInit, OnDestroy {
+    availableCountries$: Observable<GetAvailableCountries.Items[]>;
+    addressCustomFields: CustomFieldConfig[];
+    detailForm = new FormGroup({});
+    orderLineCustomFields: CustomFieldConfig[];
+    modifyOrderInput: ModifyOrderInput = {
+        dryRun: true,
+        orderId: '',
+        addItems: [],
+        adjustOrderLines: [],
+        surcharges: [],
+        note: '',
+        updateShippingAddress: {},
+        updateBillingAddress: {},
+    };
+    surchargeForm: FormGroup;
+    shippingAddressForm: FormGroup;
+    billingAddressForm: FormGroup;
+    note = '';
+    recalculateShipping = true;
+    previousState: string;
+    private addedVariants = new Map<string, ProductSelectorSearch.Items>();
+
+    constructor(
+        router: Router,
+        route: ActivatedRoute,
+        serverConfigService: ServerConfigService,
+        private changeDetector: ChangeDetectorRef,
+        protected dataService: DataService,
+        private notificationService: NotificationService,
+        private modalService: ModalService,
+    ) {
+        super(route, router, serverConfigService, dataService);
+    }
+
+    get addedLines(): AddedLine[] {
+        const getSinglePriceValue = (price: ProductSelectorSearch.Price) =>
+            price.__typename === 'SinglePrice' ? price.value : 0;
+        return (this.modifyOrderInput.addItems || [])
+            .map(row => {
+                const variantInfo = this.addedVariants.get(row.productVariantId);
+                if (variantInfo) {
+                    return {
+                        ...variantInfo,
+                        price: getSinglePriceValue(variantInfo.price),
+                        priceWithTax: getSinglePriceValue(variantInfo.priceWithTax),
+                        quantity: row.quantity,
+                    };
+                }
+            })
+            .filter(notNullOrUndefined);
+    }
+
+    ngOnInit(): void {
+        this.init();
+        this.addressCustomFields = this.getCustomFieldConfig('Address');
+        this.modifyOrderInput.orderId = this.route.snapshot.paramMap.get('id') as string;
+        this.orderLineCustomFields = this.getCustomFieldConfig('OrderLine');
+        this.entity$.pipe(takeUntil(this.destroy$)).subscribe(order => {
+            this.surchargeForm = new FormGroup({
+                description: new FormControl('', Validators.required),
+                sku: new FormControl(''),
+                price: new FormControl(0, Validators.required),
+                priceIncludesTax: new FormControl(true),
+                taxRate: new FormControl(0),
+                taxDescription: new FormControl(''),
+            });
+            this.shippingAddressForm = new FormGroup({
+                fullName: new FormControl(order.shippingAddress?.fullName),
+                company: new FormControl(order.shippingAddress?.company),
+                streetLine1: new FormControl(order.shippingAddress?.streetLine1),
+                streetLine2: new FormControl(order.shippingAddress?.streetLine2),
+                city: new FormControl(order.shippingAddress?.city),
+                province: new FormControl(order.shippingAddress?.province),
+                postalCode: new FormControl(order.shippingAddress?.postalCode),
+                countryCode: new FormControl(order.shippingAddress?.countryCode),
+                phoneNumber: new FormControl(order.shippingAddress?.phoneNumber),
+            });
+            this.billingAddressForm = new FormGroup({
+                fullName: new FormControl(order.billingAddress?.fullName),
+                company: new FormControl(order.billingAddress?.company),
+                streetLine1: new FormControl(order.billingAddress?.streetLine1),
+                streetLine2: new FormControl(order.billingAddress?.streetLine2),
+                city: new FormControl(order.billingAddress?.city),
+                province: new FormControl(order.billingAddress?.province),
+                postalCode: new FormControl(order.billingAddress?.postalCode),
+                countryCode: new FormControl(order.billingAddress?.countryCode),
+                phoneNumber: new FormControl(order.billingAddress?.phoneNumber),
+            });
+        });
+        this.availableCountries$ = this.dataService.settings
+            .getAvailableCountries()
+            .mapSingle(result => result.countries.items)
+            .pipe(shareReplay(1));
+        this.dataService.order
+            .getOrderHistory(this.id, {
+                take: 1,
+                sort: {
+                    createdAt: SortOrder.DESC,
+                },
+                filter: { type: { eq: HistoryEntryType.ORDER_STATE_TRANSITION } },
+            })
+            .single$.subscribe(({ order }) => {
+                this.previousState = order?.history.items[0].data.from;
+            });
+    }
+
+    ngOnDestroy(): void {
+        this.destroy();
+    }
+
+    transitionToPriorState(order: OrderDetail.Fragment) {
+        transitionToPreModifyingState(this.dataService, order.id).subscribe(({ transitionOrderToState }) => {
+            switch (transitionOrderToState?.__typename) {
+                case 'Order':
+                    this.router.navigate(['..'], { relativeTo: this.route });
+                    break;
+                case 'OrderStateTransitionError':
+                    this.notificationService.error(transitionOrderToState?.transitionError);
+            }
+        });
+    }
+
+    canPreviewChanges(): boolean {
+        const { addItems, adjustOrderLines, surcharges } = this.modifyOrderInput;
+        return (
+            (!!addItems?.length ||
+                !!surcharges?.length ||
+                !!adjustOrderLines?.length ||
+                (this.shippingAddressForm.dirty && this.shippingAddressForm.valid) ||
+                (this.billingAddressForm.dirty && this.billingAddressForm.valid)) &&
+            this.note !== ''
+        );
+    }
+
+    isLineModified(line: OrderDetail.Lines): boolean {
+        return !!this.modifyOrderInput.adjustOrderLines?.find(
+            l => l.orderLineId === line.id && l.quantity !== line.quantity,
+        );
+    }
+
+    updateLineQuantity(line: OrderDetail.Lines, quantity: string) {
+        const { adjustOrderLines } = this.modifyOrderInput;
+        let row = adjustOrderLines?.find(l => l.orderLineId === line.id);
+        if (row && +quantity === line.quantity) {
+            // Remove the modification if the quantity is the same as
+            // the original order
+            adjustOrderLines?.splice(adjustOrderLines?.indexOf(row), 1);
+        }
+        if (!row) {
+            row = { orderLineId: line.id, quantity: +quantity };
+            adjustOrderLines?.push(row);
+        }
+        row.quantity = +quantity;
+    }
+
+    updateAddedItemQuantity(item: AddedLine, quantity: string) {
+        const row = this.modifyOrderInput.addItems?.find(l => l.productVariantId === item.productVariantId);
+        if (row) {
+            row.quantity = +quantity;
+        }
+    }
+
+    trackByProductVariantId(index: number, item: AddedLine) {
+        return item.productVariantId;
+    }
+
+    addItemToOrder(result: ProductSelectorSearch.Items) {
+        let row = this.modifyOrderInput.addItems?.find(l => l.productVariantId === result.productVariantId);
+        if (!row) {
+            row = { productVariantId: result.productVariantId, quantity: 1 };
+            this.modifyOrderInput.addItems?.push(row);
+        } else {
+            row.quantity++;
+        }
+        this.addedVariants.set(result.productVariantId, result);
+    }
+
+    removeAddedItem(productVariantId: string) {
+        this.modifyOrderInput.addItems = this.modifyOrderInput.addItems?.filter(
+            l => l.productVariantId !== productVariantId,
+        );
+    }
+
+    getSurchargePrices(surcharge: SurchargeInput) {
+        const priceWithTax = surcharge.priceIncludesTax
+            ? surcharge.price
+            : Math.round(surcharge.price * ((100 + (surcharge.taxRate || 0)) / 100));
+        const price = surcharge.priceIncludesTax
+            ? Math.round(surcharge.price / ((100 + (surcharge.taxRate || 0)) / 100))
+            : surcharge.price;
+        return {
+            price,
+            priceWithTax,
+        };
+    }
+
+    addSurcharge(value: any) {
+        this.modifyOrderInput.surcharges?.push(value);
+        this.surchargeForm.reset({
+            price: 0,
+            priceIncludesTax: true,
+            taxRate: 0,
+        });
+    }
+
+    removeSurcharge(index: number) {
+        this.modifyOrderInput.surcharges?.splice(index, 1);
+    }
+
+    previewAndModify(order: OrderDetail.Fragment) {
+        const input: ModifyOrderInput = {
+            ...this.modifyOrderInput,
+            dryRun: true,
+            note: this.note,
+            options: {
+                recalculateShipping: this.recalculateShipping,
+            },
+        };
+        const originalTotalWithTax = order.totalWithTax;
+        this.dataService.order
+            .modifyOrder(input)
+            .pipe(
+                switchMap(({ modifyOrder }) => {
+                    switch (modifyOrder.__typename) {
+                        case 'Order':
+                            return this.modalService.fromComponent(OrderEditsPreviewDialogComponent, {
+                                size: 'xl',
+                                closable: false,
+                                locals: {
+                                    originalTotalWithTax,
+                                    order: modifyOrder,
+                                    orderLineCustomFields: this.orderLineCustomFields,
+                                    modifyOrderInput: input,
+                                },
+                            });
+                        case 'InsufficientStockError':
+                        case 'NegativeQuantityError':
+                        case 'NoChangesSpecifiedError':
+                        case 'OrderLimitError':
+                        case 'OrderModificationStateError':
+                        case 'PaymentMethodMissingError':
+                        case 'RefundPaymentIdMissingError': {
+                            this.notificationService.error(modifyOrder.message);
+                            return of(false as const);
+                        }
+                        case null:
+                        case undefined:
+                            return of(false as const);
+                        default:
+                            assertNever(modifyOrder);
+                    }
+                }),
+                switchMap(result => {
+                    if (!result || result.result === OrderEditResultType.Cancel) {
+                        // re-fetch so that the preview values get overwritten in the cache.
+                        return this.dataService.order.getOrder(this.id).mapSingle(() => false);
+                    } else {
+                        // Do the modification
+                        const wetRunInput = {
+                            ...input,
+                            dryRun: false,
+                        };
+                        if (result.result === OrderEditResultType.Refund) {
+                            wetRunInput.refund = {
+                                paymentId: result.refundPaymentId,
+                                reason: result.refundNote,
+                            };
+                        }
+                        return this.dataService.order.modifyOrder(wetRunInput).pipe(
+                            switchMap(({ modifyOrder }) => {
+                                if (modifyOrder.__typename === 'Order') {
+                                    const priceDelta = modifyOrder.totalWithTax - originalTotalWithTax;
+                                    const nextState =
+                                        0 < priceDelta ? 'ArrangingAdditionalPayment' : this.previousState;
+
+                                    return this.dataService.order
+                                        .transitionToState(order.id, nextState)
+                                        .pipe(mapTo(true));
+                                } else {
+                                    this.notificationService.error((modifyOrder as ErrorResult).message);
+                                    return EMPTY;
+                                }
+                            }),
+                        );
+                    }
+                }),
+            )
+            .subscribe(result => {
+                if (result) {
+                    this.router.navigate(['../'], { relativeTo: this.route });
+                }
+            });
+    }
+
+    protected setFormValues(entity: OrderDetail.Fragment, languageCode: LanguageCode): void {
+        /* not used */
+    }
+}

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

@@ -0,0 +1,29 @@
+<ng-template vdrDialogTitle>{{ 'order.confirm-modifications' | translate }}</ng-template>
+<vdr-order-table [order]="order" [orderLineCustomFields]="orderLineCustomFields"></vdr-order-table>
+
+<h4 class="h4">
+    {{ 'order.modify-order-price-difference' | translate }}:
+    <strong>{{ priceDifference / 100 | currency: order.currencyCode }}</strong>
+</h4>
+<div *ngIf="priceDifference < 0">
+<clr-select-container>
+    <label>{{ 'order.payment-to-refund' | translate }}</label>
+    <select clrSelect name="options" [(ngModel)]="selectedPayment">
+        <option
+            *ngFor="let payment of order.payments"
+            [ngValue]="payment"
+        >
+            #{{ payment.id }} {{ payment.method }}:
+            {{ payment.amount / 100 | currency: order.currencyCode }}
+        </option>
+    </select>
+</clr-select-container>
+    <label class="clr-control-label">{{ 'order.refund-cancellation-reason' | translate }}</label>
+    <textarea [(ngModel)]="refundNote" name="refundNote" clrTextarea required></textarea>
+</div>
+<ng-template vdrDialogButtons>
+    <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+    <button type="submit" (click)="submit()" [disabled]="priceDifference < 0 && !selectedPayment" class="btn btn-primary">
+        {{ 'common.confirm' | translate }}
+    </button>
+</ng-template>

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


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

@@ -0,0 +1,80 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { CustomFieldConfig, Dialog, ModifyOrderInput, OrderDetail } from '@vendure/admin-ui/core';
+
+export enum OrderEditResultType {
+    Refund,
+    Payment,
+    PriceUnchanged,
+    Cancel,
+}
+
+interface OrderEditsRefundResult {
+    result: OrderEditResultType.Refund;
+    refundPaymentId: string;
+    refundNote?: string;
+}
+interface OrderEditsPaymentResult {
+    result: OrderEditResultType.Payment;
+}
+interface OrderEditsPriceUnchangedResult {
+    result: OrderEditResultType.PriceUnchanged;
+}
+interface OrderEditsCancelResult {
+    result: OrderEditResultType.Cancel;
+}
+type OrderEditResult =
+    | OrderEditsRefundResult
+    | OrderEditsPaymentResult
+    | OrderEditsPriceUnchangedResult
+    | OrderEditsCancelResult;
+
+@Component({
+    selector: 'vdr-order-edits-preview-dialog',
+    templateUrl: './order-edits-preview-dialog.component.html',
+    styleUrls: ['./order-edits-preview-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class OrderEditsPreviewDialogComponent implements OnInit, Dialog<OrderEditResult> {
+    // Passed in via the modalService
+    order: OrderDetail.Fragment;
+    originalTotalWithTax: number;
+    orderLineCustomFields: CustomFieldConfig[];
+    modifyOrderInput: ModifyOrderInput;
+
+    selectedPayment?: OrderDetail.Payments;
+    refundNote: string;
+    resolveWith: (result?: OrderEditResult) => void;
+
+    get priceDifference(): number {
+        return this.order.totalWithTax - this.originalTotalWithTax;
+    }
+
+    ngOnInit() {
+        this.refundNote = this.modifyOrderInput.note || '';
+    }
+
+    cancel() {
+        this.resolveWith({
+            result: OrderEditResultType.Cancel,
+        });
+    }
+
+    submit() {
+        if (0 < this.priceDifference) {
+            this.resolveWith({
+                result: OrderEditResultType.Payment,
+            });
+        } else if (this.priceDifference < 0) {
+            this.resolveWith({
+                result: OrderEditResultType.Refund,
+                // tslint:disable-next-line:no-non-null-assertion
+                refundPaymentId: this.selectedPayment!.id,
+                refundNote: this.refundNote,
+            });
+        } else {
+            this.resolveWith({
+                result: OrderEditResultType.PriceUnchanged,
+            });
+        }
+    }
+}

+ 26 - 1
packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.html

@@ -45,6 +45,27 @@
                     }}
                     }}
                 </ng-template>
                 </ng-template>
             </ng-container>
             </ng-container>
+            <ng-container *ngSwitchCase="type.ORDER_MODIFIED">
+                <div class="title">
+                    {{ 'order.history-order-modified' | translate }}
+                </div>
+                <ng-container *ngIf="getModification(entry.data.modificationId) as modification">
+                    {{ 'order.modify-order-price-difference' | translate }}:
+                    <strong>{{ modification.priceChange / 100 | currency: order.currencyCode }}</strong>
+                    <vdr-chip colorType="success" *ngIf="modification.isSettled">{{
+                        'order.modification-settled' | translate
+                    }}</vdr-chip>
+                    <vdr-chip colorType="error" *ngIf="!modification.isSettled">{{
+                        'order.modification-not-settled' | translate
+                    }}</vdr-chip>
+                    <vdr-history-entry-detail>
+                        <vdr-modification-detail
+                            [order]="order"
+                            [modification]="modification"
+                        ></vdr-modification-detail>
+                    </vdr-history-entry-detail>
+                </ng-container>
+            </ng-container>
             <ng-container *ngSwitchCase="type.ORDER_PAYMENT_TRANSITION">
             <ng-container *ngSwitchCase="type.ORDER_PAYMENT_TRANSITION">
                 <ng-container *ngIf="entry.data.to === 'Settled'; else regularPaymentTransition">
                 <ng-container *ngIf="entry.data.to === 'Settled'; else regularPaymentTransition">
                     <div class="title">
                     <div class="title">
@@ -62,7 +83,11 @@
                     {{
                     {{
                         'order.history-payment-transition'
                         'order.history-payment-transition'
                             | translate
                             | translate
-                                : { from: entry.data.from, to: entry.data.to, id: getPayment(entry)?.transactionId }
+                                : {
+                                      from: entry.data.from,
+                                      to: entry.data.to,
+                                      id: getPayment(entry)?.transactionId
+                                  }
                     }}
                     }}
                 </ng-template>
                 </ng-template>
             </ng-container>
             </ng-container>

+ 8 - 0
packages/admin-ui/src/lib/order/src/components/order-history/order-history.component.ts

@@ -68,6 +68,9 @@ export class OrderHistoryComponent {
         if (entry.type === HistoryEntryType.ORDER_NOTE) {
         if (entry.type === HistoryEntryType.ORDER_NOTE) {
             return 'note';
             return 'note';
         }
         }
+        if (entry.type === HistoryEntryType.ORDER_MODIFIED) {
+            return 'pencil';
+        }
         if (entry.type === HistoryEntryType.ORDER_FULFILLMENT_TRANSITION) {
         if (entry.type === HistoryEntryType.ORDER_FULFILLMENT_TRANSITION) {
             if (entry.data.to === 'Shipped') {
             if (entry.data.to === 'Shipped') {
                 return 'truck';
                 return 'truck';
@@ -92,6 +95,7 @@ export class OrderHistoryComponent {
             case HistoryEntryType.ORDER_FULFILLMENT_TRANSITION:
             case HistoryEntryType.ORDER_FULFILLMENT_TRANSITION:
                 return entry.data.to === 'Delivered' || entry.data.to === 'Shipped';
                 return entry.data.to === 'Delivered' || entry.data.to === 'Shipped';
             case HistoryEntryType.ORDER_NOTE:
             case HistoryEntryType.ORDER_NOTE:
+            case HistoryEntryType.ORDER_MODIFIED:
                 return true;
                 return true;
             default:
             default:
                 return false;
                 return false;
@@ -132,6 +136,10 @@ export class OrderHistoryComponent {
         return Array.from(itemMap.entries()).map(([name, quantity]) => ({ name, quantity }));
         return Array.from(itemMap.entries()).map(([name, quantity]) => ({ name, quantity }));
     }
     }
 
 
+    getModification(id: string) {
+        return this.order.modifications.find(m => m.id === id);
+    }
+
     getName(entry: GetOrderHistory.Items): string {
     getName(entry: GetOrderHistory.Items): string {
         const { administrator } = entry;
         const { administrator } = entry;
         if (administrator) {
         if (administrator) {

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html

@@ -104,7 +104,7 @@
             <vdr-table-row-action
             <vdr-table-row-action
                 iconShape="shopping-cart"
                 iconShape="shopping-cart"
                 [label]="'common.open' | translate"
                 [label]="'common.open' | translate"
-                [linkTo]="['./', order.id]"
+                [linkTo]="order.state === 'Modifying' ? ['./', order.id, 'modify'] : ['./', order.id]"
             ></vdr-table-row-action>
             ></vdr-table-row-action>
         </td>
         </td>
     </ng-template>
     </ng-template>

+ 47 - 0
packages/admin-ui/src/lib/order/src/components/order-table/order-table-mixin.scss

@@ -0,0 +1,47 @@
+@import "variables";
+
+@mixin order-table {
+
+    .is-cancelled td {
+        text-decoration: line-through;
+        background-color: $color-grey-200;
+    }
+
+    .sub-total td {
+        border-top: 1px dashed $color-grey-300;
+    }
+
+    .total td {
+        font-weight: bold;
+        border-top: 1px dashed $color-grey-300;
+    }
+
+    .order-line-custom-field {
+        background-color: $color-grey-100;
+
+        .custom-field-ellipsis {
+            color: $color-grey-300;
+        }
+    }
+
+    .net-price {
+        font-size: 11px;
+        color: $color-grey-400;
+    }
+    .promotions-label {
+        text-decoration: underline dotted $color-grey-300;
+        font-size: 11px;
+        margin-top: 6px;
+        cursor: pointer;
+        text-transform: lowercase;
+    }
+
+    .line-promotion {
+        display: flex;
+        justify-content: space-between;
+        padding: 6px 12px;
+        .promotion-amount {
+            margin-left: 12px;
+        }
+    }
+}

+ 165 - 0
packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.html

@@ -0,0 +1,165 @@
+<table class="order-table table">
+    <thead>
+        <tr>
+            <th></th>
+            <th>{{ 'order.product-name' | translate }}</th>
+            <th>{{ 'order.product-sku' | translate }}</th>
+            <th>{{ 'order.unit-price' | translate }}</th>
+            <th>{{ 'order.quantity' | translate }}</th>
+            <ng-container *ngFor="let customField of visibleOrderLineCustomFields">
+                <th class="order-line-custom-field">
+                    <button
+                        class="custom-field-header-button"
+                        (click)="toggleOrderLineCustomFields()"
+                        [title]="'common.hide-custom-fields' | translate"
+                    >
+                        {{ customField | customFieldLabel }}
+                    </button>
+                </th>
+            </ng-container>
+            <ng-container *ngIf="showElided">
+                <th>
+                    <button
+                        class="custom-field-header-button"
+                        (click)="toggleOrderLineCustomFields()"
+                        [title]="'common.display-custom-fields' | translate"
+                    >
+                        <clr-icon shape="ellipsis-horizontal" class="custom-field-ellipsis"></clr-icon>
+                    </button>
+                </th>
+            </ng-container>
+            <th>{{ 'order.total' | translate }}</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr *ngFor="let line of order.lines" class="order-line" [class.is-cancelled]="line.quantity === 0">
+            <td class="align-middle thumb">
+                <img *ngIf="line.featuredAsset" [src]="line.featuredAsset | assetPreview: 'tiny'" />
+            </td>
+            <td class="align-middle name">{{ line.productVariant.name }}</td>
+            <td class="align-middle sku">{{ line.productVariant.sku }}</td>
+            <td class="align-middle unit-price">
+                {{ line.unitPriceWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ line.unitPrice / 100 | currency: order.currencyCode }}
+                </div>
+            </td>
+            <td class="align-middle quantity">
+                {{ line.quantity }}
+                <vdr-line-refunds [line]="line"></vdr-line-refunds>
+                <vdr-line-fulfillment [line]="line" [orderState]="order.state"></vdr-line-fulfillment>
+            </td>
+            <ng-container *ngFor="let customField of visibleOrderLineCustomFields">
+                <td class="order-line-custom-field align-middle">
+                    <ng-container [ngSwitch]="customField.type">
+                        <ng-template [ngSwitchCase]="'datetime'">
+                            <span [title]="line.customFields[customField.name]">{{
+                                line.customFields[customField.name] | date: 'short'
+                            }}</span>
+                        </ng-template>
+                        <ng-template [ngSwitchCase]="'boolean'">
+                            <ng-template [ngIf]="line.customFields[customField.name] === true">
+                                <clr-icon shape="check"></clr-icon>
+                            </ng-template>
+                            <ng-template [ngIf]="line.customFields[customField.name] === false">
+                                <clr-icon shape="times"></clr-icon>
+                            </ng-template>
+                        </ng-template>
+                        <ng-template ngSwitchDefault>
+                            {{ line.customFields[customField.name] }}
+                        </ng-template>
+                    </ng-container>
+                </td>
+            </ng-container>
+            <ng-container *ngIf="showElided"
+                ><td class="order-line-custom-field align-middle">
+                    <clr-icon shape="ellipsis-horizontal" class="custom-field-ellipsis"></clr-icon></td
+            ></ng-container>
+            <td class="align-middle total">
+                {{ line.linePriceWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ line.linePrice / 100 | currency: order.currencyCode }}
+                </div>
+
+                <ng-container *ngIf="getLineDiscounts(line) as discounts">
+                    <vdr-dropdown *ngIf="discounts.length">
+                        <div class="promotions-label" vdrDropdownTrigger>
+                            {{ 'order.promotions-applied' | translate }}
+                        </div>
+                        <vdr-dropdown-menu>
+                            <div class="line-promotion" *ngFor="let discount of discounts">
+                                <a class="promotion-name" [routerLink]="getPromotionLink(discount)">{{
+                                    discount.description
+                                }}</a>
+                                <div class="promotion-amount">
+                                    {{ discount.amount / 100 | currency: order.currencyCode }}
+                                </div>
+                            </div>
+                        </vdr-dropdown-menu>
+                    </vdr-dropdown>
+                </ng-container>
+            </td>
+        </tr>
+        <tr class="surcharge" *ngFor="let surcharge of order.surcharges">
+            <td class="align-middle name left" colspan="2">{{ surcharge.description }}</td>
+            <td class="align-middle sku">{{ surcharge.sku }}</td>
+            <td class="align-middle"></td>
+            <td [attr.colspan]="1 + visibleOrderLineCustomFields.length"></td>
+            <ng-container *ngIf="showElided"><td></td></ng-container>
+            <td class="align-middle total">
+                {{ surcharge.priceWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ surcharge.price / 100 | currency: order.currencyCode }}
+                </div>
+            </td>
+        </tr>
+        <tr class="order-adjustment" *ngFor="let discount of order.discounts">
+            <td [attr.colspan]="5 + visibleOrderLineCustomFields.length" class="left clr-align-middle">
+                <a [routerLink]="getPromotionLink(discount)">{{ discount.description }}</a>
+                <vdr-chip *ngIf="getCouponCodeForAdjustment(order, discount) as couponCode">{{
+                    couponCode
+                }}</vdr-chip>
+            </td>
+            <ng-container *ngIf="showElided"><td></td></ng-container>
+            <td class="clr-align-middle">
+                {{ discount.amount / 100 | currency: order.currencyCode }}
+            </td>
+        </tr>
+        <tr class="sub-total">
+            <td class="left clr-align-middle">{{ 'order.sub-total' | translate }}</td>
+            <td></td>
+            <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
+            <ng-container *ngIf="showElided"><td></td></ng-container>
+            <td class="clr-align-middle">
+                {{ order.subTotalWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ order.subTotal / 100 | currency: order.currencyCode }}
+                </div>
+            </td>
+        </tr>
+        <tr class="shipping">
+            <td class="left clr-align-middle">{{ 'order.shipping' | translate }}</td>
+            <td class="clr-align-middle">{{ order.shippingLines[0]?.shippingMethod?.name }}</td>
+            <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
+            <ng-container *ngIf="showElided"><td></td></ng-container>
+            <td class="clr-align-middle">
+                {{ order.shippingWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ order.shipping / 100 | currency: order.currencyCode }}
+                </div>
+            </td>
+        </tr>
+        <tr class="total">
+            <td class="left clr-align-middle">{{ 'order.total' | translate }}</td>
+            <td></td>
+            <td [attr.colspan]="3 + visibleOrderLineCustomFields.length"></td>
+            <ng-container *ngIf="showElided"><td></td></ng-container>
+            <td class="clr-align-middle">
+                {{ order.totalWithTax / 100 | currency: order.currencyCode }}
+                <div class="net-price" [title]="'order.net-price' | translate">
+                    {{ order.total / 100 | currency: order.currencyCode }}
+                </div>
+            </td>
+        </tr>
+    </tbody>
+</table>

+ 15 - 0
packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.scss

@@ -0,0 +1,15 @@
+@import "variables";
+@import "order-table-mixin";
+
+.order-table {
+    @include order-table;
+}
+
+.custom-field-header-button {
+    background: none;
+    margin: 0;
+    padding: 0;
+    border: none;
+    cursor: pointer;
+    color: $color-primary-600;
+}

+ 50 - 0
packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.ts

@@ -0,0 +1,50 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { AdjustmentType, CustomFieldConfig, OrderDetail } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'vdr-order-table',
+    templateUrl: './order-table.component.html',
+    styleUrls: ['./order-table.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class OrderTableComponent implements OnInit {
+    @Input() order: OrderDetail.Fragment;
+    @Input() orderLineCustomFields: CustomFieldConfig[];
+    orderLineCustomFieldsVisible = false;
+
+    get visibleOrderLineCustomFields(): CustomFieldConfig[] {
+        return this.orderLineCustomFieldsVisible ? this.orderLineCustomFields : [];
+    }
+
+    get showElided(): boolean {
+        return !this.orderLineCustomFieldsVisible && 0 < this.orderLineCustomFields.length;
+    }
+
+    ngOnInit(): void {
+        this.orderLineCustomFieldsVisible = this.orderLineCustomFields.length < 2;
+    }
+
+    toggleOrderLineCustomFields() {
+        this.orderLineCustomFieldsVisible = !this.orderLineCustomFieldsVisible;
+    }
+
+    getLineDiscounts(line: OrderDetail.Lines) {
+        return line.discounts.filter(a => a.type === AdjustmentType.PROMOTION);
+    }
+
+    getPromotionLink(promotion: OrderDetail.Discounts): any[] {
+        const id = promotion.adjustmentSource.split(':')[1];
+        return ['/marketing', 'promotions', id];
+    }
+
+    getCouponCodeForAdjustment(
+        order: OrderDetail.Fragment,
+        promotionAdjustment: OrderDetail.Discounts,
+    ): string | undefined {
+        const id = promotionAdjustment.adjustmentSource.split(':')[1];
+        const promotion = order.promotions.find(p => p.id === id);
+        if (promotion) {
+            return promotion.couponCode || undefined;
+        }
+    }
+}

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/refund-order-dialog/refund-order-dialog.component.html

@@ -1,7 +1,7 @@
 <ng-template vdrDialogTitle>{{ 'order.refund-and-cancel-order' | translate }}</ng-template>
 <ng-template vdrDialogTitle>{{ 'order.refund-and-cancel-order' | translate }}</ng-template>
 
 
 <div class="refund-wrapper">
 <div class="refund-wrapper">
-    <div class="order-lines">
+    <div class="order-table">
         <table class="table">
         <table class="table">
             <thead>
             <thead>
                 <tr>
                 <tr>

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/refund-order-dialog/refund-order-dialog.component.scss

@@ -8,7 +8,7 @@
 .refund-wrapper {
 .refund-wrapper {
     flex: 1;
     flex: 1;
     flex-direction: column;
     flex-direction: column;
-    .order-lines {
+    .order-table {
         flex: 1;
         flex: 1;
         overflow-y: auto;
         overflow-y: auto;
         table {
         table {

+ 10 - 0
packages/admin-ui/src/lib/order/src/order.module.ts

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
 import { RouterModule } from '@angular/router';
 import { RouterModule } from '@angular/router';
 import { SharedModule } from '@vendure/admin-ui/core';
 import { SharedModule } from '@vendure/admin-ui/core';
 
 
+import { AddManualPaymentDialogComponent } from './components/add-manual-payment-dialog/add-manual-payment-dialog.component';
 import { CancelOrderDialogComponent } from './components/cancel-order-dialog/cancel-order-dialog.component';
 import { CancelOrderDialogComponent } from './components/cancel-order-dialog/cancel-order-dialog.component';
 import { FulfillOrderDialogComponent } from './components/fulfill-order-dialog/fulfill-order-dialog.component';
 import { FulfillOrderDialogComponent } from './components/fulfill-order-dialog/fulfill-order-dialog.component';
 import { FulfillmentCardComponent } from './components/fulfillment-card/fulfillment-card.component';
 import { FulfillmentCardComponent } from './components/fulfillment-card/fulfillment-card.component';
@@ -9,8 +10,11 @@ import { FulfillmentDetailComponent } from './components/fulfillment-detail/fulf
 import { FulfillmentStateLabelComponent } from './components/fulfillment-state-label/fulfillment-state-label.component';
 import { FulfillmentStateLabelComponent } from './components/fulfillment-state-label/fulfillment-state-label.component';
 import { LineFulfillmentComponent } from './components/line-fulfillment/line-fulfillment.component';
 import { LineFulfillmentComponent } from './components/line-fulfillment/line-fulfillment.component';
 import { LineRefundsComponent } from './components/line-refunds/line-refunds.component';
 import { LineRefundsComponent } from './components/line-refunds/line-refunds.component';
+import { ModificationDetailComponent } from './components/modification-detail/modification-detail.component';
 import { OrderCustomFieldsCardComponent } from './components/order-custom-fields-card/order-custom-fields-card.component';
 import { OrderCustomFieldsCardComponent } from './components/order-custom-fields-card/order-custom-fields-card.component';
 import { OrderDetailComponent } from './components/order-detail/order-detail.component';
 import { OrderDetailComponent } from './components/order-detail/order-detail.component';
+import { OrderEditorComponent } from './components/order-editor/order-editor.component';
+import { OrderEditsPreviewDialogComponent } from './components/order-edits-preview-dialog/order-edits-preview-dialog.component';
 import { OrderHistoryComponent } from './components/order-history/order-history.component';
 import { OrderHistoryComponent } from './components/order-history/order-history.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
 import { OrderPaymentCardComponent } from './components/order-payment-card/order-payment-card.component';
 import { OrderPaymentCardComponent } from './components/order-payment-card/order-payment-card.component';
@@ -18,6 +22,7 @@ import { OrderProcessGraphDialogComponent } from './components/order-process-gra
 import { OrderProcessEdgeComponent } from './components/order-process-graph/order-process-edge.component';
 import { OrderProcessEdgeComponent } from './components/order-process-graph/order-process-edge.component';
 import { OrderProcessGraphComponent } from './components/order-process-graph/order-process-graph.component';
 import { OrderProcessGraphComponent } from './components/order-process-graph/order-process-graph.component';
 import { OrderProcessNodeComponent } from './components/order-process-graph/order-process-node.component';
 import { OrderProcessNodeComponent } from './components/order-process-graph/order-process-node.component';
+import { OrderTableComponent } from './components/order-table/order-table.component';
 import { PaymentDetailComponent } from './components/payment-detail/payment-detail.component';
 import { PaymentDetailComponent } from './components/payment-detail/payment-detail.component';
 import { PaymentStateLabelComponent } from './components/payment-state-label/payment-state-label.component';
 import { PaymentStateLabelComponent } from './components/payment-state-label/payment-state-label.component';
 import { RefundOrderDialogComponent } from './components/refund-order-dialog/refund-order-dialog.component';
 import { RefundOrderDialogComponent } from './components/refund-order-dialog/refund-order-dialog.component';
@@ -51,6 +56,11 @@ import { orderRoutes } from './order.routes';
         OrderProcessGraphDialogComponent,
         OrderProcessGraphDialogComponent,
         FulfillmentStateLabelComponent,
         FulfillmentStateLabelComponent,
         FulfillmentCardComponent,
         FulfillmentCardComponent,
+        OrderEditorComponent,
+        OrderTableComponent,
+        OrderEditsPreviewDialogComponent,
+        ModificationDetailComponent,
+        AddManualPaymentDialogComponent,
     ],
     ],
 })
 })
 export class OrderModule {}
 export class OrderModule {}

+ 29 - 1
packages/admin-ui/src/lib/order/src/order.routes.ts

@@ -1,8 +1,16 @@
 import { Route } from '@angular/router';
 import { Route } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { CanDeactivateDetailGuard, createResolveData, detailBreadcrumb, OrderDetail } from '@vendure/admin-ui/core';
+import {
+    BreadcrumbLabelLinkPair,
+    CanDeactivateDetailGuard,
+    createResolveData,
+    detailBreadcrumb,
+    OrderDetail,
+} from '@vendure/admin-ui/core';
+import { map } from 'rxjs/operators';
 
 
 import { OrderDetailComponent } from './components/order-detail/order-detail.component';
 import { OrderDetailComponent } from './components/order-detail/order-detail.component';
+import { OrderEditorComponent } from './components/order-editor/order-editor.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
 import { OrderListComponent } from './components/order-list/order-list.component';
 import { OrderResolver } from './providers/routing/order-resolver';
 import { OrderResolver } from './providers/routing/order-resolver';
 
 
@@ -23,6 +31,15 @@ export const orderRoutes: Route[] = [
             breadcrumb: orderBreadcrumb,
             breadcrumb: orderBreadcrumb,
         },
         },
     },
     },
+    {
+        path: ':id/modify',
+        component: OrderEditorComponent,
+        resolve: createResolveData(OrderResolver),
+        // canDeactivate: [CanDeactivateDetailGuard],
+        data: {
+            breadcrumb: modifyingOrderBreadcrumb,
+        },
+    },
 ];
 ];
 
 
 export function orderBreadcrumb(data: any, params: any) {
 export function orderBreadcrumb(data: any, params: any) {
@@ -34,3 +51,14 @@ export function orderBreadcrumb(data: any, params: any) {
         route: '',
         route: '',
     });
     });
 }
 }
+
+export function modifyingOrderBreadcrumb(data: any, params: any) {
+    return orderBreadcrumb(data, params).pipe(
+        map((breadcrumbs: BreadcrumbLabelLinkPair[]) => {
+            const modifiedBreadcrumbs = breadcrumbs.slice();
+            modifiedBreadcrumbs[0].link[0] = '../';
+            modifiedBreadcrumbs[1].link[0] = '../orders';
+            return modifiedBreadcrumbs.concat({ label: _('breadcrumb.modifying'), link: [''] });
+        }) as any,
+    );
+}

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/test-order-builder/test-order-builder.component.html

@@ -2,7 +2,7 @@
     <div class="card-header">
     <div class="card-header">
         {{ 'settings.test-order' | translate }}
         {{ 'settings.test-order' | translate }}
     </div>
     </div>
-    <table class="order-lines table" *ngIf="lines.length; else emptyPlaceholder">
+    <table class="order-table table" *ngIf="lines.length; else emptyPlaceholder">
         <thead>
         <thead>
             <tr>
             <tr>
                 <th></th>
                 <th></th>

+ 31 - 1
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -37,6 +37,7 @@
     "global-settings": "Všeobecné nastavení",
     "global-settings": "Všeobecné nastavení",
     "job-queue": "Fronta úloh",
     "job-queue": "Fronta úloh",
     "manage-variants": "Správa variant",
     "manage-variants": "Správa variant",
+    "modifying": "",
     "orders": "Objednávky",
     "orders": "Objednávky",
     "payment-methods": "Platební metody",
     "payment-methods": "Platební metody",
     "products": "Produkty",
     "products": "Produkty",
@@ -549,22 +550,31 @@
     "zones": "Zóny"
     "zones": "Zóny"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "Přidat poznámku",
     "add-note": "Přidat poznámku",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Částka",
     "amount": "Částka",
     "apply-filters": "Filtrovat",
     "apply-filters": "Filtrovat",
     "billing-address": "Fakturační adresa",
     "billing-address": "Fakturační adresa",
     "cancel": "Zrušit",
     "cancel": "Zrušit",
     "cancel-fulfillment": "Zrušit zpracování",
     "cancel-fulfillment": "Zrušit zpracování",
+    "cancel-modification": "",
     "cancel-order": "Zrušit objednávku",
     "cancel-order": "Zrušit objednávku",
     "cancel-reason-customer-request": "Požadavek zákazníka",
     "cancel-reason-customer-request": "Požadavek zákazníka",
     "cancel-reason-not-available": "Neuveden",
     "cancel-reason-not-available": "Neuveden",
     "cancel-selected-items": "Zrušit vybrané položky",
     "cancel-selected-items": "Zrušit vybrané položky",
     "cancellation-reason": "Důvod zrušení",
     "cancellation-reason": "Důvod zrušení",
     "cancelled-order-success": "Objednávka úspěšně zrušena",
     "cancelled-order-success": "Objednávka úspěšně zrušena",
+    "confirm-modifications": "",
     "contents": "Obsah",
     "contents": "Obsah",
     "create-fulfillment": "Zpracovat",
     "create-fulfillment": "Zpracovat",
     "create-fulfillment-success": "Zpracováno",
     "create-fulfillment-success": "Zpracováno",
     "customer": "Zákazník",
     "customer": "Zákazník",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "Vlastní",
     "filter-custom": "Vlastní",
     "filter-preset-active": "Aktivní",
     "filter-preset-active": "Aktivní",
     "filter-preset-completed": "Uzavřené",
     "filter-preset-completed": "Uzavřené",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "Objednávka zrušena",
     "history-order-cancelled": "Objednávka zrušena",
     "history-order-created": "Objednávka vytvořena",
     "history-order-created": "Objednávka vytvořena",
     "history-order-fulfilled": "Objednávka zpracována",
     "history-order-fulfilled": "Objednávka zpracována",
+    "history-order-modified": "",
     "history-order-transition": "Order transitioned from {from} to {to}",
     "history-order-transition": "Order transitioned from {from} to {to}",
     "history-payment-settled": "Platba vyřízena",
     "history-payment-settled": "Platba vyřízena",
     "history-payment-transition": "Platba #{id} proběhla od {from} pro {to}",
     "history-payment-transition": "Platba #{id} proběhla od {from} pro {to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "Všechny položky zpracovány",
     "line-fulfillment-all": "Všechny položky zpracovány",
     "line-fulfillment-none": "Žádné položky zpracovány",
     "line-fulfillment-none": "Žádné položky zpracovány",
     "line-fulfillment-partial": "{ count } z { total } položek zpracováno",
     "line-fulfillment-partial": "{ count } z { total } položek zpracováno",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "Čistá cena",
     "net-price": "Čistá cena",
+    "note": "",
     "note-is-private": "Interní poznámka",
     "note-is-private": "Interní poznámka",
     "note-only-visible-to-administrators": "Pouze pro adminy",
     "note-only-visible-to-administrators": "Pouze pro adminy",
     "note-visible-to-customer": "Pro adminy i zákazníka",
     "note-visible-to-customer": "Pro adminy i zákazníka",
@@ -607,6 +631,7 @@
     "placed-at": "Datum objednávky",
     "placed-at": "Datum objednávky",
     "placed-at-end": "Objednávky do",
     "placed-at-end": "Objednávky do",
     "placed-at-start": "Objednávky od",
     "placed-at-start": "Objednávky od",
+    "preview-changes": "",
     "product-name": "Název produktu",
     "product-name": "Název produktu",
     "product-sku": "SKU",
     "product-sku": "SKU",
     "promotions-applied": "Promotions applied",
     "promotions-applied": "Promotions applied",
@@ -627,6 +652,7 @@
     "refund-total-error": "Částka refundace musí být mezi {min} a {max}",
     "refund-total-error": "Částka refundace musí být mezi {min} a {max}",
     "refund-with-amount": "Refundovat {amount}",
     "refund-with-amount": "Refundovat {amount}",
     "refunded-count": "{count} {count, plural, one {položka} other {položky}} refundovány",
     "refunded-count": "{count} {count, plural, one {položka} other {položky}} refundovány",
+    "removed-items": "",
     "search-by-order-code": "Hledat na základě kódu objednávky",
     "search-by-order-code": "Hledat na základě kódu objednávky",
     "set-fulfillment-state": "Označit jako {state}",
     "set-fulfillment-state": "Označit jako {state}",
     "settle-payment": "Vypořádání platby",
     "settle-payment": "Vypořádání platby",
@@ -641,7 +667,9 @@
     "state": "Stav",
     "state": "Stav",
     "sub-total": "Mezisoučet",
     "sub-total": "Mezisoučet",
     "successfully-updated-fulfillment": "Zpracování aktualizováno",
     "successfully-updated-fulfillment": "Zpracování aktualizováno",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Košík",
     "adding-items": "Košík",
     "all-orders": "Všechny objednávky",
     "all-orders": "Všechny objednávky",
+    "arranging-additional-payment": "",
     "arranging-payment": "Zřizování platby",
     "arranging-payment": "Zřizování platby",
     "authorized": "Autorizováno",
     "authorized": "Autorizováno",
     "cancelled": "Zrušeno",
     "cancelled": "Zrušeno",
@@ -720,6 +749,7 @@
     "delivered": "Doručeno",
     "delivered": "Doručeno",
     "error": "Chyba",
     "error": "Chyba",
     "failed": "Selhalo",
     "failed": "Selhalo",
+    "modifying": "",
     "partially-delivered": "Částečně doručeno",
     "partially-delivered": "Částečně doručeno",
     "partially-shipped": "Částečně expedováno",
     "partially-shipped": "Částečně expedováno",
     "payment-authorized": "Platba autorizovaná",
     "payment-authorized": "Platba autorizovaná",
@@ -746,4 +776,4 @@
     "job-result": "Výsledek úlohy",
     "job-result": "Výsledek úlohy",
     "job-state": "Stav úlohy"
     "job-state": "Stav úlohy"
   }
   }
-}
+}

+ 31 - 1
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -37,6 +37,7 @@
     "global-settings": "Globale Einstellungen",
     "global-settings": "Globale Einstellungen",
     "job-queue": "Job-Warteschlange",
     "job-queue": "Job-Warteschlange",
     "manage-variants": "Varianten verwalten",
     "manage-variants": "Varianten verwalten",
+    "modifying": "",
     "orders": "Bestellungen",
     "orders": "Bestellungen",
     "payment-methods": "Zahlungsarten",
     "payment-methods": "Zahlungsarten",
     "products": "Produkte",
     "products": "Produkte",
@@ -549,22 +550,31 @@
     "zones": "Zonen"
     "zones": "Zonen"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "Notiz hinzufügen",
     "add-note": "Notiz hinzufügen",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Betrag",
     "amount": "Betrag",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "",
     "billing-address": "",
     "cancel": "Abbrechen",
     "cancel": "Abbrechen",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "Bestellung stornieren",
     "cancel-order": "Bestellung stornieren",
     "cancel-reason-customer-request": "Kundenanfrage",
     "cancel-reason-customer-request": "Kundenanfrage",
     "cancel-reason-not-available": "Nicht verfügbar",
     "cancel-reason-not-available": "Nicht verfügbar",
     "cancel-selected-items": "Auswahl aufheben",
     "cancel-selected-items": "Auswahl aufheben",
     "cancellation-reason": "Stornierungsgrund",
     "cancellation-reason": "Stornierungsgrund",
     "cancelled-order-success": "Bestellung erfolgreich storniert",
     "cancelled-order-success": "Bestellung erfolgreich storniert",
+    "confirm-modifications": "",
     "contents": "Inhalt",
     "contents": "Inhalt",
     "create-fulfillment": "Auftrag ausführen",
     "create-fulfillment": "Auftrag ausführen",
     "create-fulfillment-success": "Auftrag ausgeführt",
     "create-fulfillment-success": "Auftrag ausgeführt",
     "customer": "Kunde",
     "customer": "Kunde",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "Bestellung storniert",
     "history-order-cancelled": "Bestellung storniert",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "Auftrag ausgeführt",
     "history-order-fulfilled": "Auftrag ausgeführt",
+    "history-order-modified": "",
     "history-order-transition": "Auftragsstatus von {from} nach {to}",
     "history-order-transition": "Auftragsstatus von {from} nach {to}",
     "history-payment-settled": "Bezahlt",
     "history-payment-settled": "Bezahlt",
     "history-payment-transition": "Zahlungsstatus (#{id}) von {from} nach {to}",
     "history-payment-transition": "Zahlungsstatus (#{id}) von {from} nach {to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "Alle Positionen ausgeführt",
     "line-fulfillment-all": "Alle Positionen ausgeführt",
     "line-fulfillment-none": "Keine Positionen ausgeführt",
     "line-fulfillment-none": "Keine Positionen ausgeführt",
     "line-fulfillment-partial": "{ count } von { total } Positionen ausgeführt",
     "line-fulfillment-partial": "{ count } von { total } Positionen ausgeführt",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "Nettopreis",
     "net-price": "Nettopreis",
+    "note": "",
     "note-is-private": "Notiz ist privat",
     "note-is-private": "Notiz ist privat",
     "note-only-visible-to-administrators": "Nur für Administratoren sichtbar",
     "note-only-visible-to-administrators": "Nur für Administratoren sichtbar",
     "note-visible-to-customer": "Sichtbar für Administratoren und Kunden",
     "note-visible-to-customer": "Sichtbar für Administratoren und Kunden",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "Produktname",
     "product-name": "Produktname",
     "product-sku": "Artikelnummer",
     "product-sku": "Artikelnummer",
     "promotions-applied": "Aktivierte Werbeaktionen",
     "promotions-applied": "Aktivierte Werbeaktionen",
@@ -627,6 +652,7 @@
     "refund-total-error": "Die Erstattungssumme muss zwischen {min} und {max} liegen",
     "refund-total-error": "Die Erstattungssumme muss zwischen {min} und {max} liegen",
     "refund-with-amount": "Rückzahlung {amount}",
     "refund-with-amount": "Rückzahlung {amount}",
     "refunded-count": "{count} {count, plural, one {Artikel} other {Artikel}} erstattet",
     "refunded-count": "{count} {count, plural, one {Artikel} other {Artikel}} erstattet",
+    "removed-items": "",
     "search-by-order-code": "Suche nach Bestellcode",
     "search-by-order-code": "Suche nach Bestellcode",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "Zahlung durchführen",
     "settle-payment": "Zahlung durchführen",
@@ -641,7 +667,9 @@
     "state": "Status",
     "state": "Status",
     "sub-total": "Zwischensumme",
     "sub-total": "Zwischensumme",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Artikel hinzufügen",
     "adding-items": "Artikel hinzufügen",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "Zahlung einrichten",
     "arranging-payment": "Zahlung einrichten",
     "authorized": "",
     "authorized": "",
     "cancelled": "Storniert",
     "cancelled": "Storniert",
@@ -720,6 +749,7 @@
     "delivered": "Ausgeführt",
     "delivered": "Ausgeführt",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "Teilweise ausgeführt",
     "partially-delivered": "Teilweise ausgeführt",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "Zahlung autorisiert",
     "payment-authorized": "Zahlung autorisiert",
@@ -746,4 +776,4 @@
     "job-result": "Job-Ergebnis",
     "job-result": "Job-Ergebnis",
     "job-state": "Job-Status"
     "job-state": "Job-Status"
   }
   }
-}
+}

+ 31 - 1
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -37,6 +37,7 @@
     "global-settings": "Global settings",
     "global-settings": "Global settings",
     "job-queue": "Job queue",
     "job-queue": "Job queue",
     "manage-variants": "Manage variants",
     "manage-variants": "Manage variants",
+    "modifying": "Modifying",
     "orders": "Orders",
     "orders": "Orders",
     "payment-methods": "Payment methods",
     "payment-methods": "Payment methods",
     "products": "Products",
     "products": "Products",
@@ -549,22 +550,31 @@
     "zones": "Zones"
     "zones": "Zones"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "Add item to order",
     "add-note": "Add note",
     "add-note": "Add note",
+    "add-payment": "Add payment",
+    "add-payment-to-order": "Add payment to order",
+    "add-surcharge": "Add surcharge",
+    "added-items": "Added items",
     "amount": "Amount",
     "amount": "Amount",
     "apply-filters": "Apply filters",
     "apply-filters": "Apply filters",
     "billing-address": "Billing address",
     "billing-address": "Billing address",
     "cancel": "Cancel",
     "cancel": "Cancel",
     "cancel-fulfillment": "Cancel fulfillment",
     "cancel-fulfillment": "Cancel fulfillment",
+    "cancel-modification": "Cancel modification",
     "cancel-order": "Cancel order",
     "cancel-order": "Cancel order",
     "cancel-reason-customer-request": "Customer request",
     "cancel-reason-customer-request": "Customer request",
     "cancel-reason-not-available": "Not available",
     "cancel-reason-not-available": "Not available",
     "cancel-selected-items": "Cancel selected items",
     "cancel-selected-items": "Cancel selected items",
     "cancellation-reason": "Cancellation reason",
     "cancellation-reason": "Cancellation reason",
     "cancelled-order-success": "Successfully cancelled order",
     "cancelled-order-success": "Successfully cancelled order",
+    "confirm-modifications": "Confirm modifications",
     "contents": "Contents",
     "contents": "Contents",
     "create-fulfillment": "Create fulfillment",
     "create-fulfillment": "Create fulfillment",
     "create-fulfillment-success": "Created fulfillment",
     "create-fulfillment-success": "Created fulfillment",
     "customer": "Customer",
     "customer": "Customer",
+    "edit-billing-address": "Edit billing address",
+    "edit-shipping-address": "Edit shipping address",
     "filter-custom": "Custom",
     "filter-custom": "Custom",
     "filter-preset-active": "Active",
     "filter-preset-active": "Active",
     "filter-preset-completed": "Completed",
     "filter-preset-completed": "Completed",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "Order cancelled",
     "history-order-cancelled": "Order cancelled",
     "history-order-created": "Order created",
     "history-order-created": "Order created",
     "history-order-fulfilled": "Order fulfilled",
     "history-order-fulfilled": "Order fulfilled",
+    "history-order-modified": "Order modified",
     "history-order-transition": "Order transitioned from {from} to {to}",
     "history-order-transition": "Order transitioned from {from} to {to}",
     "history-payment-settled": "Payment settled",
     "history-payment-settled": "Payment settled",
     "history-payment-transition": "Payment #{id} transitioned from {from} to {to}",
     "history-payment-transition": "Payment #{id} transitioned from {from} to {to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "All items fulfilled",
     "line-fulfillment-all": "All items fulfilled",
     "line-fulfillment-none": "No items fulfilled",
     "line-fulfillment-none": "No items fulfilled",
     "line-fulfillment-partial": "{ count } of { total } items fulfilled",
     "line-fulfillment-partial": "{ count } of { total } items fulfilled",
+    "modification-adding-items": "Adding {count} {count, plural, one {item} other {items}}",
+    "modification-adding-surcharges": "Adding {count} {count, plural, one {surcharge} other {surcharges}}",
+    "modification-adjusting-lines": "Adjusting {count} {count, plural, one {line} other {lines}}",
+    "modification-not-settled": "Not settled",
+    "modification-recalculate-shipping": "Recalculate shipping",
+    "modification-settled": "Settled",
+    "modification-summary": "Summary of modifications",
+    "modification-updating-billing-address": "Updating billing address",
+    "modification-updating-shipping-address": "Updating shipping address",
+    "modifications": "Modifications",
+    "modify-order": "Modify order",
+    "modify-order-price-difference": "Price difference",
     "net-price": "Net price",
     "net-price": "Net price",
+    "note": "Note",
     "note-is-private": "Note is private",
     "note-is-private": "Note is private",
     "note-only-visible-to-administrators": "Visible to admins only",
     "note-only-visible-to-administrators": "Visible to admins only",
     "note-visible-to-customer": "Visible to admins and customer",
     "note-visible-to-customer": "Visible to admins and customer",
@@ -607,6 +631,7 @@
     "placed-at": "Placed at",
     "placed-at": "Placed at",
     "placed-at-end": "Placed at - until",
     "placed-at-end": "Placed at - until",
     "placed-at-start": "Placed at - from",
     "placed-at-start": "Placed at - from",
+    "preview-changes": "Preview changes",
     "product-name": "Product name",
     "product-name": "Product name",
     "product-sku": "SKU",
     "product-sku": "SKU",
     "promotions-applied": "Promotions applied",
     "promotions-applied": "Promotions applied",
@@ -627,6 +652,7 @@
     "refund-total-error": "Refund total must be between {min} and {max}",
     "refund-total-error": "Refund total must be between {min} and {max}",
     "refund-with-amount": "Refund {amount}",
     "refund-with-amount": "Refund {amount}",
     "refunded-count": "{count} {count, plural, one {item} other {items}} refunded",
     "refunded-count": "{count} {count, plural, one {item} other {items}} refunded",
+    "removed-items": "Removed items",
     "search-by-order-code": "Search by order code",
     "search-by-order-code": "Search by order code",
     "set-fulfillment-state": "Mark as {state}",
     "set-fulfillment-state": "Mark as {state}",
     "settle-payment": "Settle payment",
     "settle-payment": "Settle payment",
@@ -641,7 +667,9 @@
     "state": "State",
     "state": "State",
     "sub-total": "Sub total",
     "sub-total": "Sub total",
     "successfully-updated-fulfillment": "Successfully updated fulfillment",
     "successfully-updated-fulfillment": "Successfully updated fulfillment",
+    "surcharges": "Surcharges",
     "tax-base": "Tax base",
     "tax-base": "Tax base",
+    "tax-description": "Tax description",
     "tax-rate": "Tax rate",
     "tax-rate": "Tax rate",
     "tax-summary": "Tax summary",
     "tax-summary": "Tax summary",
     "tax-total": "Tax total",
     "tax-total": "Tax total",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Adding items",
     "adding-items": "Adding items",
     "all-orders": "All order states",
     "all-orders": "All order states",
+    "arranging-additional-payment": "Arranging additional payment",
     "arranging-payment": "Arranging payment",
     "arranging-payment": "Arranging payment",
     "authorized": "Authorized",
     "authorized": "Authorized",
     "cancelled": "Cancelled",
     "cancelled": "Cancelled",
@@ -720,6 +749,7 @@
     "delivered": "Delivered",
     "delivered": "Delivered",
     "error": "Error",
     "error": "Error",
     "failed": "Failed",
     "failed": "Failed",
+    "modifying": "Modifying",
     "partially-delivered": "Partially delivered",
     "partially-delivered": "Partially delivered",
     "partially-shipped": "Partially shipped",
     "partially-shipped": "Partially shipped",
     "payment-authorized": "Payment authorized",
     "payment-authorized": "Payment authorized",
@@ -746,4 +776,4 @@
     "job-result": "Job result",
     "job-result": "Job result",
     "job-state": "Job state"
     "job-state": "Job state"
   }
   }
-}
+}

+ 30 - 0
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -37,6 +37,7 @@
     "global-settings": "Ajustes globales",
     "global-settings": "Ajustes globales",
     "job-queue": "Cola de trabajos",
     "job-queue": "Cola de trabajos",
     "manage-variants": "Gestionar variantes",
     "manage-variants": "Gestionar variantes",
+    "modifying": "",
     "orders": "Pedidos",
     "orders": "Pedidos",
     "payment-methods": "Métodos de pago",
     "payment-methods": "Métodos de pago",
     "products": "Productos",
     "products": "Productos",
@@ -549,22 +550,31 @@
     "zones": "Zonas"
     "zones": "Zonas"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "",
     "add-note": "",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Precio",
     "amount": "Precio",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "",
     "billing-address": "",
     "cancel": "",
     "cancel": "",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "",
     "cancel-order": "",
     "cancel-reason-customer-request": "",
     "cancel-reason-customer-request": "",
     "cancel-reason-not-available": "",
     "cancel-reason-not-available": "",
     "cancel-selected-items": "",
     "cancel-selected-items": "",
     "cancellation-reason": "",
     "cancellation-reason": "",
     "cancelled-order-success": "",
     "cancelled-order-success": "",
+    "confirm-modifications": "",
     "contents": "",
     "contents": "",
     "create-fulfillment": "",
     "create-fulfillment": "",
     "create-fulfillment-success": "",
     "create-fulfillment-success": "",
     "customer": "Cliente",
     "customer": "Cliente",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "",
     "history-order-cancelled": "",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "",
     "history-order-fulfilled": "",
+    "history-order-modified": "",
     "history-order-transition": "",
     "history-order-transition": "",
     "history-payment-settled": "",
     "history-payment-settled": "",
     "history-payment-transition": "",
     "history-payment-transition": "",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "",
     "line-fulfillment-all": "",
     "line-fulfillment-none": "",
     "line-fulfillment-none": "",
     "line-fulfillment-partial": "",
     "line-fulfillment-partial": "",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "",
     "net-price": "",
+    "note": "",
     "note-is-private": "",
     "note-is-private": "",
     "note-only-visible-to-administrators": "",
     "note-only-visible-to-administrators": "",
     "note-visible-to-customer": "",
     "note-visible-to-customer": "",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "Nombre del producto",
     "product-name": "Nombre del producto",
     "product-sku": "SKU",
     "product-sku": "SKU",
     "promotions-applied": "",
     "promotions-applied": "",
@@ -627,6 +652,7 @@
     "refund-total-error": "",
     "refund-total-error": "",
     "refund-with-amount": "",
     "refund-with-amount": "",
     "refunded-count": "",
     "refunded-count": "",
+    "removed-items": "",
     "search-by-order-code": "",
     "search-by-order-code": "",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "",
     "settle-payment": "",
@@ -641,7 +667,9 @@
     "state": "Estado",
     "state": "Estado",
     "sub-total": "Sub total",
     "sub-total": "Sub total",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "",
     "adding-items": "",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "",
     "arranging-payment": "",
     "authorized": "",
     "authorized": "",
     "cancelled": "",
     "cancelled": "",
@@ -720,6 +749,7 @@
     "delivered": "",
     "delivered": "",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "",
     "partially-delivered": "",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "",
     "payment-authorized": "",

+ 53 - 3
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -37,6 +37,7 @@
     "global-settings": "Paramètres globaux",
     "global-settings": "Paramètres globaux",
     "job-queue": "File d'attente de tâches",
     "job-queue": "File d'attente de tâches",
     "manage-variants": "Gestion des variations",
     "manage-variants": "Gestion des variations",
+    "modifying": "",
     "orders": "Commandes",
     "orders": "Commandes",
     "payment-methods": "Modes de paiement",
     "payment-methods": "Modes de paiement",
     "products": "Produits",
     "products": "Produits",
@@ -57,6 +58,8 @@
     "assign-products-to-channel": "Attribuer les produits au canal",
     "assign-products-to-channel": "Attribuer les produits au canal",
     "assign-to-channel": "Attribuer au canal",
     "assign-to-channel": "Attribuer au canal",
     "assign-to-named-channel": "Attribuer à { channelCode }",
     "assign-to-named-channel": "Attribuer à { channelCode }",
+    "assign-variant-to-channel-success": "",
+    "assign-variants-to-channel": "",
     "channel-price-preview": "Prévisualisation du prix du canal",
     "channel-price-preview": "Prévisualisation du prix du canal",
     "collection-contents": "Contenu de la Collection",
     "collection-contents": "Contenu de la Collection",
     "confirm-adding-options-delete-default-body": "L'ajout d'options à ce produit supprimera les variations existantes par défaut. Voulez-vous continuer ?",
     "confirm-adding-options-delete-default-body": "L'ajout d'options à ce produit supprimera les variations existantes par défaut. Voulez-vous continuer ?",
@@ -99,6 +102,8 @@
     "no-selection": "Pas de sélection",
     "no-selection": "Pas de sélection",
     "notify-remove-product-from-channel-error": "retrait du produit du canal échoué",
     "notify-remove-product-from-channel-error": "retrait du produit du canal échoué",
     "notify-remove-product-from-channel-success": "Retrait du produit du canal réussi",
     "notify-remove-product-from-channel-success": "Retrait du produit du canal réussi",
+    "notify-remove-variant-from-channel-error": "",
+    "notify-remove-variant-from-channel-success": "",
     "option": "Option",
     "option": "Option",
     "option-name": "Nom de l'option",
     "option-name": "Nom de l'option",
     "option-values": "Valeurs de l'option",
     "option-values": "Valeurs de l'option",
@@ -121,6 +126,7 @@
     "remove-from-channel": "Retirer du canal",
     "remove-from-channel": "Retirer du canal",
     "remove-option": "Retirer l'option",
     "remove-option": "Retirer l'option",
     "remove-product-from-channel": "Retirer le produit du canal",
     "remove-product-from-channel": "Retirer le produit du canal",
+    "remove-product-variant-from-channel": "",
     "search-for-term": "Chercher le terme",
     "search-for-term": "Chercher le terme",
     "search-product-name-or-code": "Chercher par nom de produit ou code",
     "search-product-name-or-code": "Chercher par nom de produit ou code",
     "sku": "UGS",
     "sku": "UGS",
@@ -287,6 +293,16 @@
     "verified": "Verifié",
     "verified": "Verifié",
     "view-group-members": "Voir les membres du groupe"
     "view-group-members": "Voir les membres du groupe"
   },
   },
+  "dashboard": {
+    "add-widget": "",
+    "latest-orders": "",
+    "orders-summary": "",
+    "remove-widget": "",
+    "total-order-value": "",
+    "total-orders": "",
+    "widget-resize": "",
+    "widget-width": ""
+  },
   "datetime": {
   "datetime": {
     "ago-days": "Il y a {count, plural, one {1 jour} other {{count} jours}}",
     "ago-days": "Il y a {count, plural, one {1 jour} other {{count} jours}}",
     "ago-hours": "Il y a {count, plural, one {1 h} other {{count} h}}",
     "ago-hours": "Il y a {count, plural, one {1 h} other {{count} h}}",
@@ -534,22 +550,31 @@
     "zones": "Zones"
     "zones": "Zones"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "Ajouter note",
     "add-note": "Ajouter note",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Quantité",
     "amount": "Quantité",
     "apply-filters": "Applique les filtres",
     "apply-filters": "Applique les filtres",
     "billing-address": "Adresse de facturation",
     "billing-address": "Adresse de facturation",
     "cancel": "Annuler",
     "cancel": "Annuler",
     "cancel-fulfillment": "Annuler préparation",
     "cancel-fulfillment": "Annuler préparation",
+    "cancel-modification": "",
     "cancel-order": "Annuler la commande",
     "cancel-order": "Annuler la commande",
     "cancel-reason-customer-request": "Demande du client",
     "cancel-reason-customer-request": "Demande du client",
     "cancel-reason-not-available": "Pas disponible",
     "cancel-reason-not-available": "Pas disponible",
     "cancel-selected-items": "Annuler les articles selectionnés",
     "cancel-selected-items": "Annuler les articles selectionnés",
     "cancellation-reason": "Raison de l'annulation",
     "cancellation-reason": "Raison de l'annulation",
     "cancelled-order-success": "Commande annulée",
     "cancelled-order-success": "Commande annulée",
+    "confirm-modifications": "",
     "contents": "Contenu",
     "contents": "Contenu",
     "create-fulfillment": "Créer préparation",
     "create-fulfillment": "Créer préparation",
     "create-fulfillment-success": "Préparation créée",
     "create-fulfillment-success": "Préparation créée",
     "customer": "Client",
     "customer": "Client",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "Personnalisé",
     "filter-custom": "Personnalisé",
     "filter-preset-active": "Active",
     "filter-preset-active": "Active",
     "filter-preset-completed": "Terminée",
     "filter-preset-completed": "Terminée",
@@ -569,6 +594,7 @@
     "history-order-cancelled": "Commande annulée",
     "history-order-cancelled": "Commande annulée",
     "history-order-created": "Commande créée",
     "history-order-created": "Commande créée",
     "history-order-fulfilled": "Commande préparée",
     "history-order-fulfilled": "Commande préparée",
+    "history-order-modified": "",
     "history-order-transition": "Commande transférée de {from} à {to}",
     "history-order-transition": "Commande transférée de {from} à {to}",
     "history-payment-settled": "paiement validé",
     "history-payment-settled": "paiement validé",
     "history-payment-transition": "paiement #{id} transféré de {from} à {to}",
     "history-payment-transition": "paiement #{id} transféré de {from} à {to}",
@@ -577,7 +603,20 @@
     "line-fulfillment-all": "Tous les articles préparés",
     "line-fulfillment-all": "Tous les articles préparés",
     "line-fulfillment-none": "Aucun article préparé",
     "line-fulfillment-none": "Aucun article préparé",
     "line-fulfillment-partial": "{ count } sur { total } {count, plural, one {article préparé} other {articles préparés}}",
     "line-fulfillment-partial": "{ count } sur { total } {count, plural, one {article préparé} other {articles préparés}}",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "Prix net",
     "net-price": "Prix net",
+    "note": "",
     "note-is-private": "La note est privée",
     "note-is-private": "La note est privée",
     "note-only-visible-to-administrators": "Visible par les admins uniquement",
     "note-only-visible-to-administrators": "Visible par les admins uniquement",
     "note-visible-to-customer": "Visible par les admins et le client",
     "note-visible-to-customer": "Visible par les admins et le client",
@@ -592,26 +631,28 @@
     "placed-at": "Placé à",
     "placed-at": "Placé à",
     "placed-at-end": "Placé à - jusqu'à",
     "placed-at-end": "Placé à - jusqu'à",
     "placed-at-start": "Placé à - depuis",
     "placed-at-start": "Placé à - depuis",
+    "preview-changes": "",
     "product-name": "Nom du produit",
     "product-name": "Nom du produit",
     "product-sku": "UGS",
     "product-sku": "UGS",
     "promotions-applied": "Promotions utilisées",
     "promotions-applied": "Promotions utilisées",
+    "prorated-unit-price": "",
     "quantity": "Quantité",
     "quantity": "Quantité",
     "refund": "Remboursement",
     "refund": "Remboursement",
     "refund-adjustment": "Ajustement",
     "refund-adjustment": "Ajustement",
     "refund-and-cancel-order": "Rembourser et annuler la commande",
     "refund-and-cancel-order": "Rembourser et annuler la commande",
+    "refund-cancellation-reason": "",
+    "refund-cancellation-reason-required": "",
     "refund-metadata": "Métadonnées de rembousement",
     "refund-metadata": "Métadonnées de rembousement",
-    "refund-order": "Rembourser la commande",
     "refund-order-success": "Commande remboursée",
     "refund-order-success": "Commande remboursée",
     "refund-reason": "Raison du remboursement",
     "refund-reason": "Raison du remboursement",
     "refund-reason-customer-request": "Demande du client",
     "refund-reason-customer-request": "Demande du client",
     "refund-reason-not-available": "Non disponible",
     "refund-reason-not-available": "Non disponible",
-    "refund-reason-required": "La raison du remboursement est requise",
     "refund-shipping": "Rembourser la livraison",
     "refund-shipping": "Rembourser la livraison",
     "refund-total": "Remboursement total",
     "refund-total": "Remboursement total",
     "refund-total-error": "Le remboursement total doit être entre {min} et {max}",
     "refund-total-error": "Le remboursement total doit être entre {min} et {max}",
     "refund-with-amount": "Rembourser {amount}",
     "refund-with-amount": "Rembourser {amount}",
     "refunded-count": "{count} {count, plural, one {article remboursé} other {articles remboursés}}",
     "refunded-count": "{count} {count, plural, one {article remboursé} other {articles remboursés}}",
-    "return-to-stock": "Retour dans le stock",
+    "removed-items": "",
     "search-by-order-code": "Chercher par numéro de commande",
     "search-by-order-code": "Chercher par numéro de commande",
     "set-fulfillment-state": "Marquer {state}",
     "set-fulfillment-state": "Marquer {state}",
     "settle-payment": "Régler le paiement",
     "settle-payment": "Régler le paiement",
@@ -626,6 +667,12 @@
     "state": "Etat",
     "state": "Etat",
     "sub-total": "Sous total",
     "sub-total": "Sous total",
     "successfully-updated-fulfillment": "Préparation mise à jour",
     "successfully-updated-fulfillment": "Préparation mise à jour",
+    "surcharges": "",
+    "tax-base": "",
+    "tax-description": "",
+    "tax-rate": "",
+    "tax-summary": "",
+    "tax-total": "",
     "total": "Total",
     "total": "Total",
     "tracking-code": "Numéro de suivi",
     "tracking-code": "Numéro de suivi",
     "transaction-id": "Numéro de transaction",
     "transaction-id": "Numéro de transaction",
@@ -659,6 +706,7 @@
     "email-address": "Adresse email",
     "email-address": "Adresse email",
     "filter-by-member-name": "Filtrer par pays",
     "filter-by-member-name": "Filtrer par pays",
     "first-name": "Prénom",
     "first-name": "Prénom",
+    "fulfillment-handler": "",
     "global-out-of-stock-threshold": "Limite de rupture de stock globale",
     "global-out-of-stock-threshold": "Limite de rupture de stock globale",
     "global-out-of-stock-threshold-tooltip": "Régler le niveau de stock à partir duquel la variante est considéré en rupture de stock. Renseigner une valeur négative permet d'accepter des commandes en attente. La valeur peut être régler individuellement par variante de produit.",
     "global-out-of-stock-threshold-tooltip": "Régler le niveau de stock à partir duquel la variante est considéré en rupture de stock. Renseigner une valeur négative permet d'accepter des commandes en attente. La valeur peut être régler individuellement par variante de produit.",
     "last-name": "Nom",
     "last-name": "Nom",
@@ -692,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Ajout d'articles",
     "adding-items": "Ajout d'articles",
     "all-orders": "Tous les états de commande",
     "all-orders": "Tous les états de commande",
+    "arranging-additional-payment": "",
     "arranging-payment": "Paiement en cours",
     "arranging-payment": "Paiement en cours",
     "authorized": "Autorisé",
     "authorized": "Autorisé",
     "cancelled": "Annulé",
     "cancelled": "Annulé",
@@ -700,6 +749,7 @@
     "delivered": "Livré",
     "delivered": "Livré",
     "error": "Erreur",
     "error": "Erreur",
     "failed": "Echec",
     "failed": "Echec",
+    "modifying": "",
     "partially-delivered": "Partiellement livré",
     "partially-delivered": "Partiellement livré",
     "partially-shipped": "Partiellement expédié",
     "partially-shipped": "Partiellement expédié",
     "payment-authorized": "Paiement autorisé",
     "payment-authorized": "Paiement autorisé",

+ 31 - 1
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -37,6 +37,7 @@
     "global-settings": "Ustawienia globalne",
     "global-settings": "Ustawienia globalne",
     "job-queue": "Kolejka zadań",
     "job-queue": "Kolejka zadań",
     "manage-variants": "Zarządzaj wariantami",
     "manage-variants": "Zarządzaj wariantami",
+    "modifying": "",
     "orders": "Zamówienia",
     "orders": "Zamówienia",
     "payment-methods": "Metody płatności",
     "payment-methods": "Metody płatności",
     "products": "Produkty",
     "products": "Produkty",
@@ -549,22 +550,31 @@
     "zones": ""
     "zones": ""
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "Dodaj notatke",
     "add-note": "Dodaj notatke",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Ilość",
     "amount": "Ilość",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "",
     "billing-address": "",
     "cancel": "Anuluj",
     "cancel": "Anuluj",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "Anuluj zamówienie",
     "cancel-order": "Anuluj zamówienie",
     "cancel-reason-customer-request": "Prośba klienta",
     "cancel-reason-customer-request": "Prośba klienta",
     "cancel-reason-not-available": "Niedostępny",
     "cancel-reason-not-available": "Niedostępny",
     "cancel-selected-items": "Anuluj zaznaczone",
     "cancel-selected-items": "Anuluj zaznaczone",
     "cancellation-reason": "Powód anulowania",
     "cancellation-reason": "Powód anulowania",
     "cancelled-order-success": "Pomyślnie anulowano zamówienie",
     "cancelled-order-success": "Pomyślnie anulowano zamówienie",
+    "confirm-modifications": "",
     "contents": "Zawartość",
     "contents": "Zawartość",
     "create-fulfillment": "Utwórz wypełnienie",
     "create-fulfillment": "Utwórz wypełnienie",
     "create-fulfillment-success": "Utworzono wypełnienie pomyślnie",
     "create-fulfillment-success": "Utworzono wypełnienie pomyślnie",
     "customer": "Klient",
     "customer": "Klient",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "Zamówienie anulowane",
     "history-order-cancelled": "Zamówienie anulowane",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "Zamówienie zrealizowane",
     "history-order-fulfilled": "Zamówienie zrealizowane",
+    "history-order-modified": "",
     "history-order-transition": "Zamówienie wysłane z {from} do {to}",
     "history-order-transition": "Zamówienie wysłane z {from} do {to}",
     "history-payment-settled": "Opłacono",
     "history-payment-settled": "Opłacono",
     "history-payment-transition": "Płatność #{id} wysłana z {from} do {to}",
     "history-payment-transition": "Płatność #{id} wysłana z {from} do {to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "Wrzystkie zamówienia zrealizowane",
     "line-fulfillment-all": "Wrzystkie zamówienia zrealizowane",
     "line-fulfillment-none": "Brak zrealizowany zamówień",
     "line-fulfillment-none": "Brak zrealizowany zamówień",
     "line-fulfillment-partial": "{ count } z { total } zrealizowanych",
     "line-fulfillment-partial": "{ count } z { total } zrealizowanych",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "Cena netto",
     "net-price": "Cena netto",
+    "note": "",
     "note-is-private": "Notatka jest prywatna",
     "note-is-private": "Notatka jest prywatna",
     "note-only-visible-to-administrators": "Widoczne tylko dla administratora",
     "note-only-visible-to-administrators": "Widoczne tylko dla administratora",
     "note-visible-to-customer": "Widoczne dla administratora i klienta",
     "note-visible-to-customer": "Widoczne dla administratora i klienta",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "Nazwa produktu",
     "product-name": "Nazwa produktu",
     "product-sku": "SKU",
     "product-sku": "SKU",
     "promotions-applied": "Zastosowane promocje",
     "promotions-applied": "Zastosowane promocje",
@@ -627,6 +652,7 @@
     "refund-total-error": "Wartość zwrotu musi być pomiędzy {min} i {max}",
     "refund-total-error": "Wartość zwrotu musi być pomiędzy {min} i {max}",
     "refund-with-amount": "Zwróć {amount}",
     "refund-with-amount": "Zwróć {amount}",
     "refunded-count": "{count} {count, plural, one {zamówienie} other {zamówień}} zwrócono",
     "refunded-count": "{count} {count, plural, one {zamówienie} other {zamówień}} zwrócono",
+    "removed-items": "",
     "search-by-order-code": "Szukaj po numerze zamówienia",
     "search-by-order-code": "Szukaj po numerze zamówienia",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "Rozlicz płatność",
     "settle-payment": "Rozlicz płatność",
@@ -641,7 +667,9 @@
     "state": "Status",
     "state": "Status",
     "sub-total": "Sub total",
     "sub-total": "Sub total",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Dodawanie",
     "adding-items": "Dodawanie",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "Oczekiwanie na płatność",
     "arranging-payment": "Oczekiwanie na płatność",
     "authorized": "",
     "authorized": "",
     "cancelled": "Anulowano",
     "cancelled": "Anulowano",
@@ -720,6 +749,7 @@
     "delivered": "Zrealizowano",
     "delivered": "Zrealizowano",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "Częściowo zrealizowano",
     "partially-delivered": "Częściowo zrealizowano",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "Płatność zaakceptowana",
     "payment-authorized": "Płatność zaakceptowana",
@@ -746,4 +776,4 @@
     "job-result": "Rezultat zlecenia",
     "job-result": "Rezultat zlecenia",
     "job-state": "Status zlecenia"
     "job-state": "Status zlecenia"
   }
   }
-}
+}

+ 31 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -37,6 +37,7 @@
     "global-settings": "Configurações globais",
     "global-settings": "Configurações globais",
     "job-queue": "Fila de tabalho",
     "job-queue": "Fila de tabalho",
     "manage-variants": "Gerenciamento de variantes",
     "manage-variants": "Gerenciamento de variantes",
+    "modifying": "",
     "orders": "Pedidos",
     "orders": "Pedidos",
     "payment-methods": "Métodos de pagamentos",
     "payment-methods": "Métodos de pagamentos",
     "products": "Produtos",
     "products": "Produtos",
@@ -549,22 +550,31 @@
     "zones": "Zonas"
     "zones": "Zonas"
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "Adicionar nota",
     "add-note": "Adicionar nota",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "Total",
     "amount": "Total",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "Endereço de cobrança",
     "billing-address": "Endereço de cobrança",
     "cancel": "Cancelar",
     "cancel": "Cancelar",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "Cancelar Pedido",
     "cancel-order": "Cancelar Pedido",
     "cancel-reason-customer-request": "Pedido do cliente",
     "cancel-reason-customer-request": "Pedido do cliente",
     "cancel-reason-not-available": "Não disponível",
     "cancel-reason-not-available": "Não disponível",
     "cancel-selected-items": "Cancelar itens selecionados",
     "cancel-selected-items": "Cancelar itens selecionados",
     "cancellation-reason": "Motivo do cancelamento",
     "cancellation-reason": "Motivo do cancelamento",
     "cancelled-order-success": "Pedido cancelado com sucesso",
     "cancelled-order-success": "Pedido cancelado com sucesso",
+    "confirm-modifications": "",
     "contents": "Conteúdo",
     "contents": "Conteúdo",
     "create-fulfillment": "Criar a execução",
     "create-fulfillment": "Criar a execução",
     "create-fulfillment-success": "Execução criada",
     "create-fulfillment-success": "Execução criada",
     "customer": "Cliente",
     "customer": "Cliente",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "Pedido cancelado",
     "history-order-cancelled": "Pedido cancelado",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "Pedido realizado",
     "history-order-fulfilled": "Pedido realizado",
+    "history-order-modified": "",
     "history-order-transition": "Pedido transferido de {from} para {to}",
     "history-order-transition": "Pedido transferido de {from} para {to}",
     "history-payment-settled": "Pagamento concluído",
     "history-payment-settled": "Pagamento concluído",
     "history-payment-transition": "Pagamento #{id} transferido de {from} para {to}",
     "history-payment-transition": "Pagamento #{id} transferido de {from} para {to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "Todos os itens executados",
     "line-fulfillment-all": "Todos os itens executados",
     "line-fulfillment-none": "Nenhum ítem executado",
     "line-fulfillment-none": "Nenhum ítem executado",
     "line-fulfillment-partial": "{ count } of { total } itens executados",
     "line-fulfillment-partial": "{ count } of { total } itens executados",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "Preço líquido",
     "net-price": "Preço líquido",
+    "note": "",
     "note-is-private": "Nota é privada",
     "note-is-private": "Nota é privada",
     "note-only-visible-to-administrators": "Visível somente para administradores",
     "note-only-visible-to-administrators": "Visível somente para administradores",
     "note-visible-to-customer": "Visível para administradores e clientes",
     "note-visible-to-customer": "Visível para administradores e clientes",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "Nome do produto",
     "product-name": "Nome do produto",
     "product-sku": "SKU",
     "product-sku": "SKU",
     "promotions-applied": "Promoções aplicadas",
     "promotions-applied": "Promoções aplicadas",
@@ -627,6 +652,7 @@
     "refund-total-error": "Total do reembolso deve ser entre {min} e {max}",
     "refund-total-error": "Total do reembolso deve ser entre {min} e {max}",
     "refund-with-amount": "Reembolso {amount}",
     "refund-with-amount": "Reembolso {amount}",
     "refunded-count": "{count} {count, plural, one {item} other {items}} reembolsado",
     "refunded-count": "{count} {count, plural, one {item} other {items}} reembolsado",
+    "removed-items": "",
     "search-by-order-code": "Buscar por código do pedido",
     "search-by-order-code": "Buscar por código do pedido",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "Liquidar pagamento",
     "settle-payment": "Liquidar pagamento",
@@ -641,7 +667,9 @@
     "state": "Estado",
     "state": "Estado",
     "sub-total": "Subtotal",
     "sub-total": "Subtotal",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "Criando itens",
     "adding-items": "Criando itens",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "Organização de pagamento",
     "arranging-payment": "Organização de pagamento",
     "authorized": "",
     "authorized": "",
     "cancelled": "Cancelado",
     "cancelled": "Cancelado",
@@ -720,6 +749,7 @@
     "delivered": "Realizado",
     "delivered": "Realizado",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "Parcialmente realizado",
     "partially-delivered": "Parcialmente realizado",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "Pagamento autorizado",
     "payment-authorized": "Pagamento autorizado",
@@ -746,4 +776,4 @@
     "job-result": "Resultado do trabalho",
     "job-result": "Resultado do trabalho",
     "job-state": "Estado do trabalho"
     "job-state": "Estado do trabalho"
   }
   }
-}
+}

+ 30 - 0
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -37,6 +37,7 @@
     "global-settings": "语言设置",
     "global-settings": "语言设置",
     "job-queue": "",
     "job-queue": "",
     "manage-variants": "商品规格管理",
     "manage-variants": "商品规格管理",
+    "modifying": "",
     "orders": "订单管理",
     "orders": "订单管理",
     "payment-methods": "支付管理",
     "payment-methods": "支付管理",
     "products": "商品列表",
     "products": "商品列表",
@@ -549,22 +550,31 @@
     "zones": ""
     "zones": ""
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "添加备注",
     "add-note": "添加备注",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "金额",
     "amount": "金额",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "",
     "billing-address": "",
     "cancel": "取消",
     "cancel": "取消",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "取消订单",
     "cancel-order": "取消订单",
     "cancel-reason-customer-request": "客户要求",
     "cancel-reason-customer-request": "客户要求",
     "cancel-reason-not-available": "产品无库存",
     "cancel-reason-not-available": "产品无库存",
     "cancel-selected-items": "取消已选",
     "cancel-selected-items": "取消已选",
     "cancellation-reason": "取消原因",
     "cancellation-reason": "取消原因",
     "cancelled-order-success": "订单成功取消",
     "cancelled-order-success": "订单成功取消",
+    "confirm-modifications": "",
     "contents": "具体内容",
     "contents": "具体内容",
     "create-fulfillment": "确认配货",
     "create-fulfillment": "确认配货",
     "create-fulfillment-success": "确认配货成功",
     "create-fulfillment-success": "确认配货成功",
     "customer": "客户",
     "customer": "客户",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "订单已取消",
     "history-order-cancelled": "订单已取消",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "订单已配货",
     "history-order-fulfilled": "订单已配货",
+    "history-order-modified": "",
     "history-order-transition": "订单状态从{from}更新至{to}",
     "history-order-transition": "订单状态从{from}更新至{to}",
     "history-payment-settled": "已结算付款",
     "history-payment-settled": "已结算付款",
     "history-payment-transition": "付款交易 #{id} 状态从{from}更新至{to}",
     "history-payment-transition": "付款交易 #{id} 状态从{from}更新至{to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "订单已全部配货完成",
     "line-fulfillment-all": "订单已全部配货完成",
     "line-fulfillment-none": "无订单配货记录",
     "line-fulfillment-none": "无订单配货记录",
     "line-fulfillment-partial": "总共{ total }个订单项,{ count }个已配货",
     "line-fulfillment-partial": "总共{ total }个订单项,{ count }个已配货",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "净价",
     "net-price": "净价",
+    "note": "",
     "note-is-private": "隐藏备注",
     "note-is-private": "隐藏备注",
     "note-only-visible-to-administrators": "仅管理员可见",
     "note-only-visible-to-administrators": "仅管理员可见",
     "note-visible-to-customer": "管理员及客户可见",
     "note-visible-to-customer": "管理员及客户可见",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "产品名称",
     "product-name": "产品名称",
     "product-sku": "库存编码",
     "product-sku": "库存编码",
     "promotions-applied": "已使用代金券",
     "promotions-applied": "已使用代金券",
@@ -627,6 +652,7 @@
     "refund-total-error": "退款总计必须大于{min}并少于{max}之间",
     "refund-total-error": "退款总计必须大于{min}并少于{max}之间",
     "refund-with-amount": "退款金额{amount}",
     "refund-with-amount": "退款金额{amount}",
     "refunded-count": "{count}个商品已退款",
     "refunded-count": "{count}个商品已退款",
+    "removed-items": "",
     "search-by-order-code": "输入要搜索的订单编号",
     "search-by-order-code": "输入要搜索的订单编号",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "结算付款",
     "settle-payment": "结算付款",
@@ -641,7 +667,9 @@
     "state": "状态",
     "state": "状态",
     "sub-total": "小计金额",
     "sub-total": "小计金额",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "正在选择商品",
     "adding-items": "正在选择商品",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "正在付款",
     "arranging-payment": "正在付款",
     "authorized": "",
     "authorized": "",
     "cancelled": "已取消",
     "cancelled": "已取消",
@@ -720,6 +749,7 @@
     "delivered": "已完成",
     "delivered": "已完成",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "部分配货",
     "partially-delivered": "部分配货",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "已授权支付",
     "payment-authorized": "已授权支付",

+ 30 - 0
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -37,6 +37,7 @@
     "global-settings": "語言",
     "global-settings": "語言",
     "job-queue": "",
     "job-queue": "",
     "manage-variants": "商品規格管理",
     "manage-variants": "商品規格管理",
+    "modifying": "",
     "orders": "訂單管理",
     "orders": "訂單管理",
     "payment-methods": "支付方式",
     "payment-methods": "支付方式",
     "products": "商品",
     "products": "商品",
@@ -549,22 +550,31 @@
     "zones": ""
     "zones": ""
   },
   },
   "order": {
   "order": {
+    "add-item-to-order": "",
     "add-note": "新增備注",
     "add-note": "新增備注",
+    "add-payment": "",
+    "add-payment-to-order": "",
+    "add-surcharge": "",
+    "added-items": "",
     "amount": "金額",
     "amount": "金額",
     "apply-filters": "",
     "apply-filters": "",
     "billing-address": "",
     "billing-address": "",
     "cancel": "取消",
     "cancel": "取消",
     "cancel-fulfillment": "",
     "cancel-fulfillment": "",
+    "cancel-modification": "",
     "cancel-order": "取消訂單",
     "cancel-order": "取消訂單",
     "cancel-reason-customer-request": "客户要求",
     "cancel-reason-customer-request": "客户要求",
     "cancel-reason-not-available": "產品無庫存",
     "cancel-reason-not-available": "產品無庫存",
     "cancel-selected-items": "取消已選",
     "cancel-selected-items": "取消已選",
     "cancellation-reason": "取消原因",
     "cancellation-reason": "取消原因",
     "cancelled-order-success": "訂單取消成功",
     "cancelled-order-success": "訂單取消成功",
+    "confirm-modifications": "",
     "contents": "内容",
     "contents": "内容",
     "create-fulfillment": "確認配貨",
     "create-fulfillment": "確認配貨",
     "create-fulfillment-success": "確認配貨成功",
     "create-fulfillment-success": "確認配貨成功",
     "customer": "客户",
     "customer": "客户",
+    "edit-billing-address": "",
+    "edit-shipping-address": "",
     "filter-custom": "",
     "filter-custom": "",
     "filter-preset-active": "",
     "filter-preset-active": "",
     "filter-preset-completed": "",
     "filter-preset-completed": "",
@@ -584,6 +594,7 @@
     "history-order-cancelled": "訂單已取消",
     "history-order-cancelled": "訂單已取消",
     "history-order-created": "",
     "history-order-created": "",
     "history-order-fulfilled": "訂單已配貨",
     "history-order-fulfilled": "訂單已配貨",
+    "history-order-modified": "",
     "history-order-transition": "訂單狀態從{from}更新至{to}",
     "history-order-transition": "訂單狀態從{from}更新至{to}",
     "history-payment-settled": "已結算付款",
     "history-payment-settled": "已結算付款",
     "history-payment-transition": "付款交易 #{id} 狀態從{from}更新至{to}",
     "history-payment-transition": "付款交易 #{id} 狀態從{from}更新至{to}",
@@ -592,7 +603,20 @@
     "line-fulfillment-all": "訂單已全部配貨完成",
     "line-fulfillment-all": "訂單已全部配貨完成",
     "line-fulfillment-none": "無訂單配貨記錄",
     "line-fulfillment-none": "無訂單配貨記錄",
     "line-fulfillment-partial": "總共{ total }個訂單項,{ count }個已配貨",
     "line-fulfillment-partial": "總共{ total }個訂單項,{ count }個已配貨",
+    "modification-adding-items": "",
+    "modification-adding-surcharges": "",
+    "modification-adjusting-lines": "",
+    "modification-not-settled": "",
+    "modification-recalculate-shipping": "",
+    "modification-settled": "",
+    "modification-summary": "",
+    "modification-updating-billing-address": "",
+    "modification-updating-shipping-address": "",
+    "modifications": "",
+    "modify-order": "",
+    "modify-order-price-difference": "",
     "net-price": "淨價",
     "net-price": "淨價",
+    "note": "",
     "note-is-private": "隱藏備注",
     "note-is-private": "隱藏備注",
     "note-only-visible-to-administrators": "僅管理員可瀏覽",
     "note-only-visible-to-administrators": "僅管理員可瀏覽",
     "note-visible-to-customer": "管理員及客户可瀏覽",
     "note-visible-to-customer": "管理員及客户可瀏覽",
@@ -607,6 +631,7 @@
     "placed-at": "",
     "placed-at": "",
     "placed-at-end": "",
     "placed-at-end": "",
     "placed-at-start": "",
     "placed-at-start": "",
+    "preview-changes": "",
     "product-name": "產品名稱",
     "product-name": "產品名稱",
     "product-sku": "庫存編碼",
     "product-sku": "庫存編碼",
     "promotions-applied": "已使用優惠券",
     "promotions-applied": "已使用優惠券",
@@ -627,6 +652,7 @@
     "refund-total-error": "退款總計必須大於{min}並少遊{max}之間",
     "refund-total-error": "退款總計必須大於{min}並少遊{max}之間",
     "refund-with-amount": "退款金額{amount}",
     "refund-with-amount": "退款金額{amount}",
     "refunded-count": "{count}個商品已退款",
     "refunded-count": "{count}個商品已退款",
+    "removed-items": "",
     "search-by-order-code": "輸入要搜索的訂單編號",
     "search-by-order-code": "輸入要搜索的訂單編號",
     "set-fulfillment-state": "",
     "set-fulfillment-state": "",
     "settle-payment": "結算付款",
     "settle-payment": "結算付款",
@@ -641,7 +667,9 @@
     "state": "狀態",
     "state": "狀態",
     "sub-total": "小計金額",
     "sub-total": "小計金額",
     "successfully-updated-fulfillment": "",
     "successfully-updated-fulfillment": "",
+    "surcharges": "",
     "tax-base": "",
     "tax-base": "",
+    "tax-description": "",
     "tax-rate": "",
     "tax-rate": "",
     "tax-summary": "",
     "tax-summary": "",
     "tax-total": "",
     "tax-total": "",
@@ -712,6 +740,7 @@
   "state": {
   "state": {
     "adding-items": "正在選擇商品",
     "adding-items": "正在選擇商品",
     "all-orders": "",
     "all-orders": "",
+    "arranging-additional-payment": "",
     "arranging-payment": "正在付款",
     "arranging-payment": "正在付款",
     "authorized": "",
     "authorized": "",
     "cancelled": "已取消",
     "cancelled": "已取消",
@@ -720,6 +749,7 @@
     "delivered": "已完成",
     "delivered": "已完成",
     "error": "",
     "error": "",
     "failed": "",
     "failed": "",
+    "modifying": "",
     "partially-delivered": "部分配貨",
     "partially-delivered": "部分配貨",
     "partially-shipped": "",
     "partially-shipped": "",
     "payment-authorized": "已授權支付",
     "payment-authorized": "已授權支付",

+ 1 - 0
packages/admin-ui/src/lib/static/styles/theme/_forms.scss

@@ -93,6 +93,7 @@ select {
 
 
 .ng-select .ng-select-container .ng-value-container {
 .ng-select .ng-select-container .ng-value-container {
     padding-top: 0;
     padding-top: 0;
+    min-width: 60px;
     .ng-value {
     .ng-value {
         margin: 0 6px 0 0;
         margin: 0 6px 0 0;
     }
     }