@controleonline/ui-common 1.2.70 → 1.2.71

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 (61) hide show
  1. package/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
@@ -1,61 +1,115 @@
1
- import {getPaymentOptionId, getPaymentOptionLabel} from './paymentOptions';
2
-
3
- export const REMOTE_PAYMENT_MESSAGE_STORE = 'invoice';
4
- export const REMOTE_PAYMENT_REQUEST_ACTION = 'pay';
5
- export const REMOTE_PAYMENT_RESULT_ACTION = 'pay-result';
6
-
7
- const normalizeText = value => String(value || '').trim();
8
-
9
- const normalizeOrderId = value => normalizeText(value).replace(/\D/g, '');
10
-
11
- export const normalizeRemotePaymentRequestKey = value => normalizeText(value);
12
-
13
- export const buildRemotePaymentRequestKey = ({
14
- orderId,
15
- payment,
16
- targetDeviceId,
17
- }) =>
18
- [
19
- normalizeOrderId(orderId) || 'order',
20
- normalizeText(targetDeviceId) || 'device',
21
- getPaymentOptionId(payment) || 'payment',
22
- Date.now().toString(),
23
- ].join(':');
24
-
25
- export const isRemotePaymentRequestMessage = message =>
26
- normalizeText(message?.action).toLowerCase() ===
27
- REMOTE_PAYMENT_REQUEST_ACTION;
28
-
29
- export const isRemotePaymentResultMessage = message =>
30
- normalizeText(message?.action).toLowerCase() ===
31
- REMOTE_PAYMENT_RESULT_ACTION;
32
-
33
- export const buildRemotePaymentResultMessage = ({
34
- destinationDeviceId,
35
- error = '',
36
- invoice = null,
37
- orderId,
38
- paidAmount = 0,
39
- payment = null,
40
- requestKey,
41
- status = 'error',
42
- targetDeviceId,
43
- targetDeviceLabel = '',
44
- targetGateway = '',
45
- total = 0,
46
- }) => ({
47
- destination: normalizeText(destinationDeviceId),
48
- store: REMOTE_PAYMENT_MESSAGE_STORE,
49
- action: REMOTE_PAYMENT_RESULT_ACTION,
50
- requestKey: normalizeRemotePaymentRequestKey(requestKey),
51
- status: normalizeText(status).toLowerCase() === 'success' ? 'success' : 'error',
52
- order: normalizeOrderId(orderId),
53
- total: Number(total || 0),
54
- paidAmount: Number(paidAmount || 0),
55
- paymentLabel: getPaymentOptionLabel(payment),
56
- targetDeviceId: normalizeText(targetDeviceId),
57
- targetDeviceLabel: normalizeText(targetDeviceLabel),
58
- targetGateway: normalizeText(targetGateway),
59
- ...(invoice ? {invoice} : {}),
60
- ...(error ? {error: normalizeText(error)} : {}),
61
- });
1
+ import {getPaymentOptionId, getPaymentOptionLabel} from './paymentOptions';
2
+
3
+ export const REMOTE_PAYMENT_MESSAGE_STORE = 'invoice';
4
+ export const REMOTE_PAYMENT_REQUEST_ACTION = 'pay';
5
+ export const REMOTE_PAYMENT_RESULT_ACTION = 'pay-result';
6
+ export const REMOTE_PAYMENT_STATUS_SUCCESS = 'success';
7
+ export const REMOTE_PAYMENT_STATUS_ERROR = 'error';
8
+ export const REMOTE_PAYMENT_STATUS_CANCELED = 'canceled';
9
+
10
+ const normalizeText = value => String(value || '').trim();
11
+
12
+ const normalizeSearchText = value =>
13
+ normalizeText(value)
14
+ .normalize('NFD')
15
+ .replace(/[\u0300-\u036f]/g, '')
16
+ .toLowerCase();
17
+
18
+ const EXPLICIT_CANCELLATION_STATUSES = new Set([
19
+ 'cancel',
20
+ 'canceled',
21
+ 'cancelled',
22
+ ]);
23
+
24
+ const isCancellationErrorText = value => {
25
+ const text = normalizeSearchText(value);
26
+
27
+ if (!text || /(?:nao|not) (?:foi )?(?:possivel )?cancel/.test(text)) {
28
+ return false;
29
+ }
30
+
31
+ return (
32
+ /\bcancelad[ao]s?\b/.test(text) ||
33
+ /\bcancell?ed\b/.test(text) ||
34
+ /\bcancelamento\b.*\b(?:usuario|cliente)\b/.test(text)
35
+ );
36
+ };
37
+
38
+ /*
39
+ * @agents A customer cancellation is a neutral terminal outcome. Older remote
40
+ * devices reported it as status=error, so keep the text fallback until every
41
+ * deployed device sends the canonical canceled status.
42
+ */
43
+ export const normalizeRemotePaymentResultStatus = ({status, error} = {}) => {
44
+ const normalizedStatus = normalizeText(status).toLowerCase();
45
+
46
+ if (normalizedStatus === REMOTE_PAYMENT_STATUS_SUCCESS) {
47
+ return REMOTE_PAYMENT_STATUS_SUCCESS;
48
+ }
49
+
50
+ if (
51
+ EXPLICIT_CANCELLATION_STATUSES.has(normalizedStatus) ||
52
+ isCancellationErrorText(error)
53
+ ) {
54
+ return REMOTE_PAYMENT_STATUS_CANCELED;
55
+ }
56
+
57
+ return REMOTE_PAYMENT_STATUS_ERROR;
58
+ };
59
+
60
+ export const isRemotePaymentCancellation = result =>
61
+ normalizeRemotePaymentResultStatus(result) === REMOTE_PAYMENT_STATUS_CANCELED;
62
+
63
+ const normalizeOrderId = value => normalizeText(value).replace(/\D/g, '');
64
+
65
+ export const normalizeRemotePaymentRequestKey = value => normalizeText(value);
66
+
67
+ export const buildRemotePaymentRequestKey = ({
68
+ orderId,
69
+ payment,
70
+ targetDeviceId,
71
+ }) =>
72
+ [
73
+ normalizeOrderId(orderId) || 'order',
74
+ normalizeText(targetDeviceId) || 'device',
75
+ getPaymentOptionId(payment) || 'payment',
76
+ Date.now().toString(),
77
+ ].join(':');
78
+
79
+ export const isRemotePaymentRequestMessage = message =>
80
+ normalizeText(message?.action).toLowerCase() ===
81
+ REMOTE_PAYMENT_REQUEST_ACTION;
82
+
83
+ export const isRemotePaymentResultMessage = message =>
84
+ normalizeText(message?.action).toLowerCase() ===
85
+ REMOTE_PAYMENT_RESULT_ACTION;
86
+
87
+ export const buildRemotePaymentResultMessage = ({
88
+ destinationDeviceId,
89
+ error = '',
90
+ invoice = null,
91
+ orderId,
92
+ paidAmount = 0,
93
+ payment = null,
94
+ requestKey,
95
+ status = 'error',
96
+ targetDeviceId,
97
+ targetDeviceLabel = '',
98
+ targetGateway = '',
99
+ total = 0,
100
+ }) => ({
101
+ destination: normalizeText(destinationDeviceId),
102
+ store: REMOTE_PAYMENT_MESSAGE_STORE,
103
+ action: REMOTE_PAYMENT_RESULT_ACTION,
104
+ requestKey: normalizeRemotePaymentRequestKey(requestKey),
105
+ status: normalizeRemotePaymentResultStatus({status, error}),
106
+ order: normalizeOrderId(orderId),
107
+ total: Number(total || 0),
108
+ paidAmount: Number(paidAmount || 0),
109
+ paymentLabel: getPaymentOptionLabel(payment),
110
+ targetDeviceId: normalizeText(targetDeviceId),
111
+ targetDeviceLabel: normalizeText(targetDeviceLabel),
112
+ targetGateway: normalizeText(targetGateway),
113
+ ...(invoice ? {invoice} : {}),
114
+ ...(error ? {error: normalizeText(error)} : {}),
115
+ });
@@ -126,8 +126,36 @@ const MANAGER_RUNTIME_MENU_FALLBACK = [
126
126
  sortOrder: 60,
127
127
  },
