@snabcentr/client-core 3.20.1 → 3.21.0-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 (84) hide show
  1. package/catalog/dto/sc-product.d.ts +7 -0
  2. package/config/sc-i-update-interval.d.ts +12 -0
  3. package/esm2022/auth/services/sc-auth.service.mjs +3 -2
  4. package/esm2022/catalog/classes/sc-catalog-item.mjs +2 -2
  5. package/esm2022/catalog/dto/sc-product.mjs +13 -1
  6. package/esm2022/config/sc-i-update-interval.mjs +1 -1
  7. package/esm2022/helpers/index.mjs +2 -1
  8. package/esm2022/helpers/sc-date-helper.mjs +23 -0
  9. package/esm2022/helpers/sc-units-helper.mjs +3 -2
  10. package/esm2022/order/classes/index.mjs +3 -1
  11. package/esm2022/order/classes/sc-order-draft-actualization.mjs +46 -0
  12. package/esm2022/order/classes/sc-order-draft-short.mjs +7 -2
  13. package/esm2022/order/classes/sc-order-draft.mjs +15 -3
  14. package/esm2022/order/classes/sc-order-item.mjs +23 -1
  15. package/esm2022/order/classes/sc-order-short.mjs +3 -1
  16. package/esm2022/order/classes/sc-profstroy-order-item.mjs +88 -0
  17. package/esm2022/order/enums/index.mjs +2 -1
  18. package/esm2022/order/enums/sc-order-draft-actualization-status.mjs +24 -0
  19. package/esm2022/order/internal/index.mjs +6 -1
  20. package/esm2022/order/internal/sc-i-new-profstroy-order-draft-request.mjs +2 -0
  21. package/esm2022/order/internal/sc-i-order-draft-actualization.mjs +2 -0
  22. package/esm2022/order/internal/sc-i-order-draft-file.mjs +2 -0
  23. package/esm2022/order/internal/sc-i-order-draft-short.mjs +1 -1
  24. package/esm2022/order/internal/sc-i-order-draft.mjs +1 -1
  25. package/esm2022/order/internal/sc-i-order-short.mjs +1 -1
  26. package/esm2022/order/internal/sc-i-profstroy-order-item.mjs +2 -0
  27. package/esm2022/order/internal/sc-i-profstroy-product-stub.mjs +2 -0
  28. package/esm2022/order/services/sc-order-drafts.service.mjs +122 -11
  29. package/esm2022/public-api.mjs +2 -1
  30. package/esm2022/ssr-cache/classes/index.mjs +2 -0
  31. package/esm2022/ssr-cache/classes/sc-ssr-response-cache.mjs +100 -0
  32. package/esm2022/ssr-cache/index.mjs +6 -0
  33. package/esm2022/ssr-cache/interfaces/index.mjs +3 -0
  34. package/esm2022/ssr-cache/interfaces/sc-i-ssr-response-cache-settings.mjs +2 -0
  35. package/esm2022/ssr-cache/interfaces/sc-i-ssr-response-cache-stats.mjs +2 -0
  36. package/esm2022/ssr-cache/internal/is-ssr-response-cacheable.mjs +25 -0
  37. package/esm2022/ssr-cache/internal/sc-i-ssr-cached-response.mjs +2 -0
  38. package/esm2022/ssr-cache/sc-ssr-response-cache.backend.mjs +71 -0
  39. package/esm2022/ssr-cache/sc-ssr-response-cache.providers.mjs +12 -0
  40. package/esm2022/ssr-cache/tokens/index.mjs +3 -0
  41. package/esm2022/ssr-cache/tokens/sc-ssr-response-cache-settings.mjs +8 -0
  42. package/esm2022/ssr-cache/tokens/sc-ssr-response-cache.mjs +13 -0
  43. package/esm2022/user-metrika/enums/sc-user-metrika-goals-enum.mjs +19 -1
  44. package/fesm2022/snabcentr-client-core.mjs +623 -55
  45. package/fesm2022/snabcentr-client-core.mjs.map +1 -1
  46. package/helpers/index.d.ts +1 -0
  47. package/helpers/sc-date-helper.d.ts +12 -0
  48. package/helpers/sc-units-helper.d.ts +1 -1
  49. package/order/classes/index.d.ts +2 -0
  50. package/order/classes/sc-order-draft-actualization.d.ts +47 -0
  51. package/order/classes/sc-order-draft-short.d.ts +16 -2
  52. package/order/classes/sc-order-draft.d.ts +16 -2
  53. package/order/classes/sc-order-item.d.ts +12 -0
  54. package/order/classes/sc-order-short.d.ts +4 -0
  55. package/order/classes/sc-profstroy-order-item.d.ts +73 -0
  56. package/order/enums/index.d.ts +1 -0
  57. package/order/enums/sc-order-draft-actualization-status.d.ts +21 -0
  58. package/order/internal/index.d.ts +5 -0
  59. package/order/internal/sc-i-new-profstroy-order-draft-request.d.ts +14 -0
  60. package/order/internal/sc-i-order-draft-actualization.d.ts +22 -0
  61. package/order/internal/sc-i-order-draft-file.d.ts +13 -0
  62. package/order/internal/sc-i-order-draft-short.d.ts +15 -0
  63. package/order/internal/sc-i-order-draft.d.ts +2 -1
  64. package/order/internal/sc-i-order-short.d.ts +4 -0
  65. package/order/internal/sc-i-profstroy-order-item.d.ts +28 -0
  66. package/order/internal/sc-i-profstroy-product-stub.d.ts +13 -0
  67. package/order/services/sc-order-drafts.service.d.ts +63 -1
  68. package/package.json +1 -1
  69. package/public-api.d.ts +1 -0
  70. package/release_notes.tmp +2 -4
  71. package/ssr-cache/classes/index.d.ts +1 -0
  72. package/ssr-cache/classes/sc-ssr-response-cache.d.ts +59 -0
  73. package/ssr-cache/index.d.ts +5 -0
  74. package/ssr-cache/interfaces/index.d.ts +2 -0
  75. package/ssr-cache/interfaces/sc-i-ssr-response-cache-settings.d.ts +17 -0
  76. package/ssr-cache/interfaces/sc-i-ssr-response-cache-stats.d.ts +17 -0
  77. package/ssr-cache/internal/is-ssr-response-cacheable.d.ts +14 -0
  78. package/ssr-cache/internal/sc-i-ssr-cached-response.d.ts +16 -0
  79. package/ssr-cache/sc-ssr-response-cache.backend.d.ts +42 -0
  80. package/ssr-cache/sc-ssr-response-cache.providers.d.ts +6 -0
  81. package/ssr-cache/tokens/index.d.ts +2 -0
  82. package/ssr-cache/tokens/sc-ssr-response-cache-settings.d.ts +6 -0
  83. package/ssr-cache/tokens/sc-ssr-response-cache.d.ts +6 -0
  84. package/user-metrika/enums/sc-user-metrika-goals-enum.d.ts +18 -0
@@ -1,15 +1,15 @@
1
1
  import * as i1 from 'rxjs';
2
- import { of, startWith as startWith$1, Subject, ReplaySubject, partition, combineLatest, map as map$1, debounceTime, switchMap, tap, filter, shareReplay, distinctUntilChanged, BehaviorSubject, materialize, dematerialize, merge, distinctUntilKeyChanged, skip, first, catchError as catchError$1, throwError, expand, takeWhile, toArray, EMPTY, take, defaultIfEmpty, concatMap, finalize, takeUntil, interval, share, scan } from 'rxjs';
2
+ import { of, startWith as startWith$1, Subject, ReplaySubject, partition, combineLatest, map as map$1, debounceTime, switchMap, tap, filter, shareReplay, distinctUntilChanged, BehaviorSubject, materialize, dematerialize, merge, distinctUntilKeyChanged, skip, first, catchError as catchError$1, throwError, expand, takeWhile, toArray, EMPTY, take, defaultIfEmpty, concatMap, finalize, takeUntil, interval, timer, exhaustMap, share, scan, defer, from, lastValueFrom, timeout, TimeoutError } from 'rxjs';
3
3
  import { map, startWith, catchError } from 'rxjs/operators';
