@cloud-ru/uikit-product-price-summary 3.0.0 → 3.1.1

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 (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +156 -3
  3. package/dist/cjs/components/PriceSummary/PriceSummary.d.ts +5 -2
  4. package/dist/cjs/components/PriceSummary/PriceSummary.js +6 -2
  5. package/dist/cjs/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.d.ts +4 -3
  6. package/dist/cjs/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.js +2 -2
  7. package/dist/cjs/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.d.ts +3 -1
  8. package/dist/cjs/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.js +5 -4
  9. package/dist/cjs/components/PriceSummary/components/PriceChangeBlock/PriceChangeBlock.d.ts +6 -0
  10. package/dist/cjs/components/PriceSummary/components/PriceChangeBlock/PriceChangeBlock.js +20 -0
  11. package/dist/cjs/components/PriceSummary/components/PriceChangeBlock/index.d.ts +1 -0
  12. package/dist/cjs/components/PriceSummary/components/PriceChangeBlock/index.js +17 -0
  13. package/dist/cjs/components/PriceSummary/components/PriceChangeBlock/styles.module.css +18 -0
  14. package/dist/cjs/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.d.ts +2 -2
  15. package/dist/cjs/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.js +28 -2
  16. package/dist/cjs/components/PriceSummary/components/TotalValueBlock/styles.module.css +8 -0
  17. package/dist/cjs/helpers/__tests__/mapInvoiceWithCalculatePrice.spec.d.ts +1 -0
  18. package/dist/cjs/helpers/__tests__/mapInvoiceWithCalculatePrice.spec.js +78 -0
  19. package/dist/cjs/helpers/formatPriceChange.d.ts +3 -0
  20. package/dist/cjs/helpers/formatPriceChange.js +23 -0
  21. package/dist/cjs/helpers/formatters.d.ts +1 -0
  22. package/dist/cjs/helpers/formatters.js +6 -0
  23. package/dist/cjs/helpers/getPriceChangeAppearance.d.ts +1 -0
  24. package/dist/cjs/helpers/getPriceChangeAppearance.js +12 -0
  25. package/dist/cjs/helpers/index.d.ts +3 -0
  26. package/dist/cjs/helpers/index.js +3 -0
  27. package/dist/cjs/helpers/mapInvoiceWithCalculatePrice.d.ts +7 -0
  28. package/dist/cjs/helpers/mapInvoiceWithCalculatePrice.js +80 -0
  29. package/dist/cjs/types/index.d.ts +29 -1
  30. package/dist/esm/components/PriceSummary/PriceSummary.d.ts +5 -2
  31. package/dist/esm/components/PriceSummary/PriceSummary.js +6 -2
  32. package/dist/esm/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.d.ts +4 -3
  33. package/dist/esm/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.js +3 -3
  34. package/dist/esm/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.d.ts +3 -1
  35. package/dist/esm/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.js +5 -4
  36. package/dist/esm/components/PriceSummary/components/PriceChangeBlock/PriceChangeBlock.d.ts +6 -0
  37. package/dist/esm/components/PriceSummary/components/PriceChangeBlock/PriceChangeBlock.js +14 -0
  38. package/dist/esm/components/PriceSummary/components/PriceChangeBlock/index.d.ts +1 -0
  39. package/dist/esm/components/PriceSummary/components/PriceChangeBlock/index.js +1 -0
  40. package/dist/esm/components/PriceSummary/components/PriceChangeBlock/styles.module.css +18 -0
  41. package/dist/esm/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.d.ts +2 -2
  42. package/dist/esm/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.js +29 -3
  43. package/dist/esm/components/PriceSummary/components/TotalValueBlock/styles.module.css +8 -0
  44. package/dist/esm/helpers/__tests__/mapInvoiceWithCalculatePrice.spec.d.ts +1 -0
  45. package/dist/esm/helpers/__tests__/mapInvoiceWithCalculatePrice.spec.js +76 -0
  46. package/dist/esm/helpers/formatPriceChange.d.ts +3 -0
  47. package/dist/esm/helpers/formatPriceChange.js +19 -0
  48. package/dist/esm/helpers/formatters.d.ts +1 -0
  49. package/dist/esm/helpers/formatters.js +5 -0
  50. package/dist/esm/helpers/getPriceChangeAppearance.d.ts +1 -0
  51. package/dist/esm/helpers/getPriceChangeAppearance.js +9 -0
  52. package/dist/esm/helpers/index.d.ts +3 -0
  53. package/dist/esm/helpers/index.js +3 -0
  54. package/dist/esm/helpers/mapInvoiceWithCalculatePrice.d.ts +7 -0
  55. package/dist/esm/helpers/mapInvoiceWithCalculatePrice.js +76 -0
  56. package/dist/esm/types/index.d.ts +29 -1
  57. package/package.json +5 -5
  58. package/src/components/PriceSummary/PriceSummary.tsx +24 -3
  59. package/src/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.tsx +14 -4
  60. package/src/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.tsx +16 -4
  61. package/src/components/PriceSummary/components/PriceChangeBlock/PriceChangeBlock.tsx +48 -0
  62. package/src/components/PriceSummary/components/PriceChangeBlock/index.ts +1 -0
  63. package/src/components/PriceSummary/components/PriceChangeBlock/styles.module.scss +23 -0
  64. package/src/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.tsx +50 -12
  65. package/src/components/PriceSummary/components/TotalValueBlock/styles.module.scss +9 -1
  66. package/src/helpers/__tests__/mapInvoiceWithCalculatePrice.spec.ts +92 -0
  67. package/src/helpers/formatPriceChange.ts +27 -0
  68. package/src/helpers/formatters.ts +6 -0
  69. package/src/helpers/getPriceChangeAppearance.ts +11 -0
  70. package/src/helpers/index.ts +3 -0
  71. package/src/helpers/mapInvoiceWithCalculatePrice.ts +139 -0
  72. package/src/types/index.ts +34 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 3.1.1 (2026-09-29)
7
+
8
+ **Note:** Version bump only for package @cloud-ru/uikit-product-price-summary
9
+
10
+
11
+
12
+
13
+
14
+ # 3.1.0 (2026-09-23)
15
+
16
+
17
+ ### Features
18
+
19
+ * **RM-4547:** new price summary api ([1a8ac74](https://github.com/cloud-ru-tech/uikit-product/commit/1a8ac74d7bb759f73b9cac483268417fb160d9dc))
20
+
21
+
22
+
23
+
24
+
6
25
  # 3.0.0 (2026-09-22)
7
26
 
8
27
 
package/README.md CHANGED
@@ -1,8 +1,161 @@
1
1
  # Price Summary
2
2
 
3
- ## Installation
4
- `npm i @cloud-ru/uikit-product-price-summary`
3
+ Компонент отображает итоговую стоимость, детализацию изменения цены и состав заказа.
5
4
 
6
- [Changelog](./CHANGELOG.md)
5
+ ## Установка
7
6
 
7
+ ```bash
8
+ npm i @cloud-ru/uikit-product-price-summary
9
+ ```
8
10
 
11
+ [История изменений](./CHANGELOG.md)
12
+
13
+ ## Итоговая сумма
14
+
15
+ Свойство `value` принимает точное значение или диапазон:
16
+
17
+ ```ts
18
+ type TotalValueRange = {
19
+ min: number;
20
+ max: number;
21
+ };
22
+
23
+ // value?: number | TotalValueRange
24
+ ```
25
+
26
+ Для числового значения свойство `totalSumType` управляет префиксом итоговой суммы:
27
+
28
+ | Значение | Отображение |
29
+ | -------- | ------------------------- |
30
+ | `equal` | Точная сумма без префикса |
31
+ | `from` | Сумма с префиксом «от» |
32
+ | `to` | Сумма с префиксом «до» |
33
+
34
+ ```tsx
35
+ <PriceSummary value={5000} totalSumType='from' />
36
+ <PriceSummary value={10000} totalSumType='to' />
37
+ ```
38
+
39
+ Для диапазона передайте `{ min, max }`. В этом случае `totalSumType` не применяется, а знак валюты выводится один раз в конце:
40
+
41
+ ```tsx
42
+ <PriceSummary value={{ min: 5000, max: 10000 }} />
43
+ ```
44
+
45
+ Если `value` не передан, компонент отображает `N/A`.
46
+
47
+ ## Предварительный расчёт
48
+
49
+ Если `hint` не передан, компонент показывает локализованную подсказку о предварительном расчёте: «Предварительный расчёт. Фактическая стоимость зависит от потребления».
50
+
51
+ Переданный `hint` заменяет стандартный текст. Чтобы скрыть подсказку, передайте пустую строку:
52
+
53
+ ```tsx
54
+ <PriceSummary value={5000} hint='Стоимость рассчитана на текущий период' />
55
+ <PriceSummary value={5000} hint='' />
56
+ ```
57
+
58
+ ## Базовая цена и изменение стоимости
59
+
60
+ `basePrice` добавляет перед итоговой суммой строку «Базовая цена». Нулевое значение не отображается.
61
+
62
+ `priceChange` добавляет строку «Изменение» и принимает объект следующего типа:
63
+
64
+ ```ts
65
+ type PriceChangeDetails = {
66
+ value: number;
67
+ percentage?: number;
68
+ };
69
+ ```
70
+
71
+ `priceChange.value` содержит изменение со знаком: положительное число означает увеличение стоимости, отрицательное — снижение. Если передан `percentage`, он выводится с тем же знаком. Нулевое изменение отображается без знака.
72
+
73
+ Компонент не вычисляет порог значимости. Потребитель должен самостоятельно определить, нужно ли передавать `priceChange`.
74
+
75
+ ```tsx
76
+ <PriceSummary basePrice={10000} priceChange={{ value: 5000, percentage: 10 }} value={15000} />
77
+ ```
78
+
79
+ В примере строка изменения будет отформатирована как `+5 000 ₽ (+10%)`.
80
+
81
+ ## Покрытие грантом и доступность SKU
82
+
83
+ `PriceSummary` не выполняет сетевые запросы. Для заполнения признака покрытия и отделения недоступных позиций передайте `calculatePriceData`:
84
+
85
+ ```ts
86
+ type CalculatePriceData = {
87
+ skuResults: SkuPriceResult[];
88
+ allow?: boolean;
89
+ };
90
+
91
+ type SkuPriceResult = {
92
+ skuId?: string;
93
+ skuCode?: string;
94
+ resourceSpecCode?: string;
95
+ resourceSpecCodeId?: string;
96
+ skuAvailability?: boolean;
97
+ coveredByGrants?: boolean;
98
+ };
99
+ ```
100
+
101
+ Поле `allow` входит в контракт данных, но не влияет на сопоставление и отображение позиций. Тип `CalculatePriceResponse` оставлен как устаревший алиас `CalculatePriceData` для обратной совместимости.
102
+
103
+ Чтобы связать позицию инвойса с результатом расчёта:
104
+
105
+ 1. Передайте `calculatePriceData` в одноимённое свойство компонента.
106
+ 2. Добавьте `id` основной позиции `invoice`, у которой `primary === true`.
107
+ 3. Значение `id` должно совпадать с `skuId`, `skuCode`, `resourceSpecCode` или `resourceSpecCodeId` из `skuResults`.
108
+
109
+ Только основные позиции поддерживают `id` и `coveredByGrant`. Для вторичных позиций с `primary === false` эти поля недоступны на уровне TypeScript.
110
+
111
+ ### Покрытие грантом
112
+
113
+ Для сопоставленного SKU с `skuAvailability !== false` компонент записывает значение `coveredByGrants` в `coveredByGrant` позиции инвойса.
114
+
115
+ Если у всех основных позиций группы одинаковое значение `coveredByGrant`, метка покрытия отображается у заголовка группы. Для смешанной группы метки отображаются у отдельных позиций.
116
+
117
+ Если `calculatePriceData` не передан, `skuResults` пуст или `id` не найден, исходные значения `coveredByGrant` не изменяются.
118
+
119
+ ### Недоступные позиции
120
+
121
+ Основная позиция с `skuAvailability === false` удаляется из исходной группы и переносится в первый агрегированный блок «Недоступно по договору». Заголовок блока отображается красным тегом, а метки покрытия грантом внутри него скрываются.
122
+
123
+ Недоступные позиции из всех групп объединяются в один блок. Если из исходной группы вынесена хотя бы одна позиция, её `price` и `quantity` скрываются, поскольку после разделения они могут стать неверными. Группа, в которой не осталось доступных позиций, полностью удаляется из обычной детализации.
124
+
125
+ ```tsx
126
+ <PriceSummary
127
+ invoice={[
128
+ {
129
+ title: 'Виртуальная машина',
130
+ quantity: 3,
131
+ price: 300,
132
+ items: [
133
+ { id: 'sku-1', primary: true, label: 'Процессор', quantity: 1, price: 100 },
134
+ { id: 'sku-2', primary: true, label: 'Память', quantity: 1, price: 100 },
135
+ { id: 'sku-3', primary: true, label: 'Диск', quantity: 1, price: 100 },
136
+ ],
137
+ },
138
+ ]}
139
+ calculatePriceData={{
140
+ skuResults: [
141
+ {
142
+ skuCode: 'sku-1',
143
+ skuAvailability: true,
144
+ coveredByGrants: true,
145
+ },
146
+ {
147
+ skuCode: 'sku-2',
148
+ skuAvailability: true,
149
+ coveredByGrants: false,
150
+ },
151
+ {
152
+ skuCode: 'sku-3',
153
+ skuAvailability: false,
154
+ coveredByGrants: true,
155
+ },
156
+ ],
157
+ }}
158
+ />
159
+ ```
160
+
161
+ В этом примере `sku-1` помечается как покрытый грантом, `sku-2` — как не покрытый, а `sku-3` переносится в блок «Недоступно по договору».
@@ -1,12 +1,15 @@
1
1
  import { WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
2
2
  import { LinkProps } from '@snack-uikit/link';
3
- import { DiscountDetails, InvoiceDetails } from '../../types';
3
+ import { CalculatePriceData, DiscountDetails, InvoiceDetails, PriceChangeDetails } from '../../types';
4
4
  import { ContentBlockProps } from '../ContentBlock';
5
5
  import { HeaderBlockProps } from './components/HeaderBlock';
6
6
  import { TotalValueBlockProps } from './components/TotalValueBlock';
7
7
  export type PriceSummaryProps = WithLayoutType<WithSupportProps<TotalValueBlockProps & HeaderBlockProps & ContentBlockProps & {
8
+ basePrice?: number;
8
9
  discount?: DiscountDetails;
10
+ priceChange?: PriceChangeDetails;
9
11
  invoice?: InvoiceDetails[];
12
+ calculatePriceData?: CalculatePriceData;
10
13
  invoiceExpandedDefault?: boolean;
11
14
  docsLink?: {
12
15
  href?: LinkProps['href'];
@@ -14,4 +17,4 @@ export type PriceSummaryProps = WithLayoutType<WithSupportProps<TotalValueBlockP
14
17
  };
15
18
  className?: string;
16
19
  }>>;
17
- export declare function PriceSummary({ value, totalSumType, hint, period, onPeriodChanged, periodOptions, promoBadge, loading, dataError, onRetry, discount, invoice, invoiceExpandedDefault, docsLink, className, layoutType, hintAppearance, showHintTooltip, hintTooltipText, hintLink, showHintLink, valueDelta, ...rest }: PriceSummaryProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function PriceSummary({ value, totalSumType, hint, period, onPeriodChanged, periodOptions, promoBadge, loading, dataError, onRetry, discount, invoice, calculatePriceData, invoiceExpandedDefault, docsLink, className, layoutType, hintAppearance, showHintTooltip, hintTooltipText, hintLink, showHintLink, valueDelta, priceChange, basePrice, ...rest }: PriceSummaryProps): import("react/jsx-runtime").JSX.Element;
@@ -17,17 +17,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.PriceSummary = PriceSummary;
18
18
  const jsx_runtime_1 = require("react/jsx-runtime");
19
19
  const classnames_1 = __importDefault(require("classnames"));
20
+ const react_1 = require("react");
20
21
  const uikit_product_locale_1 = require("@cloud-ru/uikit-product-locale");
21
22
  const uikit_product_utils_1 = require("@cloud-ru/uikit-product-utils");
22
23
  const link_1 = require("@snack-uikit/link");
24
+ const helpers_1 = require("../../helpers");
23
25
  const ContentBlock_1 = require("../ContentBlock");
24
26
  const DiscountBlock_1 = require("./components/DiscountBlock");
25
27
  const HeaderBlock_1 = require("./components/HeaderBlock");
26
28
  const InvoiceBlock_1 = require("./components/InvoiceBlock");
29
+ const PriceChangeBlock_1 = require("./components/PriceChangeBlock");
27
30
  const TotalValueBlock_1 = require("./components/TotalValueBlock");
28
31
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
29
32
  function PriceSummary(_a) {
30
- var { value, totalSumType, hint, period, onPeriodChanged, periodOptions, promoBadge, loading, dataError, onRetry, discount, invoice, invoiceExpandedDefault = true, docsLink, className, layoutType, hintAppearance, showHintTooltip = false, hintTooltipText, hintLink, showHintLink, valueDelta } = _a, rest = __rest(_a, ["value", "totalSumType", "hint", "period", "onPeriodChanged", "periodOptions", "promoBadge", "loading", "dataError", "onRetry", "discount", "invoice", "invoiceExpandedDefault", "docsLink", "className", "layoutType", "hintAppearance", "showHintTooltip", "hintTooltipText", "hintLink", "showHintLink", "valueDelta"]);
33
+ var { value, totalSumType, hint, period, onPeriodChanged, periodOptions, promoBadge, loading, dataError, onRetry, discount, invoice, calculatePriceData, invoiceExpandedDefault = true, docsLink, className, layoutType, hintAppearance, showHintTooltip = false, hintTooltipText, hintLink, showHintLink, valueDelta, priceChange, basePrice } = _a, rest = __rest(_a, ["value", "totalSumType", "hint", "period", "onPeriodChanged", "periodOptions", "promoBadge", "loading", "dataError", "onRetry", "discount", "invoice", "calculatePriceData", "invoiceExpandedDefault", "docsLink", "className", "layoutType", "hintAppearance", "showHintTooltip", "hintTooltipText", "hintLink", "showHintLink", "valueDelta", "priceChange", "basePrice"]);
31
34
  const { t } = (0, uikit_product_locale_1.useLocale)('PriceSummary');
32
- return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ className: (0, classnames_1.default)(styles_module_scss_1.default.priceSummary, className) }, (0, uikit_product_utils_1.extractSupportProps)(rest), { children: [(0, jsx_runtime_1.jsx)(HeaderBlock_1.HeaderBlock, { period: period, onPeriodChanged: onPeriodChanged, periodOptions: periodOptions, promoBadge: promoBadge, layoutType: layoutType }), (0, jsx_runtime_1.jsxs)(ContentBlock_1.ContentBlock, { loading: loading, dataError: dataError, onRetry: onRetry, children: [discount && (0, jsx_runtime_1.jsx)(DiscountBlock_1.DiscountBlock, { value: discount, layoutType: layoutType }), (0, jsx_runtime_1.jsx)(TotalValueBlock_1.TotalValueBlock, { value: value, totalSumType: totalSumType, hint: hint, hintAppearance: hintAppearance, showHintTooltip: showHintTooltip, hintTooltipText: hintTooltipText, hintLink: hintLink, showHintLink: showHintLink, valueDelta: valueDelta }), (invoice === null || invoice === void 0 ? void 0 : invoice.length) && ((0, jsx_runtime_1.jsx)(InvoiceBlock_1.InvoiceBlock, { invoice: invoice, invoiceExpandedDefault: invoiceExpandedDefault, layoutType: layoutType })), (docsLink === null || docsLink === void 0 ? void 0 : docsLink.href) && ((0, jsx_runtime_1.jsx)(link_1.Link, { size: 'm', textMode: 'default', text: docsLink.text || t('docsLink'), href: docsLink.href, target: '_blank' }))] })] })));
35
+ const { invoice: enrichedInvoice, unavailableItems } = (0, react_1.useMemo)(() => (0, helpers_1.splitInvoiceByAvailability)(invoice, calculatePriceData), [invoice, calculatePriceData]);
36
+ return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ className: (0, classnames_1.default)(styles_module_scss_1.default.priceSummary, className) }, (0, uikit_product_utils_1.extractSupportProps)(rest), { children: [(0, jsx_runtime_1.jsx)(HeaderBlock_1.HeaderBlock, { period: period, onPeriodChanged: onPeriodChanged, periodOptions: periodOptions, promoBadge: promoBadge, layoutType: layoutType }), (0, jsx_runtime_1.jsxs)(ContentBlock_1.ContentBlock, { loading: loading, dataError: dataError, onRetry: onRetry, children: [discount && (0, jsx_runtime_1.jsx)(DiscountBlock_1.DiscountBlock, { value: discount, layoutType: layoutType }), (basePrice || priceChange) && (0, jsx_runtime_1.jsx)(PriceChangeBlock_1.PriceDetailsBlock, { priceChange: priceChange, basePrice: basePrice }), (0, jsx_runtime_1.jsx)(TotalValueBlock_1.TotalValueBlock, { value: value, totalSumType: totalSumType, hint: hint, hintAppearance: hintAppearance, showHintTooltip: showHintTooltip, hintTooltipText: hintTooltipText, hintLink: hintLink, showHintLink: showHintLink, valueDelta: valueDelta }), ((enrichedInvoice === null || enrichedInvoice === void 0 ? void 0 : enrichedInvoice.length) || (unavailableItems === null || unavailableItems === void 0 ? void 0 : unavailableItems.length)) && ((0, jsx_runtime_1.jsx)(InvoiceBlock_1.InvoiceBlock, { invoice: enrichedInvoice, unavailableItems: unavailableItems, invoiceExpandedDefault: invoiceExpandedDefault, layoutType: layoutType })), (docsLink === null || docsLink === void 0 ? void 0 : docsLink.href) && ((0, jsx_runtime_1.jsx)(link_1.Link, { size: 'm', textMode: 'default', text: docsLink.text || t('docsLink'), href: docsLink.href, target: '_blank' }))] })] })));
33
37
  }
