Browse Source

refactor(admin-ui): Update handling of variant prices to latest API

Michael Bromley 5 years ago
parent
commit
b69dd489ec

+ 1 - 0
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html

@@ -216,6 +216,7 @@
                     <vdr-product-variants-list
                         *ngIf="variantDisplayMode === 'card'"
                         [variants]="variants$ | async"
+                        [channelPriceIncludesTax]="channelPriceIncludesTax$ | async"
                         [facets]="facets$ | async"
                         [optionGroups]="product.optionGroups"
                         [productVariantsFormArray]="detailForm.get('variants')"

+ 11 - 4
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.ts

@@ -56,7 +56,6 @@ export interface VariantFormValue {
     sku: string;
     name: string;
     price: number;
-    priceIncludesTax: boolean;
     priceWithTax: number;
     taxCategoryId: string;
     stockOnHand: number;
@@ -99,6 +98,7 @@ export class ProductDetailComponent
     selectedVariantIds: string[] = [];
     variantDisplayMode: 'card' | 'table' = 'card';
     createVariantsConfig: CreateProductVariantsConfig = { groups: [], variants: [] };
+    channelPriceIncludesTax$: Observable<boolean>;
 
     constructor(
         route: ActivatedRoute,
@@ -183,6 +183,11 @@ export class ProductDetailComponent
 
         this.facetValues$ = merge(productFacetValues$, formChangeFacetValues$);
         this.productChannels$ = this.product$.pipe(map(p => p.channels));
+        this.channelPriceIncludesTax$ = this.dataService.settings
+            .getActiveChannel('cache-first')
+            .refetchOnChannelChange()
+            .mapStream(data => data.activeChannel.pricesIncludeTax)
+            .pipe(shareReplay(1));
     }
 
     ngOnDestroy() {
@@ -455,10 +460,10 @@ export class ProductDetailComponent
     }
 
     save() {
-        combineLatest(this.product$, this.languageCode$)
+        combineLatest(this.product$, this.languageCode$, this.channelPriceIncludesTax$)
             .pipe(
                 take(1),
-                mergeMap(([product, languageCode]) => {
+                mergeMap(([product, languageCode, priceIncludesTax]) => {
                     const productGroup = this.getProductFormGroup();
                     let productInput: UpdateProductInput | undefined;
                     let variantsInput: UpdateProductVariantInput[] | undefined;
@@ -476,6 +481,7 @@ export class ProductDetailComponent
                             product,
                             variantsArray as FormArray,
                             languageCode,
+                            priceIncludesTax,
                         );
                     }
 
@@ -546,7 +552,6 @@ export class ProductDetailComponent
                 sku: variant.sku,
                 name: variantTranslation ? variantTranslation.name : '',
                 price: variant.price,
-                priceIncludesTax: variant.priceIncludesTax,
                 priceWithTax: variant.priceWithTax,
                 taxCategoryId: variant.taxCategory.id,
                 stockOnHand: variant.stockOnHand,
@@ -627,6 +632,7 @@ export class ProductDetailComponent
         product: ProductWithVariants.Fragment,
         variantsFormArray: FormArray,
         languageCode: LanguageCode,
+        priceIncludesTax: boolean,
     ): UpdateProductVariantInput[] {
         const dirtyVariants = product.variants.filter((v, i) => {
             const formRow = variantsFormArray.get(i.toString());
@@ -652,6 +658,7 @@ export class ProductDetailComponent
                 });
                 result.taxCategoryId = formValue.taxCategoryId;
                 result.facetValueIds = formValue.facetValueIds;
+                result.price = priceIncludesTax ? formValue.priceWithTax : formValue.price;
                 const assetChanges = this.variantAssetChanges[variant.id];
                 if (assetChanges) {
                     result.featuredAssetId = assetChanges.featuredAssetId;

+ 6 - 3
packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html

@@ -80,14 +80,15 @@
                                             clrInput
                                             [currencyCode]="variant.currencyCode"
                                             [readonly]="!('UpdateCatalog' | hasPermission)"
-                                            formControlName="price"
+                                            [value]="variantListPrice[variant.id]"
+                                            (valueChange)="updateVariantListPrice($event, variant.id, formGroup)"
                                         ></vdr-currency-input>
                                     </clr-input-container>
                                 </div>
                                 <vdr-variant-price-detail
                                     [price]="formGroup.get('price')!.value"
                                     [currencyCode]="variant.currencyCode"
-                                    [priceIncludesTax]="variant.priceIncludesTax"
+                                    [priceIncludesTax]="channelPriceIncludesTax"
                                     [taxCategoryId]="formGroup.get('taxCategoryId')!.value"
                                 ></vdr-variant-price-detail>
                             </div>
@@ -268,7 +269,9 @@
                                 *ngIf="!isDefaultChannel(channel.code)"
                                 icon="times-circle"
                                 [title]="'catalog.remove-from-channel' | translate"
-                                (iconClick)="removeFromChannel.emit({ channelId: channel.id, variant: variant })"
+                                (iconClick)="
+                                    removeFromChannel.emit({ channelId: channel.id, variant: variant })
+                                "
                             >
                                 <vdr-channel-badge [channelCode]="channel.code"></vdr-channel-badge>
                                 {{ channel.code | channelCodeToLabel }}

+ 29 - 1
packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.ts

@@ -29,7 +29,7 @@ import {
 import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { PaginationInstance } from 'ngx-pagination';
-import { Subscription } from 'rxjs';
+import { Observable, Subscription } from 'rxjs';
 import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
 
 import { AssetChange } from '../product-assets/product-assets.component';
@@ -49,6 +49,7 @@ export interface VariantAssetChange extends AssetChange {
 export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestroy {
     @Input('productVariantsFormArray') formArray: FormArray;
     @Input() variants: ProductWithVariants.Variants[];
+    @Input() channelPriceIncludesTax: boolean;
     @Input() taxCategories: TaxCategory[];
     @Input() facets: FacetWithValues.Fragment[];
     @Input() optionGroups: ProductWithVariants.OptionGroups[];
@@ -73,6 +74,7 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
     GlobalFlag = GlobalFlag;
     globalTrackInventory: boolean;
     globalOutOfStockThreshold: number;
+    variantListPrice: { [variantId: string]: number } = {};
     private facetValues: FacetValue.Fragment[];
     private subscription: Subscription;
 
@@ -114,6 +116,9 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
                 this.pagination.currentPage = 1;
             }
         }
+        if ('channelPriceIncludesTax' in changes) {
+            this.buildVariantListPrices(this.variants);
+        }
     }
 
     ngOnDestroy() {
@@ -138,6 +143,21 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
         );
     }
 
+    updateVariantListPrice(price, variantId: string, group: FormGroup) {
+        // Why do this and not just use a conditional `formControlName` or `formControl`
+        // binding in the template? It breaks down when switching between Channels and
+        // the values no longer get updated. There seem to some lifecycle/memory-clean-up
+        // issues with Angular forms at the moment, which will hopefully be fixed soon.
+        // See https://github.com/angular/angular/issues/20007
+        this.variantListPrice[variantId] = price;
+        const controlName = this.channelPriceIncludesTax ? 'priceWithTax' : 'price';
+        const control = group.get(controlName);
+        if (control) {
+            control.setValue(price);
+            control.markAsDirty();
+        }
+    }
+
     getTaxCategoryName(group: FormGroup): string {
         const control = group.get(['taxCategoryId']);
         if (control && this.taxCategories) {
@@ -259,6 +279,14 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
             });
     }
 
+    private buildVariantListPrices(variants?: ProductWithVariants.Variants[]) {
+        if (variants) {
+            this.variantListPrice = variants.reduce((prices, v) => {
+                return { ...prices, [v.id]: this.channelPriceIncludesTax ? v.priceWithTax : v.price };
+            }, {});
+        }
+    }
+
     private buildFormGroupMap() {
         this.formGroupMap.clear();
         for (const controlGroup of this.formArray.controls) {

+ 2 - 2
packages/admin-ui/src/lib/catalog/src/components/variant-price-detail/variant-price-detail.component.ts

@@ -1,9 +1,8 @@
 import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
+import { DataService } from '@vendure/admin-ui/core';
 import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
 import { map } from 'rxjs/operators';
 
-import { DataService } from '@vendure/admin-ui/core';
-
 @Component({
     selector: 'vdr-variant-price-detail',
     templateUrl: './variant-price-detail.component.html',
@@ -30,6 +29,7 @@ export class VariantPriceDetailComponent implements OnInit, OnChanges {
             .mapStream(data => data.taxRates.items);
         const activeChannel$ = this.dataService.settings
             .getActiveChannel('cache-first')
+            .refetchOnChannelChange()
             .mapStream(data => data.activeChannel);
 
         this.taxRate$ = combineLatest(activeChannel$, taxRates$, this.taxCategoryIdChange$).pipe(

+ 17 - 14
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1287,19 +1287,6 @@ export type UpdateFacetValueInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
-export type Fulfillment = Node & {
-  __typename?: 'Fulfillment';
-  nextStates: Array<Scalars['String']>;
-  id: Scalars['ID'];
-  createdAt: Scalars['DateTime'];
-  updatedAt: Scalars['DateTime'];
-  orderItems: Array<OrderItem>;
-  state: Scalars['String'];
-  method: Scalars['String'];
-  trackingCode?: Maybe<Scalars['String']>;
-  customFields?: Maybe<Scalars['JSON']>;
-};
-
 export type UpdateGlobalSettingsInput = {
   availableLanguages?: Maybe<Array<LanguageCode>>;
   trackInventory?: Maybe<Scalars['Boolean']>;
@@ -1475,6 +1462,19 @@ export type OrderHistoryArgs = {
   options?: Maybe<HistoryEntryListOptions>;
 };
 
+export type Fulfillment = Node & {
+  __typename?: 'Fulfillment';
+  nextStates: Array<Scalars['String']>;
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  orderItems: Array<OrderItem>;
+  state: Scalars['String'];
+  method: Scalars['String'];
+  trackingCode?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
 export type UpdateOrderInput = {
   id: Scalars['ID'];
   customFields?: Maybe<Scalars['JSON']>;
@@ -1736,6 +1736,7 @@ export type ProductVariant = Node & {
   assets: Array<Asset>;
   price: Scalars['Int'];
   currencyCode: CurrencyCode;
+  /** @deprecated price now always excludes tax */
   priceIncludesTax: Scalars['Boolean'];
   priceWithTax: Scalars['Int'];
   taxRateApplied: TaxRate;
@@ -3524,6 +3525,8 @@ export enum LanguageCode {
  */
 export type OrderTaxSummary = {
   __typename?: 'OrderTaxSummary';
+  /** A description of this tax */
+  description: Scalars['String'];
   /** The taxRate as a percentage */
   taxRate: Scalars['Float'];
   /** The total net price or OrderItems to which this taxRate applies */
@@ -5641,7 +5644,7 @@ export type ProductOptionFragment = (
 
 export type ProductVariantFragment = (
   { __typename?: 'ProductVariant' }
-  & Pick<ProductVariant, 'id' | 'createdAt' | 'updatedAt' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceIncludesTax' | 'priceWithTax' | 'stockOnHand' | 'stockAllocated' | 'trackInventory' | 'outOfStockThreshold' | 'useGlobalOutOfStockThreshold' | 'sku'>
+  & Pick<ProductVariant, 'id' | 'createdAt' | 'updatedAt' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceWithTax' | 'stockOnHand' | 'stockAllocated' | 'trackInventory' | 'outOfStockThreshold' | 'useGlobalOutOfStockThreshold' | 'sku'>
   & { taxRateApplied: (
     { __typename?: 'TaxRate' }
     & Pick<TaxRate, 'id' | 'name' | 'value'>

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

@@ -81,10 +81,10 @@ const result: PossibleTypesResultData = {
             'Collection',
             'Customer',
             'Facet',
-            'Fulfillment',
             'HistoryEntry',
             'Job',
             'Order',
+            'Fulfillment',
             'PaymentMethod',
             'Product',
             'ProductVariant',

+ 0 - 1
packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts

@@ -61,7 +61,6 @@ export const PRODUCT_VARIANT_FRAGMENT = gql`
         name
         price
         currencyCode
-        priceIncludesTax
         priceWithTax
         stockOnHand
         stockAllocated

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

@@ -6,6 +6,6 @@
         [disabled]="disabled"
         [readonly]="readonly"
         (input)="onInput($event.target.value)"
-        (focus)="onTouch()"
+        (focus)="onFocus()"
     />
 </vdr-affixed-input>

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

@@ -1,4 +1,4 @@
-import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 
 /**
@@ -22,6 +22,7 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnChanges {
     @Input() readonly = false;
     @Input() value: number;
     @Input() currencyCode = '';
+    @Output() valueChange = new EventEmitter();
     onChange: (val: any) => void;
     onTouch: () => void;
     _decimalValue: string;
@@ -46,7 +47,10 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnChanges {
 
     onInput(value: string) {
         const integerValue = Math.round(+value * 100);
-        this.onChange(integerValue);
+        if (typeof this.onChange === 'function') {
+            this.onChange(integerValue);
+        }
+        this.valueChange.emit(integerValue);
         const delta = Math.abs(Number(this._decimalValue) - Number(value));
         if (0.009 < delta && delta < 0.011) {
             this._decimalValue = this.toNumericString(value);
@@ -55,6 +59,12 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnChanges {
         }
     }
 
+    onFocus() {
+        if (typeof this.onTouch === 'function') {
+            this.onTouch();
+        }
+    }
+
     writeValue(value: any): void {
         const numericValue = +value;
         if (!Number.isNaN(numericValue)) {