4
- import { parseISO, parse, isValid, format } from 'date-fns';
4
+ import { parseISO, isValid, parse, format } from 'date-fns';
5
5
  import { isString, isNil, isMatch, isArray, omit, get } from 'lodash-es';
6
6
  import * as i0 from '@angular/core';
7
- import { InjectionToken, inject, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, DestroyRef, LOCALE_ID, Pipe, assertInInjectionContext, RendererFactory2 } from '@angular/core';
7
+ import { InjectionToken, inject, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, DestroyRef, LOCALE_ID, Pipe, assertInInjectionContext, RendererFactory2, makeEnvironmentProviders } from '@angular/core';
8
8
  import { WA_LOCAL_STORAGE, WA_USER_AGENT, WA_WINDOW } from '@ng-web-apis/common';
9
9
  import { filterByKey, toValue, WA_STORAGE_EVENT, StorageService } from '@ng-web-apis/storage';
10
10
  import { isPlatformBrowser, isPlatformServer, DOCUMENT, formatDate } from '@angular/common';
11
11
  import * as i1$1 from '@angular/common/http';
12
- import { HttpContextToken, HttpContext, HttpHeaders, HttpClient, HttpParams, HttpErrorResponse, HttpResponse } from '@angular/common/http';
12
+ import { HttpContextToken, HttpContext, HttpHeaders, HttpClient, HttpParams, HttpErrorResponse, HttpResponse, FetchBackend, HttpXhrBackend, HttpStatusCode, HttpBackend } from '@angular/common/http';
13
13
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14
14
  import { tuiIsPresent, TuiDay, tuiPure, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
15
15
  import { tuiCleanObject } from '@taiga-ui/addon-doc/utils';
@@ -767,6 +767,24 @@ var ScUserMetrikaGoalsEnum;
767
767
  * В качестве параметра должен быть указан идентификатор КП/черновика (параметр "offer_id").
768
768
  */
769
769
  ScUserMetrikaGoalsEnum["orderDraftDeleted"] = "ORDER_DRAFT_DELETED";
770
+ /**
771
+ * Черновик заказа создан из файла программы «Профстрой».
772
+ *
773
+ * В качестве параметра должен быть указан идентификатор черновика (параметр "offer_id").
774
+ */
775
+ ScUserMetrikaGoalsEnum["orderDraftProfstroyCreated"] = "ORDER_DRAFT_PROFSTROY_CREATED";
776
+ /**
777
+ * Запущена актуализация черновика Профстрой.
778
+ *
779
+ * В качестве параметра должен быть указан идентификатор черновика (параметр "offer_id").
780
+ */
781
+ ScUserMetrikaGoalsEnum["orderDraftProfstroyActualized"] = "ORDER_DRAFT_PROFSTROY_ACTUALIZED";
782
+ /**
783
+ * Запущена пересборка черновика Профстрой из файла.
784
+ *
785
+ * В качестве параметра должен быть указан идентификатор черновика (параметр "offer_id").
786
+ */
787
+ ScUserMetrikaGoalsEnum["orderDraftProfstroyReloaded"] = "ORDER_DRAFT_PROFSTROY_RELOADED";
770
788
  /**
771
789
  * Открыто диалоговое окно/страница конфигуратора товара.
772
790
  *
@@ -1079,7 +1097,8 @@ class ScAuthService {
1079
1097
  this.tokenService.removeGuestToken();
1080
1098
  return of(null);
1081
1099
  }
1082
- return this.getGuestToken$();
1100
+ // На сервере ключ Гостя некуда сохранить, поэтому запрос не выполняется.
1101
+ return this.isServer ? of(null) : this.getGuestToken$();
1083
1102
  }), takeUntilDestroyed(this.destroyRef))
1084
1103
  .subscribe();
1085
1104
  if (this.isServer) {
@@ -1526,7 +1545,7 @@ class ScCatalogItem extends ScCatalogableItem {
1526
1545
  super(data);
1527
1546
  this.properties = data?.properties;
1528
1547
  // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,no-underscore-dangle,@typescript-eslint/no-unsafe-member-access
1529
- this.seo = data._Seo ?? data?.seo;
1548
+ this.seo = data?._Seo ?? data?.seo;
1530
1549
  this.image = data?.image;
1531
1550
  }
1532
1551
  }
@@ -1714,6 +1733,18 @@ class ScCategory extends ScCatalogItem {
1714
1733
  * Модель данных товара/услуги.
1715
1734
  */
1716
1735
  class ScProduct extends ScCatalogItem {
1736
+ /**
1737
+ * Возвращает товар, не найденный в каталоге, с известными названием и артикулом поставщика.
1738
+ *
1739
+ * @param name Название товара.
1740
+ * @param supplierSku Артикул поставщика.
1741
+ */
1742
+ static createNotFound(name, supplierSku) {
1743
+ const product = new ScProduct(null);
1744
+ product.name = name ?? product.name;
1745
+ product.supplierSku = supplierSku;
1746
+ return product;
1747
+ }
1717
1748
  /**
1718
1749
  * Инициализирует экземпляр класса {@link ScProduct}.
1719
1750
  *
@@ -1866,6 +1897,44 @@ class ScProduct extends ScCatalogItem {
1866
1897
  }
1867
1898
  }
1868
1899
 
1900
+ /**
1901
+ * Класс-помощник для работы со значениями единиц измерения товара.
1902
+ */
1903
+ class ScUnitsHelper {
1904
+ /**
1905
+ * Возвращает признак, что указанный товар имеет линейную единицу измерения.
1906
+ *
1907
+ * @param product Данные о товаре для которого нужно проверить линейность его единицы измерения.
1908
+ */
1909
+ static productIsMeasurable(product) {
1910
+ // У товара, не найденного в каталоге, единицы измерения нет.
1911
+ return !product.isNull && (product.unit.isLinear || product.unit.isSquare);
1912
+ }
1913
+ /**
1914
+ * Возвращает кратность количества для указанного товара.
1915
+ *
1916
+ * Кратность равна {@link ScProduct.minCount} и не зависит от {@link ScIProperties.ignoreMinCountCheck}:
1917
+ * этот признак только снимает валидацию кратности/минимума при ручном вводе, но начальное количество
1918
+ * и шаг кнопок +/- остаются кратны minCount.
1919
+ *
1920
+ * @param product Данные о товаре для которого необходимо вернуть кратность количества.
1921
+ */
1922
+ static getProductMultiplicity(product) {
1923
+ return product.minCount && !ScUnitsHelper.productIsMeasurable(product) ? product.minCount : 1;
1924
+ }
1925
+ /**
1926
+ * Возвращает кратность длины для указанного товара.
1927
+ *
1928
+ * @param product Данные о товаре для которого необходимо вернуть кратность длины.
1929
+ */
1930
+ static getProductLengthMultiplicity(product) {
1931
+ if (product.properties?.lengthStep) {
1932
+ return product.properties.lengthStep;
1933
+ }
1934
+ return !product.properties?.ignoreMinCountCheck && product.minCount ? product.minCount : 0.01;
1935
+ }
1936
+ }
1937
+
1869
1938
  /**
1870
1939
  * Данные о единичной позиции товара/услуги в заказе (корзине / черновике).
1871
1940
  */
@@ -1893,6 +1962,27 @@ class ScOrderItem {
1893
1962
  this.configurator = data.configurator;
1894
1963
  this.configuratorParams = data.configuratorParams;
1895
1964
  }
1965
+ /**
1966
+ * Признак, что позиция может попасть в заказ: товар есть в каталоге и предоставляется.
1967
+ */
1968
+ get isOrderable() {
1969
+ return !this.product.isNull && !this.product.isHidden;
1970
+ }
1971
+ /**
1972
+ * Шаг изменения количества позиции.
1973
+ */
1974
+ get quantityStep() {
1975
+ return ScUnitsHelper.getProductMultiplicity(this.product);
1976
+ }
1977
+ /**
1978
+ * Возвращает причину, по которой позиция не может попасть в заказ, или пустую строку.
1979
+ */
1980
+ getUnavailableReason() {
1981
+ if (this.product.isNull) {
1982
+ return 'Товар не найден в каталоге';
1983
+ }
1984
+ return this.product.isHidden ? 'Товар больше не предоставляется' : '';
1985
+ }
1896
1986
  /**
1897
1987
  * @inheritDoc
1898
1988
  */
@@ -4625,43 +4715,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4625
4715
  }]
