Browse Source

feat(admin-ui): Implement UI controls for setting outOfStockThreshold

Relates to #319
Michael Bromley 5 years ago
parent
commit
335c3456dc
24 changed files with 218 additions and 74 deletions
  1. 15 15
      packages/admin-ui/i18n-coverage.json
  2. 4 0
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.ts
  3. 50 14
      packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html
  4. 13 0
      packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.scss
  5. 21 1
      packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.ts
  6. 2 2
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  7. 3 0
      packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts
  8. 1 0
      packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts
  9. 2 0
      packages/admin-ui/src/lib/core/src/data/providers/product-data.service.ts
  10. 9 5
      packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts
  11. 1 6
      packages/admin-ui/src/lib/core/src/shared/components/form-field/form-field.component.html
  12. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/help-tooltip/help-tooltip.component.html
  13. 1 0
      packages/admin-ui/src/lib/core/src/shared/components/help-tooltip/help-tooltip.component.ts
  14. 9 5
      packages/admin-ui/src/lib/core/src/shared/directives/disabled.directive.ts
  15. 14 4
      packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts
  16. 25 11
      packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html
  17. 3 1
      packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.ts
  18. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  19. 8 3
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  20. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  21. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  22. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  23. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  24. 6 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

+ 15 - 15
packages/admin-ui/i18n-coverage.json

@@ -1,41 +1,41 @@
 {
-  "generatedOn": "2020-10-16T14:52:59.320Z",
-  "lastCommit": "ee39263e9928dda664ad137e7e26248a8593b2a9",
+  "generatedOn": "2020-10-21T09:57:14.594Z",
+  "lastCommit": "992682eb87470577aa9f600f5bafecbd6b69607b",
   "translationStatus": {
     "de": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 609,
-      "percentage": 90
+      "percentage": 89
     },
     "en": {
-      "tokenCount": 677,
-      "translatedCount": 675,
+      "tokenCount": 682,
+      "translatedCount": 680,
       "percentage": 100
     },
     "es": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 466,
-      "percentage": 69
+      "percentage": 68
     },
     "pl": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 564,
       "percentage": 83
     },
     "pt_BR": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 655,
-      "percentage": 97
+      "percentage": 96
     },
     "zh_Hans": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 548,
-      "percentage": 81
+      "percentage": 80
     },
     "zh_Hant": {
-      "tokenCount": 677,
+      "tokenCount": 682,
       "translatedCount": 548,
-      "percentage": 81
+      "percentage": 80
     }
   }
 }

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

@@ -60,6 +60,8 @@ export interface VariantFormValue {
     priceWithTax: number;
     taxCategoryId: string;
     stockOnHand: number;
+    useGlobalOutOfStockThreshold: boolean;
+    outOfStockThreshold: number;
     trackInventory: GlobalFlag;
     facetValueIds: string[];
     customFields?: any;
@@ -492,6 +494,8 @@ export class ProductDetailComponent
                 priceWithTax: variant.priceWithTax,
                 taxCategoryId: variant.taxCategory.id,
                 stockOnHand: variant.stockOnHand,
+                useGlobalOutOfStockThreshold: variant.useGlobalOutOfStockThreshold,
+                outOfStockThreshold: variant.outOfStockThreshold,
                 trackInventory: variant.trackInventory,
                 facetValueIds,
             };

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

@@ -67,7 +67,7 @@
                                     <ng-template #taxCategoryLabel>
                                         <label class="clr-control-label">{{
                                             'catalog.tax-category' | translate
-                                        }}</label>
+                                            }}</label>
                                         <div class="tax-category-label">
                                             {{ getTaxCategoryName(formGroup) }}
                                         </div>
@@ -92,22 +92,11 @@
                                 ></vdr-variant-price-detail>
                             </div>
                             <div class="variant-form-input-row">
