@snabcentr/client-ui 4.16.8 → 4.16.10-profstroy

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/alerts/confirm-alert/sc-confirm-alert.component.d.ts +22 -0
  2. package/alerts/index.d.ts +1 -0
  3. package/cart/add-or-editing-cart-item-dialog/sc-add-or-editing-cart-item-dialog.component.d.ts +1 -1
  4. package/cart/sc-cart-add-products-from-csv-dialog/sc-cart-add-products-from-csv-dialog.component.d.ts +3 -3
  5. package/catalog/input-quantity/sc-input-quantity.component.d.ts +17 -1
  6. package/esm2022/alerts/confirm-alert/sc-confirm-alert.component.mjs +29 -0
  7. package/esm2022/alerts/index.mjs +2 -0
  8. package/esm2022/cart/add-or-editing-cart-item-dialog/sc-add-or-editing-cart-item-dialog.component.mjs +4 -3
  9. package/esm2022/cart/sc-cart-add-products-from-csv-dialog/sc-cart-add-products-from-csv-dialog.component.mjs +5 -16
  10. package/esm2022/catalog/input-quantity/sc-input-quantity.component.mjs +24 -4
  11. package/esm2022/catalog/price-warehouse-stock/sc-price-warehouse-stock.component.mjs +3 -3
  12. package/esm2022/helpers/sc-api-error-helper.mjs +25 -0
  13. package/esm2022/helpers/sc-file-helper.mjs +29 -0
  14. package/esm2022/order/draft/helpers/sc-draft-list-helper.mjs +83 -0
  15. package/esm2022/order/draft/models/sc-i-draft-list-item.mjs +2 -0
  16. package/esm2022/order/draft/profstroy/actualization-panel/sc-profstroy-actualization-panel.component.mjs +73 -0
  17. package/esm2022/order/draft/profstroy/badge/sc-profstroy-badge.component.mjs +21 -0
  18. package/esm2022/order/draft/profstroy/badges/sc-profstroy-draft-badges.component.mjs +31 -0
  19. package/esm2022/order/draft/profstroy/create-dialog/sc-profstroy-draft-create-dialog.component.mjs +139 -0
  20. package/esm2022/order/draft/profstroy/enums/sc-profstroy-draft-state.mjs +32 -0
  21. package/esm2022/order/draft/profstroy/helpers/sc-profstroy-draft-helper.mjs +94 -0
  22. package/esm2022/order/draft/profstroy/index.mjs +8 -0
  23. package/esm2022/order/draft/profstroy/models/sc-i-profstroy-draft-state-view.mjs +2 -0
  24. package/esm2022/order/draft/sc-draft.component.mjs +148 -14
  25. package/esm2022/order/draft/services/sc-draft-expiration-clock.service.mjs +42 -0
  26. package/esm2022/order/draft/services/sc-draft-list-updates.service.mjs +288 -0
  27. package/esm2022/order/index.mjs +6 -1
  28. package/esm2022/order/new-order-items/sc-new-order-items.component.mjs +11 -3
  29. package/esm2022/order/order-form/sc-order-form.component.mjs +4 -2
  30. package/esm2022/order/order-item/sc-order-item.component.mjs +37 -5
  31. package/esm2022/order/order-items-list/sc-order-items-list.component.mjs +180 -47
  32. package/esm2022/order/order-items-list-by-stock/sc-order-items-list-by-stock.component.mjs +1 -1
  33. package/esm2022/order/order-items-list-with-actions/sc-order-items-list-with-actions.component.mjs +15 -3
  34. package/esm2022/pipes/sc-format-date.mjs +1 -1
  35. package/esm2022/providers/sc-date-formatter.mjs +1 -1
  36. package/esm2022/public-api.mjs +2 -1
  37. package/fesm2022/snabcentr-client-ui.mjs +1275 -124
  38. package/fesm2022/snabcentr-client-ui.mjs.map +1 -1
  39. package/helpers/sc-api-error-helper.d.ts +18 -0
  40. package/helpers/sc-file-helper.d.ts +13 -0
  41. package/order/draft/helpers/sc-draft-list-helper.d.ts +51 -0
  42. package/order/draft/models/sc-i-draft-list-item.d.ts +14 -0
  43. package/order/draft/profstroy/actualization-panel/sc-profstroy-actualization-panel.component.d.ts +58 -0
  44. package/order/draft/profstroy/badge/sc-profstroy-badge.component.d.ts +12 -0
  45. package/order/draft/profstroy/badges/sc-profstroy-draft-badges.component.d.ts +21 -0
  46. package/order/draft/profstroy/create-dialog/sc-profstroy-draft-create-dialog.component.d.ts +65 -0
  47. package/order/draft/profstroy/enums/sc-profstroy-draft-state.d.ts +29 -0
  48. package/order/draft/profstroy/helpers/sc-profstroy-draft-helper.d.ts +33 -0
  49. package/order/draft/profstroy/index.d.ts +7 -0
  50. package/order/draft/profstroy/models/sc-i-profstroy-draft-state-view.d.ts +17 -0
  51. package/order/draft/sc-draft.component.d.ts +73 -1
  52. package/order/draft/services/sc-draft-expiration-clock.service.d.ts +29 -0
  53. package/order/draft/services/sc-draft-list-updates.service.d.ts +158 -0
  54. package/order/index.d.ts +5 -0
  55. package/order/new-order-items/sc-new-order-items.component.d.ts +9 -1
  56. package/order/order-form/sc-order-form.component.d.ts +1 -0
  57. package/order/order-item/sc-order-item.component.d.ts +25 -1
  58. package/order/order-items-list/sc-order-items-list.component.d.ts +87 -9
  59. package/order/order-items-list-with-actions/sc-order-items-list-with-actions.component.d.ts +13 -1
  60. package/package.json +2 -2
  61. package/pipes/sc-format-date.d.ts +1 -1
  62. package/providers/sc-date-formatter.d.ts +1 -1
  63. package/public-api.d.ts +1 -0
  64. package/release_notes.tmp +1 -1
  65. package/styles/tailwind/tailwind.scss +41 -0
@@ -1,20 +1,20 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, NgZone, signal, EventEmitter, ChangeDetectorRef, Directive, Input, Output, untracked, Pipe, ElementRef, HostListener, Renderer2, ContentChildren, NgModule, input, computed, Component, ChangeDetectionStrategy, Inject, HostBinding, model, output, SkipSelf, DestroyRef, effect, ContentChild, ViewChild, viewChild, Optional, contentChild, TemplateRef, forwardRef, ViewEncapsulation, Injector, viewChildren, afterNextRender } from '@angular/core';
3
3
  import * as i1 from '@snabcentr/client-core';
4
- import { ScContactsService, ScUserService, ScLocationsService, ScAuthService, SEARCH_TERM, ScUnitsHelper, ScImageHelper, SC_PATH_IMAGE_NOT_FOUND, ScImage, ScSuggestionAddressQuality, ScPhoneService, ScUserMetrikaService, ScUserMetrikaGoalsEnum, IS_RUNNING_ON_TERMINAL, ScVCardService, ScVerificationService, ScISuggestionType, SC_MIN_LENGTH_SEARCH_TERM, ScConvertersService, ScNumberHelper, ScOpfList, ScReferencesService, ScContragentService, ScBannerService, ScMediaImageTransformerPipe, ScCartService, ScUploadedFile, loadingPlaceholder, SC_ORDER_LOADER, ScMimeTypes, ScCatalogService, SC_URLS, ScWarehouseService, SEARCH_TERM_PROVIDERS, ScPaginationService, SC_NEXT_PAGE_PAGINATION_CLICK, SC_PRODUCT_PAGINATION_OPTIONS, ScCatalogFilterService, ScIdOrSlugPipe, SC_CATEGORY_INFO, ScConfiguratorService, IS_SERVER, RESPONSE, ScIconTypesEnum, ScDocumentInfoTypesEnum, ScDeliveryAddressService, ScOrdersService, ScUIService, SC_USER_INFO as SC_USER_INFO$1, ScPaymentType, ScDeliveryType, TERMINAL_PROVIDERS, ScOrderDraftsService, ScRouteKeys, ScSeoService, scOrderIsLoaded, SC_PAGE_QUERY_PARAM, ScFrequentlyAskedQuestionsService, SchemaOrgFactory, scGetPageQueryParamChanges, ScNewsService, SC_COMPANY_INFO, ScFeedbackService } from '@snabcentr/client-core';
4
+ import { ScContactsService, ScUserService, ScLocationsService, ScAuthService, SEARCH_TERM, ScUnitsHelper, ScImageHelper, SC_PATH_IMAGE_NOT_FOUND, ScImage, ScSuggestionAddressQuality, ScPhoneService, ScUserMetrikaService, ScUserMetrikaGoalsEnum, IS_RUNNING_ON_TERMINAL, ScVCardService, ScVerificationService, ScISuggestionType, SC_MIN_LENGTH_SEARCH_TERM, ScConvertersService, ScNumberHelper, ScOpfList, ScReferencesService, ScContragentService, ScBannerService, ScMediaImageTransformerPipe, ScUploadedFile, ScCartService, loadingPlaceholder, SC_ORDER_LOADER, ScMimeTypes, ScCatalogService, SC_URLS, ScWarehouseService, SEARCH_TERM_PROVIDERS, ScPaginationService, SC_NEXT_PAGE_PAGINATION_CLICK, SC_PRODUCT_PAGINATION_OPTIONS, ScCatalogFilterService, ScIdOrSlugPipe, SC_CATEGORY_INFO, ScConfiguratorService, IS_SERVER, RESPONSE, ScIconTypesEnum, ScDocumentInfoTypesEnum, ScOrderItemsGroup, ScOrderItem, ScDeliveryAddressService, ScOrdersService, ScUIService, SC_USER_INFO as SC_USER_INFO$1, ScPaymentType, ScDeliveryType, TERMINAL_PROVIDERS, ScOrderDraft, SC_UPDATE_INTERVAL, ScOrderDraftsService, ScOrderDraftActualizationStatus, ScRouteKeys, ScSeoService, scOrderIsLoaded, SC_PAGE_QUERY_PARAM, ScFrequentlyAskedQuestionsService, SchemaOrgFactory, scGetPageQueryParamChanges, ScNewsService, SC_COMPANY_INFO, ScFeedbackService } from '@snabcentr/client-core';
5
5
  import * as i5$1 from 'rxjs';
6
- import { EMPTY, BehaviorSubject, switchMap, of, shareReplay, map, Subject, filter, tap, catchError, finalize, startWith, share, timer, scan, takeWhile, endWith, distinctUntilChanged, debounceTime, throwError, combineLatest, pairwise, Observable, Subscription, merge, skip, from, concatMap, noop, first, retry, fromEvent, animationFrames, take, takeUntil } from 'rxjs';
6
+ import { EMPTY, BehaviorSubject, switchMap, of, shareReplay, map, Subject, filter, tap, catchError, finalize, startWith, share, timer, scan, takeWhile, endWith, distinctUntilChanged, debounceTime, throwError, combineLatest, pairwise, Observable, Subscription, merge, skip, from, concatMap, noop, first, retry, defaultIfEmpty, exhaustMap, fromEvent, animationFrames, take, takeUntil } from 'rxjs';
7
7
  import { WA_WINDOW } from '@ng-web-apis/common';
8
8
  import { takeUntilDestroyed, toSignal, outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
9
9
  import * as i4$1 from '@taiga-ui/cdk';
10
10
  import { TuiDay, TuiTime, TuiValueTransformer, TUI_IS_MOBILE, TuiValidationError, tuiControlValue, tuiIsPresent, tuiMarkControlAsTouchedAndValidate, tuiIsFalsy, TuiAutoFocus, TuiRepeatTimes, TuiSwipeService, TUI_SWIPE_OPTIONS, TuiHovered, TuiDayRange, TuiMonth, tuiPure, tuiProvide, TUI_WINDOW_SIZE, tuiClamp, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
11
11
  import * as i2$1 from '@taiga-ui/kit';
12
- import { TUI_DATE_VALUE_TRANSFORMER, TuiPreview, TuiAvatar, TuiAccordionItem, TuiElasticContainer, TuiAccordion, TuiFieldErrorPipe, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiDataListWrapper, TuiButtonLoading, TuiSortCountriesPipe, TuiStepper, TuiCarousel, TuiPush, TuiCheckbox, TuiFiles, TuiChip, TuiInputNumber, TuiBadge, TuiTooltip, TuiHighlight, TuiLineClamp, TuiFade, TuiRange, TuiBlock, tuiInputNumberOptionsProvider, TuiDataListWrapperComponent, TuiChevron, TuiSelect, TuiInputSlider, TuiTreeService, TuiTreeItemContent, TUI_TREE_START, TUI_TREE_CONTENT, TUI_TREE_LOADING, TUI_TREE_LOADER, TuiTree, TuiPreviewDialogService, tuiFilesAccepted, TuiInputFiles, TuiInputFilesDirective, TuiFilesComponent, TuiFile, TuiFileRejectedPipe, TUI_CONFIRM, tuiItemsHandlersProvider as tuiItemsHandlersProvider$1, TUI_PAGINATION_TEXTS, TuiAccordionDirective, TuiAccordionItemContent } from '@taiga-ui/kit';
13
- import { HttpClient, HttpErrorResponse } from '@angular/common/http';
12
+ import { TUI_DATE_VALUE_TRANSFORMER, TuiPreview, TuiAvatar, TuiAccordionItem, TuiElasticContainer, TuiAccordion, TuiFieldErrorPipe, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiDataListWrapper, TuiButtonLoading, TuiSortCountriesPipe, TuiStepper, TuiCarousel, TuiPush, TuiCheckbox, TUI_CONFIRM_WORDS, TuiFiles, TuiChip, TuiInputNumber, TuiBadge, TuiTooltip, TuiHighlight, TuiLineClamp, TuiFade, TuiRange, TuiBlock, tuiInputNumberOptionsProvider, TuiDataListWrapperComponent, TuiChevron, TuiSelect, TuiInputSlider, TuiTreeService, TuiTreeItemContent, TUI_TREE_START, TUI_TREE_CONTENT, TUI_TREE_LOADING, TUI_TREE_LOADER, TuiTree, TuiPreviewDialogService, TUI_CONFIRM, tuiFilesAccepted, TuiInputFiles, TuiInputFilesDirective, TuiFilesComponent, TuiFile, TuiFileRejectedPipe, TUI_INPUT_FILES_OPTIONS, tuiCreateFileFormatValidator, tuiCreateFileSizeValidator, TUI_INPUT_FILE_TEXTS, tuiItemsHandlersProvider as tuiItemsHandlersProvider$1, TUI_PAGINATION_TEXTS, TuiAccordionDirective, TuiAccordionItemContent } from '@taiga-ui/kit';
13
+ import { HttpClient, HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
14
14
  import * as i1$1 from '@angular/forms';
15
15
  import { FormControl, NgControl, FormGroupDirective, FormGroup, Validators, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
16
16
  import * as i1$2 from '@taiga-ui/core';
17
- import { TuiButton, TuiDialog, TuiDialogService, TuiLink, TuiFallbackSrcPipe, TuiInitialsPipe, TuiIcon, TuiAppearance, TuiWithAppearance, TuiIcons, TuiWithIcons, TUI_DATA_LIST_HOST, tuiDropdownOptionsProvider, TuiLabel, TuiError, TuiLoader, TuiDataList, TuiNotification, TuiTextfield, TuiTitle, TuiFormatNumberPipe, TuiNumberFormat, TuiHint, TUI_MONTHS, TuiScrollbar, tuiNumberFormatProvider, tuiItemsHandlersProvider, tuiTextfieldOptionsProvider, tuiHintOptionsProvider, TuiHintDriver, TuiHintHover, TuiHintPosition, TuiHintDirective, TuiDataListComponent, TuiOption, TuiAlertService, TuiIconPipe, TUI_BUTTON_OPTIONS, TUI_BUTTON_DEFAULT_OPTIONS, TUI_SPIN_ICONS, TuiSurface, tuiFadeIn } from '@taiga-ui/core';
17
+ import { TuiButton, TuiDialog, TuiDialogService, TuiLink, TuiFallbackSrcPipe, TuiInitialsPipe, TuiIcon, TuiAppearance, TuiWithAppearance, TuiIcons, TuiWithIcons, TUI_DATA_LIST_HOST, tuiDropdownOptionsProvider, TuiLabel, TuiError, TuiLoader, TuiDataList, TuiNotification, TuiTextfield, TuiTitle, TuiFormatNumberPipe, TuiNumberFormat, TuiHint, TUI_MONTHS, TuiScrollbar, tuiNumberFormatProvider, tuiItemsHandlersProvider, tuiTextfieldOptionsProvider, tuiHintOptionsProvider, TuiHintDriver, TuiHintHover, TuiHintPosition, TuiHintDirective, TuiAlertService, TuiDataListComponent, TuiOption, TuiIconPipe, TUI_BUTTON_OPTIONS, TUI_BUTTON_DEFAULT_OPTIONS, TUI_SPIN_ICONS, TuiSurface, tuiFadeIn } from '@taiga-ui/core';
18
18
  import { isValidPhoneNumber } from 'libphonenumber-js/max';
19
19
  import * as i4 from '@taiga-ui/legacy/components/primitive-textfield';
20
20
  import * as i3 from '@taiga-ui/legacy';
@@ -4391,6 +4391,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4391
4391
  }]
4392
4392
  }] });
4393
4393
 
4394
+ /**
4395
+ * Компонент немодального уведомления с выбором «Да» или «Нет» для событий без действия пользователя, например изменения списка в фоне.
4396
+ * Уведомление завершается выбором пользователя. Действие пользователя подтверждается диалогом TUI_CONFIRM.
4397
+ */
4398
+ class ScConfirmAlertComponent {
4399
+ constructor() {
4400
+ /**
4401
+ * Контекст уведомления: вопрос, подписи и вид кнопки согласия, выбор пользователя.
4402
+ */
4403
+ this.context = injectContext();
4404
+ /**
4405
+ * {@link Observable} подписей кнопок по умолчанию.
4406
+ */
4407
+ this.words$ = inject(TUI_CONFIRM_WORDS);
4408
+ }
4409
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScConfirmAlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4410
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScConfirmAlertComponent, isStandalone: true, selector: "sc-confirm-alert", ngImport: i0, template: "@let words = words$ | async;\n<div class=\"flex flex-col items-start gap-2\">\n <span *polymorpheusOutlet=\"context.data.content as text; context: context\">{{ text }}</span>\n <div class=\"flex gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n [appearance]=\"context.data.appearance || 'primary'\"\n (click)=\"context.completeWith(true)\"\n >\n {{ context.data.yes || words?.yes }}\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n (click)=\"context.completeWith(false)\"\n >\n {{ context.data.no || words?.no }}\n </button>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4411
+ }
4412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScConfirmAlertComponent, decorators: [{
4413
+ type: Component,
4414
+ args: [{ standalone: true, selector: 'sc-confirm-alert', imports: [AsyncPipe, PolymorpheusOutlet, TuiButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let words = words$ | async;\n<div class=\"flex flex-col items-start gap-2\">\n <span *polymorpheusOutlet=\"context.data.content as text; context: context\">{{ text }}</span>\n <div class=\"flex gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n [appearance]=\"context.data.appearance || 'primary'\"\n (click)=\"context.completeWith(true)\"\n >\n {{ context.data.yes || words?.yes }}\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n (click)=\"context.completeWith(false)\"\n >\n {{ context.data.no || words?.no }}\n </button>\n </div>\n</div>\n" }]
4415
+ }] });
4416
+
4394
4417
  /**
4395
4418
  * Баннер с прокруткой переданных {@link TemplateRef} элементов, и баннеров локации.
4396
4419
  */
@@ -4571,6 +4594,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4571
4594
  }]
4572
4595
  }] });
4573
4596
 