4626
4716
  }] });
4627
4717
 
4628
- /**
4629
- * Класс-помощник для работы со значениями единиц измерения товара.
4630
- */
4631
- class ScUnitsHelper {
4632
- /**
4633
- * Возвращает признак, что указанный товар имеет линейную единицу измерения.
4634
- *
4635
- * @param product Данные о товаре для которого нужно проверить линейность его единицы измерения.
4636
- */
4637
- static productIsMeasurable(product) {
4638
- return product.unit.isLinear || product.unit.isSquare;
4639
- }
4640
- /**
4641
- * Возвращает кратность количества для указанного товара.
4642
- *
4643
- * Кратность равна {@link ScProduct.minCount} и не зависит от {@link ScIProperties.ignoreMinCountCheck}:
4644
- * этот признак только снимает валидацию кратности/минимума при ручном вводе, но начальное количество
4645
- * и шаг кнопок +/- остаются кратны minCount.
4646
- *
4647
- * @param product Данные о товаре для которого необходимо вернуть кратность количества.
4648
- */
4649
- static getProductMultiplicity(product) {
4650
- return product.minCount && !ScUnitsHelper.productIsMeasurable(product) ? product.minCount : 1;
4651
- }
4652
- /**
4653
- * Возвращает кратность длины для указанного товара.
4654
- *
4655
- * @param product Данные о товаре для которого необходимо вернуть кратность длины.
4656
- */
4657
- static getProductLengthMultiplicity(product) {
4658
- if (product.properties?.lengthStep) {
4659
- return product.properties.lengthStep;
4660
- }
4661
- return !product.properties?.ignoreMinCountCheck && product.minCount ? product.minCount : 0.01;
4662
- }
4663
- }
4664
-
4665
4718
  /**
4666
4719
  * Возвращает текущий маршрут.
4667
4720
  * Функция проходит по дереву маршрутов начиная с переданного route и возвращает самый вложенный активный маршрут.
@@ -4676,6 +4729,28 @@ const scGetCurrentRoute = (route) => {
4676
4729
  return currentRoute;
4677
4730
  };
4678
4731
 
4732
+ /**
4733
+ * Класс-помощник для работы с датами.
4734
+ */
4735
+ class ScDateHelper {
4736
+ /**
4737
+ * Возвращает дату, полученную из строки в указанном формате, или `undefined`, если значение отсутствует или не является датой.
4738
+ *
4739
+ * @param value Дата строкой или готовый объект даты.
4740
+ * @param format Формат даты строкой (например, {@link ScIDateFormat.api}).
4741
+ */
4742
+ static parse(value, format) {
4743
+ if (value instanceof Date) {
4744
+ return isValid(value) ? value : undefined;
4745
+ }
4746
+ if (!value) {
4747
+ return undefined;
4748
+ }
4749
+ const date = parse(value, format, new Date());
4750
+ return isValid(date) ? date : undefined;
4751
+ }
4752
+ }
4753
+
4679
4754
  /**
4680
4755
  * Токен состояния запроса данных о категории (загрузка / успех / ошибка).
4681
4756
  */
@@ -6808,6 +6883,160 @@ class ScOrderBase {
6808
6883
  }
6809
6884
  }
6810
6885
 
6886
+ /**
6887
+ * Перечисление статусов актуализации черновика Профстрой.
6888
+ */
6889
+ // eslint-disable-next-line no-shadow
6890
+ var ScOrderDraftActualizationStatus;
6891
+ (function (ScOrderDraftActualizationStatus) {
6892
+ /**
6893
+ * Выполняется обработка файла или обмен с 1С.
6894
+ */
6895
+ ScOrderDraftActualizationStatus["processing"] = "processing";
6896
+ /**
6897
+ * Последний обмен с 1С завершён успешно.
6898
+ */
6899
+ ScOrderDraftActualizationStatus["completed"] = "completed";
6900
+ /**
6901
+ * Обработка завершилась с ошибкой.
6902
+ */
6903
+ ScOrderDraftActualizationStatus["error"] = "error";
6904
+ /**
6905
+ * Состав черновика изменён после последней успешной актуализации.
6906
+ */
6907
+ ScOrderDraftActualizationStatus["needsActualization"] = "needs_actualization";
6908
+ })(ScOrderDraftActualizationStatus || (ScOrderDraftActualizationStatus = {}));
6909
+
6910
+ /**
6911
+ * Данные об актуализации черновика Профстрой.
6912
+ */
6913
+ class ScOrderDraftActualization {
6914
+ /**
6915
+ * Инициализирует экземпляр класса {@link ScOrderDraftActualization}.
6916
+ *
6917
+ * @param data Данные от API или готовый экземпляр актуализации.
6918
+ * @param apiDateFormat Формат дат в данных API.
6919
+ */
6920
+ constructor(data, apiDateFormat) {
6921
+ this.status = data?.status ?? ScOrderDraftActualizationStatus.processing;
6922
+ this.actualizedAt = ScDateHelper.parse(data?.actualizedAt, apiDateFormat);
6923
+ this.expiresAt = ScDateHelper.parse(data?.expiresAt, apiDateFormat);
6924
+ this.error = data?.error ?? undefined;
6925
+ }
6926
+ /**
6927
+ * Признак, что выполняется обработка.
6928
+ */
6929
+ get isProcessing() {
6930
+ return this.status === ScOrderDraftActualizationStatus.processing;
6931
+ }
6932
+ /**
6933
+ * Возвращает признак, что данные актуализации устарели на указанный момент: актуализация не завершена успешно или истёк срок её действия.
6934
+ * Момент, равный сроку действия, ещё допустим. Если срок не известен, данные считаются актуальными.
6935
+ *
6936
+ * @param now Момент проверки.
6937
+ */
6938
+ isOutdatedAt(now) {
6939
+ if (this.status !== ScOrderDraftActualizationStatus.completed || !this.actualizedAt) {
6940
+ return true;
6941
+ }
6942
+ return this.expiresAt !== undefined && this.expiresAt.getTime() < now.getTime();
6943
+ }
6944
+ /**
6945
+ * Возвращает признак, что на указанный момент из черновика можно оформить заказ.
6946
+ *
6947
+ * @param now Момент проверки.
6948
+ */
6949
+ isOrderAllowedAt(now) {
6950
+ return this.status === ScOrderDraftActualizationStatus.completed && !this.isOutdatedAt(now);
6951
+ }
6952
+ }
6953
+
6954
+ /**
6955
+ * Данные о позиции черновика Профстрой.
6956
+ */
6957
+ class ScProfstroyOrderItem extends ScOrderItem {
6958
+ /**
6959
+ * Название группы для позиций без группы.
6960
+ */
6961
+ static { this.ungroupedName = 'Без группы'; }
6962
+ /**
6963
+ * Шаг количества позиции, для которой не найден товар каталога и кратность не известна.
6964
+ */
6965
+ static { this.stubQuantityStep = 0.001; }
6966
+ /**
6967
+ * Тексты ошибок строк по их кодам.
6968
+ */
6969
+ static { this.lineErrorTexts = {
6970
+ // eslint-disable-next-line @typescript-eslint/naming-convention
6971
+ not_returned_by_1c: 'Продукт недоступен для заказа',
6972
+ }; }
6973
+ /**
6974
+ * Возвращает признак, что вместо товара каталога пришла заглушка.
6975
+ *
6976
+ * @param product Данные о товаре.
6977
+ */
6978
+ static isStub(product) {
6979
+ return !product || (product instanceof ScProduct && product.isNull) || !('id' in product);
6980
+ }
6981
+ /**
6982
+ * Возвращает название группы позиции для отображения. Позиции обычного черновика относятся к группе «Без группы».
6983
+ *
6984
+ * @param item Позиция черновика.
6985
+ */
6986
+ static getGroupTitle(item) {
6987
+ return item instanceof ScProfstroyOrderItem ? item.groupTitle : ScProfstroyOrderItem.ungroupedName;
6988
+ }
6989
+ /**
6990
+ * Инициализирует экземпляр класса {@link ScProfstroyOrderItem}.
6991
+ *
6992
+ * @param data Данные от API или готовый экземпляр позиции.
6993
+ */
6994
+ constructor(data) {
6995
+ const isProductStub = ScProfstroyOrderItem.isStub(data.product);
6996
+ // Базовому классу нужны только собственные поля позиции, прототип не используется.
6997
+ // eslint-disable-next-line @typescript-eslint/no-misused-spread
6998
+ super({ ...data, product: isProductStub ? null : data.product });
6999
+ this.isProductStub = isProductStub;
7000
+ this.groupName = data.groupName ?? undefined;
7001
+ this.isIgnored = data.isIgnored ?? false;
7002
+ this.lineError = data.lineError ?? undefined;
7003
+ this.supplierSku = data.supplierSku ?? data.product?.supplierSku ?? undefined;
7004
+ if (isProductStub) {
7005
+ this.product = ScProduct.createNotFound(data.product?.name, this.supplierSku);
7006
+ }
7007
+ }
7008
+ /**
7009
+ * Название группы для отображения: группа из файла или «Без группы».
7010
+ */
7011
+ get groupTitle() {
7012
+ return this.groupName?.trim() ? this.groupName.trim() : ScProfstroyOrderItem.ungroupedName;
7013
+ }
7014
+ /**
7015
+ * @inheritDoc
7016
+ */
7017
+ get isOrderable() {
7018
+ return !this.isProductStub && !this.isIgnored && !this.lineError && super.isOrderable;
7019
+ }
7020
+ /**
7021
+ * @inheritDoc
7022
+ */
7023
+ get quantityStep() {
7024
+ return this.isProductStub ? ScProfstroyOrderItem.stubQuantityStep : super.quantityStep;
7025
+ }
7026
+ /**
7027
+ * @inheritDoc
7028
+ */
7029
+ getUnavailableReason() {
7030
+ if (this.isIgnored) {
7031
+ return 'Группа не участвует в заказе';
7032
+ }
7033
+ if (this.lineError) {
7034
+ return ScProfstroyOrderItem.lineErrorTexts[this.lineError] ?? this.lineError;
7035
+ }
7036
+ return super.getUnavailableReason();
7037
+ }
7038
+ }
7039
+
6811
7040
  /**
6812
7041
  * Данные о коммерческом предложении/черновике заказа.
6813
7042
  */