-                                <clr-input-container>
-                                    <label>{{ 'catalog.stock-on-hand' | translate }}</label>
-                                    <input
-                                        clrInput
-                                        type="number"
-                                        min="0"
-                                        step="1"
-                                        formControlName="stockOnHand"
-                                        [readonly]="!('UpdateCatalog' | hasPermission)"
-                                    />
-                                </clr-input-container>
                                 <clr-select-container *vdrIfPermissions="'UpdateCatalog'">
                                     <label
-                                        >{{ 'catalog.track-inventory' | translate }}
+                                    >{{ 'catalog.track-inventory' | translate }}
                                         <vdr-help-tooltip
-                                            [content]="'catalog.track-inventory-info' | translate"
+                                            [content]="'catalog.track-inventory-tooltip' | translate"
                                         ></vdr-help-tooltip>
                                     </label>
                                     <select clrSelect name="options" formControlName="trackInventory">
@@ -122,6 +111,53 @@
                                         </option>
                                     </select>
                                 </clr-select-container>
+                                <clr-input-container>
+                                    <label>{{ 'catalog.stock-on-hand' | translate }}</label>
+                                    <input
+                                        [class.inventory-untracked]="inventoryIsNotTracked(formGroup)"
+                                        clrInput
+                                        type="number"
+                                        min="0"
+                                        step="1"
+                                        formControlName="stockOnHand"
+                                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                                        [vdrDisabled]="inventoryIsNotTracked(formGroup)"
+                                    />
+                                </clr-input-container>
+                            </div>
+                            <div class="variant-form-input-row">
+                                <div class="out-of-stock-threshold-wrapper" [class.inventory-untracked]="inventoryIsNotTracked(formGroup)">
+                                    <label class="clr-control-label"
+                                        >{{ 'catalog.out-of-stock-threshold' | translate
+                                        }}<vdr-help-tooltip
+                                            [content]="'catalog.out-of-stock-threshold-tooltip' | translate"
+                                        ></vdr-help-tooltip
+                                    ></label>
+                                    <div class="flex">
+                                        <clr-input-container>
+                                            <input
+                                                clrInput
+                                                type="number"
+                                                [formControl]="formGroup.get('outOfStockThreshold')"
+                                                [readonly]="!('UpdateCatalog' | hasPermission)"
+                                                [vdrDisabled]="
+                                                    formGroup.get('useGlobalOutOfStockThreshold')?.value !==
+                                                    false || inventoryIsNotTracked(formGroup)
+                                                "
+                                            />
+                                        </clr-input-container>
+                                        <clr-toggle-wrapper>
+                                            <input
+                                                type="checkbox"
+                                                clrToggle
+                                                name="useGlobalOutOfStockThreshold"
+                                                formControlName="useGlobalOutOfStockThreshold"
+                                                [vdrDisabled]="!('UpdateCatalog' | hasPermission) || inventoryIsNotTracked(formGroup)"
+                                            />
+                                            <label>{{ 'catalog.use-global-value' | translate }} ({{ globalOutOfStockThreshold }})</label>
+                                        </clr-toggle-wrapper>
+                                    </div>
+                                </div>
                             </div>
                         </div>
                         <div class="custom-fields">

+ 13 - 0
packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.scss

@@ -130,6 +130,19 @@
         color: $color-grey-400;
     }
 }
