@controleonline/ui-common 1.2.88 → 1.2.89

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@controleonline/ui-common",
3
- "version": "1.2.88",
3
+ "version": "1.2.89",
4
4
  "author": "ControleOnline",
5
5
  "description": "ControleOnline Common Quasar UI Component",
6
6
  "license": "MIT",
@@ -1,131 +1,86 @@
1
- import { openDB } from "idb";
2
-
3
- const DB_NAME = "ControleOnline";
4
-
5
- export default class localDB {
6
- constructor(state) {
7
- this.state = state;
8
-
9
- this.checkAndGetDB();
10
- }
11
-
12
- async initDB() {
13
- let dbPromise = openDB(DB_NAME, this.currentVersion, {
14
- upgrade: (db) => {
15
- if (!db.objectStoreNames.contains(this.state.resourceEndpoint)) {
16
- const store = db.createObjectStore(this.state.resourceEndpoint, {
17
- keyPath: "id",
18
- });
19
- this.state.columns
20
- .filter((col) => col.sortable || col.externalFilter === false)
21
- .forEach((col) => {
22
- store.createIndex(col.name, col.name, { unique: false });
23
- });
24
- }
25
- },
26
- });
27
- return dbPromise;
28
- }
29
-
30
- async getCurrentVersion() {
31
- // Obtém a versão atual do banco de dados
32
- return new Promise((resolve) => {
33
- const request = indexedDB.open(DB_NAME);
34
- request.onsuccess = () => {
35
- const db = request.result;
36
- const currentVersion = db.currentVersion;
37
- db.close();
38
- resolve(currentVersion);
39
- this.currentVersion = currentVersion;
40
- };
41
- request.onerror = () => resolve(1);
42
- });
43
- }
44
-
45
- async checkAndGetDB() {
46
- return this.getCurrentVersion().then(() => {
47
- return this.initDB().then((db) => {
48
- if (!this.storeExists()) {
49
- this.currentVersion = db.currentVersion + 1;
50
- db.close();
51
- return this.initDB();
52
- }
53
- return db;
54
- });
55
- });
56
- }
57
-
58
- async storeExists() {
59
- return this.checkAndGetDB().then((db) => {
60
- return db.objectStoreNames.contains(this.state.resourceEndpoint);
61
- });
62
- }
63
-
64
- async getAll() {
65
- if (!(await this.storeExists())) {
66
- return null;
67
- }
68
- const db = await this.checkAndGetDB();
69
- return db.getAll(this.state.resourceEndpoint);
70
- }
71
-
72
- async getItemsByColumn(columnName, value) {
73
- if (!(await this.storeExists())) {
74
- return null;
75
- }
76
- const db = await this.checkAndGetDB();
77
- const tx = db.transaction(this.state.resourceEndpoint, "readonly");
78
- const store = tx.objectStore(this.state.resourceEndpoint);
79
- const index = store.index(columnName);
80
- const results = await index.getAll(value);
81
- await tx.done;
82
- return results;
83
- }
84
-
85
- async get(id) {
86
- if (!(await this.storeExists())) {
87
- return null;
88
- }
89
- const db = await this.checkAndGetDB();
90
- return db.get(this.state.resourceEndpoint, id);
91
- }
92
-
93
- async saveItems(items) {
94
- const db = await this.checkAndGetDB();
95
- const tx = db.transaction(this.state.resourceEndpoint, "readwrite");
96
- const store = tx.objectStore(this.state.resourceEndpoint);
97
- await Promise.all(items.map((item) => store.put(item)));
98
- await tx.done;
99
- }
100
-
101
- async saveItem(item) {
102
- const db = await this.checkAndGetDB();
103
- return db.put(this.state.resourceEndpoint, item);
104
- }
105
-
106
- async getItemsByFilters() {
107
- if (!(await this.storeExists())) {
108
- return null;
109
- }
110
- const db = await this.checkAndGetDB();
111
- if (Object.keys(this.state.filters).length === 0) {
112
- return this.getAll();
113
- }
114
-
115
- const filterPromises = Object.entries(this.state.filters).map(
116
- async ([key, value]) => {
117
- const column = this.state.columns.find((col) => col.name === key);
118
- if (column && (column.sortable || column.externalFilter === false)) {
119
- return this.getItemsByColumn(key, value);
120
- }
121
- return null;
122
- }
123
- );
124
-
125
- const filterResults = await Promise.all(filterPromises);
126
- return filterResults.reduce((acc, curr) => {
127
- if (!acc || acc.length === 0) return curr;
128
- return acc.filter((item) => curr.some((result) => result.id === item.id));
129
- }, []);
130
- }
131
- }
1
+ import {openDB} from 'idb';
2
+
3
+ const DB_NAME = 'ControleOnline';
4
+
5
+ export default class localDB {
6
+ constructor(state) {
7
+ this.state = state;
8
+ this.db = null;
9
+ this.opening = null;
10
+ }
11
+
12
+ async initDB() {
13
+ const endpoint = this.state.resourceEndpoint;
14
+ const upgrade = db => {
15
+ if (db.objectStoreNames.contains(endpoint)) return;
16
+ const store = db.createObjectStore(endpoint, {keyPath: 'id'});
17
+ (this.state.columns || []).forEach(column => {
18
+ if (column.name && (column.sortable || column.externalFilter === false)) {
19
+ store.createIndex(column.name, column.name, {unique: false});
20
+ }
21
+ });
22
+ };
23
+ const blocking = () => { this.db?.close(); this.db = null; };
24
+ let db = await openDB(DB_NAME, undefined, {upgrade, blocking});
25
+ if (!db.objectStoreNames.contains(endpoint)) {
26
+ const version = db.version + 1;
27
+ db.close();
28
+ db = await openDB(DB_NAME, version, {upgrade, blocking});
29
+ }
30
+ this.db = db;
31
+ return db;
32
+ }
33
+
34
+ async getCurrentVersion() {
35
+ return (await this.checkAndGetDB()).version;
36
+ }
37
+
38
+ checkAndGetDB() {
39
+ if (this.db) return Promise.resolve(this.db);
40
+ if (!this.opening) {
41
+ this.opening = this.initDB().finally(() => { this.opening = null; });
42
+ }
43
+ return this.opening;
44
+ }
45
+
46
+ async storeExists() {
47
+ return (await this.checkAndGetDB()).objectStoreNames.contains(this.state.resourceEndpoint);
48
+ }
49
+
50
+ async getAll() {
51
+ return (await this.checkAndGetDB()).getAll(this.state.resourceEndpoint);
52
+ }
53
+
54
+ async getItemsByColumn(columnName, value) {
55
+ const db = await this.checkAndGetDB();
56
+ const tx = db.transaction(this.state.resourceEndpoint, 'readonly');
57
+ if (!tx.store.indexNames.contains(columnName)) return [];
58
+ const items = await tx.store.index(columnName).getAll(value);
59
+ await tx.done;
60
+ return items;
61
+ }
62
+
63
+ async get(id) {
64
+ return (await this.checkAndGetDB()).get(this.state.resourceEndpoint, id);
65
+ }
66
+
67
+ async saveItems(items) {
68
+ const db = await this.checkAndGetDB();
69
+ const tx = db.transaction(this.state.resourceEndpoint, 'readwrite');
70
+ await Promise.all(items.map(item => tx.store.put(item)));
71
+ await tx.done;
72
+ }
73
+
74
+ async saveItem(item) {
75
+ return (await this.checkAndGetDB()).put(this.state.resourceEndpoint, item);
76
+ }
77
+
78
+ async getItemsByFilters() {
79
+ const filters = Object.entries(this.state.filters || {});
80
+ if (!filters.length) return this.getAll();
81
+ const matches = await Promise.all(filters.map(([key, value]) =>
82
+ this.getItemsByColumn(key, value)));
83
+ return matches.reduce((items, next) => items.filter(item =>
84
+ next.some(result => result.id === item.id)));
85
+ }
86
+ }
@@ -1,237 +1,48 @@
1
+ import {appendScreenMetrics, hasScreenMetricsChanges} from '@controleonline/ui-common/src/react/utils/screenMetrics.js';
2
+ import {normalizeBooleanConfig, SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY} from '@controleonline/ui-common/src/react/utils/shopConfig.js';
1
3
  import {
2
- appendScreenMetrics,
3
- hasScreenMetricsChanges,
4
- } from '@controleonline/ui-common/src/react/utils/screenMetrics.js';
5
- import {
6
- normalizeBooleanConfig,
7
- SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY,
8
- } from '@controleonline/ui-common/src/react/utils/shopConfig.js';
9
-
10
- export const CIELO_DEVICES = ['quantum', 'ingenico', 'positivo'];
11
- export const SUPPORTED_POS_GATEWAYS = ['cielo', 'infinite-pay'];
12
- export const DEVICE_ALERT_SOUND_ENABLED_KEY = 'notification-sound-enabled';
13
- export const DEVICE_ALERT_SOUND_URL_KEY = 'notification-sound-url';
14
- export const DEVICE_ORDER_VISIBILITY_KEY = 'pos-order-visibility';
15
- export const DEVICE_ORDER_VISIBILITY_DEVICE = 'device';
16
- export const DEVICE_ORDER_VISIBILITY_COMPANY = 'company';
17
- export const POS_DELIVERY_ENABLED_CONFIG_KEY = 'pos-delivery-enabled';
18
- export const DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY =
19
- 'device-runtime-debug-info-enabled';
20
- export const DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY =
21
- 'display-auto-print-product';
22
- export const DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY =
23
- 'display-allow-printer-change';
24
- export const DISPLAY_SIZE_CONFIG_KEY = 'display-size';
25
- export const DISPLAY_SIDE_BREAK_CONFIG_KEY = 'display-side-break';
26
- export const DISPLAY_SIZE_MIN = 1;
27
- export const DISPLAY_SIZE_MAX = 10;
28
- export const DISPLAY_SIZE_DEFAULT = 5;
29
- export const POS_OPERATION_MODE_CONFIG_KEY = 'pos-operation-mode';
30
- /** Independent of operation mode: whether this device may run local charge/payment. */
31
- export const POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY = 'pos-local-charge-enabled';
32
- export const POS_PRODUCT_SHOWCASE_CONFIG_KEY = 'pos-product-showcase-id';
33
- export const DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY =
34
- 'android-kiosk-enabled';
35
- export const DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY =
36
- 'android-launcher-enabled';
37
- export const POS_AUTO_PRINT_ENABLED_CONFIG_KEY = 'pos-auto-print-enabled';
38
- export const POS_CASH_MANAGEMENT_MODE_CONFIG_KEY =
39
- 'pos-cash-management-mode';
40
- export const POS_CHECK_ORDER_TYPE_CONFIG_KEY = 'check-order-type';
41
- export const POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY =
42
- 'check-order-management-mode';
43
- export const POS_OPERATION_MODE_COUNTER = 'counter';
44
- export const POS_OPERATION_MODE_WAITER = 'waiter';
45
- export const POS_OPERATION_MODE_TOTEM = 'totem';
46
- export const POS_OPERATION_MODE_SINGLE_ITEM = 'single-item';
47
- export const POS_OPERATION_MODE_CASHIER = 'cashier';
48
- export const POS_CHECK_ORDER_TYPE_NONE = 'none';
49
- export const POS_CHECK_ORDER_TYPE_TAB = 'tab';
50
- export const POS_CHECK_ORDER_TYPE_TABLE = 'table';
51
- export const POS_CHECK_ORDER_TYPE_STAMP = 'stamp';
52
- export const POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE = 'manage';
53
- export const POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY = 'existing-only';
54
- export const POS_OPERATION_MODE_DEFAULT = POS_OPERATION_MODE_COUNTER;
55
- export const POS_PRINT_MODE_ORDER = 'order';
56
- export const POS_PRINT_MODE_FORM = 'form';
57
- export const POS_PRINT_MODE_DEFAULT = POS_PRINT_MODE_ORDER;
58
- export const POS_CASH_MANAGEMENT_MODE_CASH_REGISTER = 'cash-register';
59
- export const POS_CASH_MANAGEMENT_MODE_DAILY = 'daily';
60
- export const POS_CASH_MANAGEMENT_MODE_DEFAULT =
61
- POS_CASH_MANAGEMENT_MODE_CASH_REGISTER;
62
- export const POS_OPERATION_MODE_OPTIONS = [
63
- {
64
- value: POS_OPERATION_MODE_COUNTER,
65
- translationKey: 'counterService',
66
- descriptionKey: 'counterServiceDescription',
67
- },
68
- {
69
- value: POS_OPERATION_MODE_WAITER,
70
- translationKey: 'waiterService',
71
- descriptionKey: 'waiterServiceDescription',
72
- },
73
- {
74
- value: POS_OPERATION_MODE_TOTEM,
75
- translationKey: 'selfServiceKiosk',
76
- descriptionKey: 'selfServiceKioskDescription',
77
- },
78
- {
79
- value: POS_OPERATION_MODE_SINGLE_ITEM,
80
- translationKey: 'singleItemSale',
81
- descriptionKey: 'singleItemSaleDescription',
82
- },
83
- {
84
- value: POS_OPERATION_MODE_CASHIER,
85
- translationKey: 'cashierPOS',
86
- descriptionKey: 'cashierPOSDescription',
87
- },
88
- ];
89
-
90
- export const DEFAULT_DEVICE_CONFIGS = {
91
- 'pos-type': 'full',
92
- 'print-mode': 'order',
93
- 'check-type': 'manual',
94
- 'product-input-type': 'manual',
95
- 'selection-type': 'single',
96
- sound: '0',
97
- vibration: '0',
98
- [DEVICE_ORDER_VISIBILITY_KEY]: DEVICE_ORDER_VISIBILITY_DEVICE,
99
- [POS_DELIVERY_ENABLED_CONFIG_KEY]: '1',
100
- [DEVICE_ALERT_SOUND_ENABLED_KEY]: '0',
101
- [DEVICE_ALERT_SOUND_URL_KEY]: '',
102
- [DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]: '0',
103
- [DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY]: '0',
104
- [DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY]: '0',
105
- [DISPLAY_SIZE_CONFIG_KEY]: String(DISPLAY_SIZE_DEFAULT),
106
- [DISPLAY_SIDE_BREAK_CONFIG_KEY]: '0',
107
- [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: POS_CHECK_ORDER_TYPE_NONE,
108
- [POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY]:
109
- POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
110
- [POS_OPERATION_MODE_CONFIG_KEY]: POS_OPERATION_MODE_DEFAULT,
111
- [POS_PRODUCT_SHOWCASE_CONFIG_KEY]: '',
112
- [POS_AUTO_PRINT_ENABLED_CONFIG_KEY]: '0',
113
- [POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: POS_CASH_MANAGEMENT_MODE_DEFAULT,
114
- };
115
-
116
- export const isTruthyValue = value =>
117
- value === true || value === '1' || value === 1 || value === 'true';
118
-
119
- export const isMissingConfigValue = value =>
120
- value === undefined || value === null || value === '';
121
-
122
- export const parseConfigsObject = configs => {
123
- if (!configs) return {};
124
-
125
- if (typeof configs === 'string') {
126
- try {
127
- const parsed = JSON.parse(configs);
128
- return parsed && typeof parsed === 'object' ? parsed : {};
129
- } catch {
130
- return {};
131
- }
132
- }
133
-
134
- return typeof configs === 'object' ? {...configs} : {};
135
- };
136
-
137
- export const normalizeDisplaySize = value => {
138
- const normalizedValue = String(value ?? '').trim();
139
-
140
- if (normalizedValue === '') {
141
- return DISPLAY_SIZE_DEFAULT;
142
- }
143
-
144
- const numericValue = Number(normalizedValue);
145
-
146
- if (!Number.isFinite(numericValue)) {
147
- return DISPLAY_SIZE_DEFAULT;
148
- }
149
-
150
- return Math.min(
151
- DISPLAY_SIZE_MAX,
152
- Math.max(DISPLAY_SIZE_MIN, Math.round(numericValue)),
153
- );
154
- };
155
-
156
- export const resolveDisplaySize = configs =>
157
- normalizeDisplaySize(
158
- parseConfigsObject(configs)?.[DISPLAY_SIZE_CONFIG_KEY],
159
- );
160
-
161
- export const isDisplaySideBreakEnabled = configs =>
162
- isTruthyValue(
163
- parseConfigsObject(configs)?.[DISPLAY_SIDE_BREAK_CONFIG_KEY],
164
- );
165
-
166
- export const normalizePosOperationMode = value => {
167
- const normalizedValue = String(value || '')
168
- .trim()
169
- .toLowerCase()
170
- .normalize('NFD')
171
- .replace(/[\u0300-\u036f]/g, '')
172
- .replace(/[\s_]/g, '-');
173
-
174
- if (
175
- [
176
- POS_OPERATION_MODE_COUNTER,
177
- 'balcao',
178
- 'counter-service',
179
- 'front-counter',
180
- ].includes(normalizedValue)
181
- ) {
182
- return POS_OPERATION_MODE_COUNTER;
183
- }
184
-
185
- if (
186
- [
187
- POS_OPERATION_MODE_WAITER,
188
- 'garcon',
189
- 'garcom',
190
- 'table-service',
191
- ].includes(normalizedValue)
192
- ) {
193
- return POS_OPERATION_MODE_WAITER;
194
- }
195
-
196
- if (
197
- [
198
- POS_OPERATION_MODE_TOTEM,
199
- ].includes(normalizedValue)
200
- ) {
201
- return POS_OPERATION_MODE_TOTEM;
202
- }
203
-
204
- if (
205
- [
206
- POS_OPERATION_MODE_SINGLE_ITEM,
207
- 'single-sale',
208
- 'single-item-sale',
209
- 'sale-unit',
210
- 'sale-single',
211
- 'venda-unitaria',
212
- 'venda-unica',
213
- 'item-unico',
214
- 'unit-sale',
215
- 'unitary-sale',
216
- ].includes(normalizedValue)
217
- ) {
218
- return POS_OPERATION_MODE_SINGLE_ITEM;
219
- }
220
-
221
- if (
222
- [
223
- POS_OPERATION_MODE_CASHIER,
224
- 'pdv',
225
- 'pos',
226
- 'checkout',
227
- 'cashier-pos',
228
- ].includes(normalizedValue)
229
- ) {
230
- return POS_OPERATION_MODE_CASHIER;
231
- }
232
-
233
- return '';
234
- };
4
+ CIELO_DEVICES,
5
+ SUPPORTED_POS_GATEWAYS,
6
+ DEVICE_ORDER_VISIBILITY_KEY,
7
+ DEVICE_ORDER_VISIBILITY_DEVICE,
8
+ DEVICE_ORDER_VISIBILITY_COMPANY,
9
+ POS_DELIVERY_ENABLED_CONFIG_KEY,
10
+ DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY,
11
+ DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY,
12
+ POS_OPERATION_MODE_CONFIG_KEY,
13
+ POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY,
14
+ ORDER_CHARGE_ENABLED_CONFIG_KEY,
15
+ isOrderChargeEnabled,
16
+ DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY,
17
+ DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY,
18
+ POS_AUTO_PRINT_ENABLED_CONFIG_KEY,
19
+ POS_CASH_MANAGEMENT_MODE_CONFIG_KEY,
20
+ POS_CHECK_ORDER_TYPE_CONFIG_KEY,
21
+ POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY,
22
+ POS_OPERATION_MODE_COUNTER,
23
+ POS_OPERATION_MODE_WAITER,
24
+ POS_OPERATION_MODE_TOTEM,
25
+ POS_OPERATION_MODE_SINGLE_ITEM,
26
+ POS_CHECK_ORDER_TYPE_NONE,
27
+ POS_CHECK_ORDER_TYPE_TAB,
28
+ POS_CHECK_ORDER_TYPE_TABLE,
29
+ POS_CHECK_ORDER_TYPE_TABLE_TAB,
30
+ POS_CHECK_ORDER_TYPE_STAMP,
31
+ POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
32
+ POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY,
33
+ POS_PRINT_MODE_FORM,
34
+ POS_PRINT_MODE_DEFAULT,
35
+ POS_CASH_MANAGEMENT_MODE_CASH_REGISTER,
36
+ POS_CASH_MANAGEMENT_MODE_DAILY,
37
+ POS_CASH_MANAGEMENT_MODE_DEFAULT,
38
+ POS_OPERATION_MODE_OPTIONS,
39
+ DEFAULT_DEVICE_CONFIGS,
40
+ isTruthyValue,
41
+ isMissingConfigValue,
42
+ parseConfigsObject,
43
+ normalizePosOperationMode,
44
+ } from './deviceConfigValues';
45
+ export * from './deviceConfigValues';
235
46
 
236
47
  export const resolvePosOperationMode = configs =>
237
48
  normalizePosOperationMode(
@@ -264,6 +75,10 @@ export const normalizePosCheckOrderType = value => {
264
75
  return POS_CHECK_ORDER_TYPE_TABLE;
265
76
  }
266
77
 
78
+ if (normalizedValue === POS_CHECK_ORDER_TYPE_TABLE_TAB) {
79
+ return POS_CHECK_ORDER_TYPE_TABLE_TAB;
80
+ }
81
+
267
82
  if (
268
83
  [
269
84
  POS_CHECK_ORDER_TYPE_STAMP,
@@ -306,6 +121,21 @@ export const resolvePosCheckOrderTypeForShop = (
306
121
  return resolvedType;
307
122
  };
308
123
 
124
+ // A device mode is not an Order.orderType. The current entry starts at the
125
+ // table; the waiter navigation can then resolve a concrete tab under that table.
126
+ export const resolvePosCheckOrderEntryTypeForShop = (deviceConfigs, shopConfigs) => {
127
+ const type = resolvePosCheckOrderTypeForShop(deviceConfigs, shopConfigs);
128
+ return type === POS_CHECK_ORDER_TYPE_TABLE_TAB ? POS_CHECK_ORDER_TYPE_TABLE : type;
129
+ };
130
+
131
+ export const resolvePosCheckOrderTypesForShop = (deviceConfigs, shopConfigs) => {
132
+ const type = resolvePosCheckOrderTypeForShop(deviceConfigs, shopConfigs);
133
+ if (type === POS_CHECK_ORDER_TYPE_TABLE_TAB) {
134
+ return [POS_CHECK_ORDER_TYPE_TABLE, POS_CHECK_ORDER_TYPE_TAB];
135
+ }
136
+ return type === POS_CHECK_ORDER_TYPE_NONE ? [] : [type];
137
+ };
138
+
309
139
  export const normalizePosCheckOrderManagementMode = value => {
310
140
  const normalizedValue = String(value || '')
311
141
  .trim()
@@ -513,6 +343,14 @@ export const isPosLocalChargeEnabled = configs => {
513
343
  );
514
344
  };
515
345
 
346
+ // Entry-point visibility only. The consultation and invoice API enforce capability.
347
+ export const isPosChargeEntryEnabled = configs => {
348
+ const parsed = parseConfigsObject(configs);
349
+ return Object.prototype.hasOwnProperty.call(parsed, ORDER_CHARGE_ENABLED_CONFIG_KEY)
350
+ ? isOrderChargeEnabled(parsed)
351
+ : isPosLocalChargeEnabled(parsed);
352
+ };
353
+
516
354
  export const getPosOperationModeOption = mode => {
517
355
 
518
356
  const normalizedMode = normalizePosOperationMode(mode);
@@ -0,0 +1,238 @@
1
+ import {
2
+ normalizeBooleanConfig,
3
+ SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY,
4
+ } from '@controleonline/ui-common/src/react/utils/shopConfig.js';
5
+
6
+ export const CIELO_DEVICES = ['quantum', 'ingenico', 'positivo'];
7
+ export const SUPPORTED_POS_GATEWAYS = ['cielo', 'infinite-pay'];
8
+ export const DEVICE_ALERT_SOUND_ENABLED_KEY = 'notification-sound-enabled';
9
+ export const DEVICE_ALERT_SOUND_URL_KEY = 'notification-sound-url';
10
+ export const DEVICE_ORDER_VISIBILITY_KEY = 'pos-order-visibility';
11
+ export const DEVICE_ORDER_VISIBILITY_DEVICE = 'device';
12
+ export const DEVICE_ORDER_VISIBILITY_COMPANY = 'company';
13
+ export const POS_DELIVERY_ENABLED_CONFIG_KEY = 'pos-delivery-enabled';
14
+ export const DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY =
15
+ 'device-runtime-debug-info-enabled';
16
+ export const DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY =
17
+ 'display-auto-print-product';
18
+ export const DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY =
19
+ 'display-allow-printer-change';
20
+ export const DISPLAY_SIZE_CONFIG_KEY = 'display-size';
21
+ export const DISPLAY_SIDE_BREAK_CONFIG_KEY = 'display-side-break';
22
+ export const DISPLAY_SIZE_MIN = 1;
23
+ export const DISPLAY_SIZE_MAX = 10;
24
+ export const DISPLAY_SIZE_DEFAULT = 5;
25
+ export const POS_OPERATION_MODE_CONFIG_KEY = 'pos-operation-mode';
26
+ /** Independent of operation mode: whether this device may run local charge/payment. */
27
+ export const POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY = 'pos-local-charge-enabled';
28
+ // Canonical financial permission already enforced by the API.
29
+ export const ORDER_CHARGE_ENABLED_CONFIG_KEY = 'order-charge-enabled';
30
+ export const isOrderChargeEnabled = configs => {
31
+ const value = parseConfigsObject(configs)?.[ORDER_CHARGE_ENABLED_CONFIG_KEY];
32
+ return value === true || value === 1 ||
33
+ ['1', 'true', 'yes', 'on'].includes(String(value ?? '').trim().toLowerCase());
34
+ };
35
+ export const POS_PRODUCT_SHOWCASE_CONFIG_KEY = 'pos-product-showcase-id';
36
+ export const DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY =
37
+ 'android-kiosk-enabled';
38
+ export const DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY =
39
+ 'android-launcher-enabled';
40
+ export const POS_AUTO_PRINT_ENABLED_CONFIG_KEY = 'pos-auto-print-enabled';
41
+ export const POS_CASH_MANAGEMENT_MODE_CONFIG_KEY =
42
+ 'pos-cash-management-mode';
43
+ export const POS_CHECK_ORDER_TYPE_CONFIG_KEY = 'check-order-type';
44
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY =
45
+ 'check-order-management-mode';
46
+ export const POS_OPERATION_MODE_COUNTER = 'counter';
47
+ export const POS_OPERATION_MODE_WAITER = 'waiter';
48
+ export const POS_OPERATION_MODE_TOTEM = 'totem';
49
+ export const POS_OPERATION_MODE_SINGLE_ITEM = 'single-item';
50
+ export const POS_OPERATION_MODE_CASHIER = 'cashier';
51
+ export const POS_CHECK_ORDER_TYPE_NONE = 'none';
52
+ export const POS_CHECK_ORDER_TYPE_TAB = 'tab';
53
+ export const POS_CHECK_ORDER_TYPE_TABLE = 'table';
54
+ export const POS_CHECK_ORDER_TYPE_TABLE_TAB = 'table-tab';
55
+ export const POS_CHECK_ORDER_TYPE_STAMP = 'stamp';
56
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE = 'manage';
57
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY = 'existing-only';
58
+ export const POS_OPERATION_MODE_DEFAULT = POS_OPERATION_MODE_COUNTER;
59
+ export const POS_PRINT_MODE_ORDER = 'order';
60
+ export const POS_PRINT_MODE_FORM = 'form';
61
+ export const POS_PRINT_MODE_DEFAULT = POS_PRINT_MODE_ORDER;
62
+ export const POS_CASH_MANAGEMENT_MODE_CASH_REGISTER = 'cash-register';
63
+ export const POS_CASH_MANAGEMENT_MODE_DAILY = 'daily';
64
+ export const POS_CASH_MANAGEMENT_MODE_DEFAULT =
65
+ POS_CASH_MANAGEMENT_MODE_CASH_REGISTER;
66
+ export const POS_OPERATION_MODE_OPTIONS = [
67
+ {
68
+ value: POS_OPERATION_MODE_COUNTER,
69
+ translationKey: 'counterService',
70
+ descriptionKey: 'counterServiceDescription',
71
+ },
72
+ {
73
+ value: POS_OPERATION_MODE_WAITER,
74
+ translationKey: 'waiterService',
75
+ descriptionKey: 'waiterServiceDescription',
76
+ },
77
+ {
78
+ value: POS_OPERATION_MODE_TOTEM,
79
+ translationKey: 'selfServiceKiosk',
80
+ descriptionKey: 'selfServiceKioskDescription',
81
+ },
82
+ {
83
+ value: POS_OPERATION_MODE_SINGLE_ITEM,
84
+ translationKey: 'singleItemSale',
85
+ descriptionKey: 'singleItemSaleDescription',
86
+ },
87
+ {
88
+ value: POS_OPERATION_MODE_CASHIER,
89
+ translationKey: 'cashierPOS',
90
+ descriptionKey: 'cashierPOSDescription',
91
+ },
92
+ ];
93
+
94
+ export const DEFAULT_DEVICE_CONFIGS = {
95
+ 'pos-type': 'full',
96
+ 'print-mode': 'order',
97
+ 'check-type': 'manual',
98
+ 'product-input-type': 'manual',
99
+ 'selection-type': 'single',
100
+ sound: '0',
101
+ vibration: '0',
102
+ [DEVICE_ORDER_VISIBILITY_KEY]: DEVICE_ORDER_VISIBILITY_DEVICE,
103
+ [POS_DELIVERY_ENABLED_CONFIG_KEY]: '1',
104
+ [DEVICE_ALERT_SOUND_ENABLED_KEY]: '0',
105
+ [DEVICE_ALERT_SOUND_URL_KEY]: '',
106
+ [DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]: '0',
107
+ [DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY]: '0',
108
+ [DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY]: '0',
109
+ [DISPLAY_SIZE_CONFIG_KEY]: String(DISPLAY_SIZE_DEFAULT),
110
+ [DISPLAY_SIDE_BREAK_CONFIG_KEY]: '0',
111
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: POS_CHECK_ORDER_TYPE_NONE,
112
+ [POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY]:
113
+ POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
114
+ [POS_OPERATION_MODE_CONFIG_KEY]: POS_OPERATION_MODE_DEFAULT,
115
+ [POS_PRODUCT_SHOWCASE_CONFIG_KEY]: '',
116
+ [POS_AUTO_PRINT_ENABLED_CONFIG_KEY]: '0',
117
+ [POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: POS_CASH_MANAGEMENT_MODE_DEFAULT,
118
+ };
119
+
120
+ export const isTruthyValue = value =>
121
+ value === true || value === '1' || value === 1 || value === 'true';
122
+
123
+ export const isMissingConfigValue = value =>
124
+ value === undefined || value === null || value === '';
125
+
126
+ export const parseConfigsObject = configs => {
127
+ if (!configs) return {};
128
+
129
+ if (typeof configs === 'string') {
130
+ try {
131
+ const parsed = JSON.parse(configs);
132
+ return parsed && typeof parsed === 'object' ? parsed : {};
133
+ } catch {
134
+ return {};
135
+ }
136
+ }
137
+
138
+ return typeof configs === 'object' ? {...configs} : {};
139
+ };
140
+
141
+ export const normalizeDisplaySize = value => {
142
+ const normalizedValue = String(value ?? '').trim();
143
+
144
+ if (normalizedValue === '') {
145
+ return DISPLAY_SIZE_DEFAULT;
146
+ }
147
+
148
+ const numericValue = Number(normalizedValue);
149
+
150
+ if (!Number.isFinite(numericValue)) {
151
+ return DISPLAY_SIZE_DEFAULT;
152
+ }
153
+
154
+ return Math.min(
155
+ DISPLAY_SIZE_MAX,
156
+ Math.max(DISPLAY_SIZE_MIN, Math.round(numericValue)),
157
+ );
158
+ };
159
+
160
+ export const resolveDisplaySize = configs =>
161
+ normalizeDisplaySize(
162
+ parseConfigsObject(configs)?.[DISPLAY_SIZE_CONFIG_KEY],
163
+ );
164
+
165
+ export const isDisplaySideBreakEnabled = configs =>
166
+ isTruthyValue(
167
+ parseConfigsObject(configs)?.[DISPLAY_SIDE_BREAK_CONFIG_KEY],
168
+ );
169
+
170
+ export const normalizePosOperationMode = value => {
171
+ const normalizedValue = String(value || '')
172
+ .trim()
173
+ .toLowerCase()
174
+ .normalize('NFD')
175
+ .replace(/[\u0300-\u036f]/g, '')
176
+ .replace(/[\s_]/g, '-');
177
+
178
+ if (
179
+ [
180
+ POS_OPERATION_MODE_COUNTER,
181
+ 'balcao',
182
+ 'counter-service',
183
+ 'front-counter',
184
+ ].includes(normalizedValue)
185
+ ) {
186
+ return POS_OPERATION_MODE_COUNTER;
187
+ }
188
+
189
+ if (
190
+ [
191
+ POS_OPERATION_MODE_WAITER,
192
+ 'garcon',
193
+ 'garcom',
194
+ 'table-service',
195
+ ].includes(normalizedValue)
196
+ ) {
197
+ return POS_OPERATION_MODE_WAITER;
198
+ }
199
+
200
+ if (
201
+ [
202
+ POS_OPERATION_MODE_TOTEM,
203
+ ].includes(normalizedValue)
204
+ ) {
205
+ return POS_OPERATION_MODE_TOTEM;
206
+ }
207
+
208
+ if (
209
+ [
210
+ POS_OPERATION_MODE_SINGLE_ITEM,
211
+ 'single-sale',
212
+ 'single-item-sale',
213
+ 'sale-unit',
214
+ 'sale-single',
215
+ 'venda-unitaria',
216
+ 'venda-unica',
217
+ 'item-unico',
218
+ 'unit-sale',
219
+ 'unitary-sale',
220
+ ].includes(normalizedValue)
221
+ ) {
222
+ return POS_OPERATION_MODE_SINGLE_ITEM;
223
+ }
224
+
225
+ if (
226
+ [
227
+ POS_OPERATION_MODE_CASHIER,
228
+ 'pdv',
229
+ 'pos',
230
+ 'checkout',
231
+ 'cashier-pos',
232
+ ].includes(normalizedValue)
233
+ ) {
234
+ return POS_OPERATION_MODE_CASHIER;
235
+ }
236
+
237
+ return '';
238
+ };
@@ -0,0 +1,32 @@
1
+ import React, {useRef, useState} from 'react';
2
+ import {Text, View} from 'react-native';
3
+ import {ORDER_CHARGE_ENABLED_CONFIG_KEY, isOrderChargeEnabled} from '../../../config/deviceConfigBootstrap';
4
+ import styles from '../../DeviceDetailPage.styles';
5
+
6
+ export default function DeviceChargePermissionSection({configs, actionsRef, currentCompany,
7
+ deviceString, deviceType, refreshCurrentConfig, showSystemError, renderSwitchRow, brandColors}) {
8
+ const [saving, setSaving] = useState(false);
9
+ const pending = useRef(false);
10
+ async function save(value) {
11
+ if (pending.current || !currentCompany?.id || !deviceString) return;
12
+ pending.current = true;
13
+ setSaving(true);
14
+ try {
15
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({device: deviceString,
16
+ people: `/people/${currentCompany.id}`, type: deviceType,
17
+ configs: JSON.stringify({[ORDER_CHARGE_ENABLED_CONFIG_KEY]: value})});
18
+ await refreshCurrentConfig();
19
+ } catch (error) {
20
+ showSystemError(error, 'Não foi possível salvar a permissão de cobrança.');
21
+ } finally {pending.current = false; setSaving(false);}
22
+ }
23
+ const enabled = isOrderChargeEnabled(configs);
24
+ return <View style={styles.configCard}>
25
+ {renderSwitchRow({label: 'Permitir cobrança neste device', value: enabled,
26
+ valueLabel: enabled ? 'Permitida' : 'Desabilitada',
27
+ disabled: saving || !deviceString || !currentCompany?.id, onValueChange: save})}
28
+ <Text style={[styles.deviceString, {color: brandColors.textSecondary}]}>
29
+ A cobrança usa os meios de pagamento e o destino configurados. O sistema valida a autorização para receber aqui ou encaminhar a outro device.
30
+ </Text>
31
+ </View>;
32
+ }
@@ -11,6 +11,7 @@ import {
11
11
  POS_CHECK_ORDER_TYPE_NONE,
12
12
  POS_CHECK_ORDER_TYPE_TAB,
13
13
  POS_CHECK_ORDER_TYPE_TABLE,
14
+ POS_CHECK_ORDER_TYPE_TABLE_TAB,
14
15
  POS_CHECK_ORDER_TYPE_STAMP,
15
16
  POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
16
17
  POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY,
@@ -164,6 +165,10 @@ export default function DeviceDetailPdvConfigSection(ctx) {
164
165
  label: global.t?.t('orders', 'title', 'table') || 'Table',
165
166
  value: POS_CHECK_ORDER_TYPE_TABLE,
166
167
  },
168
+ {
169
+ label: 'Mesa + Comanda',
170
+ value: POS_CHECK_ORDER_TYPE_TABLE_TAB,
171
+ },
167
172
  {
168
173
  label: global.t?.t('orders', 'title', 'stamp') || 'Stamp',
169
174
  value: POS_CHECK_ORDER_TYPE_STAMP,
@@ -188,6 +193,8 @@ export default function DeviceDetailPdvConfigSection(ctx) {
188
193
  ? POS_CHECK_ORDER_TYPE_TAB
189
194
  : value === POS_CHECK_ORDER_TYPE_TABLE
190
195
  ? POS_CHECK_ORDER_TYPE_TABLE
196
+ : value === POS_CHECK_ORDER_TYPE_TABLE_TAB
197
+ ? POS_CHECK_ORDER_TYPE_TABLE_TAB
191
198
  : value === POS_CHECK_ORDER_TYPE_STAMP
192
199
  ? loyaltyCouponsEnabled
193
200
  ? POS_CHECK_ORDER_TYPE_STAMP
@@ -10,6 +10,7 @@ import styles from '../../DeviceDetailPage.styles';
10
10
  import DeviceDetailHeader from './DeviceDetailHeader';
11
11
  import CopyDeviceConfigModal from '@controleonline/ui-common/src/react/components/CopyDeviceConfigModal';
12
12
  import useDeviceDetailCopyConfig from './useDeviceDetailCopyConfig';
13
+ import DeviceChargePermissionSection from './DeviceChargePermissionSection';
13
14
  import DeviceDetailPdvConfigSection from './DeviceDetailPdvConfigSection';
14
15
  import DeviceDetailOrdersPrintSection from './DeviceDetailOrdersPrintSection';
15
16
  import DeviceDetailAlertsCommandsSection from './DeviceDetailAlertsCommandsSection';
@@ -150,7 +151,10 @@ const DeviceDetailScreen = () => {
150
151
  <DeviceDetailMovementSections {...detailCtx} />
151
152
 
152
153
  {showPdvOperationTab && (
153
- <DeviceDetailPdvConfigSection {...detailCtx} />
154
+ <>
155
+ <DeviceDetailPdvConfigSection {...detailCtx} />
156
+ <DeviceChargePermissionSection {...detailCtx} />
157
+ </>
154
158
  )}
155
159
 
156
160
  {(showPdvOrdersTab || shouldShowOrderVisibility || detailCtx.isDisplayDevice) && (
@@ -1,6 +1,6 @@
1
1
  import {normalizeEntityId} from '@controleonline/ui-common/src/react/utils/paymentDevices';
2
2
  import {isPosCashRegisterOpen} from '@controleonline/ui-common/src/react/config/deviceConfigBootstrap';
3
- import {Alert, Platform} from 'react-native';
3
+ import {confirmAction} from '../../../utils/confirmAction';
4
4
  import {PAYMENT_ICONS} from './deviceDetailConstants';
5
5
 
6
6
  export const paymentIcon = label => {
@@ -44,16 +44,7 @@ export const getProductShowcaseLabel = showcase => {
44
44
 
45
45
  export const getIsOpen = configs => isPosCashRegisterOpen(configs);
46
46
 
47
- export const confirm = (msg, cb) => {
48
- if (Platform.OS === 'web' && typeof window !== 'undefined' && window.confirm) {
49
- if (window.confirm(msg)) cb();
50
- } else {
51
- Alert.alert('Confirmação', msg, [
52
- {text: 'Cancelar', style: 'cancel'},
53
- {text: 'Confirmar', onPress: cb},
54
- ]);
55
- }
56
- };
47
+ export const confirm = confirmAction;
57
48
 
58
49
  export const getDeviceSwitchProps = ({disabled = false, palette, value = false}) => {
59
50
  const offTrackColor = disabled ? palette.switchDisabledTrack : palette.switchOffTrack;
@@ -0,0 +1,12 @@
1
+ import {Alert, Platform} from 'react-native';
2
+
3
+ export const confirmAction = (message, onConfirm) => {
4
+ if (Platform.OS === 'web' && typeof window !== 'undefined' && window.confirm) {
5
+ if (window.confirm(message)) onConfirm();
6
+ } else {
7
+ Alert.alert('Confirmação', message, [
8
+ {text: 'Cancelar', style: 'cancel'},
9
+ {text: 'Confirmar', onPress: onConfirm},
10
+ ]);
11
+ }
12
+ };
@@ -0,0 +1,42 @@
1
+ const React = require('react')
2
+ const renderer = require('react-test-renderer')
3
+ const {jest} = require('@jest/globals')
4
+ global.IS_REACT_ACT_ENVIRONMENT = true
5
+ jest.mock('react-native', () => ({Text: 'Text', View: 'View', Platform: {OS: 'web', select: x => x.web}, StyleSheet: {create: x => x}}))
6
+ jest.mock('../../../react/utils/screenMetrics', () => ({appendScreenMetrics: x => x, hasScreenMetricsChanges: () => false}))
7
+ const Section = require('../../../react/pages/Devices/detail/DeviceChargePermissionSection').default
8
+ const {isOrderChargeEnabled, isPosChargeEntryEnabled} = require('../../../react/config/deviceConfigBootstrap')
9
+ let tree, props
10
+ beforeEach(() => {
11
+ jest.clearAllMocks()
12
+ props = {configs: {'order-charge-enabled': false, 'pos-gateway': 'infinite-pay', 'payment-type-ids': [11]},
13
+ actionsRef: {current: {deviceConfigActions: {addDeviceConfigs: jest.fn(async () => {})}}},
14
+ currentCompany: {id: 3}, deviceString: 'web-10', deviceType: 'PDV', refreshCurrentConfig: jest.fn(),
15
+ showSystemError: jest.fn(), brandColors: {textSecondary: '#555'}, renderSwitchRow: value => React.createElement('Switch', value)}
16
+ })
17
+ afterEach(async () => {if (tree) await renderer.act(async () => tree.unmount()); tree = null})
18
+ it('saves the existing protected financial permission using the current device and company', async () => {
19
+ await renderer.act(async () => {tree = renderer.create(React.createElement(Section, props))})
20
+ await renderer.act(async () => tree.root.findByType('Switch').props.onValueChange(true))
21
+ expect(props.actionsRef.current.deviceConfigActions.addDeviceConfigs).toHaveBeenCalledWith({device: 'web-10', people: '/people/3', type: 'PDV', configs: '{"order-charge-enabled":true}'})
22
+ expect(props.refreshCurrentConfig).toHaveBeenCalledTimes(1)
23
+ expect(props.configs['pos-gateway']).toBe('infinite-pay')
24
+ expect(props.configs['payment-type-ids']).toEqual([11])
25
+ })
26
+ it('preserves the current permission and reports a rejected administrative write', async () => {
27
+ props.actionsRef.current.deviceConfigActions.addDeviceConfigs.mockRejectedValue(new Error('Acesso negado'))
28
+ await renderer.act(async () => {tree = renderer.create(React.createElement(Section, props))})
29
+ await renderer.act(async () => tree.root.findByType('Switch').props.onValueChange(true))
30
+ expect(props.refreshCurrentConfig).not.toHaveBeenCalled()
31
+ expect(tree.root.findByType('Switch').props.value).toBe(false)
32
+ expect(props.showSystemError).toHaveBeenCalledWith(expect.any(Error), expect.any(String))
33
+ })
34
+ it('honors an explicit canonical denial without erasing legacy config decisions', () => {
35
+ const configs = {'pos-operation-mode': 'waiter', 'pos-local-charge-enabled': true, 'order-charge-enabled': false}
36
+ expect(isOrderChargeEnabled(configs)).toBe(false)
37
+ expect(isPosChargeEntryEnabled(configs)).toBe(false)
38
+ expect(isPosChargeEntryEnabled({...configs, 'order-charge-enabled': true})).toBe(true)
39
+ expect(isOrderChargeEnabled('{"order-charge-enabled":"on"}')).toBe(true)
40
+ expect(isOrderChargeEnabled({})).toBe(false)
41
+ expect(configs['pos-local-charge-enabled']).toBe(true)
42
+ })