@@ -1,7 +1,8 @@
1
1
  import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
2
- import { InvoiceDetails } from '../../../../types';
2
+ import { InvoiceDetails, InvoiceItem } from '../../../../types';
3
3
  export type InvoiceBlockProps = WithLayoutType<{
4
- invoice: InvoiceDetails[];
4
+ invoice?: InvoiceDetails[];
5
+ unavailableItems?: InvoiceItem[];
5
6
  invoiceExpandedDefault?: boolean;
6
7
  }>;
7
- export declare function InvoiceBlock({ invoice, invoiceExpandedDefault, layoutType }: InvoiceBlockProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function InvoiceBlock({ invoice, unavailableItems, invoiceExpandedDefault, layoutType }: InvoiceBlockProps): import("react/jsx-runtime").JSX.Element;
@@ -11,8 +11,8 @@ const accordion_1 = require("@snack-uikit/accordion");
11
11
  const typography_1 = require("@snack-uikit/typography");
12
12
  const InvoiceDetailsBlock_1 = require("../InvoiceDetailsBlock");
13
13
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
14
- function InvoiceBlock({ invoice, invoiceExpandedDefault, layoutType }) {
14
+ function InvoiceBlock({ invoice, unavailableItems, invoiceExpandedDefault, layoutType }) {
15
15
  const { t } = (0, uikit_product_locale_1.useLocale)('PriceSummary');
16
16
  const invoiceBlockId = (0, react_1.useId)();
17
- return ((0, jsx_runtime_1.jsx)(accordion_1.AccordionSecondary, { expandedDefault: invoiceExpandedDefault ? invoiceBlockId : undefined, className: styles_module_scss_1.default.accordion, children: (0, jsx_runtime_1.jsx)(accordion_1.AccordionSecondary.CollapseBlock, { id: invoiceBlockId, header: (0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyM, { className: styles_module_scss_1.default.accordionHeaderTitle, children: t('orderDetails') }), children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.accordionContent, children: invoice.map((invoice, index) => ((0, jsx_runtime_1.jsx)(InvoiceDetailsBlock_1.InvoiceDetailsBlock, { invoice: invoice, layoutType: layoutType }, index))) }) }) }));
17
+ return ((0, jsx_runtime_1.jsx)(accordion_1.AccordionSecondary, { expandedDefault: invoiceExpandedDefault ? invoiceBlockId : undefined, className: styles_module_scss_1.default.accordion, children: (0, jsx_runtime_1.jsx)(accordion_1.AccordionSecondary.CollapseBlock, { id: invoiceBlockId, header: (0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyM, { className: styles_module_scss_1.default.accordionHeaderTitle, children: t('orderDetails') }), children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.accordionContent, children: [(unavailableItems === null || unavailableItems === void 0 ? void 0 : unavailableItems.length) && ((0, jsx_runtime_1.jsx)(InvoiceDetailsBlock_1.InvoiceDetailsBlock, { invoice: { title: t('unavailableByContract'), items: unavailableItems }, layoutType: layoutType, showCoveredByGrantLabels: false, titleAsRedTag: true })), invoice === null || invoice === void 0 ? void 0 : invoice.map((invoice, index) => ((0, jsx_runtime_1.jsx)(InvoiceDetailsBlock_1.InvoiceDetailsBlock, { invoice: invoice, layoutType: layoutType }, index)))] }) }) }));
18
18
  }