+
+.out-of-stock-threshold-wrapper {
+    display: flex;
+    flex-direction: column;
+    clr-toggle-wrapper {
+        margin-left: 24px;
+    }
+}
+
+.inventory-untracked {
+    opacity: 0.5;
+}
+
 .table-footer {
     display: flex;
     align-items: baseline;

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

@@ -13,6 +13,7 @@ import {
 import { FormArray, FormGroup } from '@angular/forms';
 import {
     CustomFieldConfig,
+    DataService,
     FacetValue,
     FacetWithValues,
     flattenFacetValues,
@@ -64,12 +65,23 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
     };
     formGroupMap = new Map<string, FormGroup>();
     GlobalFlag = GlobalFlag;
+    globalTrackInventory: boolean;
+    globalOutOfStockThreshold: number;
     private facetValues: FacetValue.Fragment[];
     private subscription: Subscription;
 
-    constructor(private changeDetector: ChangeDetectorRef, private modalService: ModalService) {}
+    constructor(
+        private changeDetector: ChangeDetectorRef,
+        private modalService: ModalService,
+        private dataService: DataService,
+    ) {}
 
     ngOnInit() {
+        this.dataService.settings.getGlobalSettings('cache-first').single$.subscribe(({ globalSettings }) => {
+            this.globalTrackInventory = globalSettings.trackInventory;
+            this.globalOutOfStockThreshold = globalSettings.outOfStockThreshold;
+            this.changeDetector.markForCheck();
+        });
         this.subscription = this.formArray.valueChanges.subscribe(() => this.changeDetector.markForCheck());
 
         this.subscription.add(
@@ -108,6 +120,14 @@ export class ProductVariantsListComponent implements OnChanges, OnInit, OnDestro
         return item.id;
     }
 
+    inventoryIsNotTracked(formGroup: FormGroup): boolean {
+        const trackInventory = formGroup.get('trackInventory')?.value;
+        return (
+            trackInventory === GlobalFlag.FALSE ||
+            (trackInventory === GlobalFlag.INHERIT && this.globalTrackInventory === false)
+        );
+    }
+
     getTaxCategoryName(group: FormGroup): string {
         const control = group.get(['taxCategoryId']);
         if (control && this.taxCategories) {

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

@@ -5326,7 +5326,7 @@ export type ProductOptionFragment = (
 
 export type ProductVariantFragment = (
   { __typename?: 'ProductVariant' }
-  & Pick<ProductVariant, 'id' | 'createdAt' | 'updatedAt' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceIncludesTax' | 'priceWithTax' | 'stockOnHand' | 'trackInventory' | 'sku'>
+  & Pick<ProductVariant, 'id' | 'createdAt' | 'updatedAt' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceIncludesTax' | 'priceWithTax' | 'stockOnHand' | 'stockAllocated' | 'trackInventory' | 'outOfStockThreshold' | 'useGlobalOutOfStockThreshold' | 'sku'>
   & { taxRateApplied: (
     { __typename?: 'TaxRate' }
     & Pick<TaxRate, 'id' | 'name' | 'value'>
@@ -6267,7 +6267,7 @@ export type UpdatePaymentMethodMutation = { updatePaymentMethod: (
 
 export type GlobalSettingsFragment = (
   { __typename?: 'GlobalSettings' }
-  & Pick<GlobalSettings, 'id' | 'availableLanguages' | 'trackInventory'>
+  & Pick<GlobalSettings, 'id' | 'availableLanguages' | 'trackInventory' | 'outOfStockThreshold'>
 );
 
 export type GetGlobalSettingsQueryVariables = Exact<{ [key: string]: never; }>;

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

@@ -64,7 +64,10 @@ export const PRODUCT_VARIANT_FRAGMENT = gql`
         priceIncludesTax
         priceWithTax
         stockOnHand
+        stockAllocated
         trackInventory
+        outOfStockThreshold
+        useGlobalOutOfStockThreshold
         taxRateApplied {
             id
             name

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

@@ -422,6 +422,7 @@ export const GLOBAL_SETTINGS_FRAGMENT = gql`
         id
         availableLanguages
         trackInventory
+        outOfStockThreshold
     }
 `;
 

+ 2 - 0
packages/admin-ui/src/lib/core/src/data/providers/product-data.service.ts

@@ -204,6 +204,8 @@ export class ProductDataService {
                     'featuredAssetId',
                     'assetIds',
                     'trackInventory',
+                    'outOfStockThreshold',
+                    'useGlobalOutOfStockThreshold',
                     'stockOnHand',
                     'customFields',
                 ]),

+ 9 - 5
packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts

@@ -1,4 +1,4 @@
-import { FetchPolicy } from '@apollo/client/core';
+import { FetchPolicy, WatchQueryFetchPolicy } from '@apollo/client/core';
 import { pick } from '@vendure/common/lib/pick';
 
 import {
@@ -74,10 +74,10 @@ import {
     GET_COUNTRY,
     GET_COUNTRY_LIST,
     GET_GLOBAL_SETTINGS,
-    GET_JOB_INFO,
-    GET_JOB_QUEUE_LIST,
     GET_JOBS_BY_ID,
     GET_JOBS_LIST,
+    GET_JOB_INFO,
+    GET_JOB_QUEUE_LIST,
     GET_PAYMENT_METHOD,
     GET_PAYMENT_METHOD_LIST,
     GET_TAX_CATEGORIES,
@@ -324,8 +324,12 @@ export class SettingsDataService {
         );
     }
 
-    getGlobalSettings() {
-        return this.baseDataService.query<GetGlobalSettings.Query>(GET_GLOBAL_SETTINGS);
+    getGlobalSettings(fetchPolicy?: WatchQueryFetchPolicy) {
+        return this.baseDataService.query<GetGlobalSettings.Query>(
+            GET_GLOBAL_SETTINGS,
+            undefined,
+            fetchPolicy,
+        );
     }
 
     updateGlobalSettings(input: UpdateGlobalSettingsInput) {

+ 1 - 6
packages/admin-ui/src/lib/core/src/shared/components/form-field/form-field.component.html

@@ -5,12 +5,7 @@
 >
     <label *ngIf="label" [for]="for" class="clr-control-label">
         {{ label }}
-        <clr-tooltip *ngIf="tooltip">
-            <clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
-            <clr-tooltip-content vdrPosition="top-right" clrSize="lg">
-                <span>{{ tooltip }}</span>
-            </clr-tooltip-content>
-        </clr-tooltip>
+        <vdr-help-tooltip *ngIf="tooltip" [content]="tooltip"></vdr-help-tooltip>
     </label>
     <label
         [for]="for"

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/help-tooltip/help-tooltip.component.html

@@ -1,6 +1,6 @@
 <clr-tooltip>
     <clr-icon clrTooltipTrigger shape="help" size="14"></clr-icon>
-    <clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
+    <clr-tooltip-content [clrPosition]="position" clrSize="md" *clrIfOpen>
         <span>{{ content }}</span>
     </clr-tooltip-content>
 </clr-tooltip>

+ 1 - 0
packages/admin-ui/src/lib/core/src/shared/components/help-tooltip/help-tooltip.component.ts

@@ -8,4 +8,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
 })
 export class HelpTooltipComponent {
     @Input() content: string;
+    @Input() position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'right' | 'left';
 }

+ 9 - 5
packages/admin-ui/src/lib/core/src/shared/directives/disabled.directive.ts

@@ -1,5 +1,5 @@
 import { Directive, Input, Optional } from '@angular/core';
-import { FormControl, FormControlName } from '@angular/forms';
+import { FormControl, FormControlDirective, FormControlName } from '@angular/forms';
 
 /**
  * Allows declarative binding to the "disabled" property of a reactive form
@@ -10,15 +10,19 @@ import { FormControl, FormControlName } from '@angular/forms';
 })
 export class DisabledDirective {
     @Input('vdrDisabled') set disabled(val: boolean) {
-        if (!this.formControlName || !this.formControlName.control) {
+        const formControl = this.formControlName?.control ?? this.formControl?.form;
+        if (!formControl) {
             return;
         }
         if (!!val === false) {
-            this.formControlName.control.enable({ emitEvent: false });
+            formControl.enable({ emitEvent: false });
         } else {
-            this.formControlName.control.disable({ emitEvent: false });
+            formControl.disable({ emitEvent: false });
         }
     }
 
-    constructor(@Optional() private formControlName: FormControlName) {}
+    constructor(
+        @Optional() private formControlName: FormControlName,
+        @Optional() private formControl: FormControlDirective,
+    ) {}
 }

+ 14 - 4
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts

@@ -21,7 +21,7 @@ import {
 } from '@vendure/admin-ui/core';
 import { omit } from '@vendure/common/lib/omit';
 import { EMPTY, Observable, of, Subject } from 'rxjs';
-import { map, startWith, switchMap, take } from 'rxjs/operators';
+import { map, mapTo, startWith, switchMap, take } from 'rxjs/operators';
 
 import { CancelOrderDialogComponent } from '../cancel-order-dialog/cancel-order-dialog.component';
 import { FulfillOrderDialogComponent } from '../fulfill-order-dialog/fulfill-order-dialog.component';
@@ -35,7 +35,8 @@ import { SettleRefundDialogComponent } from '../settle-refund-dialog/settle-refu
     styleUrls: ['./order-detail.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragment>
+export class OrderDetailComponent
+    extends BaseDetailComponent<OrderDetail.Fragment>
     implements OnInit, OnDestroy {
     detailForm = new FormGroup({});
     history$: Observable<GetOrderHistory.Items[] | undefined>;
@@ -226,11 +227,20 @@ export class OrderDetailComponent extends BaseDetailComponent<OrderDetail.Fragme
                         return of(undefined);
                     }
                 }),
-                switchMap(result => this.refetchOrder(result)),
+                switchMap(result => this.refetchOrder(result).pipe(mapTo(result))),
             )
             .subscribe(result => {
                 if (result) {
-                    this.notificationService.success(_('order.create-fulfillment-success'));
+                    switch (result.addFulfillmentToOrder.__typename) {
+                        case 'Fulfillment':
+                            this.notificationService.success(_('order.create-fulfillment-success'));
+                            break;
+                        case 'EmptyOrderLineSelectionError':
+                        case 'InsufficientStockOnHandError':
+                        case 'ItemsAlreadyFulfilledError':
+                            this.notificationService.error(result.addFulfillmentToOrder.message);
+                            break;
+                    }
                 }
             });
     }

+ 25 - 11
packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html

@@ -33,19 +33,33 @@
             </ng-template>
         </ng-select>
     </vdr-form-field>
-    <clr-toggle-wrapper>
+    <vdr-form-field
+        [label]="'settings.global-out-of-stock-threshold' | translate"
+        for="outOfStockThreshold"
+        [tooltip]="'settings.global-out-of-stock-threshold-tooltip' | translate"
+    >
         <input
-            type="checkbox"
-            clrToggle
-            name="enabled"
-            formControlName="trackInventory"
-            [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+            id="outOfStockThreshold"
+            type="number"
+            formControlName="outOfStockThreshold"
+            [readonly]="!('UpdateSettings' | hasPermission)"
         />
-        <label
-            >{{ 'settings.track-inventory-default' | translate }}
-            <vdr-help-tooltip [content]="'catalog.track-inventory-info' | translate"></vdr-help-tooltip>
-        </label>
-    </clr-toggle-wrapper>
+    </vdr-form-field>
+    <vdr-form-field
+        [label]="'settings.track-inventory-default' | translate"
+        for="enabled"
+        [tooltip]="'catalog.track-inventory-tooltip' | translate"
+    >
+        <clr-toggle-wrapper>
+            <input
+                type="checkbox"
+                clrToggle
+                name="enabled"
+                formControlName="trackInventory"
+                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+            />
+        </clr-toggle-wrapper>
+    </vdr-form-field>
     <section formGroupName="customFields" *ngIf="customFields.length">
         <label>{{ 'common.custom-fields' | translate }}</label>
         <ng-container *ngFor="let customField of customFields">

+ 3 - 1
packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
-import { FormBuilder, FormGroup } from '@angular/forms';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { BaseDetailComponent } from '@vendure/admin-ui/core';
@@ -34,6 +34,7 @@ export class GlobalSettingsComponent extends BaseDetailComponent<GlobalSettings>
         this.detailForm = this.formBuilder.group({
             availableLanguages: [''],
             trackInventory: false,
+            outOfStockThreshold: [0, Validators.required],
             customFields: this.formBuilder.group(
                 this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
             ),
@@ -86,6 +87,7 @@ export class GlobalSettingsComponent extends BaseDetailComponent<GlobalSettings>
         this.detailForm.patchValue({
             availableLanguages: entity.availableLanguages,
             trackInventory: entity.trackInventory,
+            outOfStockThreshold: entity.outOfStockThreshold,
         });
         if (this.customFields.length) {
             const customFieldsGroup = this.detailForm.get('customFields') as FormGroup;

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

@@ -101,6 +101,8 @@
     "option": "Option",
     "option-name": "Optionsname",
     "option-values": "Optionswerte",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "Preis",
     "price-conversion-factor": "Preisumwandlungsfaktor",
     "price-in-channel": "Preis in { channel }",
@@ -128,10 +130,11 @@
     "taxes": "Steuern",
     "track-inventory": "Bestand verfolgen",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "Produktoption aktualisieren",
+    "use-global-value": "",
     "values": "Werte",
     "variant": "Variante",
     "view-contents": "Inhalt anzeigen",
@@ -655,6 +658,8 @@
     "email-address": "E-Mail-Adresse",
     "filter-by-member-name": "Nach Land filtern",
     "first-name": "Vorname",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "Nachname",
     "no-eligible-shipping-methods": "Keine verfügbaren Versandarten",
     "order": "Bestellung",

+ 8 - 3
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -101,6 +101,8 @@
     "option": "Option",
     "option-name": "Option name",
     "option-values": "Option values",
+    "out-of-stock-threshold": "Out-of-stock threshold",
+    "out-of-stock-threshold-tooltip": "Sets the stock level at which this variant is considered to be out of stock. Using a negative value enables backorder support.",
     "price": "Price",
     "price-conversion-factor": "Price conversion factor",
     "price-in-channel": "Price in { channel }",
@@ -122,16 +124,17 @@
     "search-product-name-or-code": "Search by product name or code",
     "sku": "SKU",
     "slug": "Slug",
-    "slug-pattern-error": "",
+    "slug-pattern-error": "Slug is invalid",
     "stock-on-hand": "Stock",
     "tax-category": "Tax category",
     "taxes": "Taxes",
     "track-inventory": "Track inventory",
-    "track-inventory-false": "Do no track",
-    "track-inventory-info": "When tracked, product variant stock levels will be automatically adjusted when sold",
+    "track-inventory-false": "Do not track",
     "track-inventory-inherit": "Inherit from global settings",
+    "track-inventory-tooltip": "When tracked, product variant stock levels will be automatically adjusted when sold",
     "track-inventory-true": "Track",
     "update-product-option": "Update product option",
+    "use-global-value": "Use global value",
     "values": "Values",
     "variant": "Variant",
     "view-contents": "View contents",
@@ -655,6 +658,8 @@
     "email-address": "Email address",
     "filter-by-member-name": "Filter by country",
     "first-name": "First name",
+    "global-out-of-stock-threshold": "Global out-of-stock threshold",
+    "global-out-of-stock-threshold-tooltip": "Sets the stock level at which this a variant is considered to be out of stock. Using a negative value enables backorder support. Can be overridden by product variants.",
     "last-name": "Last name",
     "no-eligible-shipping-methods": "No eligible shipping methods",
     "order": "Order",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -101,6 +101,8 @@
     "option": "Opción",
     "option-name": "Nombre de la opción",
     "option-values": "Valores de la opción",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "Precio",
     "price-conversion-factor": "",
     "price-in-channel": "Precio en { channel }",
@@ -128,10 +130,11 @@
     "taxes": "Impuestos",
     "track-inventory": "",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "Actualizar opción",
+    "use-global-value": "",
     "values": "Valores",
     "variant": "Variantes",
     "view-contents": "Ver contenidos",
@@ -655,6 +658,8 @@
     "email-address": "Dirección de email",
     "filter-by-member-name": "Filtrar por país",
     "first-name": "Nombre",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "Apellidos",
     "no-eligible-shipping-methods": "No hay métodos de envío disponibles",
     "order": "Pedido",

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

@@ -101,6 +101,8 @@
     "option": "Opcje",
     "option-name": "Nazwa opcji",
     "option-values": "Wartość opcji",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "Cena",
     "price-conversion-factor": "Przelicznik cen",
     "price-in-channel": "Cena w { channel }",
@@ -128,10 +130,11 @@
     "taxes": "Podatki",
     "track-inventory": "Śledź magazyn",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "Zaktualizuj opcje produktu",
+    "use-global-value": "",
     "values": "Wartości",
     "variant": "Warianty",
     "view-contents": "Zobacz zawartość",
@@ -655,6 +658,8 @@
     "email-address": "Email",
     "filter-by-member-name": "",
     "first-name": "Imię",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "Nazwisko",
     "no-eligible-shipping-methods": "Brak pasujących metod wysyłki",
     "order": "Zamówienie",

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

@@ -101,6 +101,8 @@
     "option": "Opção",
     "option-name": "Nome da opção",
     "option-values": "Valor da opção",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "Preço",
     "price-conversion-factor": "Fator de conversão de preço",
     "price-in-channel": "Preço em { channel }",
@@ -128,10 +130,11 @@
     "taxes": "Impostos",
     "track-inventory": "Rastrear inventário",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "Atualizar opção do produto",
+    "use-global-value": "",
     "values": "Valores",
     "variant": "Variação",
     "view-contents": "Visualizar conteúdo",
@@ -655,6 +658,8 @@
     "email-address": "Email",
     "filter-by-member-name": "Filtrar por país",
     "first-name": "Nome",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "Sobrenome",
     "no-eligible-shipping-methods": "Nenhum método de envio qualificado",
     "order": "Pedido",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -101,6 +101,8 @@
     "option": "规格",
     "option-name": "规格名称",
     "option-values": "规格列表(按回车键添加)",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "价格",
     "price-conversion-factor": "Price conversion factor",
     "price-in-channel": "渠道{ channel }价格",
@@ -128,10 +130,11 @@
     "taxes": "价格(含税)",
     "track-inventory": "跟踪库存",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "更新产品规格",
+    "use-global-value": "",
     "values": "值",
     "variant": "商品规格",
     "view-contents": "查看详情",
@@ -655,6 +658,8 @@
     "email-address": "电子邮件",
     "filter-by-member-name": "",
     "first-name": "名",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "姓",
     "no-eligible-shipping-methods": "没有符合条件的配送方式",
     "order": "订单管理",

+ 6 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -101,6 +101,8 @@
     "option": "規格",
     "option-name": "規格名稱",
     "option-values": "規格列表(按輸入鍵新增)",
+    "out-of-stock-threshold": "",
+    "out-of-stock-threshold-tooltip": "",
     "price": "價格",
     "price-conversion-factor": "價格轉換係數",
     "price-in-channel": "渠道{ channel }價格",
@@ -128,10 +130,11 @@
     "taxes": "價格(連税)",
     "track-inventory": "跟踪庫存",
     "track-inventory-false": "",
-    "track-inventory-info": "",
     "track-inventory-inherit": "",
+    "track-inventory-tooltip": "",
     "track-inventory-true": "",
     "update-product-option": "更新產品規格",
+    "use-global-value": "",
     "values": "值",
     "variant": "商品規格",
     "view-contents": "查看詳情",
@@ -655,6 +658,8 @@
     "email-address": "電子郵件",
     "filter-by-member-name": "",
     "first-name": "名",
+    "global-out-of-stock-threshold": "",
+    "global-out-of-stock-threshold-tooltip": "",
     "last-name": "姓",
     "no-eligible-shipping-methods": "没有符合條件的配送方式",
     "order": "訂單管理",