@@ -6816,8 +7045,9 @@ class ScOrderDraft {
6816
7045
  * Инициализирует экземпляр класса {@link ScOrderDraft}.
6817
7046
  *
6818
7047
  * @param data Данные для первоначальной инициализации.
7048
+ * @param apiDateFormat Формат дат в данных API.
6819
7049
  */
6820
- constructor(data) {
7050
+ constructor(data, apiDateFormat) {
6821
7051
  /**
6822
7052
  * Дата последнего обновления данных.
6823
7053
  */
@@ -6827,7 +7057,10 @@ class ScOrderDraft {
6827
7057
  this.type = data.type;
6828
7058
  this.createdAt = data.createdAt;
6829
7059
  this.sum = data.sum;
6830
- this.products = data.products.map((item) => new ScOrderItem(item));
7060
+ this.isProfstroy = data.isProfstroy === true;
7061
+ this.actualization = this.isProfstroy ? new ScOrderDraftActualization(data.actualization, apiDateFormat) : undefined;
7062
+ this.file = data.file ?? undefined;
7063
+ this.products = data.products.map((item) => (this.isProfstroy ? new ScProfstroyOrderItem(item) : new ScOrderItem(item)));
6831
7064
  }
6832
7065
  /**
6833
7066
  * @inheritDoc
@@ -6855,6 +7088,9 @@ class ScOrderDraft {
6855
7088
  this.type = offer.type;
6856
7089
  this.sum = offer.sum;
6857
7090
  this.products = offer.products;
7091
+ this.isProfstroy = offer.isProfstroy;
7092
+ this.actualization = offer.actualization;
7093
+ this.file = offer.file;
6858
7094
  this.lastUpdateDate = new Date();
6859
7095
  }
6860
7096
  /**
@@ -6863,6 +7099,9 @@ class ScOrderDraft {
6863
7099
  updateDataFromShortOffer(offer) {
6864
7100
  this.name = offer.name;
6865
7101
  this.type = offer.type;
7102
+ this.isProfstroy = offer.isProfstroy;
7103
+ this.actualization = offer.actualization;
7104
+ this.file = offer.file;
6866
7105
  }
6867
7106
  }
6868
7107
 
@@ -6874,11 +7113,15 @@ class ScOrderDraftShort extends ScOrderBase {
6874
7113
  * Инициализирует экземпляр класса {@link ScOrderDraftShort}.
6875
7114
  *
6876
7115
  * @param data Данные для первоначальной инициализации.
7116
+ * @param apiDateFormat Формат дат в данных API.
6877
7117
  */
6878
- constructor(data) {
7118
+ constructor(data, apiDateFormat) {
6879
7119
  super(data);
6880
7120
  this.name = data.name;
6881
7121
  this.type = data.type;
7122
+ this.isProfstroy = data.isProfstroy === true;
7123
+ this.actualization = this.isProfstroy ? new ScOrderDraftActualization(data.actualization, apiDateFormat) : undefined;
7124
+ this.file = data.file ?? undefined;
6882
7125
  }
6883
7126
  }
6884
7127
 
@@ -6899,6 +7142,7 @@ class ScOrderShort extends ScOrderBase {
6899
7142
  this.payment = data.payment;
6900
7143
  this.numberDoc = data.numberDoc;
6901
7144
  this.barcode = data.barcode;
7145
+ this.isProfstroy = data.isProfstroy === true;
6902
7146
  }
6903
7147
  /**
6904
7148
  * @inheritDoc
@@ -6909,6 +7153,7 @@ class ScOrderShort extends ScOrderBase {
6909
7153
  this.payment = order.payment;
6910
7154
  this.sum = order.sum;
6911
7155
  this.delivery = order.delivery;
7156
+ this.isProfstroy = order.isProfstroy;
6912
7157
  }
6913
7158
  }
6914
7159
 
@@ -7106,6 +7351,18 @@ class ScOrderDraftsService {
7106
7351
  * Сервис для сбора метрик о действиях пользователей.
7107
7352
  */
7108
7353
  this.userMetrikaService = inject(ScUserMetrikaService);
7354
+ /**
7355
+ * Форматы дат.
7356
+ */
7357
+ this.dateFormats = inject(SC_DATE_FORMAT);
7358
+ /**
7359
+ * Сервис конвертации данных.
7360
+ */
7361
+ this.convertersService = inject(ScConvertersService);
7362
+ /**
7363
+ * Объект документа.
7364
+ */
7365
+ this.document = inject(DOCUMENT);
7109
7366
  /**
7110
7367
  * Коллекция пар ключ-значение для кэшированных коммерческих предложений/черновиков пользователя.
7111
7368
  */
@@ -7121,17 +7378,85 @@ class ScOrderDraftsService {
7121
7378
  */
7122
7379
  getList$(params) {
7123
7380
  return this.http.get(`${this.urls.apiUrl}/orders/offers`, { params: params }).pipe(map$1((offersPaginateDTO) => {
7124
- offersPaginateDTO.data = offersPaginateDTO.data.map((item) => new ScOrderDraftShort(item));
7381
+ offersPaginateDTO.data = offersPaginateDTO.data.map((item) => new ScOrderDraftShort(item, this.dateFormats.api));
7125
7382
  return offersPaginateDTO;
7126
7383
  }));
7127
7384
  }
7385
+ /**
7386
+ * Возвращает краткие данные черновиков с указанными номерами. Используется для лёгкого опроса состояния актуализации без загрузки позиций.
7387
+ *
7388
+ * @param ids Номера черновиков.
7389
+ */
7390
+ getShortList$(ids) {
7391
+ const params = { perPage: ids.length, filters: { number: { in: ids } } };
7392
+ return this.getList$(this.convertersService.toHttpParams(params, { nonNullable: true })).pipe(map$1((response) => response.data));
7393
+ }
7394
+ /**
7395
+ * Периодически запрашивает краткие данные черновиков с указанными номерами, пока есть подписка.
7396
+ * Пока вкладка браузера скрыта, запросы не выполняются; новый запрос не начинается до завершения предыдущего.
7397
+ *
7398
+ * @param ids Номера черновиков.
7399
+ */
7400
+ watchShortList$(ids) {
7401
+ return this.poll$(this.updateInterval.orderDraftStatus, () => this.getShortList$(ids));
7402
+ }
7403
+ /**
7404
+ * Периодически перезапрашивает страницу списка черновиков в фоне, пока есть подписка. Условия запросов как у {@link watchShortList$}.
7405
+ *
7406
+ * @param params Параметры запроса получения списка.
7407
+ */
7408
+ watchList$(params) {
7409
+ return this.poll$(this.updateInterval.orderDraftsList, () => this.getList$(params).pipe(map$1((response) => response.data)));
7410
+ }
7411
+ /**
7412
+ * Создаёт черновик заказа из файла программы «Профстрой» и запускает его обработку.
7413
+ *
7414
+ * @param data Данные для создания.
7415
+ */
7416
+ createProfstroy$(data) {
7417
+ return this.http.post(`${this.urls.apiUrl}/orders/offers/profstroy`, data).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7418
+ this.userMetrikaService.emitUserMetrikaEvent({
7419
+ target: ScUserMetrikaGoalsEnum.orderDraftProfstroyCreated,
7420
+ params: { offer_id: offer.id },
7421
+ });
7422
+ this.offerMap.set(offer.id, new ScCachedItem(this.updateInterval.orderData, of(offer), loadingPlaceholder));
7423
+ }));
7424
+ }
7425
+ /**
7426
+ * Запускает актуализацию черновика Профстрой (повторный обмен с 1С по текущему составу).
7427
+ *
7428
+ * @param offerId Идентификатор черновика.
7429
+ */
7430
+ actualizeProfstroy$(offerId) {
7431
+ return this.http.post(`${this.urls.apiUrl}/orders/offers/${offerId}/profstroy/actualize`, {}).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7432
+ this.userMetrikaService.emitUserMetrikaEvent({
7433
+ target: ScUserMetrikaGoalsEnum.orderDraftProfstroyActualized,
7434
+ params: { offer_id: offerId },
7435
+ });
7436
+ this.offerMap.get(offerId)?.setValue(offer);
7437
+ }));
7438
+ }
7439
+ /**
7440
+ * Запускает пересборку черновика Профстрой из ранее загруженного файла.
7441
+ *
7442
+ * @param offerId Идентификатор черновика.
7443
+ */
7444
+ reloadProfstroy$(offerId) {
7445
+ return this.http.post(`${this.urls.apiUrl}/orders/offers/${offerId}/profstroy/reload`, {}).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7446
+ this.userMetrikaService.emitUserMetrikaEvent({
7447
+ target: ScUserMetrikaGoalsEnum.orderDraftProfstroyReloaded,
7448
+ params: { offer_id: offerId },
7449
+ });
7450
+ this.offerMap.get(offerId)?.setValue(offer);
7451
+ }));
7452
+ }
7128
7453
  /**
7129
7454
  * Создаёт новое коммерческое предложение/черновик заказа.
7130
7455
  *
7131
7456
  * @param data Данные для создания.
7132
7457
  */