@@ -2,5 +2,7 @@ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
2
2
  import { InvoiceDetails } from '../../../../types';
3
3
  export type InvoiceDetailsBlockProps = WithLayoutType<{
4
4
  invoice: InvoiceDetails;
5
+ showCoveredByGrantLabels?: boolean;
6
+ titleAsRedTag?: boolean;
5
7
  }>;
6
- export declare function InvoiceDetailsBlock({ invoice, layoutType }: InvoiceDetailsBlockProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function InvoiceDetailsBlock({ invoice, layoutType, showCoveredByGrantLabels, titleAsRedTag, }: InvoiceDetailsBlockProps): import("react/jsx-runtime").JSX.Element;
@@ -6,19 +6,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.InvoiceDetailsBlock = InvoiceDetailsBlock;
7
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const uikit_product_locale_1 = require("@cloud-ru/uikit-product-locale");
9
+ const promo_tag_1 = require("@snack-uikit/promo-tag");
9
10
  const typography_1 = require("@snack-uikit/typography");
10
11
  const helpers_1 = require("../../../../helpers");
11
12
  const CoveredByGrantLabel_1 = require("../CoveredByGrantLabel");
12
13
  const Divider_1 = require("../Divider");
13
14
  const InvoiceItemBlock_1 = require("../InvoiceItemBlock");
14
15
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
15
- function InvoiceDetailsBlock({ invoice, layoutType }) {
16
+ function InvoiceDetailsBlock({ invoice, layoutType, showCoveredByGrantLabels = true, titleAsRedTag = false, }) {
16
17
  var _a;
17
18
  const { t } = (0, uikit_product_locale_1.useLocale)('PriceSummary');
18
19
  const primaryItems = invoice.items.filter(item => item.primary);
19
20
  const firstValue = (_a = primaryItems[0]) === null || _a === void 0 ? void 0 : _a.coveredByGrant;
20
21
  const allSameValue = primaryItems.length > 0 && primaryItems.every(item => item.coveredByGrant === firstValue);
21
- const showBlockLabel = allSameValue && firstValue !== undefined;
22
- const showItemLabels = !showBlockLabel;
23
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.main, children: [showBlockLabel && (0, jsx_runtime_1.jsx)(CoveredByGrantLabel_1.CoveredByGrantLabel, { covered: firstValue }), invoice.title && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.header, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { children: invoice.title }), invoice.quantity && (0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { children: (0, helpers_1.formatQuantity)(invoice.quantity) })] }), (0, jsx_runtime_1.jsx)(Divider_1.Divider, {})] })), invoice.items.map((item, index) => ((0, jsx_runtime_1.jsx)(InvoiceItemBlock_1.InvoiceItemBlock, { item: item, index: index, layoutType: layoutType, showCoveredByGrantLabel: showItemLabels }, index))), invoice.price !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Divider_1.Divider, {}), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.footer, children: (0, jsx_runtime_1.jsxs)(typography_1.Typography.SansLabelM, { children: [t('price'), ": ", (0, helpers_1.formatCurrency)(invoice.price)] }) })] }))] }));
22
+ const showBlockLabel = showCoveredByGrantLabels && allSameValue && firstValue !== undefined;
23
+ const showItemLabels = showCoveredByGrantLabels && !showBlockLabel;
24
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.main, children: [showBlockLabel && (0, jsx_runtime_1.jsx)(CoveredByGrantLabel_1.CoveredByGrantLabel, { covered: firstValue }), invoice.title && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.header, children: [titleAsRedTag ? ((0, jsx_runtime_1.jsx)(promo_tag_1.PromoTag, { appearance: 'red', color: 'decor', size: 'xxs', text: invoice.title })) : ((0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { children: invoice.title })), invoice.quantity && (0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { children: (0, helpers_1.formatQuantity)(invoice.quantity) })] }), (0, jsx_runtime_1.jsx)(Divider_1.Divider, {})] })), invoice.items.map((item, index) => ((0, jsx_runtime_1.jsx)(InvoiceItemBlock_1.InvoiceItemBlock, { item: item, index: index, layoutType: layoutType, showCoveredByGrantLabel: showItemLabels }, index))), invoice.price !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Divider_1.Divider, {}), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.footer, children: (0, jsx_runtime_1.jsxs)(typography_1.Typography.SansLabelM, { children: [t('price'), ": ", (0, helpers_1.formatCurrency)(invoice.price)] }) })] }))] }));
24
25
  }