4597
+ /**
4598
+ * Класс-помощник для работы с файлами, выбранными пользователем.
4599
+ */
4600
+ class ScFileHelper {
4601
+ /**
4602
+ * Читает файл и возвращает его для отправки в API: название и содержимое в виде data-URI. При отписке чтение прерывается.
4603
+ *
4604
+ * @param file Выбранный файл.
4605
+ */
4606
+ static readAsUploadedFile$(file) {
4607
+ return new Observable((observer) => {
4608
+ const reader = new FileReader();
4609
+ reader.addEventListener('load', () => {
4610
+ observer.next(new ScUploadedFile(file.name, reader.result));
4611
+ observer.complete();
4612
+ });
4613
+ reader.addEventListener('error', () => {
4614
+ observer.error(reader.error);
4615
+ });
4616
+ reader.readAsDataURL(file);
4617
+ return () => {
4618
+ reader.abort();
4619
+ };
4620
+ });
4621
+ }
4622
+ }
4623
+
4574
4624
  /**
4575
4625
  * Компонент диалога для добавления товаров в корзину из CSV файла.
4576
4626
  */
@@ -4613,19 +4663,7 @@ class ScCartAddProductsFromCsvDialogComponent {
4613
4663
  /**
4614
4664
  * {@link Observable} запроса на добавление товаров из CSV файла.
4615
4665
  */
4616
- this.submitRequest$ = this.onSubmit$.pipe(map(() => this.control.value), filter(tuiIsPresent), switchMap((file) => new Observable((observer) => {
4617
- const reader = new FileReader();
4618
- reader.addEventListener('load', () => {
4619
- observer.next(new ScUploadedFile(file.name, reader.result));
4620
- });
4621
- reader.addEventListener('error', (e) => {
4622
- observer.error(e);
4623
- });
4624
- reader.readAsDataURL(file);
4625
- return () => {
4626
- reader.abort();
4627
- };
4628
- })), switchMap((file) => this.cartService.addProductsFromCsv$(file).pipe(tap(() => {
4666
+ this.submitRequest$ = this.onSubmit$.pipe(map(() => this.control.value), filter(tuiIsPresent), switchMap((file) => ScFileHelper.readAsUploadedFile$(file)), switchMap((file) => this.cartService.addProductsFromCsv$(file).pipe(tap(() => {
4629
4667
  this.context.$implicit.complete();
4630
4668
  }), startWith(null))));
4631
4669
  /**
@@ -4901,7 +4939,7 @@ class ScAddOrEditingCartItemDialogComponent {
4901
4939
  this.context.$implicit.complete();
4902
4940
  }), catchError((error) => {
4903
4941
  if (error instanceof HttpErrorResponse) {
4904
- const { errors, message } = error.error;
4942
+ const { errors, message } = (error.error ?? {});
4905
4943
  if (errors && isObject(errors)) {
4906
4944
  Object.entries(errors).forEach(([k, v]) => {
4907
4945
  this.formComponent().form.get(k)?.setErrors({ serverResponse: v });
@@ -4913,7 +4951,8 @@ class ScAddOrEditingCartItemDialogComponent {
4913
4951
  this.formComponent().form.updateValueAndValidity();
4914
4952
  this.formComponent().form.markAsDirty();
4915
4953
  }
4916
- return of();
4954
+ // Значение вместо пустого потока снимает загрузку, чтобы форму можно было отправить снова.
4955
+ return of(null);
4917
4956
  }), startWith(loadingPlaceholder))), share());
4918
4957
  /**
4919
4958
  * {@link Observable} изменения состояния загрузки данных.
@@ -5251,6 +5290,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5251
5290
  class ScInputQuantityComponent extends AbstractTuiNullableControl {
5252
5291
  constructor() {
5253
5292
  super(...arguments);
5293
+ /**
5294
+ * Минимальное количество знаков после запятой.
5295
+ */
5296
+ this.minPrecision = 2;
5254
5297
  /**
5255
5298
  * Шаг увеличения количества. Отвечает за проверку кратности ввода.
5256
5299
  */
@@ -5294,6 +5337,21 @@ class ScInputQuantityComponent extends AbstractTuiNullableControl {
5294
5337
  get focused() {
5295
5338
  return !!this.numberInput?.focused;
5296
5339
  }
5340
+ /**
5341
+ * Количество знаков после запятой: по шагу, но не меньше двух.
5342
+ */
5343
+ get precision() {
5344
+ return Math.max(this.minPrecision, ScNumberHelper.getDecimalsCount(this.step));
5345
+ }
5346
+ /**
5347
+ * Настройки формата числа поля. Точность задаётся только для шага с тремя и более знаками после запятой, иначе действует общая настройка.
5348
+ */
5349
+ get numberFormat() {
5350
+ if (this.numberFormatCache?.step !== this.step) {
5351
+ this.numberFormatCache = { step: this.step, format: this.precision > this.minPrecision ? { precision: this.precision, decimalMode: 'not-zero' } : {} };
5352
+ }
5353
+ return this.numberFormatCache.format;
5354
+ }
5297
5355
  /**
5298
5356
  * Элемент формы ввода количества.
5299
5357
  */
@@ -5366,7 +5424,7 @@ class ScInputQuantityComponent extends AbstractTuiNullableControl {
5366
5424
  this.clickClearEvent.emit();
5367
5425
  }
5368
5426
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScInputQuantityComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
5369
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScInputQuantityComponent, isStandalone: true, selector: "sc-input-quantity", inputs: { step: "step", showLoader: "showLoader", showCross: "showCross", ignoreStepValidators: "ignoreStepValidators", appearance: "appearance", isDisabled: "isDisabled", size: "size" }, outputs: { clickClearEvent: "clickClearEvent" }, host: { listeners: { "keydown.arrowDown": "onArrow(-step)", "keydown.arrowUp": "onArrow(step)" }, properties: { "attr.data-appearance": "this.appearance", "attr.data-disabled": "this.isDisabled", "attr.data-size": "this.size" } }, viewQueries: [{ propertyName: "numberInput", first: true, predicate: TuiInputNumberComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiInputNumberModule }, { kind: "component", type: i3.TuiInputNumberComponent, selector: "tui-input-number", inputs: ["min", "max", "step"] }, { kind: "directive", type: i3.TuiInputNumberDirective, selector: "tui-input-number" }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i3.TuiTextfieldLabelOutsideDirective, selector: "[tuiTextfieldLabelOutside]", inputs: ["tuiTextfieldLabelOutside"] }, { kind: "directive", type: i3.TuiTextfieldSizeDirective, selector: "[tuiTextfieldSize]", inputs: ["tuiTextfieldSize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5427
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScInputQuantityComponent, isStandalone: true, selector: "sc-input-quantity", inputs: { step: "step", showLoader: "showLoader", showCross: "showCross", ignoreStepValidators: "ignoreStepValidators", appearance: "appearance", isDisabled: "isDisabled", size: "size" }, outputs: { clickClearEvent: "clickClearEvent" }, host: { listeners: { "keydown.arrowDown": "onArrow(-step)", "keydown.arrowUp": "onArrow(step)" }, properties: { "attr.data-appearance": "this.appearance", "attr.data-disabled": "this.isDisabled", "attr.data-size": "this.size" } }, viewQueries: [{ propertyName: "numberInput", first: true, predicate: TuiInputNumberComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiNumberFormat]=\"numberFormat\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiInputNumberModule }, { kind: "component", type: i3.TuiInputNumberComponent, selector: "tui-input-number", inputs: ["min", "max", "step"] }, { kind: "directive", type: i3.TuiInputNumberDirective, selector: "tui-input-number" }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i3.TuiTextfieldLabelOutsideDirective, selector: "[tuiTextfieldLabelOutside]", inputs: ["tuiTextfieldLabelOutside"] }, { kind: "directive", type: i3.TuiTextfieldSizeDirective, selector: "[tuiTextfieldSize]", inputs: ["tuiTextfieldSize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }, { kind: "directive", type: TuiNumberFormat, selector: "[tuiNumberFormat]", inputs: ["tuiNumberFormat"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5370
5428
  }
5371
5429
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScInputQuantityComponent, decorators: [{
5372
5430
  type: Component,
@@ -5383,7 +5441,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5383
5441
  ...TuiHint,
5384
5442
  TuiFieldErrorPipe,
5385
5443
  TuiLoader,
5386
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"] }]
5444
+ TuiNumberFormat,
5445
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiNumberFormat]=\"numberFormat\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"] }]
5387
5446
  }], propDecorators: { numberInput: [{
5388
5447
  type: ViewChild,
5389
5448
  args: [TuiInputNumberComponent]
@@ -5636,11 +5695,11 @@ class ScPriceWarehouseStockComponent {
5636
5695
  }))));
5637
5696
  }
5638
5697
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScPriceWarehouseStockComponent, deps: [{ token: i1.ScWarehouseService }], target: i0.ɵɵFactoryTarget.Component }); }
5639
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScPriceWarehouseStockComponent, isStandalone: true, selector: "sc-price-warehouse-stock", inputs: { classList: "classList", product: "product", withStockHint: "withStockHint", fromMain: "fromMain" }, ngImport: i0, template: "<ng-container *ngIf=\"product\">\n @let isDisabled = product.isHidden || product.isNull;\n\n <ng-container *ngIf=\"selectedWarehouse$ | async as warehouseSelect\">\n @let showStockHint = withStockHint && !!(warehousesList$ | async)?.length;\n <span>\n <ng-container *ngIf=\"!product.stockCount?.length && product.getNotStockMessage(warehouseSelect) as message\">\n @let showPlaningHint = !!(product.properties?.planingIncomingDate || product.properties?.planingProductionDate);\n <a\n tuiLink\n [pseudo]=\"showPlaningHint\"\n [tuiHint]=\"showPlaningHint && planingHint\"\n [class.disabled]=\"isDisabled\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [style.color]=\"'var(--tui-status-negative)'\"\n [style.cursor]=\"showPlaningHint && planingHint ? 'pointer' : 'default'\"\n [ngClass]=\"classList\"\n >\n {{ message }}\n </a>\n </ng-container>\n <ng-template #planingHint>\n <span *ngIf=\"product.properties?.planingIncomingDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u043E\u0441\u0442\u0443\u043F\u043B\u0435\u043D\u0438\u044F <br />\n \u043D\u0430 \u041E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0441\u043A\u043B\u0430\u0434:\n <span class=\"font-bold\">{{ product.properties?.planingIncomingDate }}</span>\n </span>\n <span *ngIf=\"product.properties?.planingProductionDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430:\n <span class=\"font-bold\">{{ product.properties?.planingProductionDate }}</span>\n </span>\n </ng-template>\n <span *ngIf=\"product.stockCount && product.stockCount.length && !product.onOrder\">\n <span\n tuiLink\n [style.color]=\"'var(--tui-status-positive)'\"\n [tuiHint]=\"showStockHint && stockHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [ngClass]=\"classList\"\n [class.disabled]=\"isDisabled\"\n class=\"!underline\"\n >\n \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u043B\u0438\u0447\u0438\u0435\n </span>\n </span>\n\n <span\n *ngIf=\"product.onOrder\"\n [style.color]=\"'var(--tui-status-warning)'\"\n [ngClass]=\"classList\"\n >\n \u041F\u043E\u0434 \u0437\u0430\u043A\u0430\u0437\n </span>\n </span>\n </ng-container>\n\n <ng-template #stockHint>\n <table\n *ngIf=\"selectedWarehouse$ | async as warehouseSelect\"\n class=\"stock-table table-auto text-body-s\"\n [ngClass]=\"classList\"\n >\n <tbody>\n <tr\n *ngFor=\"let item of warehousesList$ | async\"\n class=\"border-b\"\n >\n <ng-container>\n <td class=\"px-1\">{{ item.w?.name }}:</td>\n <td class=\"px-1\">{{ item.sc.count !== undefined && item.sc.count !== null ? item.sc.count + ' ' + product.unit.name : '\u0412 \u043D\u0430\u043B\u0438\u0447\u0438\u0438' }}</td>\n </ng-container>\n </tr>\n </tbody>\n </table>\n </ng-template>\n</ng-container>\n", styles: ["::ng-deep tui-hint:has(.stock-table){max-inline-size:22rem}[tuiIconButton]{--tui-radius-m: .75rem;--tui-height-xs: 1.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5698
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScPriceWarehouseStockComponent, isStandalone: true, selector: "sc-price-warehouse-stock", inputs: { classList: "classList", product: "product", withStockHint: "withStockHint", fromMain: "fromMain" }, ngImport: i0, template: "<ng-container *ngIf=\"product\">\n @let isDisabled = product.isHidden || product.isNull;\n\n <ng-container *ngIf=\"selectedWarehouse$ | async as warehouseSelect\">\n @let showStockHint = withStockHint && !!(warehousesList$ | async)?.length;\n <span>\n <ng-container *ngIf=\"!product.stockCount?.length && product.getNotStockMessage(warehouseSelect) as message\">\n @let showPlaningHint = !!(product.properties?.planingIncomingDate || product.properties?.planingProductionDate);\n <a\n tuiLink\n [pseudo]=\"showPlaningHint\"\n [tuiHint]=\"showPlaningHint && planingHint\"\n [class.disabled]=\"isDisabled\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [style.color]=\"'var(--tui-status-negative)'\"\n [style.cursor]=\"showPlaningHint && planingHint ? 'pointer' : 'default'\"\n [ngClass]=\"classList\"\n >\n {{ message }}\n </a>\n </ng-container>\n <ng-template #planingHint>\n <span *ngIf=\"product.properties?.planingIncomingDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u043E\u0441\u0442\u0443\u043F\u043B\u0435\u043D\u0438\u044F <br />\n \u043D\u0430 \u041E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0441\u043A\u043B\u0430\u0434:\n <span class=\"font-bold\">{{ product.properties?.planingIncomingDate }}</span>\n </span>\n <span *ngIf=\"product.properties?.planingProductionDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430:\n <span class=\"font-bold\">{{ product.properties?.planingProductionDate }}</span>\n </span>\n </ng-template>\n <span *ngIf=\"product.stockCount && product.stockCount.length && !product.onOrder\">\n <span\n tuiLink\n [style.color]=\"'var(--tui-status-positive)'\"\n [tuiHint]=\"showStockHint && stockHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [ngClass]=\"classList\"\n [class.disabled]=\"isDisabled\"\n class=\"!underline\"\n >\n \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u043B\u0438\u0447\u0438\u0435\n </span>\n </span>\n\n <span\n *ngIf=\"product.onOrder\"\n [style.color]=\"'var(--tui-status-warning)'\"\n [ngClass]=\"classList\"\n >\n \u041F\u043E\u0434 \u0437\u0430\u043A\u0430\u0437\n </span>\n </span>\n <p\n *ngIf=\"product.properties?.planingProductionDate\"\n class=\"text-tui-text-02\"\n [class.disabled]=\"isDisabled\"\n [ngClass]=\"classList\"\n >\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430: {{ product.properties?.planingProductionDate }}\n </p>\n </ng-container>\n\n <ng-template #stockHint>\n <table\n *ngIf=\"selectedWarehouse$ | async as warehouseSelect\"\n class=\"stock-table table-auto text-body-s\"\n [ngClass]=\"classList\"\n >\n <tbody>\n <tr\n *ngFor=\"let item of warehousesList$ | async\"\n class=\"border-b\"\n >\n <ng-container>\n <td class=\"px-1\">{{ item.w?.name }}:</td>\n <td class=\"px-1\">{{ item.sc.count !== undefined && item.sc.count !== null ? item.sc.count + ' ' + product.unit.name : '\u0412 \u043D\u0430\u043B\u0438\u0447\u0438\u0438' }}</td>\n </ng-container>\n </tr>\n </tbody>\n </table>\n </ng-template>\n</ng-container>\n", styles: ["::ng-deep tui-hint:has(.stock-table){max-inline-size:22rem}[tuiIconButton]{--tui-radius-m: .75rem;--tui-height-xs: 1.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5640
5699
  }
5641
5700
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScPriceWarehouseStockComponent, decorators: [{
5642
5701
  type: Component,
5643
- args: [{ standalone: true, selector: 'sc-price-warehouse-stock', imports: [CommonModule, RouterModule, TuiTextfieldControllerModule, FormsModule, ReactiveFormsModule, ...TuiHint, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"product\">\n @let isDisabled = product.isHidden || product.isNull;\n\n <ng-container *ngIf=\"selectedWarehouse$ | async as warehouseSelect\">\n @let showStockHint = withStockHint && !!(warehousesList$ | async)?.length;\n <span>\n <ng-container *ngIf=\"!product.stockCount?.length && product.getNotStockMessage(warehouseSelect) as message\">\n @let showPlaningHint = !!(product.properties?.planingIncomingDate || product.properties?.planingProductionDate);\n <a\n tuiLink\n [pseudo]=\"showPlaningHint\"\n [tuiHint]=\"showPlaningHint && planingHint\"\n [class.disabled]=\"isDisabled\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [style.color]=\"'var(--tui-status-negative)'\"\n [style.cursor]=\"showPlaningHint && planingHint ? 'pointer' : 'default'\"\n [ngClass]=\"classList\"\n >\n {{ message }}\n </a>\n </ng-container>\n <ng-template #planingHint>\n <span *ngIf=\"product.properties?.planingIncomingDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u043E\u0441\u0442\u0443\u043F\u043B\u0435\u043D\u0438\u044F <br />\n \u043D\u0430 \u041E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0441\u043A\u043B\u0430\u0434:\n <span class=\"font-bold\">{{ product.properties?.planingIncomingDate }}</span>\n </span>\n <span *ngIf=\"product.properties?.planingProductionDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430:\n <span class=\"font-bold\">{{ product.properties?.planingProductionDate }}</span>\n </span>\n </ng-template>\n <span *ngIf=\"product.stockCount && product.stockCount.length && !product.onOrder\">\n <span\n tuiLink\n [style.color]=\"'var(--tui-status-positive)'\"\n [tuiHint]=\"showStockHint && stockHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [ngClass]=\"classList\"\n [class.disabled]=\"isDisabled\"\n class=\"!underline\"\n >\n \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u043B\u0438\u0447\u0438\u0435\n </span>\n </span>\n\n <span\n *ngIf=\"product.onOrder\"\n [style.color]=\"'var(--tui-status-warning)'\"\n [ngClass]=\"classList\"\n >\n \u041F\u043E\u0434 \u0437\u0430\u043A\u0430\u0437\n </span>\n </span>\n </ng-container>\n\n <ng-template #stockHint>\n <table\n *ngIf=\"selectedWarehouse$ | async as warehouseSelect\"\n class=\"stock-table table-auto text-body-s\"\n [ngClass]=\"classList\"\n >\n <tbody>\n <tr\n *ngFor=\"let item of warehousesList$ | async\"\n class=\"border-b\"\n >\n <ng-container>\n <td class=\"px-1\">{{ item.w?.name }}:</td>\n <td class=\"px-1\">{{ item.sc.count !== undefined && item.sc.count !== null ? item.sc.count + ' ' + product.unit.name : '\u0412 \u043D\u0430\u043B\u0438\u0447\u0438\u0438' }}</td>\n </ng-container>\n </tr>\n </tbody>\n </table>\n </ng-template>\n</ng-container>\n", styles: ["::ng-deep tui-hint:has(.stock-table){max-inline-size:22rem}[tuiIconButton]{--tui-radius-m: .75rem;--tui-height-xs: 1.25rem}\n"] }]
5702
+ args: [{ standalone: true, selector: 'sc-price-warehouse-stock', imports: [CommonModule, RouterModule, TuiTextfieldControllerModule, FormsModule, ReactiveFormsModule, ...TuiHint, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"product\">\n @let isDisabled = product.isHidden || product.isNull;\n\n <ng-container *ngIf=\"selectedWarehouse$ | async as warehouseSelect\">\n @let showStockHint = withStockHint && !!(warehousesList$ | async)?.length;\n <span>\n <ng-container *ngIf=\"!product.stockCount?.length && product.getNotStockMessage(warehouseSelect) as message\">\n @let showPlaningHint = !!(product.properties?.planingIncomingDate || product.properties?.planingProductionDate);\n <a\n tuiLink\n [pseudo]=\"showPlaningHint\"\n [tuiHint]=\"showPlaningHint && planingHint\"\n [class.disabled]=\"isDisabled\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [style.color]=\"'var(--tui-status-negative)'\"\n [style.cursor]=\"showPlaningHint && planingHint ? 'pointer' : 'default'\"\n [ngClass]=\"classList\"\n >\n {{ message }}\n </a>\n </ng-container>\n <ng-template #planingHint>\n <span *ngIf=\"product.properties?.planingIncomingDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u043E\u0441\u0442\u0443\u043F\u043B\u0435\u043D\u0438\u044F <br />\n \u043D\u0430 \u041E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0441\u043A\u043B\u0430\u0434:\n <span class=\"font-bold\">{{ product.properties?.planingIncomingDate }}</span>\n </span>\n <span *ngIf=\"product.properties?.planingProductionDate\">\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430:\n <span class=\"font-bold\">{{ product.properties?.planingProductionDate }}</span>\n </span>\n </ng-template>\n <span *ngIf=\"product.stockCount && product.stockCount.length && !product.onOrder\">\n <span\n tuiLink\n [style.color]=\"'var(--tui-status-positive)'\"\n [tuiHint]=\"showStockHint && stockHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n [ngClass]=\"classList\"\n [class.disabled]=\"isDisabled\"\n class=\"!underline\"\n >\n \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u043B\u0438\u0447\u0438\u0435\n </span>\n </span>\n\n <span\n *ngIf=\"product.onOrder\"\n [style.color]=\"'var(--tui-status-warning)'\"\n [ngClass]=\"classList\"\n >\n \u041F\u043E\u0434 \u0437\u0430\u043A\u0430\u0437\n </span>\n </span>\n <p\n *ngIf=\"product.properties?.planingProductionDate\"\n class=\"text-tui-text-02\"\n [class.disabled]=\"isDisabled\"\n [ngClass]=\"classList\"\n >\n \u041F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u0430\u044F \u0434\u0430\u0442\u0430 \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0441\u0442\u0432\u0430: {{ product.properties?.planingProductionDate }}\n </p>\n </ng-container>\n\n <ng-template #stockHint>\n <table\n *ngIf=\"selectedWarehouse$ | async as warehouseSelect\"\n class=\"stock-table table-auto text-body-s\"\n [ngClass]=\"classList\"\n >\n <tbody>\n <tr\n *ngFor=\"let item of warehousesList$ | async\"\n class=\"border-b\"\n >\n <ng-container>\n <td class=\"px-1\">{{ item.w?.name }}:</td>\n <td class=\"px-1\">{{ item.sc.count !== undefined && item.sc.count !== null ? item.sc.count + ' ' + product.unit.name : '\u0412 \u043D\u0430\u043B\u0438\u0447\u0438\u0438' }}</td>\n </ng-container>\n </tr>\n </tbody>\n </table>\n </ng-template>\n</ng-container>\n", styles: ["::ng-deep tui-hint:has(.stock-table){max-inline-size:22rem}[tuiIconButton]{--tui-radius-m: .75rem;--tui-height-xs: 1.25rem}\n"] }]
5644
5703
  }], ctorParameters: () => [{ type: i1.ScWarehouseService }], propDecorators: { classList: [{
5645
5704
  type: Input
5646
5705
  }], product: [{
@@ -8809,6 +8868,37 @@ class ScOrderItemComponent extends AbstractScPriceCard {
8809
8868
  * Признак, что необходимо отобразить элементы редактирования.
8810
8869
  */
8811
8870
  this.isShowEditControls = input(true);
8871
+ /**
8872
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара (например, цена, зафиксированная в заявке).
8873
+ */
8874
+ this.useLinePrice = input(false);
8875
+ /**
8876
+ * Признак, что позиция, которая не попадёт в заказ, помечается примечанием, а неактивная позиция зачёркивается.
8877
+ */
8878
+ this.markUnavailable = input(false);
8879
+ /**
8880
+ * Признак, что изменение позиции ждёт очереди или выполняется.
8881
+ */
8882
+ this.isPending = input(false);
8883
+ /**
8884
+ * Признак, что параметры позиции можно изменить в диалоге, даже если они ещё не заданы.
8885
+ */
8886
+ this.settingsAvailable = input(false);
8887
+ }
8888
+ /**
8889
+ * Оформление примечания к позиции: группа, которая не участвует в заказе, серым, остальные причины жёлтым.
8890
+ */
8891
+ get unavailableReasonAppearance() {
8892
+ return this.orderItem?.isUnavailableReasonNeutral ? 'neutral' : 'warning';
8893
+ }
8894
+ /**
8895
+ * Подсказка к количеству, когда поля количества нет: где его изменить. У неактивной позиции с примечанием количество не меняется, и подсказки нет.
8896
+ */
8897
+ get quantityHint() {
8898
+ if (this.settingsAvailable()) {
8899
+ return 'Количество и метраж меняются в окне изменения позиции';
8900
+ }
8901
+ return this.markUnavailable() && this.orderItem?.isInactive ? '' : 'Для изменения количества воспользуйтесь конфигуратором';
8812
8902
  }
8813
8903
  /**
8814
8904
  * Отображает спецификацию.
@@ -8829,13 +8919,13 @@ class ScOrderItemComponent extends AbstractScPriceCard {
8829
8919
  }
8830
8920
  }
8831
8921
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
8832
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemComponent, isStandalone: true, selector: "sc-order-item", inputs: { isShowEditControls: { classPropertyName: "isShowEditControls", publicName: "isShowEditControls", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickSettings: "clickSettings" }, providers: [
8922
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemComponent, isStandalone: true, selector: "sc-order-item", inputs: { isShowEditControls: { classPropertyName: "isShowEditControls", publicName: "isShowEditControls", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, markUnavailable: { classPropertyName: "markUnavailable", publicName: "markUnavailable", isSignal: true, isRequired: false, transformFunction: null }, isPending: { classPropertyName: "isPending", publicName: "isPending", isSignal: true, isRequired: false, transformFunction: null }, settingsAvailable: { classPropertyName: "settingsAvailable", publicName: "settingsAvailable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickSettings: "clickSettings" }, providers: [
8833
8923
  SEARCH_TERM_PROVIDERS,
8834
8924
  tuiTextfieldOptionsProvider({
8835
8925
  appearance: signal('textfield'),
8836
8926
  }),
8837
8927
  tuiHintOptionsProvider({ direction: 'top', showDelay: 100, hideDelay: 100 }),
8838
- ], usesInheritance: true, ngImport: i0, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = product.isHidden || product.isNull;\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [step]=\"ScUnitsHelper.getProductMultiplicity(product)\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"showLoader()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker;\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"showLoader()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i2$3.PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: TuiHighlight, selector: "[tuiHighlight]", inputs: ["tuiHighlight", "tuiHighlightColor"] }, { kind: "directive", type: TuiAutoFocus, selector: "[tuiAutoFocus]", inputs: ["tuiAutoFocus"] }, { kind: "component", type: ScHoverImageCarouselComponent, selector: "sc-hover-image-carousel", inputs: ["images", "isShowActions", "productName"] }, { kind: "component", type: CostWithDiscountComponent, selector: "sc-cost-with-discount", inputs: ["product", "size"] }, { kind: "component", type: ScPriceWarehouseStockComponent, selector: "sc-price-warehouse-stock", inputs: ["classList", "product", "withStockHint", "fromMain"] }, { kind: "component", type: ScInputQuantityComponent, selector: "sc-input-quantity", inputs: ["step", "showLoader", "showCross", "ignoreStepValidators", "appearance", "isDisabled", "size"], outputs: ["clickClearEvent"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: i2$1.TuiPreviewComponent, selector: "tui-preview", inputs: ["zoomable", "rotatable", "initialScale"] }, { kind: "directive", type: i2$1.TuiPreviewAction, selector: "[tuiPreviewAction]" }, { kind: "component", type: ScNoindexWrapperComponent, selector: "sc-noindex-wrapper" }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8928
+ ], usesInheritance: true, ngImport: i0, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = orderItem.isInactive;\n @let unavailableReason = markUnavailable() ? orderItem.getUnavailableReason() : '';\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n @if (product.code) {\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n }\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n [class.!line-through]=\"markUnavailable() && isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (unavailableReason) {\n <div class=\"flex flex-wrap items-baseline gap-1\">\n <span class=\"text-tui-text-02\">\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435:</span>\n <tui-badge\n [appearance]=\"unavailableReasonAppearance\"\n size=\"m\"\n class=\"!h-auto max-w-full !whitespace-normal py-0.5 text-start\"\n >\n {{ unavailableReason }}\n </tui-badge>\n </div>\n }\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n @if (useLinePrice()) {\n @if (orderItem.costOneRub > 0 || orderItem.isOrderable) {\n <span\n scPrint=\"cost\"\n class=\"text-body-m\"\n >\n {{ orderItem.costOneRub > 0 ? orderItem.getCostInRubDescPerItem() : orderItem.getCostMessage() }}\n </span>\n }\n } @else {\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n }\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [attr.inert]=\"isDisabled || null\"\n [step]=\"orderItem.quantityStep\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"isPending()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n [tuiHint]=\"quantityHint\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker || settingsAvailable();\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"isPending()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n @if (!(markUnavailable() && product.isNull)) {\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n }\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i2$3.PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: TuiHighlight, selector: "[tuiHighlight]", inputs: ["tuiHighlight", "tuiHighlightColor"] }, { kind: "directive", type: TuiAutoFocus, selector: "[tuiAutoFocus]", inputs: ["tuiAutoFocus"] }, { kind: "component", type: ScHoverImageCarouselComponent, selector: "sc-hover-image-carousel", inputs: ["images", "isShowActions", "productName"] }, { kind: "component", type: CostWithDiscountComponent, selector: "sc-cost-with-discount", inputs: ["product", "size"] }, { kind: "component", type: ScPriceWarehouseStockComponent, selector: "sc-price-warehouse-stock", inputs: ["classList", "product", "withStockHint", "fromMain"] }, { kind: "component", type: ScInputQuantityComponent, selector: "sc-input-quantity", inputs: ["step", "showLoader", "showCross", "ignoreStepValidators", "appearance", "isDisabled", "size"], outputs: ["clickClearEvent"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: i2$1.TuiPreviewComponent, selector: "tui-preview", inputs: ["zoomable", "rotatable", "initialScale"] }, { kind: "directive", type: i2$1.TuiPreviewAction, selector: "[tuiPreviewAction]" }, { kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }, { kind: "component", type: ScNoindexWrapperComponent, selector: "sc-noindex-wrapper" }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8839
8929
  }
8840
8930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemComponent, decorators: [{
8841
8931
  type: Component,
@@ -8862,6 +8952,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8862
8952
  TuiCurrencyPipe,
8863
8953
  TuiFormatNumberPipe,
8864
8954
  TuiPreview,
8955
+ TuiBadge,
8865
8956
  ScNoindexWrapperComponent,
8866
8957
  TuiAppearance,
8867
8958
  TuiButtonLoading,
@@ -8872,7 +8963,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8872
8963
  appearance: signal('textfield'),
8873
8964
  }),
8874
8965
  tuiHintOptionsProvider({ direction: 'top', showDelay: 100, hideDelay: 100 }),
8875
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = product.isHidden || product.isNull;\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [step]=\"ScUnitsHelper.getProductMultiplicity(product)\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"showLoader()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker;\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"showLoader()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n" }]
8966
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = orderItem.isInactive;\n @let unavailableReason = markUnavailable() ? orderItem.getUnavailableReason() : '';\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n @if (product.code) {\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n }\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n [class.!line-through]=\"markUnavailable() && isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (unavailableReason) {\n <div class=\"flex flex-wrap items-baseline gap-1\">\n <span class=\"text-tui-text-02\">\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435:</span>\n <tui-badge\n [appearance]=\"unavailableReasonAppearance\"\n size=\"m\"\n class=\"!h-auto max-w-full !whitespace-normal py-0.5 text-start\"\n >\n {{ unavailableReason }}\n </tui-badge>\n </div>\n }\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n @if (useLinePrice()) {\n @if (orderItem.costOneRub > 0 || orderItem.isOrderable) {\n <span\n scPrint=\"cost\"\n class=\"text-body-m\"\n >\n {{ orderItem.costOneRub > 0 ? orderItem.getCostInRubDescPerItem() : orderItem.getCostMessage() }}\n </span>\n }\n } @else {\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n }\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [attr.inert]=\"isDisabled || null\"\n [step]=\"orderItem.quantityStep\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"isPending()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n [tuiHint]=\"quantityHint\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker || settingsAvailable();\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"isPending()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n @if (!(markUnavailable() && product.isNull)) {\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n }\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n" }]
8876
8967
  }] });
8877
8968
 
8878
8969
  /* eslint-disable no-param-reassign */
@@ -8880,6 +8971,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8880
8971
  * Компонент списка товаров корзины, заказа или черновика.
8881
8972
  */
8882
8973
  class ScOrderItemsListComponent {
8974
+ /**
8975
+ * Инициализирует экземпляр класса {@link ScOrderItemsListComponent}.
8976
+ * Входы выбора и пометки недоступных позиций могут прийти после списка позиций, поэтому при их изменении выбор синхронизируется заново.
8977
+ */
8883
8978
  constructor() {
8884
8979
  /**
8885
8980
  * Внутренний сигнал списка элементов корзины (товаров).
@@ -8893,6 +8988,43 @@ class ScOrderItemsListComponent {
8893
8988
  * Включить редактирование позиций.
8894
8989
  */
8895
8990
  this.editable = input(false);
8991
+ /**
8992
+ * Признак вывода позиций по группам (группа позиции или «Без группы») с заголовками и итогами. Без признака позиции выводятся одним списком без заголовков.
8993
+ */
8994
+ this.grouped = input(false);
8995
+ /**
8996
+ * Признак, что позиции, которые не попадут в заказ, помечаются примечанием и не выбираются. Неактивные позиции приглушаются и зачёркиваются.
8997
+ */
8998
+ this.markUnavailable = input(false);
8999
+ /**
9000
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9001
+ */
9002
+ this.useLinePrice = input(false);
9003
+ /**
9004
+ * Признак, что параметры позиции меняются в диалоге товара: диалог доступен у любой активной позиции, а количество и метраж измеряемых товаров
9005
+ * меняются только в нём, даже если метраж ещё не задан.
9006
+ */
9007
+ this.measuredInDialog = input(false);
9008
+ /**
9009
+ * Откуда удаляется позиция для текста подтверждения, например «из черновика». Если не задано, позиция удаляется без подтверждения.
9010
+ * Тот же текст используется в предупреждении о нулевом количестве.
9011
+ */
9012
+ this.deleteConfirmationTarget = input(null);
9013
+ /**
9014
+ * Подпись итоговой суммы.
9015
+ */
9016
+ this.totalLabel = input('Итого');
9017
+ /**
9018
+ * Позиции, разбитые на группы. Без группировки все позиции идут одной группой без заголовка.
9019
+ */
9020
+ this.groups = computed(() => {
9021
+ const items = this.itemsSignal();
9022
+ return this.grouped() ? ScOrderItemsGroup.fromItems(items) : [new ScOrderItemsGroup('', items)];
9023
+ });
9024
+ /**
9025
+ * Идентификаторы позиций, изменение или удаление которых ждёт очереди или выполняется.
9026
+ */
9027
+ this.pendingItemsIds = signal(new Set());
8896
9028
  /**
8897
9029
  * Внутренняя группа формы для выбора позиций.
8898
9030
  */
@@ -8908,11 +9040,12 @@ class ScOrderItemsListComponent {
8908
9040
  initialValue: [],
8909
9041
  });
8910
9042
  /**
8911
- * Состояние флага «Выбрать все»: `true`, если выбраны все позиции, `false`, если ни одной, и `null`, если только часть.
9043
+ * Состояние флага «Выбрать все»: `true`, если выбраны все доступные для выбора позиции, `false`, если ни одной или выбирать нечего,
9044
+ * и `null`, если только часть.
8912
9045
  */
8913
9046
  this.allItemsSelected = computed(() => {
8914
9047
  const checkedList = this.itemsIds().map(([, checked]) => checked);
8915
- if (checkedList.every(Boolean)) {
9048
+ if (checkedList.length > 0 && checkedList.every(Boolean)) {
8916
9049
  return true;
8917
9050
  }
8918
9051
  return checkedList.some(Boolean) ? null : false;
@@ -8937,7 +9070,7 @@ class ScOrderItemsListComponent {
8937
9070
  const items = this.itemsSignal();
8938
9071
  const selectedItemsIds = this.selectedItemsIds();
8939
9072
  const selectable = this.selectable();
8940
- return ScNumberHelper.roundMoney(items.filter((item) => !selectable || selectedItemsIds.includes(item.id)).reduce((sum, item) => sum + item.costRub, 0));
9073
+ return ScOrderItem.getCostSum(items.filter((item) => !selectable || selectedItemsIds.includes(item.id)));
8941
9074
  });
8942
9075
  /**
8943
9076
  * Сервис получения и редактирования списка товаров заказа (корзина / заказ / черновик = заказ).
@@ -8961,6 +9094,21 @@ class ScOrderItemsListComponent {
8961
9094
  * Предоставляется родителем через токен {@link SC_DIALOG_SERVICE_TOKEN}.
8962
9095
  */
8963
9096
  this.scDialogService = inject(SC_DIALOG_SERVICE_TOKEN);
9097
+ /**
9098
+ * Сервис диалоговых окон Taiga для подтверждений.
9099
+ */
9100
+ this.dialogService = inject(TuiDialogService);
9101
+ /**
9102
+ * Сервис уведомлений.
9103
+ */
9104
+ this.alerts = inject(TuiAlertService);
9105
+ effect(() => {
9106
+ this.markUnavailable();
9107
+ this.selectable();
9108
+ untracked(() => {
9109
+ this.syncSelectionControls();
9110
+ });
9111
+ });
8964
9112
  }
8965
9113
  /**
8966
9114
  * Список элементов корзины (товаров). При установке синхронно обновляет контролы выбора в форме.
@@ -8971,6 +9119,7 @@ class ScOrderItemsListComponent {
8971
9119
  }
8972
9120
  /**
8973
9121
  * Синхронизирует контролы формы выбора с текущим списком товаров: удаляет контролы для отсутствующих id, добавляет для новых.
9122
+ * Контролы меняются без событий, а значение группы пересчитывается один раз в конце: на заявках в сотни позиций это заметно.
8974
9123
  */
8975
9124
  syncSelectionControls() {
8976
9125
  if (!this.selectable()) {
@@ -8980,70 +9129,100 @@ class ScOrderItemsListComponent {
8980
9129
  const currentIds = new Set(currentItems.map((item) => item.id));
8981
9130
  Object.keys(this.selectionFormGroup.controls).forEach((name) => {
8982
9131
  if (!currentIds.has(name)) {
8983
- this.selectionFormGroup.removeControl(name);
9132
+ this.selectionFormGroup.removeControl(name, { emitEvent: false });
8984
9133
  }
8985
9134
  });
8986
9135
  currentItems.forEach((item) => {
8987
- if (!this.selectionFormGroup.contains(item.id)) {
8988
- this.selectionFormGroup.addControl(item.id, new FormControl(true, { nonNullable: true }));
9136
+ const control = this.selectionFormGroup.get(item.id);
9137
+ const isSelectable = !this.markUnavailable() || item.isOrderable;
9138
+ if (!control) {
9139
+ this.selectionFormGroup.addControl(item.id, new FormControl({ value: isSelectable, disabled: !isSelectable }, { nonNullable: true }), { emitEvent: false });
9140
+ }
9141
+ else if (!isSelectable && control.enabled) {
9142
+ control.setValue(false, { emitEvent: false });
9143
+ control.disable({ emitEvent: false });
9144
+ }
9145
+ else if (isSelectable && control.disabled) {
9146
+ control.enable({ emitEvent: false });
9147
+ control.setValue(true, { emitEvent: false });
8989
9148
  }
8990
9149
  });
9150
+ this.selectionFormGroup.updateValueAndValidity();
8991
9151
  }
8992
9152
  /**
8993
- * Выбирает все позиции или снимает выбор со всех.
9153
+ * Выбирает все доступные для выбора позиции или снимает выбор со всех.
8994
9154
  *
8995
9155
  * @param event Событие изменения флага выбора всех позиций.
8996
9156
  */
8997
9157
  selectAllChangeHandler(event) {
8998
9158
  const { checked } = event.target;
8999
- this.selectionFormGroup.setValue(Object.fromEntries(Object.keys(this.selectionFormGroup.controls).map((id) => [id, checked])));
9159
+ const enabledIds = Object.entries(this.selectionFormGroup.controls)
9160
+ .filter(([, control]) => control.enabled)
9161
+ .map(([id]) => id);
9162
+ this.selectionFormGroup.patchValue(Object.fromEntries(enabledIds.map((id) => [id, checked])));
9163
+ }
9164
+ /**
9165
+ * Возвращает признак, что позиция приглушена: она неактивна или не выбрана для заказа.
9166
+ *
9167
+ * @param item Позиция.
9168
+ */
9169
+ isItemDimmed(item) {
9170
+ const isUnselected = this.selectable() && !this.selectionFormGroup.get(item.id)?.value;
9171
+ return this.markUnavailable() ? item.isInactive || (item.isOrderable && isUnselected) : isUnselected;
9172
+ }
9173
+ /**
9174
+ * Возвращает признак, что количество и метраж товара позиции меняются в диалоге товара.
9175
+ *
9176
+ * @param item Позиция.
9177
+ */
9178
+ isEditedInDialog(item) {
9179
+ return this.measuredInDialog() && ScUnitsHelper.productIsMeasurable(item.product);
9000
9180
  }
9001
9181
  /**
9002
- * Удаляет позицию.
9182
+ * Удаляет позицию, при необходимости после подтверждения. Сообщение об ошибке показывает перехватчик приложения.
9003
9183
  *
9004
9184
  * @param item Позиция товара для удаления.
9005
- * @param priceCard Компонент карточки товара.
9006
9185
  */
9007
- deleteItem(item, priceCard) {
9008
- if (!this.canEdit()) {
9186
+ deleteItem(item) {
9187
+ if (!this.canEdit() || this.pendingItemsIds().has(item.id)) {
9009
9188
  return;
9010
9189
  }
9011
- priceCard.showLoader.set(true);
9012
- this.orderItemsListLoader
9013
- .deleteProduct$(item, this.orderOptions?.orderId())
9014
- .pipe(catchError(() => {
9015
- priceCard.quantityControl.patchValue(item.quantity, {
9016
- emitEvent: false,
9017
- });
9018
- return of();
9019
- }), finalize(() => {
9020
- priceCard.showLoader.set(false);
9190
+ this.confirmDelete$(item)
9191
+ .pipe(switchMap(() => {
9192
+ this.setItemPending(item.id, true);
9193
+ return this.orderItemsListLoader.deleteProduct$(item, this.orderOptions?.orderId()).pipe(finalize(() => {
9194
+ this.setItemPending(item.id, false);
9195
+ }));
9021
9196
  }), takeUntilDestroyed(this.destroyRef))
9022
- .subscribe();
9197
+ .subscribe({ error: noop });
9023
9198
  }
9024
9199
  /**
9025
- * Обновляет количество товара.
9200
+ * Обновляет количество товара. Количество неактивной позиции не меняется. Пустое или нулевое значение возвращается к сохранённому количеству.
9026
9201
  *
9027
9202
  * @param newQuantity Новое значение количества товара.
9028
9203
  * @param priceCard Компонент карточки товара.
9029
9204
  */
9030
9205
  patchItem(newQuantity, priceCard) {
9031
- if (!this.canEdit() || !priceCard.orderItem || !priceCard.product || !newQuantity) {
9206
+ const { orderItem } = priceCard;
9207
+ if (!this.canEdit() || !orderItem || !priceCard.product || orderItem.isInactive) {
9208
+ return;
9209
+ }
9210
+ if (!newQuantity) {
9211
+ priceCard.quantityControl.patchValue(orderItem.quantity, { emitEvent: false });
9212
+ this.warnEmptyQuantity();
9032
9213
  return;
9033
9214
  }
9034
- priceCard.showLoader.set(true);
9215
+ this.setItemPending(orderItem.id, true);
9035
9216
  this.orderItemsListLoader
9036
- .updateProduct$(priceCard.orderItem.id, { quantity: newQuantity }, this.orderOptions?.orderId())
9037
- .pipe(catchError(() => {
9038
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9039
- priceCard.quantityControl.patchValue(priceCard.orderItem.quantity, {
9040
- emitEvent: false,
9041
- });
9042
- return of();
9043
- }), finalize(() => {
9044
- priceCard.showLoader.set(false);
9217
+ .updateProduct$(orderItem.id, { quantity: newQuantity }, this.orderOptions?.orderId())
9218
+ .pipe(finalize(() => {
9219
+ this.setItemPending(orderItem.id, false);
9045
9220
  }), takeUntilDestroyed(this.destroyRef))
9046
- .subscribe();
9221
+ .subscribe({
9222
+ error: () => {
9223
+ priceCard.quantityControl.patchValue((priceCard.orderItem ?? orderItem).quantity, { emitEvent: false });
9224
+ },
9225
+ });
9047
9226
  }
9048
9227
  /**
9049
9228
  * Открывает модальное окно описания товара.
@@ -9058,7 +9237,7 @@ class ScOrderItemsListComponent {
9058
9237
  }
9059
9238
  }
9060
9239
  /**
9061
- * Открывает модальное окно изменения товара или его конфигуратора.
9240
+ * Открывает модальное окно изменения товара или его конфигуратора. Если параметры позиции меняются в диалоге, окно товара открывается и у штучного товара.
9062
9241
  *
9063
9242
  * @param item Позиция товара.
9064
9243
  */
@@ -9067,7 +9246,7 @@ class ScOrderItemsListComponent {
9067
9246
  if (isConfigurator) {
9068
9247
  this.scDialogService.openConfiguratorDialog(item.product, item, this.orderOptions?.orderId());
9069
9248
  }
9070
- else if (ScUnitsHelper.productIsMeasurable(item.product)) {
9249
+ else if (this.measuredInDialog() || ScUnitsHelper.productIsMeasurable(item.product)) {
9071
9250
  this.scDialogService.openAddOrEditingCartItemDialog(item.product, item, this.orderOptions?.orderId(), true);
9072
9251
  }
9073
9252
  }
@@ -9079,8 +9258,53 @@ class ScOrderItemsListComponent {
9079
9258
  openPriceHistoryChartDialog(product) {
9080
9259
  this.scDialogService.openPriceHistoryChartDialog(product);
9081
9260
  }
9261
+ /**
9262
+ * Возвращает {@link Observable}, который выдаёт значение, когда удаление позиции подтверждено или подтверждение не требуется.
9263
+ *
9264
+ * @param item Удаляемая позиция.
9265
+ */
9266
+ confirmDelete$(item) {
9267
+ const target = this.deleteConfirmationTarget();
9268
+ if (!target) {
9269
+ return of(true);
9270
+ }
9271
+ return this.dialogService
9272
+ .open(TUI_CONFIRM, {
9273
+ label: `Удалить позицию «${item.product.name}» ${target}?`,
9274
+ data: { yes: 'Да, удалить', no: 'Нет, оставить' },
9275
+ })
9276
+ .pipe(filter(Boolean));
9277
+ }
9278
+ /**
9279
+ * Отмечает позицию как ожидающую ответа или снимает отметку.
9280
+ *
9281
+ * @param id Идентификатор позиции.
9282
+ * @param isPending Признак ожидания.
9283
+ */
9284
+ setItemPending(id, isPending) {
9285
+ this.pendingItemsIds.update((ids) => {
9286
+ const next = new Set(ids);
9287
+ if (isPending) {
9288
+ next.add(id);
9289
+ }
9290
+ else {
9291
+ next.delete(id);
9292
+ }
9293
+ return next;
9294
+ });
9295
+ }
9296
+ /**
9297
+ * Предупреждает, что количество не может быть пустым или нулевым, а убрать позицию можно удалением. Поле возвращается к сохранённому количеству
9298
+ * в любом списке, предупреждение показывается только в списках с подтверждением удаления.
9299
+ */
9300
+ warnEmptyQuantity() {
9301
+ const target = this.deleteConfirmationTarget();
9302
+ if (target) {
9303
+ this.alerts.open(`Количество должно быть больше нуля. Чтобы убрать позицию ${target}, удалите её.`, { appearance: 'warning' }).subscribe();
9304
+ }
9305
+ }
9082
9306
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9083
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListComponent, isStandalone: true, selector: "sc-order-items-list", inputs: { selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (item of itemsSignal(); track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value;\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n [disabled]=\"itemComponent.showLoader()\"\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator\"\n [isShowEditControls]=\"canEdit()\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"selectable() && !selectionFormGroup.get(item.id)?.value\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item, itemComponent)\"\n [loading]=\"itemComponent.showLoader()\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">\u0418\u0442\u043E\u0433\u043E: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: ScOrderItemComponent, selector: "sc-order-item", inputs: ["isShowEditControls"], outputs: ["clickSettings"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9307
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListComponent, isStandalone: true, selector: "sc-order-items-list", inputs: { selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, grouped: { classPropertyName: "grouped", publicName: "grouped", isSignal: true, isRequired: false, transformFunction: null }, markUnavailable: { classPropertyName: "markUnavailable", publicName: "markUnavailable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, measuredInDialog: { classPropertyName: "measuredInDialog", publicName: "measuredInDialog", isSignal: true, isRequired: false, transformFunction: null }, deleteConfirmationTarget: { classPropertyName: "deleteConfirmationTarget", publicName: "deleteConfirmationTarget", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"selectionFormGroup.disabled\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (group of groups(); track group.name) {\n <!-- \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0433\u0440\u0443\u043F\u043F\u044B \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (group.name) {\n <div class=\"flex flex-wrap items-end justify-between gap-x-4 gap-y-1 border-b border-tui-base-03 pb-2\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"break-words text-body-l-bold\">{{ group.name }}</span>\n <span class=\"text-body-s text-tui-text-02\">\u043F\u043E\u0437\u0438\u0446\u0438\u0439: {{ group.items.length }}, \u0432 \u0437\u0430\u043A\u0430\u0437: {{ group.orderableCount }}</span>\n </div>\n <span class=\"whitespace-nowrap text-body-m-bold\">{{ group.orderableSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</span>\n </div>\n }\n\n @for (item of group.items; track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value || (markUnavailable() && !item.isOrderable);\n @let isPending = pendingItemsIds().has(item.id);\n @let editedInDialog = isEditedInDialog(item);\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator && !editedInDialog\"\n [isShowEditControls]=\"canEdit()\"\n [useLinePrice]=\"useLinePrice()\"\n [markUnavailable]=\"markUnavailable()\"\n [isPending]=\"isPending\"\n [settingsAvailable]=\"canEdit() && measuredInDialog() && !item.configurator && !item.isInactive\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"isItemDimmed(item)\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item)\"\n [loading]=\"isPending\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">{{ totalLabel() }}: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: ScOrderItemComponent, selector: "sc-order-item", inputs: ["isShowEditControls", "useLinePrice", "markUnavailable", "isPending", "settingsAvailable"], outputs: ["clickSettings"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9084
9308
  }
9085
9309
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListComponent, decorators: [{
9086
9310
  type: Component,
@@ -9096,8 +9320,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9096
9320
  TuiButton,
9097
9321
  TuiButtonLoading,
9098
9322
  TuiCurrencyPipe,
9099
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (item of itemsSignal(); track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value;\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n [disabled]=\"itemComponent.showLoader()\"\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator\"\n [isShowEditControls]=\"canEdit()\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"selectable() && !selectionFormGroup.get(item.id)?.value\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item, itemComponent)\"\n [loading]=\"itemComponent.showLoader()\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">\u0418\u0442\u043E\u0433\u043E: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n" }]
9100
- }], propDecorators: { items: [{
9323
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"selectionFormGroup.disabled\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (group of groups(); track group.name) {\n <!-- \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0433\u0440\u0443\u043F\u043F\u044B \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (group.name) {\n <div class=\"flex flex-wrap items-end justify-between gap-x-4 gap-y-1 border-b border-tui-base-03 pb-2\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"break-words text-body-l-bold\">{{ group.name }}</span>\n <span class=\"text-body-s text-tui-text-02\">\u043F\u043E\u0437\u0438\u0446\u0438\u0439: {{ group.items.length }}, \u0432 \u0437\u0430\u043A\u0430\u0437: {{ group.orderableCount }}</span>\n </div>\n <span class=\"whitespace-nowrap text-body-m-bold\">{{ group.orderableSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</span>\n </div>\n }\n\n @for (item of group.items; track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value || (markUnavailable() && !item.isOrderable);\n @let isPending = pendingItemsIds().has(item.id);\n @let editedInDialog = isEditedInDialog(item);\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator && !editedInDialog\"\n [isShowEditControls]=\"canEdit()\"\n [useLinePrice]=\"useLinePrice()\"\n [markUnavailable]=\"markUnavailable()\"\n [isPending]=\"isPending\"\n [settingsAvailable]=\"canEdit() && measuredInDialog() && !item.configurator && !item.isInactive\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"isItemDimmed(item)\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item)\"\n [loading]=\"isPending\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">{{ totalLabel() }}: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n" }]
9324
+ }], ctorParameters: () => [], propDecorators: { items: [{
9101
9325
  type: Input,
9102
9326
  args: [{ required: true }]
9103
9327
  }] } });
@@ -9122,6 +9346,18 @@ class ScOrderItemsListWithActionsComponent {
9122
9346
  * Включить редактирование позиций.
9123
9347
  */
9124
9348
  this.editable = input(false);
9349
+ /**
9350
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9351
+ */
9352
+ this.useLinePrice = input(false);
9353
+ /**
9354
+ * Откуда удаляется позиция для текста подтверждения, например «из черновика». Если не задано, позиция удаляется без подтверждения.
9355
+ */
9356
+ this.deleteConfirmationTarget = input(null);
9357
+ /**
9358
+ * Подпись итоговой суммы.
9359
+ */
9360
+ this.totalLabel = input('Итого');
9125
9361
  /**
9126
9362
  * Позиции, доступные для заказа.
9127
9363
  */
@@ -9145,11 +9381,11 @@ class ScOrderItemsListWithActionsComponent {
9145
9381
  }, { allowSignalWrites: true });
9146
9382
  }
9147
9383
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListWithActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9148
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListWithActionsComponent, isStandalone: true, selector: "sc-order-items-list-with-actions", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9384
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListWithActionsComponent, isStandalone: true, selector: "sc-order-items-list-with-actions", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, deleteConfirmationTarget: { classPropertyName: "deleteConfirmationTarget", publicName: "deleteConfirmationTarget", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9149
9385
  }
9150
9386
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListWithActionsComponent, decorators: [{
9151
9387
  type: Component,
9152
- args: [{ standalone: true, selector: 'sc-order-items-list-with-actions', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n />\n </div>\n }\n</div>\n" }]
9388
+ args: [{ standalone: true, selector: 'sc-order-items-list-with-actions', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n />\n </div>\n }\n</div>\n" }]
9153
9389
  }], ctorParameters: () => [] });
9154
9390
 
9155
9391
  /**
@@ -9169,6 +9405,14 @@ class ScNewOrderItemsComponent {
9169
9405
  * Включить выбор позиций чекбоксами.
9170
9406
  */
9171
9407
  this.selectable = input(true);
9408
+ /**
9409
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9410
+ */
9411
+ this.useLinePrice = input(false);
9412
+ /**
9413
+ * Подпись итоговой суммы.
9414
+ */
9415
+ this.totalLabel = input('Итого');
9172
9416
  /**
9173
9417
  * Время обработки и принятия заказов (из настроек окружения приложения).
9174
9418
  */
@@ -9179,11 +9423,11 @@ class ScNewOrderItemsComponent {
9179
9423
  this.selectedItemsIdsChange = output();
9180
9424
  }
9181
9425
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScNewOrderItemsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9182
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScNewOrderItemsComponent, isStandalone: true, selector: "sc-new-order-items", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, orderProcessingAcceptanceTime: { classPropertyName: "orderProcessingAcceptanceTime", publicName: "orderProcessingAcceptanceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9426
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScNewOrderItemsComponent, isStandalone: true, selector: "sc-new-order-items", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, orderProcessingAcceptanceTime: { classPropertyName: "orderProcessingAcceptanceTime", publicName: "orderProcessingAcceptanceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n [useLinePrice]=\"useLinePrice()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "useLinePrice", "deleteConfirmationTarget", "totalLabel", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9183
9427
  }
9184
9428
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScNewOrderItemsComponent, decorators: [{
9185
9429
  type: Component,
9186
- args: [{ standalone: true, selector: 'sc-new-order-items', imports: [ScOrderItemsListWithActionsComponent, RouterLink, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n" }]
9430
+ args: [{ standalone: true, selector: 'sc-new-order-items', imports: [ScOrderItemsListWithActionsComponent, RouterLink, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n [useLinePrice]=\"useLinePrice()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n" }]
9187
9431
  }] });
9188
9432
 
9189
9433
  /**
@@ -9451,8 +9695,10 @@ class ScOrderFormComponent {
9451
9695
  }
9452
9696
  /**
9453
9697
  * Обрабатывает отправку формы: валидирует и эмитит данные заказа родителю.
9698
+ * Общая ошибка сервера с прошлой отправки (например, 409) не мешает отправить форму снова, ошибки полей остаются до их исправления.
9454
9699
  */
9455
9700
  submit() {
9701
+ this.form.updateValueAndValidity({ emitEvent: false });
9456
9702
  this.form.markAllAsTouched();
9457
9703
  if (!this.form.valid) {
9458
9704
  return;
@@ -9470,7 +9716,7 @@ class ScOrderFormComponent {
9470
9716
  * @param error Ответ сервера с ошибками.
9471
9717
  */
9472
9718
  applyServerErrors(error) {
9473
- const { errors, message } = error.error;
9719
+ const { errors, message } = (error.error ?? {});
9474
9720
  if (errors) {
9475
9721
  Object.keys(errors).forEach((key) => {
9476
9722
  if (key === 'deliveryDate' && isArray(errors[key])) {
@@ -9787,7 +10033,7 @@ class ScOrderItemsListByStockComponent {
9787
10033
  this.selectedItemsIds.update((previous) => [...previous.filter((id) => !items.some((item) => item.id === id) && this.items().some((item) => item.id === id)), ...itemsIds]);
9788
10034
  }
9789
10035
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9790
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListByStockComponent, isStandalone: true, selector: "sc-order-items-list-by-stock", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, warehouseId: { classPropertyName: "warehouseId", publicName: "warehouseId", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, sectionHeader: { classPropertyName: "sectionHeader", publicName: "sectionHeader", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10036
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListByStockComponent, isStandalone: true, selector: "sc-order-items-list-by-stock", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, warehouseId: { classPropertyName: "warehouseId", publicName: "warehouseId", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, sectionHeader: { classPropertyName: "sectionHeader", publicName: "sectionHeader", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9791
10037
  }
9792
10038
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, decorators: [{
9793
10039
  type: Component,
@@ -9795,71 +10041,919 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9795
10041
  }] });
9796
10042
 
9797
10043
  /**
9798
- * Компонент страницы черновика заказа (аналог корзины для одного черновика).
10044
+ * Класс-помощник для работы со списком черновиков.
9799
10045
  */
9800
- class ScDraftComponent {
10046
+ class ScDraftListHelper {
10047
+ /**
10048
+ * Возвращает признак, что состав страницы не изменился: то же общее количество черновиков и те же черновики в том же порядке.
10049
+ *
10050
+ * @param items Текущие элементы списка.
10051
+ * @param total Текущее общее количество черновиков.
10052
+ * @param drafts Черновики полученной страницы.
10053
+ * @param receivedTotal Общее количество черновиков в полученных данных.
10054
+ */
10055
+ static isSamePage(items, total, drafts, receivedTotal) {
10056
+ return total === receivedTotal && items.length === drafts.length && drafts.every((draft, index) => draft.id === items.at(index)?.order.id);
10057
+ }
10058
+ /**
10059
+ * Возвращает подпись состава страницы: общее количество черновиков и номера черновиков страницы по порядку.
10060
+ *
10061
+ * @param page Страница списка.
10062
+ */
10063
+ static getSignature(page) {
10064
+ return `${page.meta.total}:${page.data.map(({ id }) => id).join(',')}`;
10065
+ }
10066
+ /**
10067
+ * Возвращает элементы новой страницы списка. Черновики, которые уже есть в списке, сохраняют раскрытие и полные данные.
10068
+ *
10069
+ * @param items Текущие элементы списка.
10070
+ * @param drafts Черновики новой страницы.
10071
+ */
10072
+ static fromPage(items, drafts) {
10073
+ return drafts.map((short) => {
10074
+ const item = items.find(({ order }) => order.id === short.id);
10075
+ return item ? ScDraftListHelper.update(item, short) : { order: short, isExpanded: false };
10076
+ });
10077
+ }
10078
+ /**
10079
+ * Возвращает список с обновлёнными краткими данными полученных черновиков, состав и порядок списка не меняются.
10080
+ * Полные данные раскрытых черновиков не меняются: их обновляет кэш черновика.
10081
+ *
10082
+ * @param items Текущие элементы списка.
10083
+ * @param drafts Полученные краткие данные черновиков.
10084
+ */
10085
+ static merge(items, drafts) {
10086
+ return items.map((item) => {
10087
+ const short = drafts.find(({ id }) => id === item.order.id);
10088
+ return short ? ScDraftListHelper.update(item, short) : item;
10089
+ });
10090
+ }
10091
+ /**
10092
+ * Возвращает номера черновиков с полными данными, у которых краткие данные отличаются: название или данные актуализации.
10093
+ *
10094
+ * @param items Текущие элементы списка.
10095
+ * @param drafts Полученные краткие данные черновиков.
10096
+ */
10097
+ static getChangedFullDraftIds(items, drafts) {
10098
+ return items
10099
+ .filter(({ order }) => order instanceof ScOrderDraft)
10100
+ .filter(({ order }) => {
10101
+ const short = drafts.find(({ id }) => id === order.id);
10102
+ if (!short) {
10103
+ return false;
10104
+ }
10105
+ const isSameActualization = short.actualization ? short.actualization.isSameAs(order.actualization) : order.actualization === undefined;
10106
+ return short.name !== order.name || !isSameActualization;
10107
+ })
10108
+ .map(({ order }) => order.id);
10109
+ }
10110
+ /**
10111
+ * Обновляет элемент списка краткими данными черновика. Полные данные черновика не заменяются.
10112
+ *
10113
+ * @param item Элемент списка.
10114
+ * @param short Краткие данные черновика.
10115
+ */
10116
+ static update(item, short) {
10117
+ if (!(item.order instanceof ScOrderDraft)) {
10118
+ // eslint-disable-next-line no-param-reassign
10119
+ item.order = short;
10120
+ }
10121
+ return item;
10122
+ }
10123
+ }
10124
+
10125
+ /**
10126
+ * Сервис текущего момента для меток и проверок срока актуализации черновиков Профстрой. Момент обновляется с интервалом проверки срока
10127
+ * актуализации одним таймером на всех подписчиков, поэтому список черновиков, страница черновика и оформление заказа видят один момент.
10128
+ * Таймер работает, только пока есть подписчики.
10129
+ */
10130
+ class ScDraftExpirationClockService {
9801
10131
  constructor() {
9802
10132
  /**
9803
- * Сервис маршрутизации.
10133
+ * Интервалы обновления данных.
9804
10134
  */
9805
- this.router = inject(Router);
10135
+ this.updateInterval = inject(SC_UPDATE_INTERVAL);
9806
10136
  /**
9807
- * Сервис предоставляющий доступ к данным о маршруте.
10137
+ * {@link Observable} текущего момента. Первый подписчик запускает таймер, после отписки всех подписчиков таймер останавливается.
10138
+ * Следующие подписчики сразу получают последний момент, а не новый: он может отставать от текущего времени не больше чем
10139
+ * на интервал проверки срока, и метки срока у нового подписчика обновятся на ближайшем тике общего таймера.
9808
10140
  */
9809
- this.route = inject(ActivatedRoute);
10141
+ this.now$ = timer(0, this.updateInterval.orderDraftExpiration).pipe(map(() => new Date()), shareReplay({ bufferSize: 1, refCount: true }));
10142
+ }
10143
+ /**
10144
+ * Возвращает сигнал текущего момента, который обновляется общим таймером, только пока признак истинен. Вызывается в контексте
10145
+ * внедрения зависимостей, например при объявлении поля компонента: подписка живёт, пока жив компонент.
10146
+ *
10147
+ * @param active Признак, пока он истинен, момент обновляется.
10148
+ */
10149
+ nowWhile(active) {
10150
+ return toSignal(toObservable(active).pipe(switchMap((isActive) => (isActive ? this.now$ : EMPTY))), { initialValue: new Date() });
10151
+ }
10152
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10153
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, providedIn: 'root' }); }
10154
+ }
10155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, decorators: [{
10156
+ type: Injectable,
10157
+ args: [{
10158
+ providedIn: 'root',
10159
+ }]
10160
+ }] });
10161
+
10162
+ /**
10163
+ * Сервис загрузки и фонового обновления страницы списка черновиков. Предоставляется компонентом списка.
10164
+ *
10165
+ * Страница загружается по параметрам из {@link connect} с индикатором загрузки, после показа загруженной страницы сервис сообщает о её загрузке.
10166
+ *
10167
+ * Если состав страницы не изменился, черновики обновляются на месте с сохранением раскрытых строк. Если черновики появились, пропали
10168
+ * или поменялся порядок, полученная страница откладывается и показывается уведомление с предложением перезагрузить список.
10169
+ * После отказа уведомление о том же составе не повторяется. Черновики Профстрой в обработке дополнительно опрашиваются лёгким запросом,
10170
+ * текущий момент для меток срока берётся из общего источника {@link ScDraftExpirationClockService}.
10171
+ */
10172
+ class ScDraftListUpdatesService {
10173
+ /**
10174
+ * Инициализирует экземпляр класса {@link ScDraftListUpdatesService}.
10175
+ */
10176
+ constructor() {
9810
10177
  /**
9811
10178
  * Сервис для работы с черновиками заказов.
9812
10179
  */
9813
10180
  this.draftsService = inject(ScOrderDraftsService);
9814
10181
  /**
9815
- * Сигнал с данными текущего черновика заказа (из параметра orderId в URL).
10182
+ * Сервис уведомлений.
9816
10183
  */
9817
- this.draft = model(null);
10184
+ this.alerts = inject(TuiAlertService);
9818
10185
  /**
9819
- * Идентификаторы позиций черновика, выбранных для оформления заказа.
10186
+ * Сервис текущего момента для меток срока актуализации.
9820
10187
  */
9821
- this.selectedItemsIds = signal([]);
10188
+ this.expirationClock = inject(ScDraftExpirationClockService);
9822
10189
  /**
9823
- * Источник события удаления или отмены заказа.
10190
+ * Ссылка для автоматического управления уничтожением зависимостей.
9824
10191
  */
9825
- this.deleteEvent = output();
10192
+ this.destroyRef = inject(DestroyRef);
9826
10193
  /**
9827
- * Сервис для работы с диалоговыми окнами.
10194
+ * Элементы текущей страницы списка.
9828
10195
  */
9829
- this.dialogService = inject(TuiDialogService);
10196
+ this.pageItems = signal(null);
9830
10197
  /**
9831
- * Текущий ключ маршрута.
10198
+ * Данные пагинации текущей страницы.
9832
10199
  */
9833
- this.isDraftPage = toSignal(this.route.data.pipe(map((data) => data['routes_key'] === ScRouteKeys.draft), distinctUntilChanged()), { initialValue: false });
10200
+ this.pageMeta = signal(null);
9834
10201
  /**
9835
- * Ссылка для автоматического управления уничтожением зависимостей.
10202
+ * Признак загрузки страницы списка по действию пользователя.
9836
10203
  */
9837
- this.destroyRef = inject(DestroyRef);
10204
+ this.pageLoading = signal(false);
9838
10205
  /**
9839
- * Сервис установки SEO-данных приложения.
10206
+ * {@link Subject} события загрузки страницы списка по действию пользователя.
9840
10207
  */
9841
- this.seoService = inject(ScSeoService);
9842
- }
9843
- /** @inheritDoc */
9844
- ngOnInit() {
9845
- if (!this.draft()) {
9846
- this.draftsService
9847
- .get$(Number.parseInt(this.route.snapshot.paramMap.get('orderId') ?? '', 10))
9848
- .pipe(filter(scOrderIsLoaded), takeUntilDestroyed(this.destroyRef))
9849
- .subscribe((draft) => {
9850
- this.draft.set(draft);
9851
- this.seoService.setByDefaultAsNoIndexed('Черновики заказов');
9852
- });
9853
- }
9854
- }
9855
- /**
9856
- * Переход к оформлению заказа из выбранных позиций.
9857
- */
9858
- goToNewOrder() {
9859
- this.router.navigate(['orders', 'new', this.draft()?.id, 'draft'], { queryParams: { itemsIds: this.selectedItemsIds() } }).catch(noop);
9860
- }
9861
- /**
9862
- * Удаляет черновик и перенаправляет к списку черновиков.
10208
+ this.pageLoadedSubject = new Subject();
10209
+ /**
10210
+ * Страница с изменённым составом, которая ждёт решения пользователя.
10211
+ */
10212
+ this.pendingPage = null;
10213
+ /**
10214
+ * Подпись состава страницы, от перезагрузки которой пользователь отказался.
10215
+ */
10216
+ this.declinedSignature = null;
10217
+ /**
10218
+ * Номера черновиков в обработке, которых не оказалось в ответе опроса: черновики удалены, и опрашивать их не нужно.
10219
+ */
10220
+ this.missingIds = signal(new Set());
10221
+ /**
10222
+ * Номера раскрытых черновиков, перезапрос которых уже выполняется.
10223
+ */
10224
+ this.refreshingIds = new Set();
10225
+ /**
10226
+ * Подписка на показанное уведомление об изменении списка.
10227
+ */
10228
+ this.alertSubscription = null;
10229
+ /**
10230
+ * Номера черновиков Профстрой в обработке на текущей странице.
10231
+ */
10232
+ this.processingIds = computed(() => (this.pageItems() ?? [])
10233
+ .filter(({ order }) => order.isProfstroy && order.actualization?.isProcessing === true && !this.missingIds().has(order.id))
10234
+ .map(({ order }) => order.id), { equal: (previous, current) => previous.join(',') === current.join(',') });
10235
+ /**
10236
+ * Элементы текущей страницы списка.
10237
+ */
10238
+ this.items = this.pageItems.asReadonly();
10239
+ /**
10240
+ * Данные пагинации текущей страницы.
10241
+ */
10242
+ this.meta = this.pageMeta.asReadonly();
10243
+ /**
10244
+ * Текущий момент для определения состояния черновиков Профстрой из общего источника.
10245
+ */
10246
+ this.now = toSignal(this.expirationClock.now$, { initialValue: new Date() });
10247
+ /**
10248
+ * Признак загрузки страницы списка по действию пользователя (фильтры, сортировка, пагинация). Фоновые проверки его не включают.
10249
+ */
10250
+ this.loading = this.pageLoading.asReadonly();
10251
+ /**
10252
+ * {@link Observable} события загрузки страницы списка по действию пользователя: страница уже показана.
10253
+ */
10254
+ this.pageLoaded$ = this.pageLoadedSubject.asObservable();
10255
+ toObservable(this.processingIds)
10256
+ .pipe(switchMap((ids) => (ids.length > 0 ? this.draftsService.watchShortList$(ids).pipe(map((drafts) => ({ ids, drafts }))) : EMPTY)), takeUntilDestroyed(this.destroyRef))
10257
+ .subscribe(({ ids, drafts }) => {
10258
+ this.markMissing(ids, drafts);
10259
+ this.applyDrafts(drafts);
10260
+ });
10261
+ this.destroyRef.onDestroy(() => {
10262
+ this.dismissAlert();
10263
+ });
10264
+ }
10265
+ /**
10266
+ * Подключает к сервису параметры запроса текущей страницы: при каждом изменении параметров загружает страницу с индикатором загрузки
10267
+ * и сообщает о её загрузке, а по текущим параметрам в фоне проверяет изменения списка. Если страница не загрузилась, остаётся текущая,
10268
+ * а смена параметров загружает список заново.
10269
+ *
10270
+ * @param params$ {@link Observable} параметров запроса текущей страницы.
10271
+ */
10272
+ connect(params$) {
10273
+ params$
10274
+ .pipe(switchMap((params) => this.draftsService.getList$(params).pipe(tap({
10275
+ subscribe: () => {
10276
+ this.pageLoading.set(true);
10277
+ },
10278
+ finalize: () => {
10279
+ this.pageLoading.set(false);
10280
+ },
10281
+ }), catchError(() => EMPTY))), takeUntilDestroyed(this.destroyRef))
10282
+ .subscribe((page) => {
10283
+ this.setPage(page);
10284
+ this.pageLoadedSubject.next();
10285
+ });
10286
+ params$
10287
+ .pipe(distinctUntilChanged((previous, current) => previous.toString() === current.toString()), switchMap((params) => this.draftsService.watchList$(params)), takeUntilDestroyed(this.destroyRef))
10288
+ .subscribe((page) => {
10289
+ this.checkPage(page);
10290
+ });
10291
+ }
10292
+ /**
10293
+ * Подставляет в строку текущей страницы полные данные черновика. Опрос черновиков в обработке сразу учитывает их состояние.
10294
+ *
10295
+ * @param draft Полные данные черновика.
10296
+ */
10297
+ setFullDraft(draft) {
10298
+ const items = this.pageItems();
10299
+ const item = items?.find(({ order }) => order.id === draft.id);
10300
+ if (!items || !item) {
10301
+ return;
10302
+ }
10303
+ item.order = draft;
10304
+ this.pageItems.set([...items]);
10305
+ }
10306
+ /**
10307
+ * Показывает страницу, загруженную по действию пользователя: строки свёрнуты, отложенные данные и уведомление сбрасываются.
10308
+ *
10309
+ * @param page Страница списка.
10310
+ */
10311
+ setPage(page) {
10312
+ this.pendingPage = null;
10313
+ this.declinedSignature = null;
10314
+ this.missingIds.set(new Set());
10315
+ this.dismissAlert();
10316
+ this.pageItems.set(page.data.map((order) => ({ order, isExpanded: false })));
10317
+ this.pageMeta.set(page.meta);
10318
+ }
10319
+ /**
10320
+ * Сравнивает полученную в фоне страницу с текущей: при том же составе обновляет черновики на месте, иначе откладывает страницу и предлагает перезагрузить список.
10321
+ *
10322
+ * @param page Страница, полученная в фоне.
10323
+ */
10324
+ checkPage(page) {
10325
+ const items = this.pageItems();
10326
+ const meta = this.pageMeta();
10327
+ if (!items || !meta) {
10328
+ return;
10329
+ }
10330
+ if (ScDraftListHelper.isSamePage(items, meta.total, page.data, page.meta.total)) {
10331
+ this.pendingPage = null;
10332
+ this.declinedSignature = null;
10333
+ this.dismissAlert();
10334
+ this.applyDrafts(page.data);
10335
+ return;
10336
+ }
10337
+ this.pendingPage = page;
10338
+ if (ScDraftListHelper.getSignature(page) !== this.declinedSignature) {
10339
+ this.showAlert();
10340
+ }
10341
+ }
10342
+ /**
10343
+ * Обновляет на месте краткие данные черновиков и перезапрашивает полные данные раскрытых черновиков, состояние которых изменилось.
10344
+ * Перезапрос раскрытого черновика не повторяется, пока выполняется предыдущий. Ошибка перезапроса не показывается:
10345
+ * это фоновое обновление, его повторит следующий опрос.
10346
+ *
10347
+ * @param drafts Краткие данные черновиков.
10348
+ */
10349
+ applyDrafts(drafts) {
10350
+ const items = this.pageItems();
10351
+ if (!items) {
10352
+ return;
10353
+ }
10354
+ ScDraftListHelper.getChangedFullDraftIds(items, drafts)
10355
+ .filter((draftId) => !this.refreshingIds.has(draftId))
10356
+ .forEach((draftId) => {
10357
+ this.refreshingIds.add(draftId);
10358
+ this.draftsService
10359
+ .fetch$(draftId)
10360
+ .pipe(catchError(() => EMPTY), finalize(() => {
10361
+ this.refreshingIds.delete(draftId);
10362
+ }), takeUntilDestroyed(this.destroyRef))
10363
+ .subscribe();
10364
+ });
10365
+ this.pageItems.set(ScDraftListHelper.merge(items, drafts));
10366
+ }
10367
+ /**
10368
+ * Запоминает черновики, которых не оказалось в ответе опроса, чтобы больше их не опрашивать.
10369
+ *
10370
+ * @param ids Номера опрошенных черновиков.
10371
+ * @param drafts Полученные краткие данные черновиков.
10372
+ */
10373
+ markMissing(ids, drafts) {
10374
+ const missing = ids.filter((id) => !drafts.some((draft) => draft.id === id));
10375
+ if (missing.length > 0) {
10376
+ this.missingIds.update((current) => new Set([...current, ...missing]));
10377
+ }
10378
+ }
10379
+ /**
10380
+ * Показывает уведомление об изменении списка, если оно ещё не показано. Ответ «Нет» и закрытие уведомления считаются отказом
10381
+ * от перезагрузки отложенного состава.
10382
+ */
10383
+ showAlert() {
10384
+ if (this.alertSubscription) {
10385
+ return;
10386
+ }
10387
+ this.alertSubscription = this.alerts
10388
+ .open(new PolymorpheusComponent(ScConfirmAlertComponent), {
10389
+ label: 'Список черновиков изменился',
10390
+ data: { content: 'Перезагрузить список?' },
10391
+ appearance: 'info',
10392
+ autoClose: 0,
10393
+ })
10394
+ .pipe(defaultIfEmpty(false), finalize(() => {
10395
+ this.alertSubscription = null;
10396
+ }))
10397
+ .subscribe((isConfirmed) => {
10398
+ if (isConfirmed) {
10399
+ this.applyPendingPage();
10400
+ }
10401
+ else {
10402
+ this.declinePendingPage();
10403
+ }
10404
+ });
10405
+ }
10406
+ /**
10407
+ * Закрывает показанное уведомление об изменении списка.
10408
+ */
10409
+ dismissAlert() {
10410
+ this.alertSubscription?.unsubscribe();
10411
+ this.alertSubscription = null;
10412
+ }
10413
+ /**
10414
+ * Показывает отложенную страницу с сохранением раскрытых строк. Уведомление закрывается всякий раз, когда отложенная страница
10415
+ * сбрасывается, поэтому при согласии пользователя она всегда есть.
10416
+ */
10417
+ applyPendingPage() {
10418
+ const page = this.pendingPage;
10419
+ if (!page) {
10420
+ return;
10421
+ }
10422
+ this.pendingPage = null;
10423
+ this.missingIds.set(new Set());
10424
+ this.pageItems.set(ScDraftListHelper.fromPage(this.pageItems() ?? [], page.data));
10425
+ this.pageMeta.set(page.meta);
10426
+ }
10427
+ /**
10428
+ * Запоминает отказ от перезагрузки отложенного состава: уведомление о нём больше не показывается, пока состав снова не изменится.
10429
+ */
10430
+ declinePendingPage() {
10431
+ this.declinedSignature = this.pendingPage ? ScDraftListHelper.getSignature(this.pendingPage) : null;
10432
+ }
10433
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10434
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService }); }
10435
+ }
10436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService, decorators: [{
10437
+ type: Injectable
10438
+ }], ctorParameters: () => [] });
10439
+
10440
+ /**
10441
+ * Перечисление состояний черновика Профстрой для отображения пользователю.
10442
+ */
10443
+ // eslint-disable-next-line no-shadow
10444
+ var ScProfstroyDraftState;
10445
+ (function (ScProfstroyDraftState) {
10446
+ /**
10447
+ * Выполняется обработка.
10448
+ */
10449
+ ScProfstroyDraftState["processing"] = "processing";
10450
+ /**
10451
+ * Актуализирован, срок действия не истёк.
10452
+ */
10453
+ ScProfstroyDraftState["actual"] = "actual";
10454
+ /**
10455
+ * Актуализирован, но срок действия истёк.
10456
+ */
10457
+ ScProfstroyDraftState["outdated"] = "outdated";
10458
+ /**
10459
+ * Обработка завершилась с ошибкой.
10460
+ */
10461
+ ScProfstroyDraftState["error"] = "error";
10462
+ /**
10463
+ * Требуется актуализация после изменения состава.
10464
+ */
10465
+ ScProfstroyDraftState["needsActualization"] = "needsActualization";
10466
+ /**
10467
+ * Состояние не известно.
10468
+ */
10469
+ ScProfstroyDraftState["unknown"] = "unknown";
10470
+ })(ScProfstroyDraftState || (ScProfstroyDraftState = {}));
10471
+
10472
+ /**
10473
+ * Класс-помощник для отображения черновиков Профстрой.
10474
+ */
10475
+ class ScProfstroyDraftHelper {
10476
+ /**
10477
+ * Данные об отображении состояний черновика.
10478
+ */
10479
+ static { this.stateViews = {
10480
+ [ScProfstroyDraftState.processing]: {
10481
+ label: 'Выполняется обработка',
10482
+ appearance: 'info',
10483
+ description: 'Файл обрабатывается, позиции и цены уточняются. Изменение состава и оформление заказа станут доступны после завершения.',
10484
+ },
10485
+ [ScProfstroyDraftState.actual]: {
10486
+ label: 'Актуализирован',
10487
+ appearance: 'positive',
10488
+ description: 'Цены и позиции актуальны, заказ можно оформлять.',
10489
+ },
10490
+ [ScProfstroyDraftState.outdated]: {
10491
+ label: 'Данные устарели',
10492
+ appearance: 'warning',
10493
+ description: 'С момента последней актуализации прошло много времени. Запустите её повторно, чтобы оформить заказ по актуальным ценам.',
10494
+ },
10495
+ [ScProfstroyDraftState.error]: {
10496
+ label: 'Ошибка обработки',
10497
+ appearance: 'negative',
10498
+ description: 'Обработка завершилась с ошибкой. Запустите актуализацию повторно или соберите заявку из файла заново.',
10499
+ },
10500
+ [ScProfstroyDraftState.needsActualization]: {
10501
+ label: 'Требуется актуализация',
10502
+ appearance: 'warning',
10503
+ description: 'Состав заявки изменён после последней актуализации. Запустите актуализацию, чтобы обновить цены и оформить заказ. Цены и примечания к позициям обновятся после актуализации.',
10504
+ },
10505
+ [ScProfstroyDraftState.unknown]: {
10506
+ label: 'Состояние неизвестно',
10507
+ appearance: 'neutral',
10508
+ description: '',
10509
+ },
10510
+ }; }
10511
+ /**
10512
+ * Возвращает состояние черновика на указанный момент.
10513
+ *
10514
+ * @param actualization Данные об актуализации.
10515
+ * @param now Момент проверки.
10516
+ */
10517
+ static getState(actualization, now) {
10518
+ switch (actualization?.status) {
10519
+ case ScOrderDraftActualizationStatus.processing:
10520
+ return ScProfstroyDraftState.processing;
10521
+ case ScOrderDraftActualizationStatus.completed:
10522
+ return actualization.isOutdatedAt(now) ? ScProfstroyDraftState.outdated : ScProfstroyDraftState.actual;
10523
+ case ScOrderDraftActualizationStatus.error:
10524
+ return ScProfstroyDraftState.error;
10525
+ case ScOrderDraftActualizationStatus.needsActualization:
10526
+ return ScProfstroyDraftState.needsActualization;
10527
+ default:
10528
+ return ScProfstroyDraftState.unknown;
10529
+ }
10530
+ }
10531
+ /**
10532
+ * Возвращает данные об отображении состояния черновика на указанный момент.
10533
+ *
10534
+ * @param actualization Данные об актуализации.
10535
+ * @param now Момент проверки.
10536
+ */
10537
+ static getStateView(actualization, now) {
10538
+ return ScProfstroyDraftHelper.stateViews[ScProfstroyDraftHelper.getState(actualization, now)];
10539
+ }
10540
+ /**
10541
+ * Возвращает данные об отображении панели состояния черновика: для актуальной заявки пояснение зависит от того, сколько позиций попадёт в заказ.
10542
+ *
10543
+ * @param draft Черновик Профстрой.
10544
+ * @param now Момент проверки.
10545
+ */
10546
+ static getPanelView(draft, now) {
10547
+ const view = ScProfstroyDraftHelper.getStateView(draft.actualization, now);
10548
+ if (ScProfstroyDraftHelper.getState(draft.actualization, now) !== ScProfstroyDraftState.actual) {
10549
+ return view;
10550
+ }
10551
+ const total = draft.products.length;
10552
+ const orderable = draft.products.filter((item) => item.isOrderable).length;
10553
+ if (orderable === 0) {
10554
+ return {
10555
+ ...view,
10556
+ appearance: 'warning',
10557
+ description: 'Актуализация завершена, но ни одна позиция не попадёт в заказ. Исправьте или удалите позиции с примечанием и запустите актуализацию повторно или соберите заявку заново из файла.',
10558
+ };
10559
+ }
10560
+ return orderable < total ? { ...view, description: `Цены актуальны. В заказ попадут ${orderable} из ${total} позиций, остальные отмечены в поле «Примечание».` } : view;
10561
+ }
10562
+ }
10563
+
10564
+ /**
10565
+ * Компонент панели состояния актуализации черновика Профстрой с действиями актуализации и пересборки.
10566
+ */
10567
+ class ScProfstroyActualizationPanelComponent {
10568
+ constructor() {
10569
+ /**
10570
+ * Список ссылок приложения.
10571
+ */
10572
+ this.urls = inject(SC_URLS);
10573
+ /**
10574
+ * Объект документа.
10575
+ */
10576
+ this.document = inject(DOCUMENT);
10577
+ /**
10578
+ * Черновик Профстрой.
10579
+ */
10580
+ this.draft = input.required();
10581
+ /**
10582
+ * Момент, на который определяется состояние. Передаётся снаружи и обновляется по таймеру проверки срока, иначе метка «Данные устарели» не сменится.
10583
+ */
10584
+ this.now = input.required();
10585
+ /**
10586
+ * Признак, что действия над черновиком недоступны.
10587
+ */
10588
+ this.busy = input(false);
10589
+ /**
10590
+ * Событие запуска актуализации.
10591
+ */
10592
+ this.actualize = output();
10593
+ /**
10594
+ * Событие запуска пересборки из файла.
10595
+ */
10596
+ this.reload = output();
10597
+ /**
10598
+ * Текущее состояние черновика.
10599
+ */
10600
+ this.state = computed(() => ScProfstroyDraftHelper.getState(this.draft().actualization, this.now()));
10601
+ /**
10602
+ * Данные об отображении состояния.
10603
+ */
10604
+ this.stateView = computed(() => ScProfstroyDraftHelper.getPanelView(this.draft(), this.now()));
10605
+ /**
10606
+ * Признак, что выполняется обработка.
10607
+ */
10608
+ this.isProcessing = computed(() => this.state() === ScProfstroyDraftState.processing);
10609
+ /**
10610
+ * Признак, что срок действия актуализации ещё не истёк.
10611
+ */
10612
+ this.isActual = computed(() => this.state() === ScProfstroyDraftState.actual);
10613
+ /**
10614
+ * Абсолютная ссылка на исходный файл заявки. Файл лежит на хосте API, адрес API может быть указан относительно сайта.
10615
+ */
10616
+ this.fileUrl = computed(() => {
10617
+ const url = this.draft().file?.url;
10618
+ return url ? new URL(url, new URL(this.urls.apiUrl, this.document.baseURI)).href : null;
10619
+ });
10620
+ }
10621
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyActualizationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10622
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScProfstroyActualizationPanelComponent, isStandalone: true, selector: "sc-profstroy-actualization-panel", inputs: { draft: { classPropertyName: "draft", publicName: "draft", isSignal: true, isRequired: true, transformFunction: null }, now: { classPropertyName: "now", publicName: "now", isSignal: true, isRequired: true, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actualize: "actualize", reload: "reload" }, ngImport: i0, template: "@let actualization = draft().actualization;\n@let view = stateView();\n\n<tui-notification\n [appearance]=\"view.appearance\"\n size=\"m\"\n>\n <div class=\"flex flex-col items-start gap-2 text-start\">\n <div class=\"flex items-center justify-start gap-2 text-start font-bold\">\n @if (isProcessing()) {\n <tui-loader size=\"s\" />\n }\n {{ view.label }}\n </div>\n\n <div class=\"flex flex-col items-start gap-1\">\n @if (actualization?.actualizedAt; as actualizedAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D {{ actualizedAt | scFormatDate }}</span>\n }\n @if (isActual() && actualization?.expiresAt; as expiresAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0435\u043D \u0434\u043E {{ expiresAt | scFormatDate }}</span>\n }\n @if (view.description) {\n <span>{{ view.description }}</span>\n }\n @if (actualization?.error; as error) {\n <span class=\"font-bold\">{{ error }}</span>\n }\n @if (fileUrl(); as url) {\n <span>\n \u0424\u0430\u0439\u043B \u0437\u0430\u044F\u0432\u043A\u0438:\n <a\n tuiLink\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"break-all\"\n >\n {{ draft().file?.originalName ?? '\u0441\u043A\u0430\u0447\u0430\u0442\u044C' }}\n </a>\n </span>\n }\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.refresh-cw\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"actualize.emit()\"\n >\n \u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.file-up\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"reload.emit()\"\n >\n \u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E \u0438\u0437 \u0444\u0430\u0439\u043B\u0430\n </button>\n </div>\n </div>\n</tui-notification>\n", dependencies: [{ kind: "directive", type: TuiNotification, selector: "tui-notification,a[tuiNotification],button[tuiNotification]", inputs: ["appearance", "icon", "size"] }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "pipe", type: ScFormatDatePipe, name: "scFormatDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10623
+ }
10624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyActualizationPanelComponent, decorators: [{
10625
+ type: Component,
10626
+ args: [{ standalone: true, selector: 'sc-profstroy-actualization-panel', imports: [TuiNotification, TuiLoader, TuiButton, TuiLink, ScFormatDatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let actualization = draft().actualization;\n@let view = stateView();\n\n<tui-notification\n [appearance]=\"view.appearance\"\n size=\"m\"\n>\n <div class=\"flex flex-col items-start gap-2 text-start\">\n <div class=\"flex items-center justify-start gap-2 text-start font-bold\">\n @if (isProcessing()) {\n <tui-loader size=\"s\" />\n }\n {{ view.label }}\n </div>\n\n <div class=\"flex flex-col items-start gap-1\">\n @if (actualization?.actualizedAt; as actualizedAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D {{ actualizedAt | scFormatDate }}</span>\n }\n @if (isActual() && actualization?.expiresAt; as expiresAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0435\u043D \u0434\u043E {{ expiresAt | scFormatDate }}</span>\n }\n @if (view.description) {\n <span>{{ view.description }}</span>\n }\n @if (actualization?.error; as error) {\n <span class=\"font-bold\">{{ error }}</span>\n }\n @if (fileUrl(); as url) {\n <span>\n \u0424\u0430\u0439\u043B \u0437\u0430\u044F\u0432\u043A\u0438:\n <a\n tuiLink\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"break-all\"\n >\n {{ draft().file?.originalName ?? '\u0441\u043A\u0430\u0447\u0430\u0442\u044C' }}\n </a>\n </span>\n }\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.refresh-cw\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"actualize.emit()\"\n >\n \u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.file-up\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"reload.emit()\"\n >\n \u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E \u0438\u0437 \u0444\u0430\u0439\u043B\u0430\n </button>\n </div>\n </div>\n</tui-notification>\n" }]
10627
+ }] });
10628
+
10629
+ /**
10630
+ * Компонент метки «Профстрой» у черновика или заказа, созданного из файла программы «Профстрой».
10631
+ */
10632
+ class ScProfstroyBadgeComponent {
10633
+ constructor() {
10634
+ /**
10635
+ * Размер метки.
10636
+ */
10637
+ this.size = input('m');
10638
+ }
10639
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10640
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScProfstroyBadgeComponent, isStandalone: true, selector: "sc-profstroy-badge", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<tui-badge\n class=\"!font-medium\"\n appearance=\"primary\"\n [size]=\"size()\"\n>\n \u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\n</tui-badge>\n", dependencies: [{ kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10641
+ }
10642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyBadgeComponent, decorators: [{
10643
+ type: Component,
10644
+ args: [{ standalone: true, selector: 'sc-profstroy-badge', imports: [TuiBadge], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-badge\n class=\"!font-medium\"\n appearance=\"primary\"\n [size]=\"size()\"\n>\n \u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\n</tui-badge>\n" }]
10645
+ }] });
10646
+
10647
+ /**
10648
+ * Компонент меток черновика Профстрой: признак и текущее состояние обработки.
10649
+ */
10650
+ class ScProfstroyDraftBadgesComponent {
10651
+ constructor() {
10652
+ /**
10653
+ * Данные об актуализации черновика.
10654
+ */
10655
+ this.actualization = input();
10656
+ /**
10657
+ * Момент, на который определяется состояние. Передаётся снаружи и обновляется по таймеру проверки срока, иначе метка «Данные устарели» не сменится.
10658
+ */
10659
+ this.now = input.required();
10660
+ /**
10661
+ * Данные об отображении состояния.
10662
+ */
10663
+ this.stateView = computed(() => ScProfstroyDraftHelper.getStateView(this.actualization(), this.now()));
10664
+ }
10665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftBadgesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10666
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScProfstroyDraftBadgesComponent, isStandalone: true, selector: "sc-profstroy-draft-badges", inputs: { actualization: { classPropertyName: "actualization", publicName: "actualization", isSignal: true, isRequired: false, transformFunction: null }, now: { classPropertyName: "now", publicName: "now", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<span class=\"inline-flex flex-wrap items-center gap-1\">\n <sc-profstroy-badge />\n <tui-badge\n class=\"!font-medium\"\n [appearance]=\"stateView().appearance\"\n size=\"m\"\n >\n {{ stateView().label }}\n </tui-badge>\n</span>\n", dependencies: [{ kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }, { kind: "component", type: ScProfstroyBadgeComponent, selector: "sc-profstroy-badge", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10667
+ }
10668
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftBadgesComponent, decorators: [{
10669
+ type: Component,
10670
+ args: [{ standalone: true, selector: 'sc-profstroy-draft-badges', imports: [TuiBadge, ScProfstroyBadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<span class=\"inline-flex flex-wrap items-center gap-1\">\n <sc-profstroy-badge />\n <tui-badge\n class=\"!font-medium\"\n [appearance]=\"stateView().appearance\"\n size=\"m\"\n >\n {{ stateView().label }}\n </tui-badge>\n</span>\n" }]
10671
+ }] });
10672
+
10673
+ /**
10674
+ * Класс-помощник для сообщений об ошибках запросов к API.
10675
+ */
10676
+ class ScApiErrorHelper {
10677
+ /**
10678
+ * Возвращает текст ошибки из ответа API или указанный текст, если ответ его не содержит.
10679
+ *
10680
+ * @param error Ошибка запроса.
10681
+ * @param fallback Текст по умолчанию.
10682
+ */
10683
+ static getMessage(error, fallback) {
10684
+ const message = error instanceof HttpErrorResponse ? error.error?.message : undefined;
10685
+ return message?.trim() ? message : fallback;
10686
+ }
10687
+ /**
10688
+ * Возвращает признак ответа 404: запись не найдена, например удалена в другой вкладке.
10689
+ *
10690
+ * @param error Ошибка запроса.
10691
+ */
10692
+ static isNotFound(error) {
10693
+ return error instanceof HttpErrorResponse && error.status === HttpStatusCode.NotFound;
10694
+ }
10695
+ }
10696
+
10697
+ /* eslint-disable @typescript-eslint/unbound-method */
10698
+ /**
10699
+ * Компонент диалога создания черновика заказа из файла программы «Профстрой».
10700
+ */
10701
+ class ScProfstroyDraftCreateDialogComponent {
10702
+ constructor() {
10703
+ /**
10704
+ * Контекст диалогового окна, в котором открыт компонент.
10705
+ */
10706
+ this.context = inject(POLYMORPHEUS_CONTEXT);
10707
+ /**
10708
+ * Сервис для работы с черновиками заказов.
10709
+ */
10710
+ this.draftsService = inject(ScOrderDraftsService);
10711
+ /**
10712
+ * Ссылка для автоматического управления уничтожением зависимостей.
10713
+ */
10714
+ this.destroyRef = inject(DestroyRef);
10715
+ /**
10716
+ * Настройки поля выбора файлов, в том числе наибольший размер файла из настроек приложения.
10717
+ */
10718
+ this.inputFilesOptions = inject(TUI_INPUT_FILES_OPTIONS);
10719
+ /**
10720
+ * Допустимое расширение файла.
10721
+ */
10722
+ this.accept = '.xlsx';
10723
+ /**
10724
+ * Наибольшая длина названия заявки.
10725
+ */
10726
+ this.nameMaxLength = 255;
10727
+ /**
10728
+ * Форма создания черновика. Формат и размер файла проверяются на самом поле: после выбора файла поле выбора убирается вместе со своими проверками.
10729
+ */
10730
+ this.form = new FormGroup({
10731
+ file: new FormControl(null, [Validators.required, tuiCreateFileFormatValidator(this.accept), tuiCreateFileSizeValidator(this.inputFilesOptions.maxFileSize)]),
10732
+ name: new FormControl('', { nonNullable: true, validators: Validators.maxLength(this.nameMaxLength) }),
10733
+ });
10734
+ /**
10735
+ * Признак выполнения запроса создания.
10736
+ */
10737
+ this.submitting = signal(false);
10738
+ /**
10739
+ * Признак попытки создать заявку. До неё незаполненное поле файла ошибкой не подсвечивается: поле теряет фокус уже при открытии окна выбора файла.
10740
+ */
10741
+ this.submitAttempted = signal(false);
10742
+ }
10743
+ /**
10744
+ * Удаляет выбранный файл.
10745
+ */
10746
+ removeFile() {
10747
+ this.form.controls.file.setValue(null);
10748
+ }
10749
+ /**
10750
+ * Отправляет файл и создаёт черновик. Ошибки сервера с прошлой отправки не мешают отправить форму снова.
10751
+ */
10752
+ submit() {
10753
+ this.submitAttempted.set(true);
10754
+ Object.values(this.form.controls).forEach((control) => {
10755
+ control.updateValueAndValidity();
10756
+ });
10757
+ this.form.markAllAsTouched();
10758
+ const file = this.form.controls.file.value;
10759
+ if (this.form.invalid || !file || this.submitting()) {
10760
+ return;
10761
+ }
10762
+ this.submitting.set(true);
10763
+ ScFileHelper.readAsUploadedFile$(file)
10764
+ .pipe(switchMap((uploadedFile) => this.draftsService.createProfstroy$({ file: uploadedFile, name: this.form.controls.name.value.trim() || undefined })), finalize(() => {
10765
+ this.submitting.set(false);
10766
+ }), takeUntilDestroyed(this.destroyRef))
10767
+ .subscribe({
10768
+ next: (draft) => {
10769
+ this.context.completeWith(draft);
10770
+ },
10771
+ error: (error) => {
10772
+ this.applyError(error);
10773
+ },
10774
+ });
10775
+ }
10776
+ /**
10777
+ * Показывает ошибку создания в форме: ошибки проверки данных у полей, ошибку чтения файла у поля файла, остальные ошибкой формы (401 обрабатывает авторизация).
10778
+ * Диалог остаётся открытым.
10779
+ *
10780
+ * @param error Ошибка чтения файла или запроса.
10781
+ */
10782
+ applyError(error) {
10783
+ if (!(error instanceof HttpErrorResponse)) {
10784
+ this.form.controls.file.setErrors({ serverResponse: new TuiValidationError('Не удалось прочитать файл.') });
10785
+ return;
10786
+ }
10787
+ if (error.status === HttpStatusCode.Unauthorized) {
10788
+ return;
10789
+ }
10790
+ const { errors } = (error.error ?? {});
10791
+ if (error.status !== HttpStatusCode.UnprocessableEntity || !errors) {
10792
+ this.form.setErrors({ serverResponse: new TuiValidationError(ScApiErrorHelper.getMessage(error, 'Не удалось создать заявку.')) });
10793
+ return;
10794
+ }
10795
+ Object.entries(errors).forEach(([key, messages]) => {
10796
+ const control = key === 'name' ? this.form.controls.name : this.form.controls.file;
10797
+ control.setErrors({ serverResponse: new TuiValidationError(messages.join(' ')) });
10798
+ control.markAsTouched();
10799
+ });
10800
+ }
10801
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftCreateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10802
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScProfstroyDraftCreateDialogComponent, isStandalone: true, selector: "sc-profstroy-draft-create-dialog", providers: [
10803
+ {
10804
+ provide: TUI_INPUT_FILE_TEXTS,
10805
+ useFactory: () => inject(TUI_INPUT_FILE_TEXTS, { skipSelf: true }).pipe(map((texts) => ({ ...texts, formatRejectionReason: 'Файл должен быть в формате .xlsx' }))),
10806
+ },
10807
+ ], ngImport: i0, template: "<form\n [formGroup]=\"form\"\n class=\"flex flex-col gap-5\"\n (ngSubmit)=\"submit()\"\n>\n <!-- \u0424\u0430\u0439\u043B. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u0430 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u0430 \u0444\u0430\u0439\u043B\u0430, \u043F\u043E\u0434 \u043F\u043E\u043B\u0435\u043C \u043E\u043D\u0430 \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F. \u041D\u0435\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u043F\u043E\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443. -->\n <div class=\"flex flex-col gap-1\">\n @let file = form.controls.file.value;\n @let rejected = file | tuiFileRejected: { accept } | async;\n @if (!file) {\n <label tuiInputFiles>\n <input\n tuiInputFiles\n formControlName=\"file\"\n [accept]=\"accept\"\n [invalid]=\"submitAttempted() ? null : false\"\n />\n </label>\n }\n <tui-files>\n @if (file) {\n <tui-file\n [state]=\"rejected ? 'error' : 'normal'\"\n [file]=\"rejected ?? file\"\n (remove)=\"removeFile()\"\n />\n }\n </tui-files>\n <span class=\"text-body-s text-tui-text-02\">\u0424\u0430\u0439\u043B Excel, \u0432\u044B\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0439 \u0438\u0437 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u00AB\u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\u00BB: {{ accept }}</span>\n @if (!rejected) {\n @let fileErrorVisible = submitAttempted() || !form.controls.file.hasError('required');\n <tui-error\n formControlName=\"file\"\n [error]=\"fileErrorVisible ? (['required', 'serverResponse'] | tuiFieldError | async) : null\"\n />\n }\n </div>\n\n <!-- \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438. -->\n <label tuiLabel>\n \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438\n <tui-textfield>\n <input\n tuiTextfield\n formControlName=\"name\"\n placeholder=\"\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0411\u0430\u043B\u043A\u043E\u043D\u044B, \u043E\u0431\u044A\u0435\u043A\u0442 \u043D\u0430 \u041B\u0435\u043D\u0438\u043D\u0430\"\n autocomplete=\"off\"\n [maxLength]=\"nameMaxLength\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n @if (form.errors?.['serverResponse']) {\n <tui-error [error]=\"[] | tuiFieldError | async\" />\n }\n\n <tui-notification\n appearance=\"info\"\n size=\"m\"\n >\n \u041E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0430 \u0444\u0430\u0439\u043B\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F \u0432 \u0444\u043E\u043D\u0435: \u043F\u043E\u0437\u0438\u0446\u0438\u0438 \u0438 \u0446\u0435\u043D\u044B \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435 \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u0440\u0435\u043C\u044F.\n </tui-notification>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n [loading]=\"submitting()\"\n >\n \u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443\n </button>\n <button\n tuiButton\n type=\"button\"\n appearance=\"secondary\"\n [disabled]=\"submitting()\"\n (click)=\"context.$implicit.complete()\"\n >\n \u041E\u0442\u043C\u0435\u043D\u0430\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i2$1.TuiFile, selector: "tui-file,a[tuiFile],button[tuiFile]", inputs: ["file", "state", "size", "showDelete", "showSize", "leftContent"], outputs: ["remove"] }, { kind: "component", type: i2$1.TuiInputFiles, selector: "label[tuiInputFiles]" }, { kind: "component", type: i2$1.TuiFilesComponent, selector: "tui-files", inputs: ["max", "expanded"], outputs: ["expandedChange"] }, { kind: "pipe", type: i2$1.TuiFileRejectedPipe, name: "tuiFileRejected" }, { kind: "directive", type: i2$1.TuiInputFilesDirective, selector: "input[tuiInputFiles]", outputs: ["reject"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "component", type: TuiError, selector: "tui-error", inputs: ["error"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: i1$2.TuiTextfieldComponent, selector: "tui-textfield:not([multi])" }, { kind: "directive", type: i1$2.TuiTextfieldDirective, selector: "input[tuiTextfield]:not([tuiInputCard]):not([tuiInputExpire]):not([tuiInputCVC])" }, { kind: "directive", type: TuiNotification, selector: "tui-notification,a[tuiNotification],button[tuiNotification]", inputs: ["appearance", "icon", "size"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10808
+ }
10809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftCreateDialogComponent, decorators: [{
10810
+ type: Component,
10811
+ args: [{ standalone: true, selector: 'sc-profstroy-draft-create-dialog', imports: [ReactiveFormsModule, TuiFiles, TuiFileRejectedPipe, TuiLabel, TuiError, TuiFieldErrorPipe, TuiTextfield, TuiNotification, TuiButton, TuiButtonLoading, AsyncPipe], providers: [
10812
+ {
10813
+ provide: TUI_INPUT_FILE_TEXTS,
10814
+ useFactory: () => inject(TUI_INPUT_FILE_TEXTS, { skipSelf: true }).pipe(map((texts) => ({ ...texts, formatRejectionReason: 'Файл должен быть в формате .xlsx' }))),
10815
+ },
10816
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form\n [formGroup]=\"form\"\n class=\"flex flex-col gap-5\"\n (ngSubmit)=\"submit()\"\n>\n <!-- \u0424\u0430\u0439\u043B. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u0430 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u0430 \u0444\u0430\u0439\u043B\u0430, \u043F\u043E\u0434 \u043F\u043E\u043B\u0435\u043C \u043E\u043D\u0430 \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F. \u041D\u0435\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u043F\u043E\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443. -->\n <div class=\"flex flex-col gap-1\">\n @let file = form.controls.file.value;\n @let rejected = file | tuiFileRejected: { accept } | async;\n @if (!file) {\n <label tuiInputFiles>\n <input\n tuiInputFiles\n formControlName=\"file\"\n [accept]=\"accept\"\n [invalid]=\"submitAttempted() ? null : false\"\n />\n </label>\n }\n <tui-files>\n @if (file) {\n <tui-file\n [state]=\"rejected ? 'error' : 'normal'\"\n [file]=\"rejected ?? file\"\n (remove)=\"removeFile()\"\n />\n }\n </tui-files>\n <span class=\"text-body-s text-tui-text-02\">\u0424\u0430\u0439\u043B Excel, \u0432\u044B\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0439 \u0438\u0437 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u00AB\u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\u00BB: {{ accept }}</span>\n @if (!rejected) {\n @let fileErrorVisible = submitAttempted() || !form.controls.file.hasError('required');\n <tui-error\n formControlName=\"file\"\n [error]=\"fileErrorVisible ? (['required', 'serverResponse'] | tuiFieldError | async) : null\"\n />\n }\n </div>\n\n <!-- \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438. -->\n <label tuiLabel>\n \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438\n <tui-textfield>\n <input\n tuiTextfield\n formControlName=\"name\"\n placeholder=\"\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0411\u0430\u043B\u043A\u043E\u043D\u044B, \u043E\u0431\u044A\u0435\u043A\u0442 \u043D\u0430 \u041B\u0435\u043D\u0438\u043D\u0430\"\n autocomplete=\"off\"\n [maxLength]=\"nameMaxLength\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n @if (form.errors?.['serverResponse']) {\n <tui-error [error]=\"[] | tuiFieldError | async\" />\n }\n\n <tui-notification\n appearance=\"info\"\n size=\"m\"\n >\n \u041E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0430 \u0444\u0430\u0439\u043B\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F \u0432 \u0444\u043E\u043D\u0435: \u043F\u043E\u0437\u0438\u0446\u0438\u0438 \u0438 \u0446\u0435\u043D\u044B \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435 \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u0440\u0435\u043C\u044F.\n </tui-notification>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n [loading]=\"submitting()\"\n >\n \u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443\n </button>\n <button\n tuiButton\n type=\"button\"\n appearance=\"secondary\"\n [disabled]=\"submitting()\"\n (click)=\"context.$implicit.complete()\"\n >\n \u041E\u0442\u043C\u0435\u043D\u0430\n </button>\n </div>\n</form>\n" }]
10817
+ }] });
10818
+
10819
+ /**
10820
+ * Компонент страницы черновика заказа (аналог корзины для одного черновика).
10821
+ */
10822
+ class ScDraftComponent {
10823
+ /**
10824
+ * Инициализирует экземпляр класса {@link ScDraftComponent}.
10825
+ */
10826
+ constructor() {
10827
+ /**
10828
+ * Сервис маршрутизации.
10829
+ */
10830
+ this.router = inject(Router);
10831
+ /**
10832
+ * Сервис предоставляющий доступ к данным о маршруте.
10833
+ */
10834
+ this.route = inject(ActivatedRoute);
10835
+ /**
10836
+ * Сервис для работы с черновиками заказов.
10837
+ */
10838
+ this.draftsService = inject(ScOrderDraftsService);
10839
+ /**
10840
+ * Сервис уведомлений.
10841
+ */
10842
+ this.alerts = inject(TuiAlertService);
10843
+ /**
10844
+ * Сигнал с данными текущего черновика заказа (из параметра orderId в URL).
10845
+ */
10846
+ this.draft = model(null);
10847
+ /**
10848
+ * Идентификаторы позиций черновика, выбранных для оформления заказа.
10849
+ */
10850
+ this.selectedItemsIds = signal([]);
10851
+ /**
10852
+ * Источник события удаления или отмены заказа.
10853
+ */
10854
+ this.deleteEvent = output();
10855
+ /**
10856
+ * Сервис для работы с диалоговыми окнами.
10857
+ */
10858
+ this.dialogService = inject(TuiDialogService);
10859
+ /**
10860
+ * Текущий ключ маршрута.
10861
+ */
10862
+ this.isDraftPage = toSignal(this.route.data.pipe(map((data) => data['routes_key'] === ScRouteKeys.draft), distinctUntilChanged()), { initialValue: false });
10863
+ /**
10864
+ * Признак черновика Профстрой.
10865
+ */
10866
+ this.isProfstroy = computed(() => this.draft()?.isProfstroy === true);
10867
+ /**
10868
+ * Признак, что идёт обработка черновика Профстрой.
10869
+ */
10870
+ this.isProcessing = computed(() => this.draft()?.actualization?.isProcessing === true);
10871
+ /**
10872
+ * Сервис текущего момента для проверки срока актуализации.
10873
+ */
10874
+ this.expirationClock = inject(ScDraftExpirationClockService);
10875
+ /**
10876
+ * Текущий момент для проверки срока актуализации из общего источника. Обновляется, пока открыт черновик Профстрой.
10877
+ */
10878
+ this.now = this.expirationClock.nowWhile(this.isProfstroy);
10879
+ /**
10880
+ * Признак выполнения актуализации или пересборки черновика Профстрой.
10881
+ */
10882
+ this.actionRunning = signal(false);
10883
+ /**
10884
+ * Признак, что для черновика выполняются или ждут очереди запросы изменения или перечитывания.
10885
+ */
10886
+ this.queueBusy = toSignal(toObservable(this.draft).pipe(map((draft) => draft?.id), distinctUntilChanged(), switchMap((draftId) => (draftId === undefined ? of(false) : this.draftsService.isBusy$(draftId)))), { initialValue: false });
10887
+ /**
10888
+ * Признак, что действия над черновиком Профстрой недоступны, пока выполняется действие или запрос черновика.
10889
+ */
10890
+ this.busy = computed(() => this.actionRunning() || this.queueBusy());
10891
+ /**
10892
+ * Признак, что можно менять состав черновика Профстрой.
10893
+ */
10894
+ this.canEditProfstroy = computed(() => this.isDraftPage() && !this.actionRunning() && !this.isProcessing());
10895
+ /**
10896
+ * Причина, по которой нельзя оформить заказ из черновика Профстрой, или пустая строка.
10897
+ */
10898
+ this.profstroyOrderBlockReason = computed(() => {
10899
+ const reason = this.draft()?.getOrderBlockReason(this.now()) ?? '';
10900
+ return reason || (this.selectedItemsIds().length === 0 ? 'Выберите позиции для заказа.' : '');
10901
+ });
10902
+ /**
10903
+ * Ссылка для автоматического управления уничтожением зависимостей.
10904
+ */
10905
+ this.destroyRef = inject(DestroyRef);
10906
+ /**
10907
+ * Сервис установки SEO-данных приложения.
10908
+ */
10909
+ this.seoService = inject(ScSeoService);
10910
+ this.watchProfstroyProcessing();
10911
+ }
10912
+ /** @inheritDoc */
10913
+ ngOnInit() {
10914
+ if (!this.draft()) {
10915
+ this.draftsService
10916
+ .get$(Number.parseInt(this.route.snapshot.paramMap.get('orderId') ?? '', 10))
10917
+ .pipe(filter(scOrderIsLoaded), takeUntilDestroyed(this.destroyRef))
10918
+ .subscribe((draft) => {
10919
+ this.draft.set(draft);
10920
+ this.seoService.setByDefaultAsNoIndexed('Черновики заказов');
10921
+ });
10922
+ }
10923
+ }
10924
+ /**
10925
+ * Переход к оформлению заказа из выбранных позиций.
10926
+ */
10927
+ goToNewOrder() {
10928
+ this.router.navigate(['orders', 'new', this.draft()?.id, 'draft'], { queryParams: { itemsIds: this.selectedItemsIds() } }).catch(noop);
10929
+ }
10930
+ /**
10931
+ * Запускает актуализацию черновика Профстрой.
10932
+ */
10933
+ actualizeProfstroy() {
10934
+ this.runProfstroyAction((draftId) => this.draftsService.actualizeProfstroy$(draftId));
10935
+ }
10936
+ /**
10937
+ * Запускает пересборку черновика Профстрой из файла после подтверждения.
10938
+ */
10939
+ reloadProfstroy() {
10940
+ this.dialogService
10941
+ .open(TUI_CONFIRM, {
10942
+ label: 'Собрать заявку заново?',
10943
+ data: {
10944
+ content: 'Заявка будет собрана заново из загруженного файла. Изменённые количество, метраж и маркировка позиций, а также удалённые позиции будут потеряны. Продолжить?',
10945
+ yes: 'Да, собрать заново',
10946
+ no: 'Отмена',
10947
+ },
10948
+ })
10949
+ .pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
10950
+ .subscribe(() => {
10951
+ this.runProfstroyAction((draftId) => this.draftsService.reloadProfstroy$(draftId));
10952
+ });
10953
+ }
10954
+ /**
10955
+ * Удаляет черновик и перенаправляет к списку черновиков. Пока черновик удаляется, действия над ним заблокированы.
10956
+ * Если удалить не удалось, пользователь остаётся на странице, сообщение об ошибке показывает перехватчик приложения.
9863
10957
  */
9864
10958
  clearDraft() {
9865
10959
  const { id } = this.draft() ?? {};
@@ -9875,29 +10969,86 @@ class ScDraftComponent {
9875
10969
  no: 'Нет, оставить',
9876
10970
  },
9877
10971
  })
9878
- .pipe(filter(Boolean), switchMap(() => this.draftsService.delete$(id)), takeUntilDestroyed(this.destroyRef))
9879
- .subscribe(() => {
9880
- this.deleteEvent.emit();
9881
- this.router.navigate(['/drafts']).catch(noop);
10972
+ .pipe(filter(Boolean), switchMap(() => {
10973
+ this.actionRunning.set(true);
10974
+ return this.draftsService.delete$(id).pipe(finalize(() => {
10975
+ this.actionRunning.set(false);
10976
+ }));
10977
+ }), takeUntilDestroyed(this.destroyRef))
10978
+ .subscribe({
10979
+ next: () => {
10980
+ this.deleteEvent.emit();
10981
+ this.router.navigate(['/drafts']).catch(noop);
10982
+ },
10983
+ error: noop,
9882
10984
  });
9883
10985
  }
10986
+ /**
10987
+ * Выполняет действие над черновиком Профстрой. Пока действие выполняется, остальные заблокированы.
10988
+ * Ответ попадает в кэш черновика, после отказа 403 или 409 сервис сам перезапрашивает черновик. Сообщение об ошибке показывает перехватчик приложения.
10989
+ *
10990
+ * @param action Функция, возвращающая запрос действия по идентификатору черновика.
10991
+ */
10992
+ runProfstroyAction(action) {
10993
+ const draftId = this.draft()?.id;
10994
+ if (draftId === undefined || this.busy()) {
10995
+ return;
10996
+ }
10997
+ this.actionRunning.set(true);
10998
+ action(draftId)
10999
+ .pipe(finalize(() => {
11000
+ this.actionRunning.set(false);
11001
+ }), takeUntilDestroyed(this.destroyRef))
11002
+ .subscribe({ error: noop });
11003
+ }
11004
+ /**
11005
+ * Пока черновик Профстрой на его странице в обработке, опрашивает состояние лёгким запросом списка и после окончания обработки перезапрашивает черновик.
11006
+ * Опрос идёт, пока перезапрошенный черновик остаётся в обработке: неудачный перезапрос повторяется на следующем шаге.
11007
+ * Если черновика больше нет (удалён в другой вкладке), опрос завершается, а пользователь возвращается к списку. В списке черновиков состояние опрашивает сам список.
11008
+ */
11009
+ watchProfstroyProcessing() {
11010
+ toObservable(computed(() => (this.isDraftPage() && this.isProcessing() ? this.draft()?.id : undefined)))
11011
+ .pipe(distinctUntilChanged(), switchMap((draftId) => draftId === undefined
11012
+ ? EMPTY
11013
+ : this.draftsService.watchShortList$([draftId]).pipe(filter((drafts) => !drafts.some((short) => short.id === draftId && short.actualization?.isProcessing)), exhaustMap(() => this.draftsService.fetch$(draftId).pipe(catchError((error) => (ScApiErrorHelper.isNotFound(error) ? of(null) : EMPTY)))), takeWhile((draft) => draft?.actualization?.isProcessing === true, true), filter((draft) => draft === null), map(() => draftId))), takeUntilDestroyed(this.destroyRef))
11014
+ .subscribe((draftId) => {
11015
+ this.leaveMissingDraft(draftId);
11016
+ });
11017
+ }
11018
+ /**
11019
+ * Сообщает, что черновик не найден, и возвращает к списку черновиков.
11020
+ *
11021
+ * @param draftId Идентификатор черновика.
11022
+ */
11023
+ leaveMissingDraft(draftId) {
11024
+ this.alerts.open(`Черновик №${draftId} не найден. Возможно, он удалён.`, { appearance: 'warning' }).subscribe();
11025
+ this.router.navigate(['/drafts']).catch(noop);
11026
+ }
9884
11027
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9885
11028
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScDraftComponent, isStandalone: true, selector: "sc-draft", inputs: { draft: { classPropertyName: "draft", publicName: "draft", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { draft: "draftChange", deleteEvent: "deleteEvent" }, providers: [
9886
11029
  {
9887
11030
  provide: SC_ORDER_LOADER,
9888
11031
  useExisting: ScOrderDraftsService,
9889
11032
  },
9890
- ], ngImport: i0, template: "@let items = draft()?.products;\n\n@if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n}\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: ScOrderAccessorDirective, selector: "[scOrder]", inputs: ["orderId"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11033
+ ], ngImport: i0, template: "@let currentDraft = draft();\n\n@if (currentDraft && currentDraft.isProfstroy) {\n <div class=\"flex flex-col gap-5\">\n <sc-profstroy-draft-badges\n [actualization]=\"currentDraft.actualization\"\n [now]=\"now()\"\n />\n\n <sc-profstroy-actualization-panel\n [draft]=\"currentDraft\"\n [now]=\"now()\"\n [busy]=\"busy() || !isDraftPage()\"\n (actualize)=\"actualizeProfstroy()\"\n (reload)=\"reloadProfstroy()\"\n />\n\n @if (currentDraft.products.length) {\n <sc-order-items-list\n scOrder\n [orderId]=\"currentDraft.id\"\n [items]=\"currentDraft.products\"\n [grouped]=\"true\"\n [markUnavailable]=\"true\"\n [useLinePrice]=\"true\"\n [measuredInDialog]=\"true\"\n [editable]=\"canEditProfstroy()\"\n [selectable]=\"true\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0437\u0430\u044F\u0432\u043A\u0438\"\n totalLabel=\"\u0418\u0422\u041E\u0413\u041E \u041A \u0417\u0410\u041A\u0410\u0417\u0423\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n } @else {\n <p class=\"text-base font-bold\">{{ isProcessing() ? '\u041F\u043E\u0437\u0438\u0446\u0438\u0438 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0444\u0430\u0439\u043B\u0430' : '\u0412 \u0437\u0430\u044F\u0432\u043A\u0435 \u043D\u0435\u0442 \u043F\u043E\u0437\u0438\u0446\u0438\u0439' }}</p>\n }\n\n <div class=\"flex flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap-reverse sm:items-start\">\n <button\n tuiButton\n appearance=\"secondary\"\n iconStart=\"@tui.trash-2\"\n [disabled]=\"busy()\"\n (click)=\"clearDraft()\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <div class=\"flex flex-col items-center gap-1 sm:ml-auto sm:items-end\">\n <button\n tuiButton\n type=\"button\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"busy() || !!profstroyOrderBlockReason()\"\n (click)=\"goToNewOrder()\"\n >\n \u041E\u0444\u043E\u0440\u043C\u0438\u0442\u044C \u0437\u0430\u043A\u0430\u0437\n </button>\n @if (profstroyOrderBlockReason(); as reason) {\n <span class=\"text-body-s text-tui-text-02\">{{ reason }}</span>\n }\n </div>\n </div>\n </div>\n} @else {\n @let items = draft()?.products;\n\n @if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "useLinePrice", "deleteConfirmationTarget", "totalLabel", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: ScOrderAccessorDirective, selector: "[scOrder]", inputs: ["orderId"] }, { kind: "component", type: ScProfstroyActualizationPanelComponent, selector: "sc-profstroy-actualization-panel", inputs: ["draft", "now", "busy"], outputs: ["actualize", "reload"] }, { kind: "component", type: ScProfstroyDraftBadgesComponent, selector: "sc-profstroy-draft-badges", inputs: ["actualization", "now"] }, { kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9891
11034
  }
9892
11035
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftComponent, decorators: [{
9893
11036
  type: Component,
9894
- args: [{ standalone: true, selector: 'sc-draft', imports: [ScOrderItemsListWithActionsComponent, ScOrderAccessorDirective, TuiButton, TuiIcons], providers: [
11037
+ args: [{ standalone: true, selector: 'sc-draft', imports: [
11038
+ ScOrderItemsListWithActionsComponent,
11039
+ ScOrderAccessorDirective,
11040
+ ScProfstroyActualizationPanelComponent,
11041
+ ScProfstroyDraftBadgesComponent,
11042
+ ScOrderItemsListComponent,
11043
+ TuiButton,
11044
+ TuiIcons,
11045
+ ], providers: [
9895
11046
  {
9896
11047
  provide: SC_ORDER_LOADER,
9897
11048
  useExisting: ScOrderDraftsService,
9898
11049
  },
9899
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let items = draft()?.products;\n\n@if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n}\n" }]
9900
- }] });
11050
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let currentDraft = draft();\n\n@if (currentDraft && currentDraft.isProfstroy) {\n <div class=\"flex flex-col gap-5\">\n <sc-profstroy-draft-badges\n [actualization]=\"currentDraft.actualization\"\n [now]=\"now()\"\n />\n\n <sc-profstroy-actualization-panel\n [draft]=\"currentDraft\"\n [now]=\"now()\"\n [busy]=\"busy() || !isDraftPage()\"\n (actualize)=\"actualizeProfstroy()\"\n (reload)=\"reloadProfstroy()\"\n />\n\n @if (currentDraft.products.length) {\n <sc-order-items-list\n scOrder\n [orderId]=\"currentDraft.id\"\n [items]=\"currentDraft.products\"\n [grouped]=\"true\"\n [markUnavailable]=\"true\"\n [useLinePrice]=\"true\"\n [measuredInDialog]=\"true\"\n [editable]=\"canEditProfstroy()\"\n [selectable]=\"true\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0437\u0430\u044F\u0432\u043A\u0438\"\n totalLabel=\"\u0418\u0422\u041E\u0413\u041E \u041A \u0417\u0410\u041A\u0410\u0417\u0423\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n } @else {\n <p class=\"text-base font-bold\">{{ isProcessing() ? '\u041F\u043E\u0437\u0438\u0446\u0438\u0438 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0444\u0430\u0439\u043B\u0430' : '\u0412 \u0437\u0430\u044F\u0432\u043A\u0435 \u043D\u0435\u0442 \u043F\u043E\u0437\u0438\u0446\u0438\u0439' }}</p>\n }\n\n <div class=\"flex flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap-reverse sm:items-start\">\n <button\n tuiButton\n appearance=\"secondary\"\n iconStart=\"@tui.trash-2\"\n [disabled]=\"busy()\"\n (click)=\"clearDraft()\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <div class=\"flex flex-col items-center gap-1 sm:ml-auto sm:items-end\">\n <button\n tuiButton\n type=\"button\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"busy() || !!profstroyOrderBlockReason()\"\n (click)=\"goToNewOrder()\"\n >\n \u041E\u0444\u043E\u0440\u043C\u0438\u0442\u044C \u0437\u0430\u043A\u0430\u0437\n </button>\n @if (profstroyOrderBlockReason(); as reason) {\n <span class=\"text-body-s text-tui-text-02\">{{ reason }}</span>\n }\n </div>\n </div>\n </div>\n} @else {\n @let items = draft()?.products;\n\n @if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n }\n}\n" }]
11051
+ }], ctorParameters: () => [] });
9901
11052
 
9902
11053
  /**
9903
11054
  * Компонент статуса оплаты заказа.
@@ -12006,5 +13157,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12006
13157
  * Generated bundle index. Do not edit.
12007
13158
  */
12008
13159
 
12009
- export { AbstractScPriceCard, AuthMethod, CURRENT_COUNTRY_ID, CostWithDiscountComponent, FilesAndDocumentsComponent, FilesAndDocumentsModule, FinishDateTimeTransformerDirective, IDLE_ITEM_STATE, IS_DEFAULT_COUNTRY, MAX_FILES_IN_FORM_INPUT, SC_ALLOW_SELECT_TERMINATED, SC_BANNER_DURATION, SC_BLOCK_INCOMPLETE_ADDRESS, SC_CATALOG_FILTER_PREVIEWS, SC_CATALOG_PRODUCTS_FILTERS, SC_CATALOG_SHOW_PRODUCTS_RECURSIVELY, SC_DATE_FORMATTER, SC_DEBOUNCE_TIME_DEFAULT, SC_DIALOG_SERVICE_TOKEN, SC_ERROR_CHANGE_HANDLER, SC_FAQ_GROUP_QUERY_PARAM, SC_FAQ_QUESTION_ANCHOR_PREFIX, SC_HELP_NOTIFICATION_CLOSE, SC_HELP_NOTIFICATION_LIMIT, SC_HIDDEN_PRINT_ELEMENTS, SC_MANAGER_QR_HANDLER, SC_NOTIFY_WHEN_IN_STOCK_REQUIRED_FIELDS, SC_ORDER_FORM_CONFIG, SC_ORDER_OPTIONS, SC_PAGE_SIZE_OPTIONS$1 as SC_PAGE_SIZE_OPTIONS, SC_SHOW_HELP_NOTIFICATION_IN_PHONE_INPUT, SC_USER_CITY_INFO, SC_USER_INFO, SC_USER_PROVIDERS, SC_VERIFICATION_CODE_TIMEOUT, ScAccordionComponent, ScAccordionContentDirective, ScAccordionModule, ScAddContactDialogComponent, ScAddContragentBankAccountsDialogComponent, ScAddContragentDialogComponent, ScAddDeliveryAddressDialogComponent, ScAddOrEditingCartItemDialogComponent, ScAddOrEditingCartItemFormComponent, ScAddressQualityDirective, ScAddressQualityService, ScAddressesSelectionFieldComponent, ScAuthModule, ScBannerComponent, ScBannerModule, ScBrandsListComponent, ScBrandsListModule, ScCartAddProductsFromCsvDialogComponent, ScCatalogFilterCollapsibleComponent, ScCatalogFiltersComponent, ScCatalogModule, ScCategoryCardComponent, ScContactsAccordionComponent, ScContactsModule, ScContragentsAccordionComponent, ScContragentsAccordionItemComponent, ScContragentsModule, ScDeliveryAddressAccordionComponent, ScDeliveryAddressAccordionItemComponent, ScDeliveryAddressModule, ScDownloadPriceListComponent, ScDraftComponent, ScEmailLinkDirective, ScErrorBlockStatusComponent, ScErrorHandlerComponent, ScFavoriteButtonComponent, ScFeedbackFormComponent, ScFocusFirstInvalidFieldDirective, ScFormFieldsModule, ScFormatDatePipe, ScFrequentlyAskedQuestionsComponent, ScFrequentlyAskedQuestionsGroupSelectorComponent, ScFrequentlyAskedQuestionsWithGroupsComponent, ScGratitudeComponent, ScHelpNotificationService, ScHoverImageCarouselComponent, ScInputQuantityComponent, ScLinks, ScManagerCardComponent, ScManagerCardPushComponent, ScNewContactFormComponent, ScNewContragentBankAccountsFormComponent, ScNewContragentFormComponent, ScNewOrderItemsComponent, ScNewsCardComponent, ScNewsCardSkeletonComponent, ScNewsListComponent, ScNewsModule, ScNextInputFocusDirective, ScNextInputFocusModule, ScNoindexDirective, ScNoindexWrapperComponent, ScNotifyWhenInStockDialogComponent, ScOrderAccessorDirective, ScOrderFormComponent, ScOrderItemComponent, ScOrderItemsListByStockComponent, ScOrderItemsListComponent, ScOrderItemsListWithActionsComponent, ScOrderModule, ScPaginationLinksComponent, ScPaymentStatusComponent, ScPersonalDataProcessingPolicyComponent, ScPhoneFormatPipe, ScPreviewSampleComponent, ScPreviewSampleModule, ScPreviewSamplesMosquitoComponent, ScPriceCardComponent, ScPriceCardInlineComponent, ScPriceHistoryComponent, ScPriceListPaginationComponent, ScPriceWarehouseStockComponent, ScPrintDirective, ScPrintService, ScPrivacyPolicyComponent, ScProductInAllWarehousesPipe, ScProfileAccordionsContentComponent, ScProfileModule, ScPublicOfferComponent, ScQRCodeDialogComponent, ScQRCodeModule, ScResetUserPasswordComponent, ScResourcePreviewComponent, ScSandwichComponent, ScSandwichM2Component, ScSandwichM2SkeletonComponent, ScSandwichSkeletonComponent, ScSelectOnFocusinDirective, ScSeoTagsComponent, ScShareButtonComponent, ScShareDialogComponent, ScShareMessengersLinksComponent, ScShareService, ScSignInFormByEmailComponent, ScSignInFormByPhoneComponent, ScSignInFormComponent, ScSignUpFormComponent, ScSimpleSignUpFormComponent, ScSuggestionFieldComponent, ScTelLinkDirective, ScTerminalLinkDirective, ScUpdateUserInfoDialogComponent, ScUserManagersComponent, ScUserModule, ScUserPhoneApproveDialogComponent, ScVerificationModule, ScVerificationPhoneCheckFormComponent, TreeDirective, TreeIconService, TreeLoaderService, TreeTopDirective, cleanupRecaptchaArtifacts, phoneValidator, scAtLeastOneRequiredValidator, scBicValidator, scClientUiIconsName, scCorrespondentAccountValidator, scGetCurrentRoute, scGetFaqGroupQueryParams, scGetFaqQuestionAnchor, scGetFaqQuestionIdFromAnchor, scPasswordConfirmMatchingValidator, stepValidator, tuiDateValueTransformerDefaultProvider };
13160
+ export { AbstractScPriceCard, AuthMethod, CURRENT_COUNTRY_ID, CostWithDiscountComponent, FilesAndDocumentsComponent, FilesAndDocumentsModule, FinishDateTimeTransformerDirective, IDLE_ITEM_STATE, IS_DEFAULT_COUNTRY, MAX_FILES_IN_FORM_INPUT, SC_ALLOW_SELECT_TERMINATED, SC_BANNER_DURATION, SC_BLOCK_INCOMPLETE_ADDRESS, SC_CATALOG_FILTER_PREVIEWS, SC_CATALOG_PRODUCTS_FILTERS, SC_CATALOG_SHOW_PRODUCTS_RECURSIVELY, SC_DATE_FORMATTER, SC_DEBOUNCE_TIME_DEFAULT, SC_DIALOG_SERVICE_TOKEN, SC_ERROR_CHANGE_HANDLER, SC_FAQ_GROUP_QUERY_PARAM, SC_FAQ_QUESTION_ANCHOR_PREFIX, SC_HELP_NOTIFICATION_CLOSE, SC_HELP_NOTIFICATION_LIMIT, SC_HIDDEN_PRINT_ELEMENTS, SC_MANAGER_QR_HANDLER, SC_NOTIFY_WHEN_IN_STOCK_REQUIRED_FIELDS, SC_ORDER_FORM_CONFIG, SC_ORDER_OPTIONS, SC_PAGE_SIZE_OPTIONS$1 as SC_PAGE_SIZE_OPTIONS, SC_SHOW_HELP_NOTIFICATION_IN_PHONE_INPUT, SC_USER_CITY_INFO, SC_USER_INFO, SC_USER_PROVIDERS, SC_VERIFICATION_CODE_TIMEOUT, ScAccordionComponent, ScAccordionContentDirective, ScAccordionModule, ScAddContactDialogComponent, ScAddContragentBankAccountsDialogComponent, ScAddContragentDialogComponent, ScAddDeliveryAddressDialogComponent, ScAddOrEditingCartItemDialogComponent, ScAddOrEditingCartItemFormComponent, ScAddressQualityDirective, ScAddressQualityService, ScAddressesSelectionFieldComponent, ScAuthModule, ScBannerComponent, ScBannerModule, ScBrandsListComponent, ScBrandsListModule, ScCartAddProductsFromCsvDialogComponent, ScCatalogFilterCollapsibleComponent, ScCatalogFiltersComponent, ScCatalogModule, ScCategoryCardComponent, ScConfirmAlertComponent, ScContactsAccordionComponent, ScContactsModule, ScContragentsAccordionComponent, ScContragentsAccordionItemComponent, ScContragentsModule, ScDeliveryAddressAccordionComponent, ScDeliveryAddressAccordionItemComponent, ScDeliveryAddressModule, ScDownloadPriceListComponent, ScDraftComponent, ScDraftExpirationClockService, ScDraftListHelper, ScDraftListUpdatesService, ScEmailLinkDirective, ScErrorBlockStatusComponent, ScErrorHandlerComponent, ScFavoriteButtonComponent, ScFeedbackFormComponent, ScFocusFirstInvalidFieldDirective, ScFormFieldsModule, ScFormatDatePipe, ScFrequentlyAskedQuestionsComponent, ScFrequentlyAskedQuestionsGroupSelectorComponent, ScFrequentlyAskedQuestionsWithGroupsComponent, ScGratitudeComponent, ScHelpNotificationService, ScHoverImageCarouselComponent, ScInputQuantityComponent, ScLinks, ScManagerCardComponent, ScManagerCardPushComponent, ScNewContactFormComponent, ScNewContragentBankAccountsFormComponent, ScNewContragentFormComponent, ScNewOrderItemsComponent, ScNewsCardComponent, ScNewsCardSkeletonComponent, ScNewsListComponent, ScNewsModule, ScNextInputFocusDirective, ScNextInputFocusModule, ScNoindexDirective, ScNoindexWrapperComponent, ScNotifyWhenInStockDialogComponent, ScOrderAccessorDirective, ScOrderFormComponent, ScOrderItemComponent, ScOrderItemsListByStockComponent, ScOrderItemsListComponent, ScOrderItemsListWithActionsComponent, ScOrderModule, ScPaginationLinksComponent, ScPaymentStatusComponent, ScPersonalDataProcessingPolicyComponent, ScPhoneFormatPipe, ScPreviewSampleComponent, ScPreviewSampleModule, ScPreviewSamplesMosquitoComponent, ScPriceCardComponent, ScPriceCardInlineComponent, ScPriceHistoryComponent, ScPriceListPaginationComponent, ScPriceWarehouseStockComponent, ScPrintDirective, ScPrintService, ScPrivacyPolicyComponent, ScProductInAllWarehousesPipe, ScProfileAccordionsContentComponent, ScProfileModule, ScProfstroyActualizationPanelComponent, ScProfstroyBadgeComponent, ScProfstroyDraftBadgesComponent, ScProfstroyDraftCreateDialogComponent, ScProfstroyDraftHelper, ScProfstroyDraftState, ScPublicOfferComponent, ScQRCodeDialogComponent, ScQRCodeModule, ScResetUserPasswordComponent, ScResourcePreviewComponent, ScSandwichComponent, ScSandwichM2Component, ScSandwichM2SkeletonComponent, ScSandwichSkeletonComponent, ScSelectOnFocusinDirective, ScSeoTagsComponent, ScShareButtonComponent, ScShareDialogComponent, ScShareMessengersLinksComponent, ScShareService, ScSignInFormByEmailComponent, ScSignInFormByPhoneComponent, ScSignInFormComponent, ScSignUpFormComponent, ScSimpleSignUpFormComponent, ScSuggestionFieldComponent, ScTelLinkDirective, ScTerminalLinkDirective, ScUpdateUserInfoDialogComponent, ScUserManagersComponent, ScUserModule, ScUserPhoneApproveDialogComponent, ScVerificationModule, ScVerificationPhoneCheckFormComponent, TreeDirective, TreeIconService, TreeLoaderService, TreeTopDirective, cleanupRecaptchaArtifacts, phoneValidator, scAtLeastOneRequiredValidator, scBicValidator, scClientUiIconsName, scCorrespondentAccountValidator, scGetCurrentRoute, scGetFaqGroupQueryParams, scGetFaqQuestionAnchor, scGetFaqQuestionIdFromAnchor, scPasswordConfirmMatchingValidator, stepValidator, tuiDateValueTransformerDefaultProvider };
12010
13161
  //# sourceMappingURL=snabcentr-client-ui.mjs.map