128
128
  ],
129
- },
130
- ];
129
+ },
130
+ ];
131
+
132
+ const ADMIN_RUNTIME_MENU_FALLBACK = [
133
+ {
134
+ id: 'admin-configuracoes',
135
+ label: 'Configuracoes',
136
+ icon: 'settings',
137
+ menus: [
138
+ {
139
+ id: 'menu_access',
140
+ menuKey: 'menu_access',
141
+ label: 'Menus por perfil',
142
+ route: 'MenuAccessConfigPage',
143
+ icon: 'list',
144
+ color: '#64748B',
145
+ sortOrder: 10,
146
+ },
147
+ {
148
+ id: 'test_results',
149
+ menuKey: 'test_results',
150
+ label: 'Resultados de testes',
151
+ route: 'TestsPlaygroundPage',
152
+ icon: 'clipboard',
153
+ color: '#0EA5E9',
154
+ sortOrder: 20,
155
+ },
156
+ ],
157
+ },
158
+ ];
131
159
 
132
160
  const cloneRuntimeMenuModules = modules =>
133
161
  (Array.isArray(modules) ? modules : []).map(module => ({
@@ -177,7 +205,11 @@ export const normalizeRuntimeMenuResponse = (
177
205
  if (allowFallback && normalizedAppType === 'MANAGER') {
178
206
  return cloneRuntimeMenuModules(MANAGER_RUNTIME_MENU_FALLBACK);
179
207
  }
180
-
208
+
209
+ if (allowFallback && normalizedAppType === 'ADMIN') {
210
+ return cloneRuntimeMenuModules(ADMIN_RUNTIME_MENU_FALLBACK);
211
+ }
212
+
181
213
  return [];
182
214
  };
183
215
 
@@ -46,12 +46,14 @@ export const SHOP_FRANCHISE_VISIBLE_ADDRESS_IDS_CONFIG_KEY =
46
46
  'shop-franchise-visible-address-ids';
47
47
  export const SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY =
48
48
  'shop-loyalty-coupons-enabled';
49
- export const SHOP_LOYALTY_PRODUCT_IDS_CONFIG_KEY =
50
- 'shop-loyalty-product-ids';
51
- export const SHOP_LOYALTY_REQUIRED_SALES_CONFIG_KEY =
52
- 'shop-loyalty-required-sales';
53
- export const SHOP_LOYALTY_GIFT_PRODUCT_ID_CONFIG_KEY =
54
- 'shop-loyalty-gift-product-id';
49
+ export const SHOP_LOYALTY_PRODUCT_IDS_CONFIG_KEY =
50
+ 'shop-loyalty-product-ids';
51
+ export const SHOP_LOYALTY_REQUIRED_SALES_CONFIG_KEY =
52
+ 'shop-loyalty-required-sales';
53
+ export const SHOP_LOYALTY_GIFT_PRODUCT_ID_CONFIG_KEY =
54
+ 'shop-loyalty-gift-product-id';
55
+ export const SHOP_LOYALTY_STAMP_ICON_URL_CONFIG_KEY =
56
+ 'shop-loyalty-stamp-icon-url';
55
57
 
56
58
  export const normalizeShopEntityId = value => {
57
59
  if (!value) {
@@ -218,11 +220,11 @@ export const normalizeShopPrimaryEntry = (
218
220
  return enabledOptions[0] || '';
219
221
  };
220
222
 
221
- export const resolveShopSettings = configs => {
222
- const configMap =
223
- configs && typeof configs === 'object' && !Array.isArray(configs)
224
- ? configs
225
- : {};
223
+ export const resolveShopSettings = configs => {
224
+ const configMap =
225
+ configs && typeof configs === 'object' && !Array.isArray(configs)
226
+ ? configs
227
+ : {};
226
228
 
227
229
  const salesPageEnabled = normalizeBooleanConfig(
228
230
  configMap[SHOP_SALES_PAGE_ENABLED_CONFIG_KEY],
@@ -241,11 +243,11 @@ export const resolveShopSettings = configs => {
241
243
  configMap[SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY],
242
244
  );
243
245
 
244
- return {
245
- salesPageEnabled,
246
- franchiseLocatorEnabled,
247
- loyaltyCouponsEnabled,
248
- enabledHomeOptions,
246
+ return {
247
+ salesPageEnabled,
248
+ franchiseLocatorEnabled,
249
+ loyaltyCouponsEnabled,
250
+ enabledHomeOptions,
249
251
  primaryEntry: normalizeShopPrimaryEntry(
250
252
  configMap[SHOP_PRIMARY_ENTRY_CONFIG_KEY],
251
253
  {
@@ -284,11 +286,35 @@ export const resolveShopSettings = configs => {
284
286
  loyaltyProductIds: normalizeShopEntityIds(
285
287
  configMap[SHOP_LOYALTY_PRODUCT_IDS_CONFIG_KEY],
286
288
  ),
287
- loyaltyRequiredSales: normalizeShopLoyaltyRequiredSales(
288
- configMap[SHOP_LOYALTY_REQUIRED_SALES_CONFIG_KEY],
289
- ),
290
- loyaltyGiftProductId: normalizeShopEntityId(
291
- configMap[SHOP_LOYALTY_GIFT_PRODUCT_ID_CONFIG_KEY],
292
- ),
293
- };
294
- };
289
+ loyaltyRequiredSales: normalizeShopLoyaltyRequiredSales(
290
+ configMap[SHOP_LOYALTY_REQUIRED_SALES_CONFIG_KEY],
291
+ ),
292
+ loyaltyGiftProductId: normalizeShopEntityId(
293
+ configMap[SHOP_LOYALTY_GIFT_PRODUCT_ID_CONFIG_KEY],
294
+ ),
295
+ loyaltyStampIconUrl: normalizeShopTextConfig(
296
+ configMap[SHOP_LOYALTY_STAMP_ICON_URL_CONFIG_KEY],
297
+ ),
298
+ };
299
+ };
300
+
301
+ export const saveAndUpdateConfigValue = ({
302
+ configKey,
303
+ nextValue,
304
+ saveConfig,
305
+ setValue,
306
+ }) => {
307
+ setValue(nextValue);
308
+ return saveConfig(configKey, nextValue);
309
+ };
310
+
311
+ export const toggleAndSaveBooleanConfig = ({
312
+ configKey,
313
+ currentValue,
314
+ saveConfig,
315
+ setValue,
316
+ }) => {
317
+ const nextValue = !Boolean(currentValue);
318
+ setValue(nextValue);
319
+ return saveConfig(configKey, nextValue ? '1' : '0');
320
+ };
@@ -10,12 +10,21 @@ export const SHOP_FRANCHISE_PAGE_SIZE = 50;
10
10
 
11
11
  const normalizeItemsPerPage = value =>
12
12
  Math.max(1, Math.min(SHOP_FRANCHISE_PAGE_SIZE, Number(value) || SHOP_FRANCHISE_PAGE_SIZE));
13
-
14
- const sortByLabel = (left, right) =>
15
- String(left || '')
16
- .localeCompare(String(right || ''), 'pt-BR', {
17
- sensitivity: 'base',
18
- });
13
+
14
+ const normalizeFranchiseDirectoryItem = company => ({
15
+ ...company,
16
+ shopAddresses: Array.isArray(company?.shopAddresses)
17
+ ? company.shopAddresses
18
+ : Array.isArray(company?.address)
19
+ ? company.address
20
+ : [],
21
+ });
22
+
23
+ const sortByLabel = (left, right) =>
24
+ String(left || '')
25
+ .localeCompare(String(right || ''), 'pt-BR', {
26
+ sensitivity: 'base',
27
+ });
19
28
 
20
29
  export const resolveFranchiseCompanyLabel = company =>
21
30
  String(company?.alias || company?.name || '').trim() ||
@@ -49,14 +58,46 @@ export const fetchShopFranchiseCompanies = async ({
49
58
  const items = extractCollectionItems(response);
50
59
 
51
60
  return items
52
- .map(company => ({
53
- ...company,
54
- shopAddresses: Array.isArray(company?.shopAddresses)
55
- ? company.shopAddresses
56
- : Array.isArray(company?.address)
57
- ? company.address
58
- : [],
59
- }))
61
+ .map(normalizeFranchiseDirectoryItem)
62
+ .sort((left, right) =>
63
+ sortByLabel(
64
+ resolveFranchiseCompanyLabel(left),
65
+ resolveFranchiseCompanyLabel(right),
66
+ ),
67
+ );
68
+ };
69
+
70
+ export const fetchAllShopFranchiseDirectory = async ({
71
+ companyId,
72
+ publicDirectory = false,
73
+ search = '',
74
+ itemsPerPage = SHOP_FRANCHISE_PAGE_SIZE,
75
+ } = {}) => {
76
+ const normalizedItemsPerPage = normalizeItemsPerPage(itemsPerPage);
77
+ const items = [];
78
+ let page = 1;
79
+
80
+ while (true) {
81
+ const pageItems = await fetchShopFranchiseCompanies({
82
+ companyId,
83
+ publicDirectory,
84
+ search,
85
+ page,
86
+ itemsPerPage: normalizedItemsPerPage,
87
+ });
88
+ const normalizedPageItems = Array.isArray(pageItems) ? pageItems : [];
89
+
90
+ items.push(...normalizedPageItems);
91
+
92
+ if (normalizedPageItems.length < normalizedItemsPerPage) {
93
+ break;
94
+ }
95
+
96
+ page += 1;
97
+ }
98
+
99
+ return items
100
+ .map(normalizeFranchiseDirectoryItem)
60
101
  .sort((left, right) =>
61
102
  sortByLabel(
62
103
  resolveFranchiseCompanyLabel(left),
@@ -102,13 +143,6 @@ export const fetchShopFranchiseDirectory = async ({
102
143
  itemsPerPage,
103
144
  });
104
145
 
105
- return companies.map(company => ({
106
- ...company,
107
- shopAddresses: Array.isArray(company?.shopAddresses)
108
- ? company.shopAddresses
109
- : Array.isArray(company?.address)
110
- ? company.address
111
- : [],
112
- }));
146
+ return companies.map(normalizeFranchiseDirectoryItem);
113
147
  };
114
148
  // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
@@ -104,15 +104,15 @@ const createSocketRuntimePipeline = ({
104
104
  : 'socket:open'
105
105
  : `socket:${normalizedStatus || 'off'}`;
106
106
 
107
- const indicatorColor =
108
- socketConnected && socketIdentified
109
- ? '#10b981'
110
- : (socketConnected ||
111
- ['connecting', 'identifying', 'open', 'reconnecting'].includes(
112
- normalizedStatus,
113
- ))
114
- ? '#e67e22'
115
- : '#c10015';
107
+ const indicatorTone =
108
+ socketConnected && socketIdentified
109
+ ? 'success'
110
+ : (socketConnected ||
111
+ ['connecting', 'identifying', 'open', 'reconnecting'].includes(
112
+ normalizedStatus,
113
+ ))
114
+ ? 'warning'
115
+ : 'error';
116
116
 
117
117
  const lastSocketStores = Array.isArray(state?.lastStores)
118
118
  ? state.lastStores.filter(Boolean).join(', ')
@@ -122,11 +122,11 @@ const createSocketRuntimePipeline = ({
122
122
  : '';
123
123
 
124
124
  runtimeDebugActions.setFooterEntry({
125
- key: 'socket',
126
- order: 10,
127
- indicatorColor,
128
- updatedAt: state?.updatedAt || new Date().toISOString(),
129
- lines: [
125
+ key: 'socket',
126
+ order: 10,
127
+ indicatorTone,
128
+ updatedAt: state?.updatedAt || new Date().toISOString(),
129
+ lines: [
130
130
  `Realtime ${stateLabel} | empresa: ${normalizeText(
131
131
  getCurrentCompanyId(),
132
132
  ) || '--'} | device: ${truncateText(
@@ -6,8 +6,8 @@ import * as customActions from './customActions';
6
6
  export default {
7
7
  namespaced: true,
8
8
  state: {
9
- item: null, //Don´t Touch plz....
10
- items: null, //Don´t Touch plz....
9
+ item: null, //Don't Touch plz....
10
+ items: null, //Don't Touch plz....
11
11
  resourceEndpoint: 'configs',
12
12
  isLoading: false,
13
13
  isSaving: false,
@@ -0,0 +1,75 @@
1
+ const {afterAll, afterEach, beforeEach, describe, expect, it} = global;
2
+ const {jest} = require('@jest/globals');
3
+
4
+ jest.mock('react-native', () => ({
5
+ Platform: {
6
+ OS: 'web',
7
+ select: options => options.web || options.default || options.ios || options.android,
8
+ },
9
+ }));
10
+
11
+ const originalLocalStorage = global.localStorage;
12
+
13
+ global.localStorage = {
14
+ clear: jest.fn(),
15
+ getItem: jest.fn(() => '{}'),
16
+ removeItem: jest.fn(),
17
+ setItem: jest.fn(),
18
+ };
19
+
20
+ const {api} = require('@controleonline/ui-common/src/api');
21
+
22
+ describe('smoke api helper', () => {
23
+ const originalFetch = global.fetch;
24
+ const originalGetToken = api.getToken;
25
+
26
+ beforeEach(() => {
27
+ global.fetch = jest.fn();
28
+ api.getToken = jest.fn().mockResolvedValue('session-token');
29
+ });
30
+
31
+ afterEach(() => {
32
+ global.fetch = originalFetch;
33
+ api.getToken = originalGetToken;
34
+ jest.restoreAllMocks();
35
+ });
36
+
37
+ afterAll(() => {
38
+ global.localStorage = originalLocalStorage;
39
+ });
40
+
41
+ it('loads the smoke index from the canonical /tests endpoint and sends the app-domain', async () => {
42
+ global.fetch.mockResolvedValue({
43
+ ok: true,
44
+ status: 200,
45
+ statusText: 'OK',
46
+ text: async () =>
47
+ JSON.stringify({
48
+ status: 'idle',
49
+ summary: {
50
+ types: {total: 0, passed: 0, failed: 0},
51
+ suites: {total: 0, passed: 0, failed: 0},
52
+ tests: {total: 0, passed: 0, failed: 0},
53
+ },
54
+ types: [],
55
+ suites: [],
56
+ }),
57
+ });
58
+
59
+ const response = await api.loadSmokeIndex({
60
+ apiBaseUrl: 'https://smoke.example.test',
61
+ domain: 'https://admin.controleonline.com/tests-playground',
62
+ });
63
+
64
+ expect(global.fetch).toHaveBeenCalledTimes(1);
65
+
66
+ const [url, options] = global.fetch.mock.calls[0];
67
+ expect(url).toBe('https://smoke.example.test/tests');
68
+ expect(options.headers.get('App-Domain')).toBe('admin.controleonline.com');
69
+ expect(options.headers.get('API-TOKEN')).toBe('session-token');
70
+ expect(options.headers.get('X-API-KEY')).toBeNull();
71
+ expect(options.headers.get('Accept')).toBe('application/ld+json');
72
+ expect(options.headers.get('Content-Type')).toBe('application/ld+json');
73
+ expect(response.status).toBe('idle');
74
+ });
75
+ });
@@ -18,6 +18,8 @@ const {
18
18
  POS_CASH_MANAGEMENT_MODE_CONFIG_KEY,
19
19
  POS_DELIVERY_ENABLED_CONFIG_KEY,
20
20
  POS_OPERATION_MODE_CONFIG_KEY,
21
+ POS_CHECK_ORDER_TYPE_CONFIG_KEY,
22
+ POS_CHECK_ORDER_TYPE_STAMP,
21
23
  isPosSingleItemMode,
22
24
  isPosAutoPrintEnabled,
23
25
  isPosCashRegisterClosed,
@@ -30,9 +32,11 @@ const {
30
32
  isDisplaySideBreakEnabled,
31
33
  normalizeDisplaySize,
32
34
  resolveDisplaySize,
35
+ resolvePosCheckOrderTypeForShop,
33
36
  resolvePosCashManagementMode,
34
37
  resolvePosOperationMode,
35
38
  resolvePosPrintMode,
39
+ resolvePosCheckOrderType,
36
40
  shouldEnableAndroidKioskMode,
37
41
  shouldEnableAndroidLauncherMode,
38
42
  shouldUsePosCashRegisterLifecycle,
@@ -179,6 +183,49 @@ describe('deviceConfigBootstrap POS operation helpers', () => {
179
183
  ).toBe(false)
180
184
  })
181
185
 
186
+ it('normalizes stamp linked order types for the POS config', () => {
187
+ expect(
188
+ resolvePosCheckOrderType({
189
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: 'stamp',
190
+ }),
191
+ ).toBe(POS_CHECK_ORDER_TYPE_STAMP)
192
+ expect(
193
+ resolvePosCheckOrderType({
194
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: 'carimbo',
195
+ }),
196
+ ).toBe(POS_CHECK_ORDER_TYPE_STAMP)
197
+ })
198
+
199
+ it('disables stamp when shop loyalty coupons are off', () => {
200
+ expect(
201
+ resolvePosCheckOrderTypeForShop(
202
+ {
203
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: 'stamp',
204
+ },
205
+ {
206
+ 'shop-loyalty-coupons-enabled': '0',
207
+ },
208
+ ),
209
+ ).toBe('none')
210
+
211
+ expect(
212
+ resolvePosCheckOrderTypeForShop(
213
+ {
214
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: 'stamp',
215
+ },
216
+ {
217
+ 'shop-loyalty-coupons-enabled': '1',
218
+ },
219
+ ),
220
+ ).toBe(POS_CHECK_ORDER_TYPE_STAMP)
221
+
222
+ expect(
223
+ resolvePosCheckOrderTypeForShop({
224
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: 'stamp',
225
+ }),
226
+ ).toBe(POS_CHECK_ORDER_TYPE_STAMP)
227
+ })
228
+
182
229
  it('defaults display size to 5 and clamps the configured range to 1..10', () => {
183
230
  expect(resolveDisplaySize({})).toBe(DISPLAY_SIZE_DEFAULT)
184
231
  expect(
@@ -2,7 +2,7 @@ const {jest} = require('@jest/globals')
2
2
 
3
3
  const mockPrint = jest.fn()
4
4
 
5
- jest.mock('@controleonline/ui-orders/src/react/services/Cielo/Print', () => ({
5
+ jest.mock('../../../react/services/Cielo/Print', () => ({
6
6
  CieloPrint: jest.fn().mockImplementation(() => ({
7
7
  print: mockPrint,
8
8
  })),