@@ -0,0 +1,6 @@
1
+ import { PriceChangeDetails } from '../../../../types';
2
+ export type PriceDetailsBlockProps = {
3
+ basePrice?: number;
4
+ priceChange?: PriceChangeDetails;
5
+ };
6
+ export declare function PriceDetailsBlock({ basePrice, priceChange }: PriceDetailsBlockProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PriceDetailsBlock = PriceDetailsBlock;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const classnames_1 = __importDefault(require("classnames"));
9
+ const uikit_product_locale_1 = require("@cloud-ru/uikit-product-locale");
10
+ const typography_1 = require("@snack-uikit/typography");
11
+ const helpers_1 = require("../../../../helpers");
12
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
13
+ function PriceDetailsBlock({ basePrice, priceChange }) {
14
+ const { t } = (0, uikit_product_locale_1.useLocale)('PriceSummary');
15
+ const formattedValue = priceChange ? (0, helpers_1.formatPriceChange)(priceChange) : undefined;
16
+ if (!basePrice && !priceChange) {
17
+ return null;
18
+ }
19
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.priceDetails, children: [basePrice && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.priceDetailsRow, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { tag: 'div', children: t('basePrice') }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.priceDetailsValue, children: (0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { tag: 'div', children: (0, helpers_1.formatCurrency)(basePrice) }) })] })), priceChange && ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.priceDetailsRow, styles_module_scss_1.default.priceChangeRow), "data-appearance": (0, helpers_1.getPriceChangeAppearance)(priceChange.value), children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { tag: 'div', children: t('priceChange') }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.priceDetailsValue, title: formattedValue, children: (0, jsx_runtime_1.jsx)(typography_1.Typography.SansLabelM, { tag: 'div', children: formattedValue }) })] }))] }));
20
+ }
@@ -0,0 +1 @@
1
+ export * from './PriceChangeBlock';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./PriceChangeBlock"), exports);
@@ -0,0 +1,18 @@
1
+ .priceDetailsValue{
2
+ overflow-wrap:anywhere;
3
+ text-align:end;
4
+ }
5
+
6
+ .priceDetailsRow{
7
+ display:flex;
8
+ gap:var(--dimension-1m, 8px);
9
+ color:var(--sys-neutral-text-support, #6d707f);
10
+ }
11
+ .priceDetailsRow > *{
12
+ flex:1 1 0;
13
+ min-width:0;
14
+ }
15
+
16
+ .priceChangeRow[data-appearance=increased]{
17
+ color:var(--sys-red-text-main, #7a2d2d);
18
+ }
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { ValueOf } from '@snack-uikit/utils';
3
- import { PriceDeltaDetails, TotalSumType } from '../../../../types';
3
+ import { PriceDeltaDetails, TotalSumType, TotalValueRange } from '../../../../types';
4
4
  export declare const APPEARANCE_STATE: {
5
5
  readonly Default: "default";
6
6
  readonly UserError: "userError";
@@ -9,7 +9,7 @@ export declare const APPEARANCE_STATE: {
9
9
  };
10
10
  export type AppearanceState = ValueOf<typeof APPEARANCE_STATE>;
11
11
  export type TotalValueBlockProps = {
12
- value?: number;
12
+ value?: number | TotalValueRange;
13
13
  valueDelta?: PriceDeltaDetails;
14
14
  totalSumType?: TotalSumType;
15
15
  hint?: string;
@@ -39,8 +39,34 @@ function getAppearanceIcon(appearance) {
39
39
  }
40
40
  return (0, jsx_runtime_1.jsx)(Component, { size: 16, "data-appearance": appearance, className: styles_module_scss_1.default.hintIcon });
41
41
  }
42
+ function getTotalSumPrefix(totalSumType, fromPrefix, toPrefix) {
43
+ if (totalSumType === 'from') {
44
+ return `${fromPrefix} `;
45
+ }
46
+ if (totalSumType === 'to') {
47
+ return `${toPrefix} `;
48
+ }
49
+ return '';
50
+ }
51
+ function isTotalValueRange(value) {
52
+ return typeof value === 'object' && value !== null;
53
+ }
54
+ function formatTotalValueRange(value) {
55
+ return `${(0, helpers_1.formatCurrencyValue)(value.min)} — ${(0, helpers_1.formatCurrencyValue)(value.max)} ₽`;
56
+ }
57
+ function formatTotalValue(value, totalSumType, fromPrefix, toPrefix) {
58
+ if (isTotalValueRange(value)) {
59
+ return formatTotalValueRange(value);
60
+ }
61
+ if (value === undefined) {
62
+ return 'N/A';
63
+ }
64
+ return `${getTotalSumPrefix(totalSumType, fromPrefix, toPrefix)}${(0, helpers_1.formatCurrency)(value)}`;
65
+ }
42
66
  function TotalValueBlock({ value, totalSumType = 'equal', hint, hintAppearance = exports.APPEARANCE_STATE.Default, showHintTooltip, hintTooltipText, hintLink, showHintLink, valueDelta, }) {
43
67
  const { t } = (0, uikit_product_locale_1.useLocale)('PriceSummary');
44
- const totalSumPrefix = totalSumType === 'from' ? `${t('totalSumFromPrefix')} ` : '';
45
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.content, "data-appearance": hintAppearance, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.LightHeadlineS, { children: value !== undefined ? `${totalSumPrefix}${(0, helpers_1.formatCurrency)(Number(value))}` : 'N/A' }), valueDelta && ((0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { className: styles_module_scss_1.default.valueDelta, children: `${t(`${valueDelta.type}Price`)} ${(0, helpers_1.formatCurrency)(valueDelta.value)}` })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { open: showHintTooltip && hintTooltipText ? undefined : false, tip: hintTooltipText, placement: 'left-start', children: hint && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.hint, "data-appearance": hintAppearance, children: [getAppearanceIcon(hintAppearance), (0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { children: hint })] })) }), showHintLink && ((0, jsx_runtime_1.jsx)(link_1.Link, { textMode: 'accent', appearance: 'neutral', size: 's', href: hintLink === null || hintLink === void 0 ? void 0 : hintLink.href, text: hintLink === null || hintLink === void 0 ? void 0 : hintLink.text, className: styles_module_scss_1.default.link }))] }));
68
+ const displayedAppearance = hintAppearance !== null && hintAppearance !== void 0 ? hintAppearance : exports.APPEARANCE_STATE.Default;
69
+ const displayedHint = hint !== null && hint !== void 0 ? hint : t('preliminaryHint');
70
+ const totalValue = formatTotalValue(value, totalSumType, t('totalSumFromPrefix'), t('totalSumToPrefix'));
71
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.content, "data-appearance": displayedAppearance, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.LightHeadlineS, { className: isTotalValueRange(value) ? styles_module_scss_1.default.rangeValue : undefined, children: totalValue }), valueDelta && ((0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { className: styles_module_scss_1.default.valueDelta, children: `${t(`${valueDelta.type}Price`)} ${(0, helpers_1.formatCurrency)(valueDelta.value)}` })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { open: showHintTooltip && hintTooltipText ? undefined : false, tip: hintTooltipText, placement: 'left-start', children: displayedHint && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.hint, "data-appearance": displayedAppearance, children: [getAppearanceIcon(displayedAppearance), (0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { children: displayedHint })] })) }), showHintLink && ((0, jsx_runtime_1.jsx)(link_1.Link, { textMode: 'accent', appearance: 'neutral', size: 's', href: hintLink === null || hintLink === void 0 ? void 0 : hintLink.href, text: hintLink === null || hintLink === void 0 ? void 0 : hintLink.text, className: styles_module_scss_1.default.link }))] }));
46
72
  }