7133
7458
  create$(data) {
7134
- return this.http.post(`${this.urls.apiUrl}/orders/offers`, data).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO)), tap((offer) => {
7459
+ return this.http.post(`${this.urls.apiUrl}/orders/offers`, data).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7135
7460
  this.userMetrikaService.emitUserMetrikaEvent({
7136
7461
  target: ScUserMetrikaGoalsEnum.orderDraftCreated,
7137
7462
  params: { offer_id: offer.id },
@@ -7149,7 +7474,7 @@ class ScOrderDraftsService {
7149
7474
  get$(draftId, cacheable = true) {
7150
7475
  let cachedItem = this.offerMap.get(draftId);
7151
7476
  if (!cachedItem) {
7152
- cachedItem = new ScCachedItem(this.updateInterval.orderData, this.http.get(`${this.urls.apiUrl}/orders/offers/${draftId}`).pipe(map$1((data) => new ScOrderDraft(data))), loadingPlaceholder);
7477
+ cachedItem = new ScCachedItem(this.updateInterval.orderData, this.http.get(`${this.urls.apiUrl}/orders/offers/${draftId}`).pipe(map$1((data) => new ScOrderDraft(data, this.dateFormats.api))), loadingPlaceholder);
7153
7478
  if (cacheable) {
7154
7479
  this.offerMap.set(draftId, cachedItem);
7155
7480
  }
@@ -7159,6 +7484,22 @@ class ScOrderDraftsService {
7159
7484
  }
7160
7485
  return cachedItem.item$;
7161
7486
  }
7487
+ /**
7488
+ * Запрашивает полную информацию о черновике в обход кэша и обновляет кэш полученными данными.
7489
+ *
7490
+ * @param draftId Идентификатор записи.
7491
+ */
7492
+ fetch$(draftId) {
7493
+ return this.http.get(`${this.urls.apiUrl}/orders/offers/${draftId}`).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7494
+ const cachedItem = this.offerMap.get(draftId);
7495
+ if (cachedItem) {
7496
+ cachedItem.setValue(offer);
7497
+ }
7498
+ else {
7499
+ this.offerMap.set(draftId, new ScCachedItem(this.updateInterval.orderData, of(offer), loadingPlaceholder));
7500
+ }
7501
+ }));
7502
+ }
7162
7503
  /**
7163
7504
  * Возвращает список элементов коммерческого предложения/черновика.
7164
7505
  *
@@ -7175,7 +7516,7 @@ class ScOrderDraftsService {
7175
7516
  */
7176
7517
  update$(draftId, patch) {
7177
7518
  const cachedItem = this.offerMap.get(draftId);
7178
- return this.http.patch(`${this.urls.apiUrl}/orders/offers/${draftId}`, patch).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO)), tap((offer) => {
7519
+ return this.http.patch(`${this.urls.apiUrl}/orders/offers/${draftId}`, patch).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7179
7520
  this.userMetrikaService.emitUserMetrikaEvent({
7180
7521
  target: ScUserMetrikaGoalsEnum.orderDraftUpdated,
7181
7522
  params: { offer_id: draftId },
@@ -7214,6 +7555,15 @@ class ScOrderDraftsService {
7214
7555
  this.removeOrderedProductsFromCache(offerId, data.items ?? []);
7215
7556
  }));
7216
7557
  }
7558
+ /**
7559
+ * Выполняет запрос с указанным интервалом, пока вкладка браузера видна. Новый запрос не начинается до завершения предыдущего, ошибки пропускаются.
7560
+ *
7561
+ * @param period Интервал запросов (ms).
7562
+ * @param request Функция, возвращающая запрос.
7563
+ */
7564
+ poll$(period, request) {
7565
+ return timer(period, period).pipe(filter(() => this.document.visibilityState !== 'hidden'), exhaustMap(() => request().pipe(catchError$1(() => EMPTY))));
7566
+ }
7217
7567
  /**
7218
7568
  * Убирает заказанные позиции из кэша черновика локально (сервер сразу после заказа ещё может вернуть их в списке).
7219
7569
  *
@@ -7231,11 +7581,15 @@ class ScOrderDraftsService {
7231
7581
  return;
7232
7582
  }
7233
7583
  const currentDraft = draft;
7584
+ // Из черновика Профстрой позиции после оформления заказа не удаляются.
7585
+ if (currentDraft.isProfstroy) {
7586
+ return;
7587
+ }
7234
7588
  const remainingProducts = currentDraft.products.filter((product) => !orderedIds.has(product.id));
7235
7589
  if (remainingProducts.length === currentDraft.products.length) {
7236
7590
  return;
7237
7591
  }
7238
- const updatedDraft = new ScOrderDraft(currentDraft);
7592
+ const updatedDraft = new ScOrderDraft(currentDraft, this.dateFormats.api);
7239
7593
  updatedDraft.products = remainingProducts;
7240
7594
  updatedDraft.sum = remainingProducts.reduce((sum, product) => sum + product.costRub, 0);
7241
7595
  cachedItem.setValue(updatedDraft);
@@ -7248,7 +7602,7 @@ class ScOrderDraftsService {
7248
7602
  * @param offerId Идентификатор коммерческого предложения/черновика.
7249
7603
  */
7250
7604
  addProduct$(productData, offerId) {
7251
- return this.http.post(`${this.urls.apiUrl}/orders/offers/${offerId}/products`, productData).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO)), tap((offer) => {
7605
+ return this.http.post(`${this.urls.apiUrl}/orders/offers/${offerId}/products`, productData).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7252
7606
  if (this.offerMap.has(offerId)) {
7253
7607
  this.offerMap.get(offerId)?.setValue(offer);
7254
7608
  }
@@ -7262,7 +7616,7 @@ class ScOrderDraftsService {
7262
7616
  * @param offerId Идентификатор коммерческого предложения/черновика.
7263
7617
  */
7264
7618
  updateProduct$(productId, patch, offerId) {
7265
- return this.http.patch(`${this.urls.apiUrl}/orders/offers/${offerId}/products/${productId}`, patch).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO)), tap((offer) => {
7619
+ return this.http.patch(`${this.urls.apiUrl}/orders/offers/${offerId}/products/${productId}`, patch).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7266
7620
  if (this.offerMap.has(offerId)) {
7267
7621
  this.offerMap.get(offerId)?.setValue(offer);
7268
7622
  }
@@ -7275,7 +7629,7 @@ class ScOrderDraftsService {
7275
7629
  * @param offerId Идентификатор коммерческого предложения/черновика.
7276
7630
  */
7277
7631
  deleteProduct$(item, offerId) {
7278
- return this.http.delete(`${this.urls.apiUrl}/orders/offers/${offerId}/products/${item.id}`).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO)), tap((offer) => {
7632
+ return this.http.delete(`${this.urls.apiUrl}/orders/offers/${offerId}/products/${item.id}`).pipe(map$1((offerDTO) => new ScOrderDraft(offerDTO, this.dateFormats.api)), tap((offer) => {
7279
7633
  if (this.offerMap.has(offerId)) {
7280
7634
  this.offerMap.get(offerId)?.setValue(offer);
7281
7635
  }
@@ -8714,6 +9068,220 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8714
9068
  }]
8715
9069
  }] });
8716
9070
 
9071
+ /**
9072
+ * Кэш ответов API, один на процесс: переживает пересоздание инжектора между SSR-рендерами.
9073
+ * Одинаковые одновременные запросы склеиваются, при переполнении вытесняется самая давняя запись.
9074
+ */
9075
+ class ScSsrResponseCache {
9076
+ /**
9077
+ * Инициализирует экземпляр класса {@link ScSsrResponseCache}.
9078
+ *
9079
+ * @param maxEntries Максимальное количество записей в кэше.
9080
+ */
9081
+ constructor(maxEntries = 100) {
9082
+ /**
9083
+ * Закэшированные ответы по ключу запроса.
9084
+ */
9085
+ this.entries = new Map();
9086
+ /**
9087
+ * Выполняемые запросы по ключу запроса.
9088
+ */
9089
+ this.pending = new Map();
9090
+ /**
9091
+ * Количество запросов, отданных без обращения к API.
9092
+ */
9093
+ this.hits = 0;
9094
+ /**
9095
+ * Количество запросов, выполненных к API.
9096
+ */
9097
+ this.misses = 0;
9098
+ this.maxEntries = Number.isFinite(maxEntries) ? Math.max(1, maxEntries) : 100;
9099
+ }
9100
+ /**
9101
+ * Возвращает ответ из кэша, при отсутствии или устаревании загружает и кэширует. Ошибка загрузки не кэшируется.
9102
+ *
9103
+ * @param key Ключ запроса.
9104
+ * @param lifeTime Время жизни ответа (мс).
9105
+ * @param loader Функция загрузки ответа.
9106
+ */
9107
+ getOrLoad(key, lifeTime, loader) {
9108
+ const cached = this.get(key);
9109
+ if (cached) {
9110
+ this.hits += 1;
9111
+ return Promise.resolve(cached.response);
9112
+ }
9113
+ const pending = this.pending.get(key);
9114
+ if (pending) {
9115
+ this.hits += 1;
9116
+ return pending;
9117
+ }
9118
+ this.misses += 1;
9119
+ const loading = loader()
9120
+ .then((response) => {
9121
+ this.set(key, { response, expiresAt: Date.now() + lifeTime });
9122
+ return response;
9123
+ })
9124
+ .finally(() => {
9125
+ this.pending.delete(key);
9126
+ });
9127
+ this.pending.set(key, loading);
9128
+ return loading;
9129
+ }
9130
+ /**
9131
+ * Возвращает статистику работы кэша.
9132
+ */
9133
+ getStats() {
9134
+ return { size: this.entries.size, hits: this.hits, misses: this.misses };
9135
+ }
9136
+ /**
9137
+ * Возвращает актуальную запись или `null`.
9138
+ *
9139
+ * @param key Ключ запроса.
9140
+ */
9141
+ get(key) {
9142
+ const entry = this.entries.get(key);
9143
+ if (!entry) {
9144
+ return null;
9145
+ }
9146
+ if (entry.expiresAt <= Date.now()) {
9147
+ this.entries.delete(key);
9148
+ return null;
9149
+ }
9150
+ // Перестановка в конец помечает запись как недавно использованную.
9151
+ this.entries.delete(key);
9152
+ this.entries.set(key, entry);
9153
+ return entry;
9154
+ }
9155
+ /**
9156
+ * Сохраняет запись, при переполнении вытесняя самую давнюю.
9157
+ *
9158
+ * @param key Ключ запроса.
9159
+ * @param entry Запись кэша.
9160
+ */
9161
+ set(key, entry) {
9162
+ this.entries.delete(key);
9163
+ if (this.entries.size >= this.maxEntries) {
9164
+ const [oldestKey] = this.entries.keys();
9165
+ this.entries.delete(oldestKey);
9166
+ }
9167
+ this.entries.set(key, entry);
9168
+ }
9169
+ }
9170
+
9171
+ /**
9172
+ * Кэш по умолчанию, один на процесс.
9173
+ */
9174
+ const defaultSsrResponseCache = new ScSsrResponseCache();
9175
+ /**
9176
+ * Токен кэша ответов API между SSR-рендерами. По умолчанию один экземпляр на процесс, можно переопределить провайдером.
9177
+ */
9178
+ const SC_SSR_RESPONSE_CACHE = new InjectionToken('SC_SSR_RESPONSE_CACHE', {
9179
+ factory: () => defaultSsrResponseCache,
9180
+ });
9181
+
9182
+ /**
9183
+ * Токен настроек кэша ответов API между SSR-рендерами. По умолчанию кэш выключен.
9184
+ */
9185
+ const SC_SSR_RESPONSE_CACHE_SETTINGS = new InjectionToken('SC_SSR_RESPONSE_CACHE_SETTINGS', {
9186
+ factory: () => ({ endpoints: [], lifeTime: 0, loadTimeout: 0 }),
9187
+ });
9188
+
9189
+ /**
9190
+ * Возвращает признак, что ответ можно кэшировать между SSR-рендерами: GET за JSON без ключей доступа по адресам из настроек.
9191
+ *
9192
+ * @param request Данные запроса.
9193
+ * @param settings Настройки кэша.
9194
+ * @param apiUrl Адрес API.
9195
+ * @param apiKeys Названия заголовков ключей доступа к API.
9196
+ *
9197
+ * @internal
9198
+ */
9199
+ function isSsrResponseCacheable(request, settings, apiUrl, apiKeys) {
9200
+ if (!Number.isFinite(settings.lifeTime) || settings.lifeTime <= 0 || request.method !== 'GET' || request.responseType !== 'json') {
9201
+ return false;
9202
+ }
9203
+ if (request.headers.has(apiKeys.keyName) || request.headers.has(apiKeys.guestKeyName)) {
9204
+ return false;
9205
+ }
9206
+ const [url] = request.url.split('?');
9207
+ if (!url.startsWith(apiUrl)) {
9208
+ return false;
9209
+ }
9210
+ const path = url.slice(apiUrl.length);
9211
+ return settings.endpoints.some((endpoint) => (endpoint.endsWith('*') ? path.startsWith(endpoint.slice(0, -1)) : path === endpoint));
9212
+ }
9213
+
9214
+ /**
9215
+ * Обработчик запросов с кэшем ответов API между SSR-рендерами.
9216
+ *
9217
+ * Стоит на уровне {@link HttpBackend}, ниже перехватчиков: ответ из кэша проходит через перенос в браузер,
9218
+ * а проверка ключей доступа видит итоговые заголовки. Отдаёт только итоговый {@link HttpResponse};
9219
+ * запрос не отменяется при отписке рендера, а ограничен `loadTimeout` из настроек.
9220
+ */
9221
+ class ScSsrResponseCacheBackend {
9222
+ constructor() {
9223
+ /**
9224
+ * Штатный обработчик, выполняющий запросы к API.
9225
+ */
9226
+ // TODO: Angular 21: проверить, какой backend регистрирует provideHttpClient.
9227
+ this.backend = inject(FetchBackend, { optional: true }) ?? inject(HttpXhrBackend);
9228
+ /**
9229
+ * Кэш ответов API.
9230
+ */
9231
+ this.cache = inject(SC_SSR_RESPONSE_CACHE);
9232
+ /**
9233
+ * Настройки кэша.
9234
+ */
9235
+ this.settings = inject(SC_SSR_RESPONSE_CACHE_SETTINGS);
9236
+ /**
9237
+ * Адрес API.
9238
+ */
9239
+ this.apiUrl = inject(SC_URLS).apiUrl;
9240
+ /**
9241
+ * Названия заголовков ключей доступа к API.
9242
+ */
9243
+ this.apiKeys = inject(SC_API_KEYS);
9244
+ }
9245
+ /** @inheritDoc */
9246
+ handle(request) {
9247
+ if (!isSsrResponseCacheable(request, this.settings, this.apiUrl, this.apiKeys)) {
9248
+ return this.backend.handle(request);
9249
+ }
9250
+ return defer(() => from(this.cache.getOrLoad(request.urlWithParams, this.settings.lifeTime, () => this.load(request)))).pipe(
9251
+ // Копия тела на каждый рендер, чтобы изменения не утекали между рендерами.
9252
+ map$1((response) => response.clone({ body: structuredClone(response.body) })));
9253
+ }
9254
+ /**
9255
+ * Выполняет запрос к API и возвращает его ответ.
9256
+ *
9257
+ * @param request Данные запроса.
9258
+ */
9259
+ load(request) {
9260
+ const response$ = this.backend.handle(request).pipe(filter((event) => event instanceof HttpResponse));
9261
+ if (this.settings.loadTimeout <= 0) {
9262
+ return lastValueFrom(response$);
9263
+ }
9264
+ // По таймауту запрос отменяется, ошибка отдаётся как HttpErrorResponse, как и остальные ошибки запросов.
9265
+ return lastValueFrom(response$.pipe(timeout(this.settings.loadTimeout), catchError$1((error) => throwError(() => error instanceof TimeoutError
9266
+ ? new HttpErrorResponse({ status: HttpStatusCode.GatewayTimeout, statusText: 'Gateway Timeout', url: request.url, error })
9267
+ : error))));
9268
+ }
9269
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSsrResponseCacheBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9270
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSsrResponseCacheBackend }); }
9271
+ }
9272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSsrResponseCacheBackend, decorators: [{
9273
+ type: Injectable
9274
+ }] });
9275
+
9276
+ /**
9277
+ * Возвращает провайдеры кэша ответов API между SSR-рендерами.
9278
+ * Подключать только в серверной конфигурации после `provideHttpClient()`: подменяют {@link HttpBackend}.
9279
+ */
9280
+ // TODO: Angular 21: проверить, что подмена HttpBackend остаётся последней после provideServerRendering(withRoutes()).
9281
+ function provideSsrResponseCache() {
9282
+ return makeEnvironmentProviders([{ provide: HttpBackend, useClass: ScSsrResponseCacheBackend }]);
9283
+ }
9284
+
8717
9285
  /**
8718
9286
  * Перечисление типов подсказок запросов.
8719
9287
  */