@@ -1,4 +1,5 @@
1
1
  .content{
2
+ container-type:inline-size;
2
3
  display:flex;
3
4
  flex-direction:column;
4
5
  text-align:start;
@@ -13,6 +14,13 @@
13
14
  color:var(--sys-neutral-text-disabled, #aaaebd);
14
15
  }
15
16
 
17
+ .rangeValue{
18
+ max-width:100%;
19
+ font-size:clamp(18px, 8cqi, 24px);
20
+ line-height:30px;
21
+ overflow-wrap:anywhere;
22
+ }
23
+
16
24
  .hint{
17
25
  display:flex;
18
26
  align-items:center;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const mapInvoiceWithCalculatePrice_1 = require("../mapInvoiceWithCalculatePrice");
5
+ const coveredSkuId = 'd4d5b1d9-7318-44c2-830b-5be7650f760f';
6
+ const uncoveredSkuId = 'ee5a2062-fdb3-43f9-a93e-2ae6224a35d6';
7
+ const unavailableSkuId = 'adb93523-71f8-4010-a469-3eb06079d4a5';
8
+ const resourceSpecCode = 'cpu.1c';
9
+ const invoice = [
10
+ {
11
+ quantity: 1,
12
+ title: 'Compute',
13
+ price: 100,
14
+ items: [
15
+ { id: coveredSkuId, quantity: 1, primary: true, label: 'Covered SKU', price: 40 },
16
+ { id: uncoveredSkuId, quantity: 1, primary: true, label: 'Uncovered SKU', price: 30 },
17
+ { id: unavailableSkuId, quantity: 1, primary: true, label: 'Unavailable SKU', price: 20 },
18
+ { quantity: 1, primary: false, label: 'Secondary SKU', price: 10 },
19
+ { quantity: 1, primary: true, label: 'SKU without id', price: 10 },
20
+ ],
21
+ },
22
+ ];
23
+ const calculatePriceData = {
24
+ skuResults: [
25
+ {
26
+ resourceSpecCodeId: coveredSkuId,
27
+ skuAvailability: true,
28
+ coveredByGrants: true,
29
+ },
30
+ {
31
+ resourceSpecCodeId: uncoveredSkuId,
32
+ skuAvailability: true,
33
+ coveredByGrants: false,
34
+ },
35
+ {
36
+ resourceSpecCodeId: unavailableSkuId,
37
+ skuAvailability: false,
38
+ coveredByGrants: true,
39
+ },
40
+ {
41
+ resourceSpecCode,
42
+ skuAvailability: true,
43
+ coveredByGrants: true,
44
+ },
45
+ ],
46
+ };
47
+ (0, vitest_1.describe)('mapInvoiceWithCalculatePrice', () => {
48
+ (0, vitest_1.it)('should return the original invoice when calculatePriceData is missing', () => {
49
+ (0, vitest_1.expect)((0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoice, undefined)).toBe(invoice);
50
+ });
51
+ (0, vitest_1.it)('should return the original invoice when skuResults are empty', () => {
52
+ (0, vitest_1.expect)((0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoice, { skuResults: [] })).toBe(invoice);
53
+ });
54
+ (0, vitest_1.it)('should set coveredByGrant for available SKUs matched by resourceSpecCodeId', () => {
55
+ const result = (0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoice, calculatePriceData);
56
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[0]).toMatchObject({ id: coveredSkuId, coveredByGrant: true });
57
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[1]).toMatchObject({ id: uncoveredSkuId, coveredByGrant: false });
58
+ });
59
+ (0, vitest_1.it)('should not set coveredByGrant when skuAvailability is false', () => {
60
+ const result = (0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoice, calculatePriceData);
61
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[2]).toEqual(invoice[0].items[2]);
62
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[2]).not.toHaveProperty('coveredByGrant');
63
+ });
64
+ (0, vitest_1.it)('should not set coveredByGrant for secondary items or items without id', () => {
65
+ const result = (0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoice, calculatePriceData);
66
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[3]).toEqual(invoice[0].items[3]);
67
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[4]).toEqual(invoice[0].items[4]);
68
+ });
69
+ (0, vitest_1.it)('should match SKUs by resourceSpecCode', () => {
70
+ const invoiceWithResourceSpecCode = [
71
+ {
72
+ items: [{ id: resourceSpecCode, quantity: 1, primary: true, label: 'CPU', price: 100 }],
73
+ },
74
+ ];
75
+ const result = (0, mapInvoiceWithCalculatePrice_1.mapInvoiceWithCalculatePrice)(invoiceWithResourceSpecCode, calculatePriceData);
76
+ (0, vitest_1.expect)(result === null || result === void 0 ? void 0 : result[0].items[0]).toMatchObject({ id: resourceSpecCode, coveredByGrant: true });
77
+ });
78
+ });
@@ -0,0 +1,3 @@
1
+ import { PriceChangeDetails } from '../types';
2
+ export declare function getSign(value: number): "+" | "-" | "";
3
+ export declare function formatPriceChange(value: PriceChangeDetails): string;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSign = getSign;
4
+ exports.formatPriceChange = formatPriceChange;
5
+ const ft_formatters_1 = require("@cloud-ru/ft-formatters");
6
+ const formatters_1 = require("./formatters");
7
+ function getSign(value) {
8
+ if (value > 0) {
9
+ return '+';
10
+ }
11
+ if (value < 0) {
12
+ return '-';
13
+ }
14
+ return '';
15
+ }
16
+ function formatPriceChange(value) {
17
+ const sign = getSign(value.value);
18
+ const amount = `${sign}${(0, formatters_1.formatCurrency)(Math.abs(value.value))}`;
19
+ if (value.percentage === undefined) {
20
+ return amount;
21
+ }
22
+ return `${amount} (${sign}${(0, ft_formatters_1.formatNumber)(Math.abs(value.percentage))}%)`;
23
+ }
@@ -1,2 +1,3 @@
1
+ export declare function formatCurrencyValue(value: number): string;
1
2
  export declare function formatCurrency(value: number): string;