@@ -9093,5 +9661,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9093
9661
  * Generated bundle index. Do not edit.
9094
9662
  */
9095
9663
 
9096
- export { CATEGORY_PROVIDERS, EMPTY_CART, IS_BROWSER, IS_RUNNING_ON_TERMINAL, IS_SERVER, RESPONSE, SC_ACCESS_AUTH_TOKEN_STORAGE_KEY, SC_API_KEYS, SC_AUTH_ADD_HEADER_REQUIRED, SC_AUTH_AS_CLIENT_DEFAULT_OPTIONS, SC_AUTH_AS_CLIENT_OPTIONS, SC_CACHE_LIFETIME, SC_CACHE_SETTINGS, SC_CANONICAL_REDIRECT_ENABLED, SC_CATALOG_ITEM_PAGE_META, SC_CATALOG_ROOT_PAGE_META, SC_CATEGORY_INFO, SC_CATEGORY_STATE, SC_COMPANY_INFO, SC_COMPANY_NAME, SC_CONFIGURATOR_COMPONENTS, SC_CONFIGURATOR_SETTINGS, SC_COUNT_FIRST_DISCOUNTED_PRODUCTS, SC_COUNT_LAST_NEWS, SC_DATE_FORMAT, SC_DEFAULT_PAGE_META, SC_FEEDBACK_API, SC_GUEST_ENDPOINTS_TOKEN, SC_GUEST_PARAMETER_NAME_TOKEN, SC_GUEST_TOKEN_STORAGE_KEY, SC_ID_OR_SLUG_IN_ROUTE, SC_IS_HEADER_REQUIRED, SC_IS_HIDDEN_ERROR_ALERT, SC_IS_HIDDEN_ERROR_ALERT_HTTP_CONTEXT, SC_IS_LOGOUT_REQUEST, SC_IS_REFRESH_REQUIRED, SC_LINEAR_VALUES, SC_LINEAR_VALUES_TOKEN, SC_MAX_LENGTH_SEARCH_TERM, SC_MIN_LENGTH_SEARCH_TERM, SC_NEXT_PAGE_PAGINATION_CLICK, SC_ORDER_LOADER, SC_ORDER_LOADING, SC_PAGE_QUERY_PARAM, SC_PATH_IMAGE_NOT_FOUND, SC_PRODUCTS_PAGINATION_LOADER, SC_PRODUCT_PAGINATION_DEFAULT_OPTIONS, SC_PRODUCT_PAGINATION_OPTIONS, SC_REFRESH_AUTH_TOKEN_STORAGE_KEY, SC_RELEASE, SC_UPDATE_INTERVAL, SC_URLS, SC_USER_INFO, SC_VACANCIES_DATA_SOURCE, SC_VIRTUAL_CATEGORY_INFO, SC_VIRTUAL_CATEGORY_PROVIDERS, SEARCH_TERM, SEARCH_TERM_PROVIDERS, ScAuthAsClientGuard, ScAuthInterceptor, ScAuthService, ScBanner, ScBannerService, ScCacheInterceptor, ScCacheSettings, ScCachedData, ScCachedItem, ScCart, ScCartService, ScCatalogFilterService, ScCatalogFormat, ScCatalogItem, ScCatalogRedirectService, ScCatalogService, ScCatalogableItem, ScCategory, ScClientType, ScCodedIdentity, ScConfiguratorService, ScContactsService, ScContragentService, ScConvertInterceptor, ScConvertersService, ScDateFormatInterceptor, ScDeletableNamedIdentity, ScDeliveryAddressService, ScDeliveryCost, ScDeliveryType, ScDocumentInfoNode, ScDocumentInfoTypesEnum, ScErrorRequestState, ScErrorsInterceptor, ScFavoriteService, ScFeedbackForms, ScFeedbackService, ScFilesService, ScFrequentlyAskedQuestionsService, ScGuestInterceptor, ScHiddenCatalogableItem, ScIBreadcrumbItem, ScISuggestionType, ScIconTypesEnum, ScIdOrSlugGuard, ScIdOrSlugPipe, ScIdentity, ScImage, ScImageHelper, ScLoadingRequestState, ScLocationsService, ScMediaImageTransformerPipe, ScMimeTypes, ScNamedIdentity, ScNews, ScNewsService, ScNotificationActionTypes, ScNotificationLevelNames, ScNotificationsService, ScOpfList, ScOptionsInterceptor, ScOrder, ScOrderBase, ScOrderDraft, ScOrderDraftShort, ScOrderDraftType, ScOrderDraftsService, ScOrderItem, ScOrderShort, ScOrderStateStatus, ScOrdersService, ScPaginationPageQueryParamService, ScPaginationService, ScPaymentStatus, ScPaymentType, ScPhoneService, ScPrimaryCatalogableItem, ScProduct, ScProductTileType, ScQuestionnaireService, ScQuestionnaireStatusEnum, ScReclamationService, ScReclamationStatus, ScRecommendationService, ScRedirectStatusEnum, ScReference, ScReferenceName, ScReferencesService, ScRequestStateStatusEnum, ScRequisitesService, ScRouteKeys, ScSearchService, ScSeoService, ScSocialType, ScSuccessRequestState, ScSuggestionService, ScSum, ScTokenService, ScUIService, ScUTMService, ScUnitsHelper, ScUpdatableNamedIdentity, ScUploadedFile, ScUser, ScUserMetadata, ScUserMetrikaGoalsEnum, ScUserMetrikaService, ScUserService, ScUserType, ScVCardService, ScVacanciesList, ScVacanciesService, ScVacancy, ScVerificationService, ScVirtualCategory, ScWarehouseService, SchemaOrgFactory, TERMINAL_PROVIDERS, USER_AGENT_TERMINAL, filterChangedByKey, loadingPlaceholder, mapRequestStateToDataOrNull, mapToRequestState, runningOnTerminalFactory, scGetCurrentRoute, scGetPageQueryParamChanges, scOrderIsLoaded, scParsePageQueryParam, searchTermFactory };
9664
+ export { CATEGORY_PROVIDERS, EMPTY_CART, IS_BROWSER, IS_RUNNING_ON_TERMINAL, IS_SERVER, RESPONSE, SC_ACCESS_AUTH_TOKEN_STORAGE_KEY, SC_API_KEYS, SC_AUTH_ADD_HEADER_REQUIRED, SC_AUTH_AS_CLIENT_DEFAULT_OPTIONS, SC_AUTH_AS_CLIENT_OPTIONS, SC_CACHE_LIFETIME, SC_CACHE_SETTINGS, SC_CANONICAL_REDIRECT_ENABLED, SC_CATALOG_ITEM_PAGE_META, SC_CATALOG_ROOT_PAGE_META, SC_CATEGORY_INFO, SC_CATEGORY_STATE, SC_COMPANY_INFO, SC_COMPANY_NAME, SC_CONFIGURATOR_COMPONENTS, SC_CONFIGURATOR_SETTINGS, SC_COUNT_FIRST_DISCOUNTED_PRODUCTS, SC_COUNT_LAST_NEWS, SC_DATE_FORMAT, SC_DEFAULT_PAGE_META, SC_FEEDBACK_API, SC_GUEST_ENDPOINTS_TOKEN, SC_GUEST_PARAMETER_NAME_TOKEN, SC_GUEST_TOKEN_STORAGE_KEY, SC_ID_OR_SLUG_IN_ROUTE, SC_IS_HEADER_REQUIRED, SC_IS_HIDDEN_ERROR_ALERT, SC_IS_HIDDEN_ERROR_ALERT_HTTP_CONTEXT, SC_IS_LOGOUT_REQUEST, SC_IS_REFRESH_REQUIRED, SC_LINEAR_VALUES, SC_LINEAR_VALUES_TOKEN, SC_MAX_LENGTH_SEARCH_TERM, SC_MIN_LENGTH_SEARCH_TERM, SC_NEXT_PAGE_PAGINATION_CLICK, SC_ORDER_LOADER, SC_ORDER_LOADING, SC_PAGE_QUERY_PARAM, SC_PATH_IMAGE_NOT_FOUND, SC_PRODUCTS_PAGINATION_LOADER, SC_PRODUCT_PAGINATION_DEFAULT_OPTIONS, SC_PRODUCT_PAGINATION_OPTIONS, SC_REFRESH_AUTH_TOKEN_STORAGE_KEY, SC_RELEASE, SC_SSR_RESPONSE_CACHE, SC_SSR_RESPONSE_CACHE_SETTINGS, SC_UPDATE_INTERVAL, SC_URLS, SC_USER_INFO, SC_VACANCIES_DATA_SOURCE, SC_VIRTUAL_CATEGORY_INFO, SC_VIRTUAL_CATEGORY_PROVIDERS, SEARCH_TERM, SEARCH_TERM_PROVIDERS, ScAuthAsClientGuard, ScAuthInterceptor, ScAuthService, ScBanner, ScBannerService, ScCacheInterceptor, ScCacheSettings, ScCachedData, ScCachedItem, ScCart, ScCartService, ScCatalogFilterService, ScCatalogFormat, ScCatalogItem, ScCatalogRedirectService, ScCatalogService, ScCatalogableItem, ScCategory, ScClientType, ScCodedIdentity, ScConfiguratorService, ScContactsService, ScContragentService, ScConvertInterceptor, ScConvertersService, ScDateFormatInterceptor, ScDateHelper, ScDeletableNamedIdentity, ScDeliveryAddressService, ScDeliveryCost, ScDeliveryType, ScDocumentInfoNode, ScDocumentInfoTypesEnum, ScErrorRequestState, ScErrorsInterceptor, ScFavoriteService, ScFeedbackForms, ScFeedbackService, ScFilesService, ScFrequentlyAskedQuestionsService, ScGuestInterceptor, ScHiddenCatalogableItem, ScIBreadcrumbItem, ScISuggestionType, ScIconTypesEnum, ScIdOrSlugGuard, ScIdOrSlugPipe, ScIdentity, ScImage, ScImageHelper, ScLoadingRequestState, ScLocationsService, ScMediaImageTransformerPipe, ScMimeTypes, ScNamedIdentity, ScNews, ScNewsService, ScNotificationActionTypes, ScNotificationLevelNames, ScNotificationsService, ScOpfList, ScOptionsInterceptor, ScOrder, ScOrderBase, ScOrderDraft, ScOrderDraftActualization, ScOrderDraftActualizationStatus, ScOrderDraftShort, ScOrderDraftType, ScOrderDraftsService, ScOrderItem, ScOrderShort, ScOrderStateStatus, ScOrdersService, ScPaginationPageQueryParamService, ScPaginationService, ScPaymentStatus, ScPaymentType, ScPhoneService, ScPrimaryCatalogableItem, ScProduct, ScProductTileType, ScProfstroyOrderItem, ScQuestionnaireService, ScQuestionnaireStatusEnum, ScReclamationService, ScReclamationStatus, ScRecommendationService, ScRedirectStatusEnum, ScReference, ScReferenceName, ScReferencesService, ScRequestStateStatusEnum, ScRequisitesService, ScRouteKeys, ScSearchService, ScSeoService, ScSocialType, ScSsrResponseCache, ScSsrResponseCacheBackend, ScSuccessRequestState, ScSuggestionService, ScSum, ScTokenService, ScUIService, ScUTMService, ScUnitsHelper, ScUpdatableNamedIdentity, ScUploadedFile, ScUser, ScUserMetadata, ScUserMetrikaGoalsEnum, ScUserMetrikaService, ScUserService, ScUserType, ScVCardService, ScVacanciesList, ScVacanciesService, ScVacancy, ScVerificationService, ScVirtualCategory, ScWarehouseService, SchemaOrgFactory, TERMINAL_PROVIDERS, USER_AGENT_TERMINAL, filterChangedByKey, loadingPlaceholder, mapRequestStateToDataOrNull, mapToRequestState, provideSsrResponseCache, runningOnTerminalFactory, scGetCurrentRoute, scGetPageQueryParamChanges, scOrderIsLoaded, scParsePageQueryParam, searchTermFactory };
9097
9665
  //# sourceMappingURL=snabcentr-client-core.mjs.map