2
3
  export declare function formatQuantity(value: string | number): string;
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCurrencyValue = formatCurrencyValue;
3
4
  exports.formatCurrency = formatCurrency;
4
5
  exports.formatQuantity = formatQuantity;
5
6
  const ft_formatters_1 = require("@cloud-ru/ft-formatters");
7
+ function formatCurrencyValue(value) {
8
+ return (0, ft_formatters_1.formatNumber)(value.toFixed(2), {
9
+ precision: 2,
10
+ });
11
+ }
6
12
  function formatCurrency(value) {
7
13
  return (0, ft_formatters_1.formatNumber)(value.toFixed(2), {
8
14
  precision: 2,
@@ -0,0 +1 @@
1
+ export declare function getPriceChangeAppearance(value: number): "increased" | "decreased" | "unchanged";
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPriceChangeAppearance = getPriceChangeAppearance;
4
+ function getPriceChangeAppearance(value) {
5
+ if (value > 0) {
6
+ return 'increased';
7
+ }
8
+ if (value < 0) {
9
+ return 'decreased';
10
+ }
11
+ return 'unchanged';
12
+ }
@@ -1 +1,4 @@
1
1
  export * from './formatters';
2
+ export * from './mapInvoiceWithCalculatePrice';
3
+ export * from './formatPriceChange';
4
+ export * from './getPriceChangeAppearance';
@@ -15,3 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./formatters"), exports);
18
+ __exportStar(require("./mapInvoiceWithCalculatePrice"), exports);
19
+ __exportStar(require("./formatPriceChange"), exports);
20
+ __exportStar(require("./getPriceChangeAppearance"), exports);